cgen: optionals/autofree fixes

pull/9002/head
Alexander Medvednikov 2021-02-27 17:11:17 +03:00
parent 970bb09eca
commit d39866d4f7
7 changed files with 185 additions and 163 deletions

View File

@ -295,9 +295,10 @@ jobs:
run: ./v -cc gcc -cflags "-Werror" test-self
- name: Build examples
run: ./v build-examples
- name: Build examples with -autofree
- name: Build examples/certain tests with -autofree
run: |
./v -autofree -experimental -o tetris examples/tetris/tetris.v
./v -autofree vlib/v/tests/option_test.v
- name: Build modules
run: |
./v build-module vlib/os

View File

@ -16,7 +16,7 @@ const (
==================
C error. This should never happen.
If you were not working with C interop, please raise an issue on GitHub:
If you were not working with C interop, this is a compiler bug, please raise an issue on GitHub:
https://github.com/vlang/v/issues/new/choose

View File

@ -0,0 +1,108 @@
// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module c
import v.ast
import v.table
fn (mut g Gen) gen_assert_stmt(original_assert_statement ast.AssertStmt) {
mut node := original_assert_statement
g.writeln('// assert')
if mut node.expr is ast.InfixExpr {
if mut node.expr.left is ast.CallExpr {
node.expr.left = g.new_ctemp_var_then_gen(node.expr.left, node.expr.left_type)
}
if mut node.expr.right is ast.CallExpr {
node.expr.right = g.new_ctemp_var_then_gen(node.expr.right, node.expr.right_type)
}
}
g.inside_ternary++
if g.is_test {
g.write('if (')
g.expr(node.expr)
g.write(')')
g.decrement_inside_ternary()
g.writeln(' {')
g.writeln('\tg_test_oks++;')
metaname_ok := g.gen_assert_metainfo(node)
g.writeln('\tmain__cb_assertion_ok(&$metaname_ok);')
g.writeln('} else {')
g.writeln('\tg_test_fails++;')
metaname_fail := g.gen_assert_metainfo(node)
g.writeln('\tmain__cb_assertion_failed(&$metaname_fail);')
g.writeln('\tlongjmp(g_jump_buffer, 1);')
g.writeln('\t// TODO')
g.writeln('\t// Maybe print all vars in a test function if it fails?')
g.writeln('}')
} else {
g.write('if (!(')
g.expr(node.expr)
g.write('))')
g.decrement_inside_ternary()
g.writeln(' {')
metaname_panic := g.gen_assert_metainfo(node)
g.writeln('\t__print_assert_failure(&$metaname_panic);')
g.writeln('\tv_panic(_SLIT("Assertion failed..."));')
g.writeln('}')
}
}
fn (mut g Gen) gen_assert_metainfo(node ast.AssertStmt) string {
mod_path := cestring(g.file.path)
fn_name := g.fn_decl.name
line_nr := node.pos.line_nr
src := cestring(node.expr.str())
metaname := 'v_assert_meta_info_$g.new_tmp_var()'
g.writeln('\tVAssertMetaInfo $metaname = {0};')
g.writeln('\t${metaname}.fpath = ${ctoslit(mod_path)};')
g.writeln('\t${metaname}.line_nr = $line_nr;')
g.writeln('\t${metaname}.fn_name = ${ctoslit(fn_name)};')
g.writeln('\t${metaname}.src = ${cnewlines(ctoslit(src))};')
match mut node.expr {
ast.InfixExpr {
g.writeln('\t${metaname}.op = ${ctoslit(node.expr.op.str())};')
g.writeln('\t${metaname}.llabel = ${cnewlines(ctoslit(node.expr.left.str()))};')
g.writeln('\t${metaname}.rlabel = ${cnewlines(ctoslit(node.expr.right.str()))};')
g.write('\t${metaname}.lvalue = ')
g.gen_assert_single_expr(node.expr.left, node.expr.left_type)
g.writeln(';')
//
g.write('\t${metaname}.rvalue = ')
g.gen_assert_single_expr(node.expr.right, node.expr.right_type)
g.writeln(';')
}
ast.CallExpr {
g.writeln('\t${metaname}.op = _SLIT("call");')
}
else {}
}
return metaname
}
fn (mut g Gen) gen_assert_single_expr(expr ast.Expr, typ table.Type) {
unknown_value := '*unknown value*'
match expr {
ast.CastExpr, ast.IndexExpr, ast.MatchExpr {
g.write(ctoslit(unknown_value))
}
ast.PrefixExpr {
if expr.right is ast.CastExpr {
// TODO: remove this check;
// vlib/builtin/map_test.v (a map of &int, set to &int(0)) fails
// without special casing ast.CastExpr here
g.write(ctoslit(unknown_value))
} else {
g.gen_expr_to_string(expr, typ)
}
}
ast.Type {
sym := g.table.get_type_symbol(typ)
g.write(ctoslit('$sym.name'))
}
else {
g.gen_expr_to_string(expr, typ)
}
}
g.write(' /* typeof: ' + expr.type_name() + ' type: ' + typ.str() + ' */ ')
}

View File

@ -1637,111 +1637,10 @@ fn (mut g Gen) gen_attrs(attrs []table.Attr) {
}
}
fn (mut g Gen) gen_assert_stmt(original_assert_statement ast.AssertStmt) {
mut node := original_assert_statement
g.writeln('// assert')
if mut node.expr is ast.InfixExpr {
if mut node.expr.left is ast.CallExpr {
node.expr.left = g.new_ctemp_var_then_gen(node.expr.left, node.expr.left_type)
}
if mut node.expr.right is ast.CallExpr {
node.expr.right = g.new_ctemp_var_then_gen(node.expr.right, node.expr.right_type)
}
}
g.inside_ternary++
if g.is_test {
g.write('if (')
g.expr(node.expr)
g.write(')')
g.decrement_inside_ternary()
g.writeln(' {')
g.writeln('\tg_test_oks++;')
metaname_ok := g.gen_assert_metainfo(node)
g.writeln('\tmain__cb_assertion_ok(&$metaname_ok);')
g.writeln('} else {')
g.writeln('\tg_test_fails++;')
metaname_fail := g.gen_assert_metainfo(node)
g.writeln('\tmain__cb_assertion_failed(&$metaname_fail);')
g.writeln('\tlongjmp(g_jump_buffer, 1);')
g.writeln('\t// TODO')
g.writeln('\t// Maybe print all vars in a test function if it fails?')
g.writeln('}')
} else {
g.write('if (!(')
g.expr(node.expr)
g.write('))')
g.decrement_inside_ternary()
g.writeln(' {')
metaname_panic := g.gen_assert_metainfo(node)
g.writeln('\t__print_assert_failure(&$metaname_panic);')
g.writeln('\tv_panic(_SLIT("Assertion failed..."));')
g.writeln('}')
}
}
fn cnewlines(s string) string {
return s.replace('\n', r'\n')
}
fn (mut g Gen) gen_assert_metainfo(node ast.AssertStmt) string {
mod_path := cestring(g.file.path)
fn_name := g.fn_decl.name
line_nr := node.pos.line_nr
src := cestring(node.expr.str())
metaname := 'v_assert_meta_info_$g.new_tmp_var()'
g.writeln('\tVAssertMetaInfo $metaname = {0};')
g.writeln('\t${metaname}.fpath = ${ctoslit(mod_path)};')
g.writeln('\t${metaname}.line_nr = $line_nr;')
g.writeln('\t${metaname}.fn_name = ${ctoslit(fn_name)};')
g.writeln('\t${metaname}.src = ${cnewlines(ctoslit(src))};')
match mut node.expr {
ast.InfixExpr {
g.writeln('\t${metaname}.op = ${ctoslit(node.expr.op.str())};')
g.writeln('\t${metaname}.llabel = ${cnewlines(ctoslit(node.expr.left.str()))};')
g.writeln('\t${metaname}.rlabel = ${cnewlines(ctoslit(node.expr.right.str()))};')
g.write('\t${metaname}.lvalue = ')
g.gen_assert_single_expr(node.expr.left, node.expr.left_type)
g.writeln(';')
//
g.write('\t${metaname}.rvalue = ')
g.gen_assert_single_expr(node.expr.right, node.expr.right_type)
g.writeln(';')
}
ast.CallExpr {
g.writeln('\t${metaname}.op = _SLIT("call");')
}
else {}
}
return metaname
}
fn (mut g Gen) gen_assert_single_expr(expr ast.Expr, typ table.Type) {
unknown_value := '*unknown value*'
match expr {
ast.CastExpr, ast.IndexExpr, ast.MatchExpr {
g.write(ctoslit(unknown_value))
}
ast.PrefixExpr {
if expr.right is ast.CastExpr {
// TODO: remove this check;
// vlib/builtin/map_test.v (a map of &int, set to &int(0)) fails
// without special casing ast.CastExpr here
g.write(ctoslit(unknown_value))
} else {
g.gen_expr_to_string(expr, typ)
}
}
ast.Type {
sym := g.table.get_type_symbol(typ)
g.write(ctoslit('$sym.name'))
}
else {
g.gen_expr_to_string(expr, typ)
}
}
g.write(' /* typeof: ' + expr.type_name() + ' type: ' + typ.str() + ' */ ')
}
fn (mut g Gen) write_fn_ptr_decl(func &table.FnType, ptr_name string) {
ret_styp := g.typ(func.func.return_type)
g.write('$ret_styp (*$ptr_name) (')
@ -1830,8 +1729,9 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
// return;
// }
// int pos = *(int*)_t190.data;
mut tmp_opt := ''
is_optional := g.is_autofree && (assign_stmt.op in [.decl_assign, .assign])
// mut tmp_opt := ''
/*
is_optional := false && g.is_autofree && (assign_stmt.op in [.decl_assign, .assign])
&& assign_stmt.left_types.len == 1 && assign_stmt.right[0] is ast.CallExpr
if is_optional {
// g.write('/* optional assignment */')
@ -1849,6 +1749,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
// return
}
}
*/
// json_test failed w/o this check
if return_type != table.void_type && return_type != 0 {
sym := g.table.get_type_symbol(return_type)
@ -2175,7 +2076,9 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
// Unwrap the optional now that the testing code has been prepended.
// `pos := s.index(...
// `int pos = *(int)_t10.data;`
if g.is_autofree {
// if g.is_autofree {
/*
if is_optional {
g.write('*($styp*)')
g.write(tmp_opt + '.data/*FFz*/')
g.right_is_opt = false
@ -2185,6 +2088,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
}
return
}
*/
}
g.is_shared = var_type.has_flag(.shared_f)
if !cloned {
@ -5619,7 +5523,7 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type table.
if is_none_ok {
g.writeln('if (!${cvar_name}.ok && !${cvar_name}.is_none) {')
} else {
g.writeln('if (!${cvar_name}.ok) {')
g.writeln('if (!${cvar_name}.ok) { /*or block*/ ')
}
if or_block.kind == .block {
if g.inside_or_block {

View File

@ -391,6 +391,7 @@ fn (mut g Gen) fn_args(args []table.Param, is_variadic bool) ([]string, []string
}
fn (mut g Gen) call_expr(node ast.CallExpr) {
// g.write('/*call expr*/')
// NOTE: everything could be done this way
// see my comment in parser near anon_fn
if node.left is ast.AnonFn {
@ -408,20 +409,25 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
defer {
g.inside_call = false
}
gen_or := node.or_block.kind != .absent && !g.is_autofree
// if gen_or {
// g.writeln('/*start*/')
// }
gen_or := node.or_block.kind != .absent // && !g.is_autofree
is_gen_or_and_assign_rhs := gen_or && g.is_assign_rhs
cur_line := if is_gen_or_and_assign_rhs && !g.is_autofree {
cur_line := if is_gen_or_and_assign_rhs { // && !g.is_autofree {
// `x := foo() or { ...}`
// cut everything that has been generated to prepend optional variable creation
line := g.go_before_stmt(0)
g.out.write_string(tabs[g.indent])
// g.write('/*is_gen_or_and_assign_rhs*/')
line
} else {
''
}
if gen_or && g.pref.autofree && g.inside_return {
// TODO optional return af hack (tmp_count gets increased in .return_statement())
g.tmp_count--
}
tmp_opt := if gen_or { g.new_tmp_var() } else { '' }
if gen_or {
if gen_or && !g.inside_return {
// if is_gen_or_and_assign_rhs {
styp := g.typ(node.return_type.set_flag(.optional))
g.write('$styp $tmp_opt = ')
}
@ -437,16 +443,16 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
g.fn_call(node)
}
if gen_or { // && !g.autofree {
if !g.is_autofree {
g.or_block(tmp_opt, node.or_block, node.return_type)
}
// if !g.is_autofree {
g.or_block(tmp_opt, node.or_block, node.return_type)
//}
if is_gen_or_and_assign_rhs {
unwrapped_typ := node.return_type.clear_flag(.optional)
unwrapped_styp := g.typ(unwrapped_typ)
if unwrapped_typ == table.void_type {
g.write('\n $cur_line')
} else if g.table.get_type_symbol(node.return_type).kind == .multi_return {
g.write('\n $cur_line $tmp_opt')
g.write('\n $cur_line $tmp_opt /*U*/')
} else {
g.write('\n $cur_line *($unwrapped_styp*)${tmp_opt}.data')
}

View File

@ -50,9 +50,7 @@ fn test_option_for_base_type_without_variable() {
0
}
assert val == 42
val = ret_none() or {
return
}
val = ret_none() or { return }
assert false
// This is invalid:
// x := 5 or {
@ -101,30 +99,32 @@ fn foo_str() ?string {
}
fn propagate_optional(b bool) ?int {
a := err_call(b)?
a := err_call(b) ?
return a
}
fn propagate_different_type(b bool) ?bool {
err_call(b)?
err_call(b) ?
return true
}
fn test_propagation() {
a := propagate_optional(true) or {
0
}
println(1)
a := propagate_optional(true) or { 0 }
println(2)
assert a == 42
println(3)
if _ := propagate_optional(false) {
assert false
}
b := propagate_different_type(true) or {
false
}
println(4)
b := propagate_different_type(true) or { false }
assert b == true
println(5)
if _ := propagate_different_type(false) {
assert false
}
println(6)
}
fn test_q() {
@ -132,23 +132,17 @@ fn test_q() {
}
fn or_return_val() int {
a := ret_none() or {
return 1
}
a := ret_none() or { return 1 }
return a
}
fn or_return_error() ?int {
a := ret_none() or {
return error('Nope')
}
a := ret_none() or { return error('Nope') }
return a
}
fn or_return_none() ?int {
a := ret_none() or {
return none
}
a := ret_none() or { return none }
return a
}
@ -193,9 +187,7 @@ mut:
}
fn test_field_or() {
name := foo_str() or {
'nada'
}
name := foo_str() or { 'nada' }
assert name == 'something'
/*
QTODO
@ -225,8 +217,6 @@ mut:
opt ?Thing
}
fn test_opt_field() {
/*
QTODO
@ -251,13 +241,9 @@ fn test_opt_ptr() {
else {
}
a := 3
mut r := opt_ptr(&a) or {
&int(0)
}
mut r := opt_ptr(&a) or { &int(0) }
assert r == &a
r = opt_ptr(&int(0)) or {
return
}
r = opt_ptr(&int(0)) or { return }
assert false
}
@ -283,34 +269,34 @@ fn test_multi_return_opt() {
*/
fn test_optional_val_with_empty_or() {
ret_none() or {}
ret_none() or { }
assert true
}
fn test_optional_void_return_types_of_anon_fn() {
f := fn(i int) ? {
f := fn (i int) ? {
if i == 0 {
return error("0")
return error('0')
}
return
}
f(0) or {
assert err == "0"
assert err == '0'
return
}
}
struct Foo {
f fn(int) ?
f fn (int) ?
}
fn test_option_void_return_types_of_anon_fn_in_struct() {
foo := Foo {
f: fn(i int) ? {
foo := Foo{
f: fn (i int) ? {
if i == 0 {
return error("0")
return error('0')
}
return
@ -318,7 +304,7 @@ fn test_option_void_return_types_of_anon_fn_in_struct() {
}
foo.f(0) or {
assert err == "0"
assert err == '0'
return
}
}
@ -377,3 +363,21 @@ struct MultiOptionalFieldTest {
a ?int
b ?int
}
/*
fn foo() ?int {
return 0
}
fn foo2() ?int {
for _ in 0 .. 5 {
return foo() or { continue }
}
return 0
}
fn test_return_or() {
x := foo2() or { return }
assert x == 0
}
*/

View File

@ -210,15 +210,11 @@ fn if_expr() string {
}
fn return_if_expr() string {
return if true {
get_string('a' + 'b')
} else {
get_string('c' + 'd')
}
return if true { get_string('a' + 'b') } else { get_string('c' + 'd') }
}
fn loop_map() {
m := {
m := map{
'UK': 'London'
'France': 'Paris'
}
@ -337,6 +333,9 @@ fn comp_if() {
println(s)
}
fn anon_fn() {
}
fn main() {
println('start')
simple()