From 43c3a3b08068751a8ca3f81a9aab2b4d6fd9a408 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sat, 30 Jan 2021 14:24:16 +0000 Subject: [PATCH] checker: fix compile-time call with string identifier expression (#8415) --- vlib/v/ast/ast.v | 5 ++- vlib/v/checker/checker.v | 37 ++++++++++++++++--- .../tests/comptime_call_no_unused_var.out | 30 ++++++++++++++- .../tests/comptime_call_no_unused_var.vv | 11 +++++- vlib/v/gen/comptime.v | 15 ++++---- vlib/v/gen/embed.v | 2 +- vlib/v/parser/comptime.v | 22 +++++++---- vlib/v/tests/comptime_call_test.v | 26 +++++++++++++ 8 files changed, 123 insertions(+), 25 deletions(-) create mode 100644 vlib/v/tests/comptime_call_test.v diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index be6145939e..a86d9abfee 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1130,6 +1130,8 @@ pub struct ComptimeCall { pub: has_parens bool // if $() is used, for vfmt method_name string + method_pos token.Position + scope &Scope left Expr is_vweb bool vweb_tmpl File @@ -1137,7 +1139,8 @@ pub: is_embed bool embed_file EmbeddedFile pub mut: - sym table.TypeSymbol + sym table.TypeSymbol + result_type table.Type } pub struct None { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index ea0790a804..c1ab6bfb06 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3677,14 +3677,39 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) table.Type { c.nr_errors += c2.nr_errors } if node.method_name == 'html' { - return c.table.find_type_idx('vweb.Result') + rtyp := c.table.find_type_idx('vweb.Result') + node.result_type = rtyp + return rtyp } - mut var := c.fn_scope.objects[node.method_name] - if mut var is ast.Var { - var.is_used = true - c.fn_scope.objects[node.method_name] = var + if node.is_vweb || node.method_name == 'method' { + return table.string_type } - return table.string_type + // s.$my_str() + v := node.scope.find_var(node.method_name) or { + c.error('unknown identifier `$node.method_name`', node.method_pos) + return table.void_type + } + if v.typ != table.string_type { + s := c.expected_msg(v.typ, table.string_type) + c.error('invalid string method call: $s', node.method_pos) + return table.void_type + } + // note: we should use a compile-time evaluation function rather than handle here + // mut variables will not work after init + mut method_name := '' + if v.expr is ast.StringLiteral { + method_name = v.expr.val + } else { + c.add_error_detail(v.expr.type_name()) + c.error('todo: not a string literal', node.method_pos) + } + f := node.sym.find_method(method_name) or { + c.error('could not find method `$method_name`', node.method_pos) + return table.void_type + } + println(f.name + ' ' + c.table.type_to_str(f.return_type)) + node.result_type = f.return_type + return f.return_type } fn (mut c Checker) at_expr(mut node ast.AtExpr) table.Type { diff --git a/vlib/v/checker/tests/comptime_call_no_unused_var.out b/vlib/v/checker/tests/comptime_call_no_unused_var.out index 8b13789179..c4726e12cb 100644 --- a/vlib/v/checker/tests/comptime_call_no_unused_var.out +++ b/vlib/v/checker/tests/comptime_call_no_unused_var.out @@ -1 +1,29 @@ - +print void +vlib/v/checker/tests/comptime_call_no_unused_var.vv:11:8: error: unknown identifier `w` + 9 | abc := 'print' + 10 | test.$abc() // OK + 11 | test.$w() + | ^ + 12 | v := 4 + 13 | test.$v() +vlib/v/checker/tests/comptime_call_no_unused_var.vv:13:8: error: invalid string method call: expected `string`, not `int` + 11 | test.$w() + 12 | v := 4 + 13 | test.$v() + | ^ + 14 | s := 'x' + 'y' + 15 | test.$s() +vlib/v/checker/tests/comptime_call_no_unused_var.vv:15:8: error: todo: not a string literal + 13 | test.$v() + 14 | s := 'x' + 'y' + 15 | test.$s() + | ^ + 16 | s2 := 'x' + 17 | test.$s2() +details: v.ast.InfixExpr +vlib/v/checker/tests/comptime_call_no_unused_var.vv:17:8: error: could not find method `x` + 15 | test.$s() + 16 | s2 := 'x' + 17 | test.$s2() + | ~~ + 18 | } diff --git a/vlib/v/checker/tests/comptime_call_no_unused_var.vv b/vlib/v/checker/tests/comptime_call_no_unused_var.vv index 51027324eb..b38c031516 100644 --- a/vlib/v/checker/tests/comptime_call_no_unused_var.vv +++ b/vlib/v/checker/tests/comptime_call_no_unused_var.vv @@ -7,5 +7,12 @@ fn (test Test) print() { fn main() { test := Test{} abc := 'print' - test.$abc() -} \ No newline at end of file + test.$abc() // OK + test.$w() + v := 4 + test.$v() + s := 'x' + 'y' + test.$s() + s2 := 'x' + test.$s2() +} diff --git a/vlib/v/gen/comptime.v b/vlib/v/gen/comptime.v index a517d599ba..0e3870c0ba 100644 --- a/vlib/v/gen/comptime.v +++ b/vlib/v/gen/comptime.v @@ -57,9 +57,7 @@ fn (mut g Gen) comptime_call(node ast.ComptimeCall) { } return } - g.writeln('// $' + 'method call. sym="$node.sym.name"') - mut j := 0 - result_type := g.table.find_type_idx('vweb.Result') // TODO not just vweb + g.writeln('// \$method call. sym="$node.sym.name"') if node.method_name == 'method' { // `app.$method()` m := node.sym.find_method(g.comp_for_method) or { return } @@ -95,9 +93,10 @@ fn (mut g Gen) comptime_call(node ast.ComptimeCall) { g.write(' ); // vweb action call with args') return } + mut j := 0 for method in node.sym.methods { // if method.return_type != table.void_type { - if method.return_type != result_type { + if method.return_type != node.result_type { continue } if method.params.len != 1 { @@ -108,10 +107,12 @@ fn (mut g Gen) comptime_call(node ast.ComptimeCall) { // p.error('`$p.expr_var.name` needs to be a reference') // } amp := '' // if receiver.is_mut && !p.expr_var.ptr { '&' } else { '' } - if j > 0 { - g.write(' else ') + if node.is_vweb { + if j > 0 { + g.write(' else ') + } + g.write('if (string_eq($node.method_name, _SLIT("$method.name"))) ') } - g.write('if (string_eq($node.method_name, _SLIT("$method.name"))) ') g.write('${util.no_dots(node.sym.name)}_${method.name}($amp ') g.expr(node.left) g.writeln(');') diff --git a/vlib/v/gen/embed.v b/vlib/v/gen/embed.v index f7cba01406..388d198f0a 100644 --- a/vlib/v/gen/embed.v +++ b/vlib/v/gen/embed.v @@ -27,7 +27,7 @@ fn (mut g Gen) gen_embed_file_init(node ast.ComptimeCall) { g.writeln('\t\t.free_compressed = 0,') g.writeln('\t\t.free_uncompressed = 0,') g.writeln('\t\t.len = $file_size') - g.writeln('} // $' + 'embed_file("$node.embed_file.apath")') + g.writeln('} // \$embed_file("$node.embed_file.apath")') } // gen_embedded_data embeds data into the V target executable. diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index c82a86135f..96f0cc14ad 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -43,20 +43,23 @@ fn (mut p Parser) hash() ast.HashStmt { } fn (mut p Parser) comp_call() ast.ComptimeCall { + err_node := ast.ComptimeCall{ + scope: 0 + } p.check(.dollar) error_msg := 'only `\$tmpl()`, `\$embed_file()` and `\$vweb.html()` comptime functions are supported right now' if p.peek_tok.kind == .dot { n := p.check_name() // skip `vweb.html()` TODO if n != 'vweb' { p.error(error_msg) - return ast.ComptimeCall{} + return err_node } p.check(.dot) } n := p.check_name() // (.name) if n !in parser.supported_comptime_calls { p.error(error_msg) - return ast.ComptimeCall{} + return err_node } is_embed_file := n == 'embed_file' is_html := n == 'html' @@ -74,7 +77,7 @@ fn (mut p Parser) comp_call() ast.ComptimeCall { if epath == '' { p.error_with_pos('please supply a valid relative or absolute file path to the file to embed', spos) - return ast.ComptimeCall{} + return err_node } if !p.pref.is_fmt { abs_path := os.real_path(epath) @@ -85,12 +88,12 @@ fn (mut p Parser) comp_call() ast.ComptimeCall { if !os.exists(epath) { p.error_with_pos('"$epath" does not exist so it cannot be embedded', spos) - return ast.ComptimeCall{} + return err_node } if !os.is_file(epath) { p.error_with_pos('"$epath" is not a file so it cannot be embedded', spos) - return ast.ComptimeCall{} + return err_node } } else { epath = abs_path @@ -98,6 +101,7 @@ fn (mut p Parser) comp_call() ast.ComptimeCall { } p.register_auto_import('v.embed_file') return ast.ComptimeCall{ + scope: 0 is_embed: true embed_file: ast.EmbeddedFile{ rpath: s @@ -126,11 +130,10 @@ fn (mut p Parser) comp_call() ast.ComptimeCall { if !os.exists(path) { if is_html { p.error('vweb HTML template "$path" not found') - return ast.ComptimeCall{} } else { p.error('template file "$path" not found') - return ast.ComptimeCall{} } + return err_node } // println('path is now "$path"') } @@ -185,6 +188,7 @@ fn (mut p Parser) comp_call() ast.ComptimeCall { } } return ast.ComptimeCall{ + scope: 0 is_vweb: true vweb_tmpl: file method_name: n @@ -257,7 +261,9 @@ fn (mut p Parser) at() ast.AtExpr { fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr { p.check(.dollar) if p.peek_tok.kind == .lpar { + method_pos := p.tok.position() method_name := p.check_name() + p.mark_var_as_used(method_name) // `app.$action()` (`action` is a string) p.check(.lpar) mut args_var := '' @@ -273,6 +279,8 @@ fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr { return ast.ComptimeCall{ left: left method_name: method_name + method_pos: method_pos + scope: p.scope args_var: args_var } } diff --git a/vlib/v/tests/comptime_call_test.v b/vlib/v/tests/comptime_call_test.v new file mode 100644 index 0000000000..6ad96e6797 --- /dev/null +++ b/vlib/v/tests/comptime_call_test.v @@ -0,0 +1,26 @@ +struct Test {} + +fn (test Test) v() { + println('in v()') +} +fn (test Test) i() int { + return 4 +} +fn (test Test) s() string { + return 'test' +} +fn (test Test) s2() string { + return 'Two' +} + +fn test_call() { + test := Test{} + sv := 'v' + test.$sv() + si := 'i' + ri := test.$si() + assert ri == 4 + ss := 's' + rs := test.$ss() + assert rs == 'test' +}