diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index dc63996999..718b1df336 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1402,7 +1402,6 @@ pub mut: pub struct None { pub: pos token.Position - foo int // todo } pub enum SqlStmtKind { diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 2bc64d2b19..57c915417e 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -362,6 +362,9 @@ pub fn (x Expr) str() string { UnsafeExpr { return 'unsafe { $x.expr }' } + None { + return 'none' + } else {} } return '[unhandled expr type $x.type_name()]' diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 49fcaabc87..8806741a05 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -4351,7 +4351,7 @@ pub fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { ast.f64_type }) } - if !c.table.sumtype_has_variant(node.typ, node.expr_type) { + if !c.table.sumtype_has_variant(node.typ, node.expr_type) && !node.typ.has_flag(.optional) { c.error('cannot cast `$from_type_sym.name` to `$to_type_sym.name`', node.pos) } } else if mut to_type_sym.info is ast.Alias { @@ -4400,7 +4400,7 @@ pub fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { c.type_implements(node.expr_type, node.typ, node.pos) } else if node.typ == ast.bool_type { c.error('cannot cast to bool - use e.g. `some_int != 0` instead', node.pos) - } else if node.expr_type == ast.none_type { + } else if node.expr_type == ast.none_type && !node.typ.has_flag(.optional) { type_name := c.table.type_to_str(node.typ) c.error('cannot cast `none` to `$type_name`', node.pos) } else if from_type_sym.kind == .struct_ && !node.expr_type.is_ptr() { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 718d3f4ba0..fdc2a7022c 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1761,6 +1761,10 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ g.write('*') } } + if expected_type.has_flag(.optional) && expr is ast.None { + g.gen_optional_error(expected_type, expr) + return + } // no cast g.expr(expr) } @@ -2902,61 +2906,7 @@ fn (mut g Gen) expr(node ast.Expr) { */ } ast.CastExpr { - // g.write('/*cast*/') - if g.is_amp { - // &Foo(0) => ((Foo*)0) - g.out.go_back(1) - } - g.is_amp = false - sym := g.table.get_type_symbol(node.typ) - if sym.kind == .string && !node.typ.is_ptr() { - // `string(x)` needs `tos()`, but not `&string(x) - // `tos(str, len)`, `tos2(str)` - if node.has_arg { - g.write('tos((byteptr)') - } else { - g.write('tos2((byteptr)') - } - g.expr(node.expr) - expr_sym := g.table.get_type_symbol(node.expr_type) - if expr_sym.kind == .array { - // if we are casting an array, we need to add `.data` - g.write('.data') - } - if node.has_arg { - // len argument - g.write(', ') - g.expr(node.arg) - } - g.write(')') - } else if sym.kind in [.sum_type, .interface_] { - g.expr_with_cast(node.expr, node.expr_type, node.typ) - } else if sym.kind == .struct_ && !node.typ.is_ptr() - && !(sym.info as ast.Struct).is_typedef { - // deprecated, replaced by Struct{...exr} - styp := g.typ(node.typ) - g.write('*(($styp *)(&') - g.expr(node.expr) - g.write('))') - } else { - styp := g.typ(node.typ) - mut cast_label := '' - // `ast.string_type` is done for MSVC's bug - if sym.kind != .alias - || (sym.info as ast.Alias).parent_type !in [node.expr_type, ast.string_type] { - cast_label = '($styp)' - } - g.write('(${cast_label}(') - g.expr(node.expr) - if node.expr is ast.IntegerLiteral { - if node.typ in [ast.u64_type, ast.u32_type, ast.u16_type] { - if !node.expr.val.starts_with('-') { - g.write('U') - } - } - } - g.write('))') - } + g.cast_expr(node) } ast.ChanInit { elem_typ_str := g.typ(node.elem_type) @@ -4403,6 +4353,66 @@ fn (mut g Gen) ident(node ast.Ident) { g.write(g.get_ternary_name(name)) } +fn (mut g Gen) cast_expr(node ast.CastExpr) { + if g.is_amp { + // &Foo(0) => ((Foo*)0) + g.out.go_back(1) + } + g.is_amp = false + sym := g.table.get_type_symbol(node.typ) + if sym.kind == .string && !node.typ.is_ptr() { + // `string(x)` needs `tos()`, but not `&string(x) + // `tos(str, len)`, `tos2(str)` + if node.has_arg { + g.write('tos((byteptr)') + } else { + g.write('tos2((byteptr)') + } + g.expr(node.expr) + expr_sym := g.table.get_type_symbol(node.expr_type) + if expr_sym.kind == .array { + // if we are casting an array, we need to add `.data` + g.write('.data') + } + if node.has_arg { + // len argument + g.write(', ') + g.expr(node.arg) + } + g.write(')') + } else if sym.kind in [.sum_type, .interface_] { + g.expr_with_cast(node.expr, node.expr_type, node.typ) + } else if sym.kind == .struct_ && !node.typ.is_ptr() && !(sym.info as ast.Struct).is_typedef { + // deprecated, replaced by Struct{...exr} + styp := g.typ(node.typ) + g.write('*(($styp *)(&') + g.expr(node.expr) + g.write('))') + } else { + styp := g.typ(node.typ) + mut cast_label := '' + // `ast.string_type` is done for MSVC's bug + if sym.kind != .alias + || (sym.info as ast.Alias).parent_type !in [node.expr_type, ast.string_type] { + cast_label = '($styp)' + } + if node.typ.has_flag(.optional) && node.expr is ast.None { + g.gen_optional_error(node.typ, node.expr) + } else { + g.write('(${cast_label}(') + g.expr(node.expr) + if node.expr is ast.IntegerLiteral { + if node.typ in [ast.u64_type, ast.u32_type, ast.u16_type] { + if !node.expr.val.starts_with('-') { + g.write('U') + } + } + } + g.write('))') + } + } +} + fn (mut g Gen) concat_expr(node ast.ConcatExpr) { styp := g.typ(node.return_type) sym := g.table.get_type_symbol(node.return_type) @@ -4598,6 +4608,13 @@ fn (g &Gen) expr_is_multi_return_call(expr ast.Expr) bool { } } +fn (mut g Gen) gen_optional_error(target_type ast.Type, expr ast.Expr) { + styp := g.typ(target_type) + g.write('($styp){ .state=2, .err=') + g.expr(expr) + g.write(' }') +} + fn (mut g Gen) return_statement(node ast.Return) { g.write_v_source_line_info(node.pos) if node.exprs.len > 0 { @@ -4635,18 +4652,10 @@ fn (mut g Gen) return_statement(node ast.Return) { optional_none := node.exprs[0] is ast.None ftyp := g.typ(node.types[0]) mut is_regular_option := ftyp in ['Option2', 'Option'] - if optional_none || is_regular_option { - styp := g.typ(g.fn_decl.return_type) - g.write('return ($styp){ .state=2, .err=') - g.expr_with_cast(node.exprs[0], node.types[0], g.fn_decl.return_type) - g.writeln(' };') - return - } else if node.types[0] == ast.error_type_idx { - // foo() or { return err } - styp := g.typ(g.fn_decl.return_type) - g.write('return ($styp){.state=2, .err=') - g.expr_with_cast(node.exprs[0], node.types[0], g.fn_decl.return_type) - g.writeln(' };') + if optional_none || is_regular_option || node.types[0] == ast.error_type_idx { + g.write('return ') + g.gen_optional_error(g.fn_decl.return_type, node.exprs[0]) + g.writeln(';') return } } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 8cb73ceb42..9d266dcd43 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1938,14 +1938,16 @@ pub fn (mut p Parser) name_expr() ast.Expr { } else { false } + is_optional := p.tok.kind == .question // p.warn('name expr $p.tok.lit $p.peek_tok.str()') same_line := p.tok.line_nr == p.peek_tok.line_nr // `(` must be on same line as name token otherwise it's a ParExpr if !same_line && p.peek_tok.kind == .lpar { node = p.parse_ident(language) - } else if p.peek_tok.kind == .lpar || p.is_generic_call() { + } else if p.peek_tok.kind == .lpar + || (is_optional && p.peek_token(2).kind == .lpar) || p.is_generic_call() { // foo(), foo() or type() cast - mut name := p.tok.lit + mut name := if is_optional { p.peek_tok.lit } else { p.tok.lit } if mod.len > 0 { name = '${mod}.$name' } @@ -1992,6 +1994,9 @@ pub fn (mut p Parser) name_expr() ast.Expr { } else { // fn call // println('calling $p.tok.lit') + if is_optional { + p.error_with_pos('unexpected $p.prev_tok', p.prev_tok.position()) + } node = p.call_expr(language, mod) } } else if (p.peek_tok.kind == .lcbr || (p.peek_tok.kind == .lt && lit0_is_capital)) diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index 3d77195060..ae1b1d0abf 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -25,7 +25,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { node = p.parse_ident(ast.Language.v) p.is_stmt_ident = is_stmt_ident } - .name { + .name, .question { if p.tok.lit == 'sql' && p.peek_tok.kind == .name { p.inside_match = true // reuse the same var for perf instead of inside_sql TODO rename node = p.sql_expr() diff --git a/vlib/v/tests/optional_assign_test.v b/vlib/v/tests/optional_assign_test.v new file mode 100644 index 0000000000..d191f7b8f6 --- /dev/null +++ b/vlib/v/tests/optional_assign_test.v @@ -0,0 +1,25 @@ +type Foo = int | string + +fn test_optional_none_assign() { + x := ?Foo(none) + // assert x.err == none + // assert !x.has_value +} + +fn test_optional_none_assign_nonsumtype() { + x := ?int(none) + // assert x.err == none + // assert !x.has_value +} + +// TODO: make this working next +/* +fn test_optional_value_assign() { + x := ?Foo('test') +} + +fn test_optional_none_reassign() { + mut x := ?Foo('test') + x = none +} +*/