From eff319f869061b0778d1200e9085f99c4f33f4c0 Mon Sep 17 00:00:00 2001 From: spaceface777 Date: Thu, 27 Aug 2020 15:00:44 +0200 Subject: [PATCH] comp_for: allow checking full mehod and arg types (#5997) --- examples/compiletime/compile-time-for.v | 61 ++++++-------- vlib/builtin/builtin.v | 21 +++-- vlib/v/gen/comptime.v | 102 ++++++++++++++++-------- vlib/v/parser/comptime.v | 28 +++++-- vlib/v/tests/comptime_for_test.v | 21 +++-- vlib/vweb/vweb.v | 4 +- 6 files changed, 142 insertions(+), 95 deletions(-) diff --git a/examples/compiletime/compile-time-for.v b/examples/compiletime/compile-time-for.v index 4c0bf3e0c0..d9a68d99a8 100644 --- a/examples/compiletime/compile-time-for.v +++ b/examples/compiletime/compile-time-for.v @@ -1,45 +1,32 @@ -module main +struct App {} -struct App { - test string [test] -mut: - a string -} +fn (mut app App) method_one() {} +fn (mut app App) method_two() int { return 0 } +fn (mut app App) method_three(s string) string { return s } fn main() { - println('All functions') $for method in App.methods { - $if method.@type is int { - println('hi') + $if method.Type is fn(string) string { + println('$method.name IS `fn(string) string`') + } $else { + println('$method.name is NOT `fn(string) string`') } - println('$method.name.len') - println('$method.name.str') - println('Method: $method.name') - println('Attributes: $method.attrs') - println('Return type: $method.ret_type') - } - println('All integer functions') - $for method in App.methods { - println('Method: $method.name') - println('Attributes: $method.attrs') - } - $for field in App.fields { - $if field.@type is string { - println(field) + $if method.ReturnType !is int { + println('$method.name does NOT return `int`') + } $else { + println('$method.name DOES return `int`') } + $if method.args[0].Type !is string { + println("${method.name}'s first arg is NOT `string`") + } $else { + println("${method.name}'s first arg IS `string`") + } + // TODO: Double inversion, should this even be allowed? + $if method.Type is fn() { + println('$method.name IS a void method') + } $else { + println('$method.name is NOT a void method') + } + println('') } } - -fn (mut app App) method_one() { -} - -fn (mut app App) method_two() { -} - -fn (mut app App) method_three() int { - return 0 -} - -fn (mut app App) method_four() int { - return 1 -} diff --git a/vlib/builtin/builtin.v b/vlib/builtin/builtin.v index c3274070ad..25ee8406f6 100644 --- a/vlib/builtin/builtin.v +++ b/vlib/builtin/builtin.v @@ -291,26 +291,25 @@ fn __print_assert_failure(i &VAssertMetaInfo) { } } -pub struct MethodAttr { +pub struct MethodArgs { pub: - value string - method string + Type int } pub struct FunctionData { pub: - name string - attrs []string - ret_type string - @type int + name string + attrs []string + args []MethodArgs + ReturnType int + Type int } pub struct FieldData { pub: - name string - attrs []string - typ string + name string + attrs []string is_pub bool is_mut bool - @type int + Type int } diff --git a/vlib/v/gen/comptime.v b/vlib/v/gen/comptime.v index fc3265363f..fcd92d110e 100644 --- a/vlib/v/gen/comptime.v +++ b/vlib/v/gen/comptime.v @@ -107,31 +107,33 @@ fn (mut g Gen) comp_if(mut it ast.CompIf) { } if it.kind == .typecheck { mut comptime_var_type := table.Type(0) + mut name := '' if it.tchk_expr is ast.SelectorExpr { se := it.tchk_expr as ast.SelectorExpr - x := se.expr.str() - comptime_var_type = g.comptime_var_type_map[x] + name = '${se.expr}.$se.field_name' + comptime_var_type = g.comptime_var_type_map[name] } - if comptime_var_type == 0 { - $if trace_gen ? { - eprintln('Known compile time types: ') - eprintln(g.comptime_var_type_map.str()) - } - verror('the compile time type of `$it.tchk_expr.str()` is unknown') + // if comptime_var_type == 0 { + // $if trace_gen ? { + // eprintln('Known compile time types: ') + // eprintln(g.comptime_var_type_map.str()) + // } + // // verror('the compile time type of `$it.tchk_expr.str()` is unknown') + // return + // } + it_type_name := g.table.get_type_name(it.tchk_type) + should_write := (comptime_var_type == it.tchk_type && !it.is_not) || + (comptime_var_type != it.tchk_type && it.is_not) + if should_write { + inversion := if it.is_not { '!' } else { '' } + g.writeln('/* \$if $name ${inversion}is $it_type_name */ {') + g.stmts(it.stmts) + g.writeln('}') + } else if it.has_else { + g.writeln('/* \$else */ {') + g.stmts(it.else_stmts) + g.writeln('}') } - ret_type_name := g.table.get_type_symbol(comptime_var_type).name - it_type_name := g.table.get_type_symbol(it.tchk_type).name - types_match := comptime_var_type == it.tchk_type - g.writeln('{ // \$if $it.val is $it_type_name, typecheck start, $comptime_var_type == $it.tchk_type => $ret_type_name == $it_type_name => $types_match ') - mut stmts := it.stmts - if !types_match { - stmts = []ast.Stmt{} - if it.has_else { - stmts = it.else_stmts - } - } - g.stmts(stmts) - g.writeln('} // typecheck end') return } ifdef := g.comp_if_to_ifdef(it.val, it.is_opt) @@ -192,17 +194,53 @@ fn (mut g Gen) comp_for(node ast.CompFor) { g.writeln('\t${node.val_var}.attrs = new_array_from_c_array($attrs.len, $attrs.len, sizeof(string), _MOV((string[$attrs.len]){' + attrs.join(', ') + '}));') } - method_sym := g.table.get_type_symbol(method.return_type) - g.writeln('\t${node.val_var}.ret_type = tos_lit("$method_sym.name");') - styp := int(method.return_type).str() - g.writeln('\t${node.val_var}.type = $styp;') + if method.args.len < 2 { + // 0 or 1 (the receiver) args + g.writeln('\t${node.val_var}.args = __new_array_with_default(0, 0, sizeof(MethodArgs), 0);') + } else { + len := method.args.len - 1 + g.write('\t${node.val_var}.args = new_array_from_c_array($len, $len, sizeof(MethodArgs), _MOV((MethodArgs[$len]){') + // Skip receiver arg + for j, arg in method.args[1..] { + typ := arg.typ.idx() + g.write(typ.str()) + if j < len - 1 { + g.write(', ') + } + g.comptime_var_type_map['${node.val_var}.args[$j].Type'] = typ + } + g.writeln('}));') + } + mut sig := 'anon_fn_' + // skip the first (receiver) arg + for j, arg in method.args[1..] { + // TODO: ignore mut/pts in sig for now + typ := arg.typ.set_nr_muls(0) + sig += '$typ' + if j < method.args.len - 2 { + sig += '_' + } + } + sig += '_$method.return_type' + styp := g.table.find_type_idx(sig) + // println(styp) + // if styp == 0 { } + // TODO: type aliases + ret_typ := method.return_type.idx() + g.writeln('\t${node.val_var}.Type = $styp;') + g.writeln('\t${node.val_var}.ReturnType = $ret_typ;') // - g.comptime_var_type_map[node.val_var] = method.return_type + g.comptime_var_type_map['${node.val_var}.ReturnType'] = ret_typ + g.comptime_var_type_map['${node.val_var}.Type'] = styp g.stmts(node.stmts) i++ g.writeln('') + for key, _ in g.comptime_var_type_map { + if key.starts_with(node.val_var) { + g.comptime_var_type_map.delete(key) + } + } } - g.comptime_var_type_map.delete(node.val_var) } else if node.kind == .fields { // TODO add fields if sym.info is table.Struct { @@ -224,13 +262,13 @@ fn (mut g Gen) comp_for(node ast.CompFor) { g.writeln('\t${node.val_var}.attrs = new_array_from_c_array($attrs.len, $attrs.len, sizeof(string), _MOV((string[$attrs.len]){' + attrs.join(', ') + '}));') } - field_sym := g.table.get_type_symbol(field.typ) - g.writeln('\t${node.val_var}.typ = tos_lit("$field_sym.name");') - styp := int(field.typ).str() - g.writeln('\t${node.val_var}.type = $styp;') + // field_sym := g.table.get_type_symbol(field.typ) + // g.writeln('\t${node.val_var}.typ = tos_lit("$field_sym.name");') + styp := field.typ + g.writeln('\t${node.val_var}.Type = $styp;') g.writeln('\t${node.val_var}.is_pub = $field.is_pub;') g.writeln('\t${node.val_var}.is_mut = $field.is_mut;') - g.comptime_var_type_map[node.val_var] = field.typ + g.comptime_var_type_map[node.val_var + '.Type'] = styp g.stmts(node.stmts) i++ g.writeln('') diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index be580adc0c..49a61bb4e7 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -193,7 +193,8 @@ fn (mut p Parser) comp_if() ast.Stmt { // return p.vweb() // } p.check(.key_if) - is_not := p.tok.kind == .not + mut is_not := p.tok.kind == .not + inversion_pos := p.tok.position() if is_not { p.next() } @@ -202,7 +203,7 @@ fn (mut p Parser) comp_if() ast.Stmt { mut val := '' mut tchk_expr := ast.Expr{} if p.peek_tok.kind == .dot { - vname := p.parse_ident(table.Language.v) + vname := p.parse_ident(.v) cobj := p.scope.find(vname.name) or { p.error_with_pos('unknown variable `$vname.name`', name_pos_start) return ast.Stmt{} @@ -210,9 +211,17 @@ fn (mut p Parser) comp_if() ast.Stmt { if cobj is ast.Var { tchk_expr = p.dot_expr(vname) val = vname.name - if tchk_expr is ast.SelectorExpr { - if tchk_expr.field_name !in ['type', '@type'] { - p.error_with_pos('only the `.@type` field name is supported for now', + if tchk_expr is ast.SelectorExpr as tchk_expr2 { + if p.tok.kind == .lsbr && tchk_expr2.field_name == 'args' { + tchk_expr = p.index_expr(tchk_expr) + if p.tok.kind == .dot && p.peek_tok.lit == 'Type' { + tchk_expr = p.dot_expr(tchk_expr) + } else { + p.error_with_pos('only the `Type` field is supported for arguments', + p.peek_tok.position()) + } + } else if tchk_expr2.field_name !in ['Type', 'ReturnType'] { + p.error_with_pos('only the `Type` and `ReturnType` fields are supported for now', name_pos_start) } } @@ -280,10 +289,17 @@ fn (mut p Parser) comp_if() ast.Stmt { if p.tok.kind == .question { p.next() is_opt = true - } else if p.tok.kind == .key_is { + } else if p.tok.kind in [.key_is, .not_is] { + typecheck_inversion := p.tok.kind == .not_is p.next() tchk_type = p.parse_type() is_typecheck = true + if is_not { + name := p.table.get_type_name(tchk_type) + p.error_with_pos('use `\$if $tchk_expr !is $name {`, not `\$if !$tchk_expr is $name {`', + inversion_pos) + } + is_not = typecheck_inversion } if !skip { stmts = p.parse_block() diff --git a/vlib/v/tests/comptime_for_test.v b/vlib/v/tests/comptime_for_test.v index a6e4f29113..c585fe154b 100644 --- a/vlib/v/tests/comptime_for_test.v +++ b/vlib/v/tests/comptime_for_test.v @@ -28,11 +28,14 @@ fn (mut app App) int_method2() int { return 1 } +fn (mut app App) string_arg(x string) { +} + fn no_lines(s string) string { return s.replace('\n', ' ') } fn test_comptime_for() { println(@FN) - methods := ['run', 'method2', 'int_method1', 'int_method2'] + methods := ['run', 'method2', 'int_method1', 'int_method2', 'string_arg'] $for method in App.methods { println(' method: $method.name | ' + no_lines('$method')) assert method.name in methods @@ -41,12 +44,16 @@ fn test_comptime_for() { fn test_comptime_for_with_if() { println(@FN) - methods := ['int_method1', 'int_method2'] $for method in App.methods { println(' method: ' + no_lines('$method')) - $if method.@type is int { - println(method.attrs) - assert method.name in methods + $if method.Type is fn() { + assert method.name in ['run', 'method2'] + } + $if method.ReturnType is int { + assert method.name in ['int_method1', 'int_method2'] + } + $if method.args[0].Type is string { + assert method.name == 'string_arg' } } } @@ -55,10 +62,10 @@ fn test_comptime_for_fields() { println(@FN) $for field in App.fields { println(' field: $field.name | ' + no_lines('$field')) - $if field.@type is string { + $if field.Type is string { assert field.name in ['a', 'b', 'g'] } - $if field.@type is f32 { + $if field.Type is f32 { assert field.name in ['d', 'e'] } if field.is_mut { diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v index c8392aeb73..8c5859db23 100644 --- a/vlib/vweb/vweb.v +++ b/vlib/vweb/vweb.v @@ -370,7 +370,7 @@ fn handle_conn(conn net.Socket, mut app T) { mut vars := []string{cap: route_words_a.len} mut action := '' $for method in T.methods { - $if method.@type is Result { + $if method.ReturnType is Result { attrs := method.attrs route_words_a = [][]string{} if attrs.len == 0 { @@ -469,7 +469,7 @@ fn handle_conn(conn net.Socket, mut app T) { return } $for method in T.methods { - $if method.@type is Result { + $if method.ReturnType is Result { // search again for method if action == method.name && method.attrs.len > 0 { // call action method