From 402e55d115a9edc76ca786a680ab5a9cc359ff63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=C3=A4schle?= Date: Fri, 17 Apr 2020 02:38:39 +0200 Subject: [PATCH] ast: expr.Position; struct field refactoring --- vlib/v/ast/ast.v | 103 ++++++++++- vlib/v/checker/checker.v | 162 ++++-------------- .../tests/inout/short_struct_too_many.out | 6 + .../tests/inout/short_struct_too_many.vv | 7 + .../tests/inout/struct_unknown_field.out | 7 + .../tests/inout/struct_unknown_field.vv | 10 ++ vlib/v/fmt/fmt.v | 12 +- vlib/v/gen/cgen.v | 14 +- vlib/v/gen/js/js.v | 32 ++-- vlib/v/parser/parser.v | 43 +++-- 10 files changed, 223 insertions(+), 173 deletions(-) create mode 100644 vlib/v/checker/tests/inout/short_struct_too_many.out create mode 100644 vlib/v/checker/tests/inout/short_struct_too_many.vv create mode 100644 vlib/v/checker/tests/inout/struct_unknown_field.out create mode 100644 vlib/v/checker/tests/inout/struct_unknown_field.vv diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index dd65d49f64..ce2c6135bd 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -157,15 +157,23 @@ pub: field_names []string } +pub struct StructInitField { +pub: + name string + expr Expr + pos token.Position +mut: + typ table.Type + expected_type table.Type +} + pub struct StructInit { pub: - pos token.Position - fields []string - exprs []Expr + pos token.Position + fields []StructInitField + is_short bool mut: - typ table.Type - expr_types []table.Type - expected_types []table.Type + typ table.Type } // import statement @@ -723,3 +731,86 @@ pub fn expr_is_call(expr Expr) bool { } } } + +fn (expr Expr) position() token.Position { + // all uncommented have to be implemented + match mut expr { + ArrayInit { + return it.pos + } + AsCast { + return it.pos + } + // ast.Ident { } + AssignExpr { + return it.pos + } + // ast.CastExpr { } + Assoc { + return it.pos + } + // ast.BoolLiteral { } + CallExpr { + return it.pos + } + // ast.CharLiteral { } + EnumVal { + return it.pos + } + // ast.FloatLiteral { } + IfExpr { + return it.pos + } + // ast.IfGuardExpr { } + IndexExpr { + return it.pos + } + InfixExpr { + left_pos := it.left.position() + right_pos := it.right.position() + if left_pos.pos == 0 || right_pos.pos == 0 { + return it.pos + } + return token.Position{ + line_nr: it.pos.line_nr + pos: left_pos.pos + len: right_pos.pos - left_pos.pos + right_pos.len + } + } + IntegerLiteral { + return it.pos + } + MapInit { + return it.pos + } + MatchExpr { + return it.pos + } + PostfixExpr { + return it.pos + } + // ast.None { } + PrefixExpr { + return it.pos + } + // ast.ParExpr { } + SelectorExpr { + return it.pos + } + // ast.SizeOf { } + StringLiteral { + return it.pos + } + StringInterLiteral { + return it.pos + } + // ast.Type { } + StructInit { + return it.pos + } + // ast.TypeOf { } + else { + return token.Position{} + } + } +} diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 3907251c0c..a53aa20093 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -123,56 +123,51 @@ pub fn (c mut Checker) struct_init(struct_init mut ast.StructInit) table.Type { // string & array are also structs but .kind of string/array .struct_, .string, .array { info := type_sym.info as table.Struct - is_short_syntax := struct_init.fields.len == 0 - if struct_init.exprs.len > info.fields.len { + if struct_init.is_short && struct_init.fields.len > info.fields.len { c.error('too many fields', struct_init.pos) } mut inited_fields := []string - for i, expr in struct_init.exprs { - if is_short_syntax && i >= info.fields.len { - // It doesn't make sense to check for fields that don't exist. - // We should just stop here. - break - } - // struct_field info. - field_name := if is_short_syntax { info.fields[i].name } else { struct_init.fields[i] } - if field_name in inited_fields { - c.error('duplicate field name in struct literal: `$field_name`', struct_init.pos) - continue - } - inited_fields << field_name - mut field := if is_short_syntax { - info.fields[i] + for i, field in struct_init.fields { + mut info_field := table.Field{} + mut field_name := '' + if struct_init.is_short { + if i >= info.fields.len { + // It doesn't make sense to check for fields that don't exist. + // We should just stop here. + break + } + info_field = info.fields[i] + field_name = info_field.name + struct_init.fields[i].name = field_name } else { - // There is no guarantee that `i` will not be out of bounds of `info.fields` - // So we just use an empty field as placeholder here. - table.Field{} - } - if !is_short_syntax { - mut found_field := false + field_name = field.name + mut exists := false for f in info.fields { if f.name == field_name { - field = f - found_field = true + info_field = f + exists = true break } } - if !found_field { - c.error('struct init: no such field `$field_name` for struct `$type_sym.name`', - struct_init.pos) + if !exists { + c.error('struct init: no such field `$field.name` for struct `$type_sym.name`', field.pos) + continue + } + if field_name in inited_fields { + c.error('duplicate field name in struct literal: `$field_name`', field.pos) continue } } - c.expected_type = field.typ - expr_type := c.expr(expr) + inited_fields << field_name + c.expected_type = info_field.typ + expr_type := c.expr(field.expr) expr_type_sym := c.table.get_type_symbol(expr_type) - field_type_sym := c.table.get_type_symbol(field.typ) - if !c.table.check(expr_type, field.typ) { - c.error('cannot assign `$expr_type_sym.name` as `$field_type_sym.name` for field `$field.name`', - struct_init.pos) + field_type_sym := c.table.get_type_symbol(info_field.typ) + if !c.table.check(expr_type, info_field.typ) { + c.error('cannot assign `$expr_type_sym.name` as `$field_type_sym.name` for field `$info_field.name`', field.pos) } - struct_init.expr_types << expr_type - struct_init.expected_types << field.typ + struct_init.fields[i].typ = expr_type + struct_init.fields[i].expected_type = info_field.typ } // Check uninitialized refs for field in info.fields { @@ -206,7 +201,7 @@ pub fn (c mut Checker) infix_expr(infix_expr mut ast.InfixExpr) table.Type { if infix_expr.op == .left_shift { if left.kind != .array && !left.is_int() { // c.error('<< can only be used with numbers and arrays', infix_expr.pos) - c.error('cannot shift type $right.name into $left.name', expr_pos(infix_expr.right)) + c.error('cannot shift type $right.name into $left.name', infix_expr.right.position()) return table.void_type } if left.kind == .array { @@ -220,7 +215,7 @@ pub fn (c mut Checker) infix_expr(infix_expr mut ast.InfixExpr) table.Type { // []T << []T return table.void_type } - c.error('cannot shift type $right.name into $left.name', expr_pos(infix_expr.right)) + c.error('cannot shift type $right.name into $left.name', infix_expr.right.position()) return table.void_type } } @@ -279,7 +274,7 @@ fn (c mut Checker) assign_expr(assign_expr mut ast.AssignExpr) { left_type_sym := c.table.get_type_symbol(left_type) right_type_sym := c.table.get_type_symbol(right_type) c.error('cannot assign `$right_type_sym.name` to variable `${assign_expr.left.str()}` of type `$left_type_sym.name`', - expr_pos(assign_expr.val)) + assign_expr.val.position()) } c.check_expr_opt_call(assign_expr.val, right_type, true) } @@ -662,7 +657,7 @@ pub fn (c mut Checker) enum_decl(decl ast.EnumDecl) { ast.IntegerLiteral {} ast.PrefixExpr {} else { - mut pos := expr_pos(field.expr) + mut pos := field.expr.position() if pos.pos == 0 { pos = field.pos } @@ -999,7 +994,7 @@ fn (c mut Checker) stmt(node ast.Stmt) { value_type := c.table.value_type(typ) if value_type == table.void_type { typ_sym := c.table.get_type_symbol(typ) - c.error('for in: cannot index `$typ_sym.name`', expr_pos(it.cond)) + c.error('for in: cannot index `$typ_sym.name`', it.cond.position()) } it.cond_type = typ it.kind = sym.kind @@ -1011,7 +1006,7 @@ fn (c mut Checker) stmt(node ast.Stmt) { } ast.GoStmt { if !is_call_expr(it.call_expr) { - c.error('expression in `go` must be a function call', expr_pos(it.call_expr))1 + c.error('expression in `go` must be a function call', it.call_expr.position()) } c.expr(it.call_expr) } @@ -1196,89 +1191,6 @@ pub fn (c mut Checker) expr(node ast.Expr) table.Type { return table.void_type } -fn expr_pos(node ast.Expr) token.Position { - // all uncommented have to be implemented - match mut node { - ast.ArrayInit { - return it.pos - } - ast.AsCast { - return it.pos - } - // ast.Ident { } - ast.AssignExpr { - return it.pos - } - // ast.CastExpr { } - ast.Assoc { - return it.pos - } - // ast.BoolLiteral { } - ast.CallExpr { - return it.pos - } - // ast.CharLiteral { } - ast.EnumVal { - return it.pos - } - // ast.FloatLiteral { } - ast.IfExpr { - return it.pos - } - // ast.IfGuardExpr { } - ast.IndexExpr { - return it.pos - } - ast.InfixExpr { - left_pos := expr_pos(it.left) - right_pos := expr_pos(it.right) - if left_pos.pos == 0 || right_pos.pos == 0 { - return it.pos - } - return token.Position{ - line_nr: it.pos.line_nr - pos: left_pos.pos - len: right_pos.pos - left_pos.pos + right_pos.len - } - } - ast.IntegerLiteral { - return it.pos - } - ast.MapInit { - return it.pos - } - ast.MatchExpr { - return it.pos - } - ast.PostfixExpr { - return it.pos - } - // ast.None { } - ast.PrefixExpr { - return it.pos - } - // ast.ParExpr { } - ast.SelectorExpr { - return it.pos - } - // ast.SizeOf { } - ast.StringLiteral { - return it.pos - } - ast.StringInterLiteral { - return it.pos - } - // ast.Type { } - ast.StructInit { - return it.pos - } - // ast.TypeOf { } - else { - return token.Position{} - } - } -} - pub fn (c mut Checker) ident(ident mut ast.Ident) table.Type { if ident.name == c.var_decl_name { // c.checked_ident { c.error('unresolved: `$ident.name`', ident.pos) diff --git a/vlib/v/checker/tests/inout/short_struct_too_many.out b/vlib/v/checker/tests/inout/short_struct_too_many.out new file mode 100644 index 0000000000..b403965637 --- /dev/null +++ b/vlib/v/checker/tests/inout/short_struct_too_many.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/inout/short_struct_too_many.v:6:7: error: too many fields + 4| + 5| fn main() { + 6| t := Test{true, false} + ~~~~~~~~~~~~~~~~~ + 7| } \ No newline at end of file diff --git a/vlib/v/checker/tests/inout/short_struct_too_many.vv b/vlib/v/checker/tests/inout/short_struct_too_many.vv new file mode 100644 index 0000000000..a707dc3513 --- /dev/null +++ b/vlib/v/checker/tests/inout/short_struct_too_many.vv @@ -0,0 +1,7 @@ +struct Test { + foo bool +} + +fn main() { + t := Test{true, false} +} diff --git a/vlib/v/checker/tests/inout/struct_unknown_field.out b/vlib/v/checker/tests/inout/struct_unknown_field.out new file mode 100644 index 0000000000..9f6f87ab24 --- /dev/null +++ b/vlib/v/checker/tests/inout/struct_unknown_field.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/inout/struct_unknown_field.v:8:9: error: struct init: no such field `bar` for struct `Test` + 6| t := Test{ + 7| foo: true + 8| bar: false + ^ + 9| } + 10| } \ No newline at end of file diff --git a/vlib/v/checker/tests/inout/struct_unknown_field.vv b/vlib/v/checker/tests/inout/struct_unknown_field.vv new file mode 100644 index 0000000000..8a1145d01e --- /dev/null +++ b/vlib/v/checker/tests/inout/struct_unknown_field.vv @@ -0,0 +1,10 @@ +struct Test { + foo bool +} + +fn main() { + t := Test{ + foo: true + bar: false + } +} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 0b3d27364b..48c20f51b1 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -695,15 +695,15 @@ fn (f mut Fmt) expr(node ast.Expr) { if name == 'void' { name = '' } - if it.fields.len == 0 && it.exprs.len == 0 { + if it.fields.len == 0 { // `Foo{}` on one line if there are no fields f.write('$name{}') } else if it.fields.len == 0 { // `Foo{1,2,3}` (short syntax ) f.write('$name{') - for i, expr in it.exprs { - f.expr(expr) - if i < it.exprs.len - 1 { + for i, field in it.fields { + f.expr(field.expr) + if i < it.fields.len - 1 { f.write(', ') } } @@ -712,8 +712,8 @@ fn (f mut Fmt) expr(node ast.Expr) { f.writeln('$name{') f.indent++ for i, field in it.fields { - f.write('$field: ') - f.expr(it.exprs[i]) + f.write('$field.name: ') + f.expr(field.expr) f.writeln('') } f.indent-- diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 58b05d76e7..c08c86277f 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -1918,22 +1918,22 @@ fn (var g Gen) struct_init(struct_init ast.StructInit) { } else { g.writeln('($styp){') } - var fields := []string + // var fields := []string var inited_fields := []string // TODO this is done in checker, move to ast node - if struct_init.fields.len == 0 && struct_init.exprs.len > 0 { + /*if struct_init.fields.len == 0 && struct_init.exprs.len > 0 { // Get fields for {a,b} short syntax. Fields array wasn't set in the parser. for f in info.fields { fields << f.name } } else { fields = struct_init.fields - } + }*/ // User set fields - for i, field in fields { - field_name := c_name(field) - inited_fields << field + for i, field in struct_init.fields { + field_name := c_name(field.name) + inited_fields << field.name g.write('\t.$field_name = ') - g.expr_with_cast(struct_init.exprs[i], struct_init.expr_types[i], struct_init.expected_types[i]) + g.expr_with_cast(field.expr, field.typ, field.expected_type) g.writeln(',') } // The rest of the fields are zeroed. diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 1d82d54780..6812c0d2b8 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -27,7 +27,7 @@ struct JsGen { pref &pref.Preferences doc &JsDoc mut: - constants strings.Builder // all global V constants + constants strings.Builder // all global V constants file ast.File tmp_count int inside_ternary bool @@ -178,7 +178,7 @@ fn (g mut JsGen) to_js_typ(typ string) string { } 'voidptr' { styp = 'Object' - } + } 'byteptr' { styp = 'string' } @@ -196,7 +196,7 @@ fn (g mut JsGen) to_js_typ(typ string) string { } } return styp -} +} pub fn (g &JsGen) save() {} @@ -385,12 +385,12 @@ fn (g mut JsGen) expr(node ast.Expr) { } ast.InfixExpr { g.expr(it.left) - + mut op := it.op.str() // in js == is non-strict & === is strict, always do strict if op == '==' { op = '===' } else if op == '!=' { op = '!==' } - + g.write(' $op ') g.expr(it.right) } @@ -461,7 +461,7 @@ fn (g mut JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { .struct_ { g.expr(expr) if sym.has_method('str') { - g.write('.str()') + g.write('.str()') } } else { @@ -543,7 +543,7 @@ fn (g mut JsGen) gen_assign_stmt(it ast.AssignStmt) { val := it.right[i] ident_var_info := ident.var_info() mut styp := g.typ(ident_var_info.typ) - + match val { ast.EnumVal { // we want the type of the enum value not the enum @@ -554,11 +554,11 @@ fn (g mut JsGen) gen_assign_stmt(it ast.AssignStmt) { styp = '' } else {} } - + if !g.inside_loop && styp.len > 0 { g.writeln(g.doc.gen_typ(styp, ident.name)) } - + if g.inside_loop || ident.is_mut { g.write('let ') } else { @@ -726,7 +726,7 @@ fn (g mut JsGen) gen_method_decl(it ast.FnDecl) { g.write(')();') } g.writeln('') - + g.fn_decl = 0 } @@ -835,7 +835,7 @@ fn (g mut JsGen) fn_args(args []table.Arg, is_variadic bool) { fn (g mut JsGen) gen_go_stmt(node ast.GoStmt) { // x := node.call_expr as ast.CallEpxr // TODO match node.call_expr { - ast.CallExpr { + ast.CallExpr { mut name := it.name if it.is_method { receiver_sym := g.table.get_type_symbol(it.receiver_type) @@ -964,10 +964,10 @@ fn (g mut JsGen) gen_struct_init(it ast.StructInit) { g.writeln('new ${type_sym.name}({') g.inc_indent() for i, field in it.fields { - g.write('$field: ') - g.expr(it.exprs[i]) + g.write('$field.name: ') + g.expr(field.expr) if i < it.fields.len - 1 { - g.write(', ') + g.write(', ') } g.writeln('') } @@ -975,7 +975,7 @@ fn (g mut JsGen) gen_struct_init(it ast.StructInit) { g.write('})') } -fn (g mut JsGen) gen_ident(node ast.Ident) { +fn (g mut JsGen) gen_ident(node ast.Ident) { if node.kind == .constant { g.write('CONSTANTS.') } @@ -1061,4 +1061,4 @@ fn fn_has_go(it ast.FnDecl) bool { } } return has_go -} \ No newline at end of file +} diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 667e53ac26..551a414e26 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -570,6 +570,7 @@ pub fn (var p Parser) parse_ident(is_c, is_js bool) ast.Ident { } fn (var p Parser) struct_init(short_syntax bool) ast.StructInit { + first_pos := p.tok.position() typ := if short_syntax { table.void_type } else { p.parse_type() } p.expr_mod = '' // sym := p.table.get_type_symbol(typ) @@ -577,8 +578,7 @@ fn (var p Parser) struct_init(short_syntax bool) ast.StructInit { if !short_syntax { p.check(.lcbr) } - var field_names := []string - var exprs := []ast.Expr + var fields := []ast.StructInitField var i := 0 is_short_syntax := p.peek_tok.kind != .colon && p.tok.kind != .rcbr // `Vec{a,b,c} // p.warn(is_short_syntax.str()) @@ -587,15 +587,27 @@ fn (var p Parser) struct_init(short_syntax bool) ast.StructInit { var field_name := '' if is_short_syntax { expr := p.expr(0) - exprs << expr + fields << ast.StructInitField{ + // name will be set later in checker + expr: expr + pos: expr.position() + } } else { + first_field_pos := p.tok.position() field_name = p.check_name() - field_names << field_name - } - if !is_short_syntax { p.check(.colon) expr := p.expr(0) - exprs << expr + last_field_pos := expr.position() + field_pos := token.Position{ + line_nr: first_field_pos.line_nr + pos: first_field_pos.pos + len: last_field_pos.pos - first_field_pos.pos + last_field_pos.len + } + fields << ast.StructInitField{ + name: field_name + expr: expr + pos: field_pos + } } i++ if p.tok.kind == .comma { @@ -603,15 +615,20 @@ fn (var p Parser) struct_init(short_syntax bool) ast.StructInit { } p.check_comment() } - node := ast.StructInit{ - typ: typ - exprs: exprs - fields: field_names - pos: p.tok.position() - } + last_pos := p.tok.position() if !short_syntax { p.check(.rcbr) } + node := ast.StructInit{ + typ: typ + fields: fields + pos: token.Position{ + line_nr: first_pos.line_nr + pos: first_pos.pos + len: last_pos.pos - first_pos.pos + last_pos.len + } + is_short: is_short_syntax + } return node }