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

View File

@ -43,8 +43,9 @@ pub:
pub struct ExprStmt {
pub:
expr Expr
typ table.Type
pos token.Position
pub mut:
typ table.Type
}
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)
}
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)
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) {
match x {
ast.CallExpr {
if it.return_type.flag_is(.optional) {
c.check_or_block(it, xtype, is_return_used)
} else if it.or_block.is_used && it.name != 'json.decode' { // TODO remove decode hack
c.error('unexpected `or` block, the function `$it.name` does not return an optional',
it.pos)
}
pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, xtype table.Type, is_return_used bool) {
if expr is ast.CallExpr {
call_expr := expr as ast.CallExpr
if call_expr.return_type.flag_is(.optional) {
c.check_or_block(call_expr, xtype, is_return_used)
} else if call_expr.or_block.is_used {
c.error('unexpected `or` block, the function `$call_expr.name` does not return an optional',
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 {
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)
if type_fits || is_panic_or_exit {
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
c.error('wrong return type `$type_name` in the `or {}` block, expected `$expected_type_name`',
it.pos)
@ -1509,9 +1510,9 @@ fn (mut c Checker) stmt(node ast.Stmt) {
c.enum_decl(it)
}
ast.ExprStmt {
etype := c.expr(it.expr)
it.typ = c.expr(it.expr)
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 {
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] {
ast.ExprStmt {
ret_type = c.expr(it.expr)
it.typ = ret_type
}
else {
// 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 {
last_expr := branch.stmts[branch.stmts.len - 1] as ast.ExprStmt
c.expected_type = former_expected_type
expr_type := c.expr(last_expr.expr)
if expr_type != node.typ {
// first branch of if expression
last_expr.typ = c.expr(last_expr.expr)
if last_expr.typ != node.typ {
if node.typ == table.void_type {
// first branch of if expression
node.is_expr = true
node.typ = expr_type
node.typ = last_expr.typ
} 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)
}
}

View File

@ -903,16 +903,11 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
g.is_assign_rhs = true
g.expr(assign_stmt.right[0])
g.is_assign_rhs = false
if is_optional {
val := assign_stmt.right[0]
match val {
ast.CallExpr {
or_stmts = it.or_block.stmts
return_type = it.return_type
g.or_block(mr_var_name, or_stmts, return_type)
}
else {}
}
if is_optional && assign_stmt.right[0] is ast.CallExpr {
val := assign_stmt.right[0] as ast.CallExpr
or_stmts = val.or_block.stmts
return_type = val.return_type
g.or_block(mr_var_name, or_stmts, return_type)
}
g.writeln(';')
for i, ident in assign_stmt.left {
@ -969,7 +964,6 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
}
else {}
}
gen_or := is_call && return_type.flag_is(.optional)
g.is_assign_rhs = true
if blank_assign {
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)
}
}
if gen_or {
g.or_block(ident.name, or_stmts, return_type)
}
}
g.is_assign_rhs = false
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
// to access its fields (`.ok`, `.error` etc)
// `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) {
cvar_name := c_name(var_name)
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('\tstring err = ${cvar_name}.v_error;')
g.writeln('\tint errcode = ${cvar_name}.ecode;')
last_type, type_of_last_expression := g.type_of_last_statement(stmts)
if last_type == 'v.ast.ExprStmt' && type_of_last_expression != 'void' {
if stmts.len > 0 && stmts[stmts.len - 1] is ast.ExprStmt && (stmts[stmts.len - 1] as ast.ExprStmt).typ !=
table.void_type {
g.indent++
for i, stmt in stmts {
if i == stmts.len - 1 {
g.indent--
g.write('\t*(${mr_styp}*) ${cvar_name}.data = ')
expr_stmt := stmt as ast.ExprStmt
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 {
g.stmts(stmts)
}
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 {
match node {
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 {
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 { '' }
if gen_or {
styp := g.typ(node.return_type)
@ -297,6 +304,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
}
if gen_or {
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 {}``
mut or_stmts := []ast.Stmt{}
if p.tok.kind == .key_orelse {
was_inside_or_expr := p.inside_or_expr
p.inside_or_expr = true
p.next()
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
or_stmts = p.parse_block_no_scope()
p.close_scope()
p.inside_or_expr = false
p.inside_or_expr = was_inside_or_expr
}
if p.tok.kind == .question {
// `foo()?`

View File

@ -668,13 +668,12 @@ fn (mut p Parser) parse_multi_expr() ast.Stmt {
expr: p.assign_expr(collected[0])
pos: epos
}
} else {
return ast.ExprStmt{
expr: p.assign_expr(ast.ConcatExpr{
vals: collected
})
pos: epos
}
}
return ast.ExprStmt{
expr: p.assign_expr(ast.ConcatExpr{
vals: collected
})
pos: epos
}
} else {
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
}
}
mut node := ast.Expr{}
node = mcall_expr
if is_filter {
p.close_scope()
}
return node
return mcall_expr
}
sel_expr := ast.SelectorExpr{
expr: left

View File

@ -1,4 +1,3 @@
struct Abc {
x int
}
@ -24,12 +23,18 @@ fn string_0(x int) ?string {
return '$x'
}
fn b_0(b bool) ?bool {
if b == false {
return error('my error 4')
}
return b
}
fn return_a_string() string {
return 'abcdef'
}
//
fn test_optional_int() {
a := i_0(0) or {
4999
@ -41,6 +46,18 @@ fn test_optional_int() {
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() {
sa := struct_0(0) or {
Abc{7999}
@ -58,6 +75,10 @@ fn test_optional_with_statements_before_last_expression() {
Abc{12345}
}
assert s.x == 12345
b := b_0(true) or {
false
}
assert b == true
}
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'
}
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
}
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() {
mut x2 := foo_ok() or {
assert false
@ -107,7 +142,7 @@ fn test_reassignment() {
assert x2 == 100
x2 += 1
assert x2 == 101
///
//
mut x3 := 0
x3 = foo_ok() or {
assert false
@ -170,11 +205,9 @@ fn opt_ptr(a &int) ?&int {
fn test_opt_ptr() {
if true {
}
//
else{
else {
}
a := 3
mut r := opt_ptr(&a) or {
@ -207,7 +240,6 @@ fn test_multi_return_opt() {
}
}
*/
fn foo() ?void {
return error('something')
}
@ -220,7 +252,6 @@ fn test_optional_void() {
}
}
fn bar() ? {
return error('bar error')
}
@ -232,3 +263,13 @@ fn test_optional_void_only_question() {
return
}
}
fn test_optional_void_with_empty_or() {
foo() or {}
assert true
}
fn test_optional_val_with_empty_or() {
ret_none() or {}
assert true
}