From 5841d5d8e1c581c14946a01bf51dccdd442cd6f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=C3=A4schle?= Date: Tue, 5 Jan 2021 15:11:43 +0100 Subject: [PATCH] all: implement basic comptime field selector (#7888) --- vlib/v/ast/ast.v | 23 ++++-- vlib/v/ast/str.v | 3 + vlib/v/checker/checker.v | 34 ++++++++- ...comptime_field_selector_not_in_for_err.out | 7 ++ .../comptime_field_selector_not_in_for_err.vv | 14 ++++ .../comptime_field_selector_not_name_err.out | 7 ++ .../comptime_field_selector_not_name_err.vv | 17 +++++ vlib/v/fmt/fmt.v | 11 ++- .../fmt/tests/comptime_field_selector_keep.vv | 23 ++++++ ...omptime_field_selector_parentheses_keep.vv | 23 ++++++ vlib/v/gen/cgen.v | 8 ++- vlib/v/gen/comptime.v | 30 ++++++-- vlib/v/gen/js/js.v | 3 + vlib/v/parser/comptime.v | 71 ++++++++----------- vlib/v/parser/parser.v | 2 +- vlib/v/tests/comptime_field_selector_test.v | 62 ++++++++++++++++ 16 files changed, 281 insertions(+), 57 deletions(-) create mode 100644 vlib/v/checker/tests/comptime_field_selector_not_in_for_err.out create mode 100644 vlib/v/checker/tests/comptime_field_selector_not_in_for_err.vv create mode 100644 vlib/v/checker/tests/comptime_field_selector_not_name_err.out create mode 100644 vlib/v/checker/tests/comptime_field_selector_not_name_err.vv create mode 100644 vlib/v/fmt/tests/comptime_field_selector_keep.vv create mode 100644 vlib/v/fmt/tests/comptime_field_selector_parentheses_keep.vv create mode 100644 vlib/v/tests/comptime_field_selector_test.v diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 52717d3ffb..f4f4028273 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -10,11 +10,11 @@ import v.errors pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl pub type Expr = AnonFn | ArrayDecompose | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral | - CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ConcatExpr | - EnumVal | FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | - Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr | - RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | - StructInit | Type | TypeOf | UnsafeExpr + CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ComptimeSelector | + ConcatExpr | EnumVal | FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | + IntegerLiteral | Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | + PrefixExpr | RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | + StringLiteral | StructInit | Type | TypeOf | UnsafeExpr pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt | EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt | @@ -1056,8 +1056,19 @@ pub mut: val string } +pub struct ComptimeSelector { +pub: + has_parens bool // if $() is used, for vfmt + left Expr + field_expr Expr +pub mut: + left_type table.Type + typ table.Type +} + pub struct ComptimeCall { pub: + has_parens bool // if $() is used, for vfmt method_name string left Expr is_vweb bool @@ -1147,7 +1158,7 @@ pub fn (expr Expr) position() token.Position { IfGuardExpr { return expr.expr.position() } - ComptimeCall { + ComptimeCall, ComptimeSelector { return expr.left.position() } InfixExpr { diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index f75cd0e1d7..eae56fc4ce 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -212,6 +212,9 @@ pub fn (x Expr) str() string { CharLiteral { return '`$x.val`' } + ComptimeSelector { + return '${x.left}.$$x.field_expr' + } EnumVal { return '.$x.val' } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 8b42e8e484..b794047104 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -69,6 +69,7 @@ mut: prevent_sum_type_unwrapping_once bool // needed for assign new values to sum type, stopping unwrapping then loop_label string // set when inside a labelled for loop timers &util.Timers = util.new_timers(false) + comptime_fields_type map[string]table.Type } pub fn new_checker(table &table.Table, pref &pref.Preferences) Checker { @@ -979,6 +980,9 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) { // TODO return '', pos } + ast.ComptimeSelector { + return '', pos + } ast.Ident { if expr.obj is ast.Var { mut v := expr.obj as ast.Var @@ -3201,10 +3205,26 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type { } if node.method_name == 'html' { return c.table.find_type_idx('vweb.Result') - } else { - return table.string_type } - // return table.void_type + return table.string_type + } + ast.ComptimeSelector { + node.left_type = c.unwrap_generic(c.expr(node.left)) + expr_type := c.unwrap_generic(c.expr(node.field_expr)) + expr_sym := c.table.get_type_symbol(expr_type) + if expr_type != table.string_type { + c.error('expected `string` instead of `$expr_sym.name` (e.g. `field.name`)', + node.field_expr.position()) + } + if node.field_expr is ast.SelectorExpr { + expr_name := node.field_expr.expr.str() + if expr_name in c.comptime_fields_type { + return c.comptime_fields_type[expr_name] + } + } + c.error('compile time field access can only be used when iterating over `T.fields`', + node.field_expr.position()) + return table.void_type } ast.ConcatExpr { return c.concat_expr(mut node) @@ -4210,6 +4230,14 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type { } } if node.is_comptime { // Skip checking if needed + // smartcast field type on comptime if + if branch.cond is ast.InfixExpr { + if branch.cond.op == .key_is { + se := branch.cond.left as ast.SelectorExpr + got_type := (branch.cond.right as ast.Type).typ + c.comptime_fields_type[se.expr.str()] = got_type + } + } cur_skip_flags := c.skip_flags if found_branch { c.skip_flags = true diff --git a/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.out b/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.out new file mode 100644 index 0000000000..e8e07aed9d --- /dev/null +++ b/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/comptime_field_selector_not_in_for_err.vv:9:5: error: compile time field access can only be used when iterating over `T.fields` + 7 | mut t := T{} + 8 | name := 'test' + 9 | t.$name = '3' + | ~~~~ + 10 | } + 11 | \ No newline at end of file diff --git a/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.vv b/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.vv new file mode 100644 index 0000000000..9f6dd63ba7 --- /dev/null +++ b/vlib/v/checker/tests/comptime_field_selector_not_in_for_err.vv @@ -0,0 +1,14 @@ +struct Foo { + test int + name string +} + +fn test() { + mut t := T{} + name := 'test' + t.$name = '3' +} + +fn main() { + test() +} diff --git a/vlib/v/checker/tests/comptime_field_selector_not_name_err.out b/vlib/v/checker/tests/comptime_field_selector_not_name_err.out new file mode 100644 index 0000000000..2bedf135e0 --- /dev/null +++ b/vlib/v/checker/tests/comptime_field_selector_not_name_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/comptime_field_selector_not_name_err.vv:10:7: error: expected `string` instead of `FieldData` (e.g. `field.name`) + 8 | $for f in T.fields { + 9 | $if f.typ is string { + 10 | t.$f = '3' + | ^ + 11 | } + 12 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/comptime_field_selector_not_name_err.vv b/vlib/v/checker/tests/comptime_field_selector_not_name_err.vv new file mode 100644 index 0000000000..3683352fb5 --- /dev/null +++ b/vlib/v/checker/tests/comptime_field_selector_not_name_err.vv @@ -0,0 +1,17 @@ +struct Foo { + test int + name string +} + +fn test() { + mut t := T{} + $for f in T.fields { + $if f.typ is string { + t.$f = '3' + } + } +} + +fn main() { + test() +} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 2f60d11f3b..6511669560 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -924,9 +924,18 @@ pub fn (mut f Fmt) expr(node ast.Expr) { f.write("\$tmpl('$node.args_var')") } } else { - f.write('${node.left}.\$${node.method_name}($node.args_var)') + method_expr := if node.has_parens { + '(${node.method_name}($node.args_var))' + } else { + '${node.method_name}($node.args_var)' + } + f.write('${node.left}.$$method_expr') } } + ast.ComptimeSelector { + field_expr := if node.has_parens { '($node.field_expr)' } else { node.field_expr.str() } + f.write('${node.left}.$$field_expr') + } ast.ConcatExpr { for i, val in node.vals { if i != 0 { diff --git a/vlib/v/fmt/tests/comptime_field_selector_keep.vv b/vlib/v/fmt/tests/comptime_field_selector_keep.vv new file mode 100644 index 0000000000..5d3e177da8 --- /dev/null +++ b/vlib/v/fmt/tests/comptime_field_selector_keep.vv @@ -0,0 +1,23 @@ +struct Foo { +mut: + test string + name string +} + +fn (f Foo) print() { + println('test') +} + +fn test() { + mut t := T{} + t.name = '2' + $for f in T.fields { + $if f.typ is string { + println(t.$f.name) + } + } +} + +fn main() { + test() +} diff --git a/vlib/v/fmt/tests/comptime_field_selector_parentheses_keep.vv b/vlib/v/fmt/tests/comptime_field_selector_parentheses_keep.vv new file mode 100644 index 0000000000..e9752dee84 --- /dev/null +++ b/vlib/v/fmt/tests/comptime_field_selector_parentheses_keep.vv @@ -0,0 +1,23 @@ +struct Foo { +mut: + test string + name string +} + +fn (f Foo) print() { + println('test') +} + +fn test() { + mut t := T{} + t.name = '2' + $for f in T.fields { + $if f.typ is string { + println(t.$(f.name)) + } + } +} + +fn main() { + test() +} diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 89c7819d78..86ee4aea52 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -109,7 +109,10 @@ mut: inside_call bool has_main bool inside_const bool - comp_for_method string // $for method in T { + comp_for_method string // $for method in T.methods {} + comp_for_field_var string // $for field in T.fields {}; the variable name + comp_for_field_value table.Field // value of the field variable + comp_for_field_type table.Type // type of the field variable inferred from `$if field.typ is T {}` comptime_var_type_map map[string]table.Type // tmp_arg_vars_to_free []string // autofree_pregen map[string]string @@ -2491,6 +2494,9 @@ fn (mut g Gen) expr(node ast.Expr) { ast.ComptimeCall { g.comptime_call(node) } + ast.ComptimeSelector { + g.comptime_selector(node) + } ast.Comment {} ast.ConcatExpr { g.concat_expr(node) diff --git a/vlib/v/gen/comptime.v b/vlib/v/gen/comptime.v index 2c02035bbf..944900e294 100644 --- a/vlib/v/gen/comptime.v +++ b/vlib/v/gen/comptime.v @@ -7,6 +7,26 @@ import v.ast import v.table import v.util +fn (mut g Gen) comptime_selector(node ast.ComptimeSelector) { + g.expr(node.left) + if node.left_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + // check for field.name + if node.field_expr is ast.SelectorExpr { + if node.field_expr.expr is ast.Ident { + if node.field_expr.expr.name == g.comp_for_field_var && + node.field_expr.field_name == 'name' { + g.write(g.comp_for_field_value.name) + return + } + } + } + g.expr(node.field_expr) +} + fn (mut g Gen) comptime_call(node ast.ComptimeCall) { if node.is_vweb { is_html := node.method_name == 'html' @@ -314,17 +334,17 @@ fn (mut g Gen) comp_for(node ast.CompFor) { } } else if node.kind == .fields { // TODO add fields - // TODO: temporary, remove this - sym_info := sym.info - if sym_info is table.Struct { - mut fields := sym_info.fields.filter(it.attrs.len == 0) - fields_with_attrs := sym_info.fields.filter(it.attrs.len > 0) + if sym.info is table.Struct { + mut fields := sym.info.fields.filter(it.attrs.len == 0) + fields_with_attrs := sym.info.fields.filter(it.attrs.len > 0) fields << fields_with_attrs if fields.len > 0 { g.writeln('\tFieldData $node.val_var;') g.writeln('\tmemset(&$node.val_var, 0, sizeof(FieldData));') } for field in fields { + g.comp_for_field_var = node.val_var + g.comp_for_field_value = field g.writeln('\t// field $i') g.writeln('\t${node.val_var}.name = _SLIT("$field.name");') if field.attrs.len == 0 { diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 924eb147d2..b8d29b7e33 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -573,6 +573,9 @@ fn (mut g JsGen) expr(node ast.Expr) { ast.ComptimeCall { // TODO } + ast.ComptimeSelector { + // TODO + } ast.UnsafeExpr { g.expr(node.expr) } diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index 507c3423aa..c5865c618b 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -263,53 +263,44 @@ fn os_from_string(os string) pref.OS { return .linux } -// `app.$action()` (`action` is a string) -// `typ` is `App` in this example -// fn (mut p Parser) comptime_method_call(typ table.Type) ast.ComptimeCall { -fn (mut p Parser) comptime_method_call(left ast.Expr) ast.ComptimeCall { +fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr { p.check(.dollar) - method_name := p.check_name() - /* - mut j := 0 - sym := p.table.get_type_symbol(typ) - if sym.kind != .struct_ { - p.error('not a struct') + mut has_parens := false + if p.tok.kind == .lpar { + p.check(.lpar) + has_parens = true } - // info := sym.info as table.Struct - for method in sym.methods { - if method.return_type != table.void_type { - continue + if p.peek_tok.kind == .lpar { + method_name := p.check_name() + // `app.$action()` (`action` is a string) + if has_parens { + p.check(.rpar) } - /* - receiver := method.args[0] - if !p.expr_var.ptr { - p.error('`$p.expr_var.name` needs to be a reference') + p.check(.lpar) + mut args_var := '' + if p.tok.kind == .name { + args_var = p.tok.lit + p.next() } - amp := if receiver.is_mut && !p.expr_var.ptr { '&' } else { '' } - if j > 0 { - p.gen(' else ') + p.check(.rpar) + if p.tok.kind == .key_orelse { + p.check(.key_orelse) + p.check(.lcbr) + } + return ast.ComptimeCall{ + has_parens: has_parens + left: left + method_name: method_name + args_var: args_var } - p.genln('if (string_eq($method_name, _STR("$method.name")) ) ' + '${typ.name}_$method.name ($amp $p.expr_var.name);') - */ - j++ } - */ - p.check(.lpar) - mut args_var := '' - if p.tok.kind == .name { - args_var = p.tok.lit - p.next() + expr := p.expr(0) + if has_parens { + p.check(.rpar) } - p.check(.rpar) - if p.tok.kind == .key_orelse { - p.check(.key_orelse) - // p.genln('else {') - p.check(.lcbr) - // p.statements() - } - return ast.ComptimeCall{ + return ast.ComptimeSelector{ + has_parens: has_parens left: left - method_name: method_name - args_var: args_var + field_expr: expr } } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 1d8239da08..d1cfd5e352 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1413,7 +1413,7 @@ fn (mut p Parser) scope_register_ab() { fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { p.next() if p.tok.kind == .dollar { - return p.comptime_method_call(left) + return p.comptime_selector(left) } is_generic_call := p.is_generic_call() name_pos := p.tok.position() diff --git a/vlib/v/tests/comptime_field_selector_test.v b/vlib/v/tests/comptime_field_selector_test.v new file mode 100644 index 0000000000..94fbc2ad95 --- /dev/null +++ b/vlib/v/tests/comptime_field_selector_test.v @@ -0,0 +1,62 @@ +struct Foo { + immutable int +mut: + test string + name string +} + +fn comptime_field_selector_read() []string { + mut t := T{} + t.name = '2' + t.test = '1' + mut value_list := []string{} + $for f in T.fields { + $if f.typ is string { + value_list << t.$f.name + } + } + return value_list +} + +fn test_comptime_field_selector_read() { + assert comptime_field_selector_read() == ['1', '2'] +} + +fn comptime_field_selector_write() T { + mut t := T{} + $for f in T.fields { + $if f.typ is string { + t.$f.name = '1' + } + $if f.typ is int { + t.$f.name = 1 + } + } + return t +} + +fn test_comptime_field_selector_write() { + res := comptime_field_selector_write() + assert res.immutable == 1 + assert res.test == '1' + assert res.name == '1' +} + +struct Foo2 { + f Foo +} + +fn nested_with_parentheses() T { + mut t := T{} + $for f in T.fields { + $if f.typ is Foo { + t.$(f.name).test = '1' + } + } + return t +} + +fn test_nested_with_parentheses() { + res := nested_with_parentheses() + assert res.f.test == '1' +}