gen: fix nested `or`

pull/4984/head
Enzo Baldisserri 2020-05-21 22:35:43 +02:00 committed by GitHub
parent d3ce6fd2e7
commit 1633675c11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 224 additions and 110 deletions

View File

@ -2,7 +2,7 @@ import json
struct Employee { struct Employee {
name string name string
age int age int
} }
fn test_simple() { fn test_simple() {
@ -11,23 +11,24 @@ fn test_simple() {
assert s == '{"name":"Peter","age":28}' assert s == '{"name":"Peter","age":28}'
y := json.decode(Employee, s) or { y := json.decode(Employee, s) or {
assert false assert false
Employee{}
} }
assert y.name == 'Peter' assert y.name == 'Peter'
assert y.age == 28 assert y.age == 28
} }
struct User2 { struct User2 {
age int age int
nums []int nums []int
} }
struct User { struct User {
age int age int
nums []int nums []int
last_name string [json:lastName] last_name string [json:lastName]
is_registered bool [json:IsRegistered] is_registered bool [json:IsRegistered]
typ int [json:'type'] typ int [json:'type']
pets string [raw; json:'pet_animals'] pets string [raw; json:'pet_animals']
} }
fn test_parse_user() { fn test_parse_user() {
@ -51,8 +52,15 @@ fn test_parse_user() {
assert u.pets == '{"name":"Bob","animal":"Dog"}' assert u.pets == '{"name":"Bob","animal":"Dog"}'
} }
fn test_encode_user(){ fn test_encode_user() {
usr := User{ age: 10, nums: [1,2,3], last_name: 'Johnson', is_registered: true, typ: 0, pets: 'foo'} usr := User{
age: 10
nums: [1, 2, 3]
last_name: 'Johnson'
is_registered: true
typ: 0
pets: 'foo'
}
expected := '{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"foo"}' expected := '{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"foo"}'
out := json.encode(usr) out := json.encode(usr)
println(out) println(out)
@ -60,17 +68,17 @@ fn test_encode_user(){
} }
struct Color { struct Color {
space string space string
point string [raw] point string [raw]
} }
fn test_raw_json_field() { fn test_raw_json_field() {
color := json.decode(Color, '{"space": "YCbCr", "point": {"Y": 123}}') or { color := json.decode(Color, '{"space": "YCbCr", "point": {"Y": 123}}') or {
println('text') println('text')
return return
} }
assert color.point == '{"Y":123}' assert color.point == '{"Y":123}'
assert color.space == 'YCbCr' assert color.space == 'YCbCr'
} }
struct City { struct City {
@ -79,7 +87,7 @@ struct City {
struct Country { struct Country {
cities []City cities []City
name string name string
} }
fn test_struct_in_struct() { fn test_struct_in_struct() {
@ -92,6 +100,4 @@ fn test_struct_in_struct() {
assert country.cities[0].name == 'London' assert country.cities[0].name == 'London'
assert country.cities[1].name == 'Manchester' assert country.cities[1].name == 'Manchester'
println(country.cities) println(country.cities)
} }

View File

@ -43,8 +43,9 @@ pub:
pub struct ExprStmt { pub struct ExprStmt {
pub: pub:
expr Expr expr Expr
typ table.Type
pos token.Position pos token.Position
pub mut:
typ table.Type
} }
pub struct IntegerLiteral { pub struct IntegerLiteral {

View File

@ -848,7 +848,9 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
c.error('json.decode: second argument needs to be a string', call_expr.pos) c.error('json.decode: second argument needs to be a string', call_expr.pos)
} }
typ := expr as ast.Type typ := expr as ast.Type
return typ.typ.set_flag(.optional) ret_type := typ.typ.set_flag(.optional)
call_expr.return_type = ret_type
return ret_type
} }
// look for function in format `mod.fn` or `fn` (main/builtin) // look for function in format `mod.fn` or `fn` (main/builtin)
mut f := table.Fn{} mut f := table.Fn{}
@ -1012,17 +1014,15 @@ fn (mut c Checker) type_implements(typ, inter_typ table.Type, pos token.Position
} }
} }
pub fn (mut c Checker) check_expr_opt_call(x ast.Expr, xtype table.Type, is_return_used bool) { pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, xtype table.Type, is_return_used bool) {
match x { if expr is ast.CallExpr {
ast.CallExpr { call_expr := expr as ast.CallExpr
if it.return_type.flag_is(.optional) { if call_expr.return_type.flag_is(.optional) {
c.check_or_block(it, xtype, is_return_used) c.check_or_block(call_expr, xtype, is_return_used)
} else if it.or_block.is_used && it.name != 'json.decode' { // TODO remove decode hack } else if call_expr.or_block.is_used {
c.error('unexpected `or` block, the function `$it.name` does not return an optional', c.error('unexpected `or` block, the function `$call_expr.name` does not return an optional',
it.pos) call_expr.pos)
}
} }
else {}
} }
} }
@ -1052,12 +1052,13 @@ pub fn (mut c Checker) check_or_block(mut call_expr ast.CallExpr, ret_type table
} }
match last_stmt { match last_stmt {
ast.ExprStmt { ast.ExprStmt {
type_fits := c.table.check(c.expr(it.expr), ret_type) it.typ = c.expr(it.expr)
type_fits := c.table.check(it.typ, ret_type)
is_panic_or_exit := is_expr_panic_or_exit(it.expr) is_panic_or_exit := is_expr_panic_or_exit(it.expr)
if type_fits || is_panic_or_exit { if type_fits || is_panic_or_exit {
return return
} }
type_name := c.table.get_type_symbol(c.expr(it.expr)).name type_name := c.table.get_type_symbol(it.typ).name
expected_type_name := c.table.get_type_symbol(ret_type).name expected_type_name := c.table.get_type_symbol(ret_type).name
c.error('wrong return type `$type_name` in the `or {}` block, expected `$expected_type_name`', c.error('wrong return type `$type_name` in the `or {}` block, expected `$expected_type_name`',
it.pos) it.pos)
@ -1509,9 +1510,9 @@ fn (mut c Checker) stmt(node ast.Stmt) {
c.enum_decl(it) c.enum_decl(it)
} }
ast.ExprStmt { ast.ExprStmt {
etype := c.expr(it.expr) it.typ = c.expr(it.expr)
c.expected_type = table.void_type c.expected_type = table.void_type
c.check_expr_opt_call(it.expr, etype, false) c.check_expr_opt_call(it.expr, it.typ, false)
} }
ast.FnDecl { ast.FnDecl {
c.fn_decl(it) c.fn_decl(it)
@ -1986,6 +1987,7 @@ pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) table.Type {
match branch.stmts[branch.stmts.len - 1] { match branch.stmts[branch.stmts.len - 1] {
ast.ExprStmt { ast.ExprStmt {
ret_type = c.expr(it.expr) ret_type = c.expr(it.expr)
it.typ = ret_type
} }
else { else {
// TODO: ask alex about this // TODO: ask alex about this
@ -2106,14 +2108,14 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
if branch.stmts.len > 0 && branch.stmts[branch.stmts.len - 1] is ast.ExprStmt { if branch.stmts.len > 0 && branch.stmts[branch.stmts.len - 1] is ast.ExprStmt {
last_expr := branch.stmts[branch.stmts.len - 1] as ast.ExprStmt last_expr := branch.stmts[branch.stmts.len - 1] as ast.ExprStmt
c.expected_type = former_expected_type c.expected_type = former_expected_type
expr_type := c.expr(last_expr.expr) last_expr.typ = c.expr(last_expr.expr)
if expr_type != node.typ { if last_expr.typ != node.typ {
// first branch of if expression
if node.typ == table.void_type { if node.typ == table.void_type {
// first branch of if expression
node.is_expr = true node.is_expr = true
node.typ = expr_type node.typ = last_expr.typ
} else { } else {
c.error('mismatched types `${c.table.type_to_str(node.typ)}` and `${c.table.type_to_str(expr_type)}`', c.error('mismatched types `${c.table.type_to_str(node.typ)}` and `${c.table.type_to_str(last_expr.typ)}`',
node.pos) node.pos)
} }
} }

View File

@ -903,16 +903,11 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
g.is_assign_rhs = true g.is_assign_rhs = true
g.expr(assign_stmt.right[0]) g.expr(assign_stmt.right[0])
g.is_assign_rhs = false g.is_assign_rhs = false
if is_optional { if is_optional && assign_stmt.right[0] is ast.CallExpr {
val := assign_stmt.right[0] val := assign_stmt.right[0] as ast.CallExpr
match val { or_stmts = val.or_block.stmts
ast.CallExpr { return_type = val.return_type
or_stmts = it.or_block.stmts g.or_block(mr_var_name, or_stmts, return_type)
return_type = it.return_type
g.or_block(mr_var_name, or_stmts, return_type)
}
else {}
}
} }
g.writeln(';') g.writeln(';')
for i, ident in assign_stmt.left { for i, ident in assign_stmt.left {
@ -969,7 +964,6 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
} }
else {} else {}
} }
gen_or := is_call && return_type.flag_is(.optional)
g.is_assign_rhs = true g.is_assign_rhs = true
if blank_assign { if blank_assign {
if is_call { if is_call {
@ -1060,9 +1054,6 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
g.expr_with_cast(val, assign_stmt.left_types[i], ident_var_info.typ) g.expr_with_cast(val, assign_stmt.left_types[i], ident_var_info.typ)
} }
} }
if gen_or {
g.or_block(ident.name, or_stmts, return_type)
}
} }
g.is_assign_rhs = false g.is_assign_rhs = false
if g.inside_ternary == 0 { if g.inside_ternary == 0 {
@ -2945,6 +2936,7 @@ fn (mut g Gen) insert_before_stmt(s string) {
// If the user is not using the optional return value. We need to pass a temp var // If the user is not using the optional return value. We need to pass a temp var
// to access its fields (`.ok`, `.error` etc) // to access its fields (`.ok`, `.error` etc)
// `os.cp(...)` => `Option bool tmp = os__cp(...); if (!tmp.ok) { ... }` // `os.cp(...)` => `Option bool tmp = os__cp(...); if (!tmp.ok) { ... }`
// Returns the type of the last stmt
fn (mut g Gen) or_block(var_name string, stmts []ast.Stmt, return_type table.Type) { fn (mut g Gen) or_block(var_name string, stmts []ast.Stmt, return_type table.Type) {
cvar_name := c_name(var_name) cvar_name := c_name(var_name)
mr_styp := g.base_type(return_type) mr_styp := g.base_type(return_type)
@ -2952,50 +2944,37 @@ fn (mut g Gen) or_block(var_name string, stmts []ast.Stmt, return_type table.Typ
g.writeln('if (!${cvar_name}.ok) {') g.writeln('if (!${cvar_name}.ok) {')
g.writeln('\tstring err = ${cvar_name}.v_error;') g.writeln('\tstring err = ${cvar_name}.v_error;')
g.writeln('\tint errcode = ${cvar_name}.ecode;') g.writeln('\tint errcode = ${cvar_name}.ecode;')
last_type, type_of_last_expression := g.type_of_last_statement(stmts) if stmts.len > 0 && stmts[stmts.len - 1] is ast.ExprStmt && (stmts[stmts.len - 1] as ast.ExprStmt).typ !=
if last_type == 'v.ast.ExprStmt' && type_of_last_expression != 'void' { table.void_type {
g.indent++ g.indent++
for i, stmt in stmts { for i, stmt in stmts {
if i == stmts.len - 1 { if i == stmts.len - 1 {
g.indent-- expr_stmt := stmt as ast.ExprStmt
g.write('\t*(${mr_styp}*) ${cvar_name}.data = ') g.stmt_path_pos << g.out.len
g.write('*(${mr_styp}*) ${cvar_name}.data = ')
is_opt_call := expr_stmt.expr is ast.CallExpr && expr_stmt.typ.flag_is(.optional)
if is_opt_call {
g.write('*(${mr_styp}*) ')
}
g.expr(expr_stmt.expr)
if is_opt_call {
g.write('.data')
}
if g.inside_ternary == 0 && !(expr_stmt.expr is ast.IfExpr) {
g.writeln(';')
}
g.stmt_path_pos.delete(g.stmt_path_pos.len - 1)
} else {
g.stmt(stmt)
} }
g.stmt(stmt)
} }
g.indent--
} else { } else {
g.stmts(stmts) g.stmts(stmts)
} }
g.write('}') g.write('}')
} }
fn (mut g Gen) type_of_last_statement(stmts []ast.Stmt) (string, string) {
mut last_type := ''
mut last_expr_result_type := ''
if stmts.len > 0 {
last_stmt := stmts[stmts.len - 1]
last_type = typeof(last_stmt)
if last_type == 'v.ast.ExprStmt' {
match last_stmt {
ast.ExprStmt {
it_expr_type := typeof(it.expr)
if it_expr_type == 'v.ast.CallExpr' {
g.writeln('\t // typeof it_expr_type: $it_expr_type')
last_expr_result_type = g.type_of_call_expr(it.expr)
} else {
last_expr_result_type = it_expr_type
}
}
else {
last_expr_result_type = last_type
}
}
}
}
g.writeln('\t// last_type: $last_type')
g.writeln('\t// last_expr_result_type: $last_expr_result_type')
return last_type, last_expr_result_type
}
fn (mut g Gen) type_of_call_expr(node ast.Expr) string { fn (mut g Gen) type_of_call_expr(node ast.Expr) string {
match node { match node {
ast.CallExpr { return g.typ(it.return_type) } ast.CallExpr { return g.typ(it.return_type) }

View File

@ -284,7 +284,14 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
if node.should_be_skipped { if node.should_be_skipped {
return return
} }
gen_or := !g.is_assign_rhs && node.or_block.stmts.len > 0 gen_or := node.or_block.stmts.len > 0
cur_line := if gen_or && g.is_assign_rhs {
line := g.go_before_stmt(0)
g.out.write(tabs[g.indent])
line
} else {
''
}
tmp_opt := if gen_or { g.new_tmp_var() } else { '' } tmp_opt := if gen_or { g.new_tmp_var() } else { '' }
if gen_or { if gen_or {
styp := g.typ(node.return_type) styp := g.typ(node.return_type)
@ -297,6 +304,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
} }
if gen_or { if gen_or {
g.or_block(tmp_opt, node.or_block.stmts, node.return_type) g.or_block(tmp_opt, node.or_block.stmts, node.return_type)
g.write('\n${cur_line}${tmp_opt}')
} }
} }

View File

@ -45,6 +45,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
// `foo() or {}`` // `foo() or {}``
mut or_stmts := []ast.Stmt{} mut or_stmts := []ast.Stmt{}
if p.tok.kind == .key_orelse { if p.tok.kind == .key_orelse {
was_inside_or_expr := p.inside_or_expr
p.inside_or_expr = true p.inside_or_expr = true
p.next() p.next()
p.open_scope() p.open_scope()
@ -63,7 +64,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
is_or_block_used = true is_or_block_used = true
or_stmts = p.parse_block_no_scope() or_stmts = p.parse_block_no_scope()
p.close_scope() p.close_scope()
p.inside_or_expr = false p.inside_or_expr = was_inside_or_expr
} }
if p.tok.kind == .question { if p.tok.kind == .question {
// `foo()?` // `foo()?`

View File

@ -668,13 +668,12 @@ fn (mut p Parser) parse_multi_expr() ast.Stmt {
expr: p.assign_expr(collected[0]) expr: p.assign_expr(collected[0])
pos: epos pos: epos
} }
} else { }
return ast.ExprStmt{ return ast.ExprStmt{
expr: p.assign_expr(ast.ConcatExpr{ expr: p.assign_expr(ast.ConcatExpr{
vals: collected vals: collected
}) })
pos: epos pos: epos
}
} }
} else { } else {
if collected.len == 1 { if collected.len == 1 {
@ -1000,12 +999,10 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
is_used: is_or_block_used is_used: is_or_block_used
} }
} }
mut node := ast.Expr{}
node = mcall_expr
if is_filter { if is_filter {
p.close_scope() p.close_scope()
} }
return node return mcall_expr
} }
sel_expr := ast.SelectorExpr{ sel_expr := ast.SelectorExpr{
expr: left expr: left

View File

@ -1,4 +1,3 @@
struct Abc { struct Abc {
x int x int
} }
@ -24,12 +23,18 @@ fn string_0(x int) ?string {
return '$x' return '$x'
} }
fn b_0(b bool) ?bool {
if b == false {
return error('my error 4')
}
return b
}
fn return_a_string() string { fn return_a_string() string {
return 'abcdef' return 'abcdef'
} }
// //
fn test_optional_int() { fn test_optional_int() {
a := i_0(0) or { a := i_0(0) or {
4999 4999
@ -41,6 +46,18 @@ fn test_optional_int() {
assert b == 4123 assert b == 4123
} }
/*
fn test_optional_bool() {
a := true && b_0(false) {
true
}
assert a == true
b := false || b_0(true) {
false
}
assert b == true
}
*/
fn test_optional_struct() { fn test_optional_struct() {
sa := struct_0(0) or { sa := struct_0(0) or {
Abc{7999} Abc{7999}
@ -58,6 +75,10 @@ fn test_optional_with_statements_before_last_expression() {
Abc{12345} Abc{12345}
} }
assert s.x == 12345 assert s.x == 12345
b := b_0(true) or {
false
}
assert b == true
} }
fn test_optional_with_fn_call_as_last_expression() { fn test_optional_with_fn_call_as_last_expression() {
@ -75,3 +96,61 @@ fn test_optional_with_fn_call_last_expr_and_preceding_statements() {
} }
assert s == 'abcdef' assert s == 'abcdef'
} }
fn test_nested_optional() {
a := i_0(1) or {
b := i_0(0) or {
3
}
b
}
assert a == 1
b := i_0(0) or {
c := i_0(1) or {
3
}
c
}
assert b == 1
c := i_0(0) or {
d := i_0(0) or {
3
}
d
}
assert c == 3
}
fn test_nested_optional_with_opt_fn_call_as_last_value() {
a := i_0(1) or {
i_0(0) or {
3
}
}
assert a == 1
b := i_0(0) or {
i_0(1) or {
3
}
}
assert b == 1
c := i_0(0) or {
i_0(0) or {
3
}
}
assert c == 3
// TODO Enable once optional in boolean expressions are working
// d := b_0(true) or {
// false && b_0(true) or {
// true
// }
// }
// assert d == false
// e := b_0(true) or {
// true && b_0(true) or {
// false
// }
// }
// assert e == false
}

View File

@ -97,6 +97,41 @@ fn test_q() {
// assert foo_ok()? == true // assert foo_ok()? == true
} }
fn or_return_val() int {
a := ret_none() or {
return 1
}
return a
}
fn or_return_error() ?int {
a := ret_none() or {
return error('Nope')
}
return a
}
fn or_return_none() ?int {
a := ret_none() or {
return none
}
return a
}
fn test_or_return() {
assert or_return_val() == 1
if _ := or_return_error() {
assert false
} else {
assert true
}
if _ := or_return_none() {
assert false
} else {
assert true
}
}
fn test_reassignment() { fn test_reassignment() {
mut x2 := foo_ok() or { mut x2 := foo_ok() or {
assert false assert false
@ -107,7 +142,7 @@ fn test_reassignment() {
assert x2 == 100 assert x2 == 100
x2 += 1 x2 += 1
assert x2 == 101 assert x2 == 101
/// //
mut x3 := 0 mut x3 := 0
x3 = foo_ok() or { x3 = foo_ok() or {
assert false assert false
@ -170,11 +205,9 @@ fn opt_ptr(a &int) ?&int {
fn test_opt_ptr() { fn test_opt_ptr() {
if true { if true {
} }
// //
else{ else {
} }
a := 3 a := 3
mut r := opt_ptr(&a) or { mut r := opt_ptr(&a) or {
@ -207,7 +240,6 @@ fn test_multi_return_opt() {
} }
} }
*/ */
fn foo() ?void { fn foo() ?void {
return error('something') return error('something')
} }
@ -220,7 +252,6 @@ fn test_optional_void() {
} }
} }
fn bar() ? { fn bar() ? {
return error('bar error') return error('bar error')
} }
@ -232,3 +263,13 @@ fn test_optional_void_only_question() {
return return
} }
} }
fn test_optional_void_with_empty_or() {
foo() or {}
assert true
}
fn test_optional_val_with_empty_or() {
ret_none() or {}
assert true
}