1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

compiler: map[string]pointer, ?pointer, fix []pointer

This commit is contained in:
れもん 2019-12-22 07:44:16 +09:00 committed by Alexander Medvednikov
parent b76227b781
commit 28ecfb231d
14 changed files with 98 additions and 48 deletions

View File

@ -4,7 +4,7 @@ const (
) )
fn test_pointer() { fn test_pointer() {
mut arr := []*int mut arr := []&int
a := 1 a := 1
b := 2 b := 2
c := 3 c := 3
@ -14,6 +14,10 @@ fn test_pointer() {
assert *arr[0] == 1 assert *arr[0] == 1
arr[1] = &c arr[1] = &c
assert *arr[1] == 3 assert *arr[1] == 3
mut d_arr := [arr] // [][]&int
d_arr << arr
assert *d_arr[0][1] == 3
assert *d_arr[1][0] == 1
} }
fn test_ints() { fn test_ints() {

View File

@ -132,13 +132,13 @@ fn test_various_map_value() {
m13['test'] = rune(0) m13['test'] = rune(0)
assert m13['test'] == rune(0) assert m13['test'] == rune(0)
//mut m14 := map[string]voidptr mut m14 := map[string]voidptr
//m14['test'] = voidptr(0) m14['test'] = voidptr(0)
//assert m14['test'] == voidptr(0) assert m14['test'] == voidptr(0)
//mut m15 := map[string]byteptr mut m15 := map[string]byteptr
//m15['test'] = byteptr(0) m15['test'] = byteptr(0)
//assert m15['test'] == byteptr(0) assert m15['test'] == byteptr(0)
mut m16 := map[string]i64 mut m16 := map[string]i64
m16['test'] = i64(0) m16['test'] = i64(0)
@ -147,6 +147,10 @@ fn test_various_map_value() {
mut m17 := map[string]u64 mut m17 := map[string]u64
m17['test'] = u64(0) m17['test'] = u64(0)
assert m17['test'] == u64(0) assert m17['test'] == u64(0)
mut m18 := map[string]&int
m18['test'] = &int(0)
assert m18['test'] == &int(0)
} }

View File

@ -191,7 +191,7 @@ fn (p mut Parser) print_error_context() {
fn normalized_error(s string) string { fn normalized_error(s string) string {
// Print `[]int` instead of `array_int` in errors // Print `[]int` instead of `array_int` in errors
mut res := s.replace('array_', '[]').replace('__', '.').replace('Option_', '?').replace('main.', '') mut res := s.replace('array_', '[]').replace('__', '.').replace('Option_', '?').replace('main.', '').replace('ptr_', '&')
if res.contains('_V_MulRet_') { if res.contains('_V_MulRet_') {
res = res.replace('_V_MulRet_', '(').replace('_V_', ', ') res = res.replace('_V_MulRet_', '(').replace('_V_', ', ')
res = res[..res.len - 1] + ')"' res = res[..res.len - 1] + ')"'

View File

@ -321,7 +321,7 @@ fn (p mut Parser) gen_array_str(typ Type) {
is_public: true is_public: true
receiver_typ: typ.name receiver_typ: typ.name
}) })
elm_type := typ.name[6..] elm_type := parse_pointer(typ.name[6..])
elm_type2 := p.table.find_type(elm_type) elm_type2 := p.table.find_type(elm_type)
is_array := elm_type.starts_with('array_') is_array := elm_type.starts_with('array_')
if is_array { if is_array {
@ -416,7 +416,7 @@ fn (p mut Parser) gen_array_filter(str_typ string, method_ph int) {
} }
array_int b = tmp2; array_int b = tmp2;
*/ */
val_type := str_typ[6..] val_type := parse_pointer(str_typ[6..])
p.open_scope() p.open_scope()
p.register_var(Var{ p.register_var(Var{
name: 'it' name: 'it'
@ -456,7 +456,7 @@ fn (p mut Parser) gen_array_map(str_typ string, method_ph int) string {
} }
array_int b = tmp2; array_int b = tmp2;
*/ */
val_type := str_typ[6..] val_type := parse_pointer(str_typ[6..])
p.open_scope() p.open_scope()
p.register_var(Var{ p.register_var(Var{
name: 'it' name: 'it'
@ -477,7 +477,7 @@ fn (p mut Parser) gen_array_map(str_typ string, method_ph int) string {
p.gen(tmp) // TODO why does this `gen()` work? p.gen(tmp) // TODO why does this `gen()` work?
p.check(.rpar) p.check(.rpar)
p.close_scope() p.close_scope()
return 'array_' + map_type return 'array_' + stringify_pointer(map_type)
} }
fn (p mut Parser) comptime_if_block(name string) { fn (p mut Parser) comptime_if_block(name string) {

View File

@ -413,7 +413,7 @@ fn (p mut Parser) name_expr() string {
f.typ = p.gen_handle_option_or_else(f.typ, '', fn_call_ph) f.typ = p.gen_handle_option_or_else(f.typ, '', fn_call_ph)
} }
else if !p.is_var_decl && !is_or_else && !p.inside_return_expr && f.typ.starts_with('Option_') { else if !p.is_var_decl && !is_or_else && !p.inside_return_expr && f.typ.starts_with('Option_') {
opt_type := f.typ[7..] opt_type := f.typ[7..].replace('ptr_', '&')
p.error('unhandled option type: `?$opt_type`') p.error('unhandled option type: `?$opt_type`')
} }
// dot after a function call: `get_user().age` // dot after a function call: `get_user().age`
@ -450,7 +450,7 @@ fn (p mut Parser) expression() string {
// a << 7 => int tmp = 7; array_push(&a, &tmp); // a << 7 => int tmp = 7; array_push(&a, &tmp);
// _PUSH(&a, expression(), tmp, string) // _PUSH(&a, expression(), tmp, string)
tmp := p.get_tmp() tmp := p.get_tmp()
tmp_typ := typ[6..].replace('_ptr', '*') // skip "array_" tmp_typ := parse_pointer(typ[6..]) // skip "array_"
p.check_space(.left_shift) p.check_space(.left_shift)
// Get the value we are pushing // Get the value we are pushing
p.gen(', (') p.gen(', (')

View File

@ -1353,7 +1353,7 @@ fn (p mut Parser) extract_type_inst(f &Fn, args_ []string) TypeInst {
// replace a generic type using TypeInst // replace a generic type using TypeInst
fn replace_generic_type(gen_type string, ti &TypeInst) string { fn replace_generic_type(gen_type string, ti &TypeInst) string {
mut typ := gen_type.replace('map_', '').replace('varg_', '').trim_right('*') mut typ := gen_type.replace('map_', '').replace('varg_', '').trim_right('*').replace('ptr_', '')
for typ.starts_with('array_') { for typ.starts_with('array_') {
typ = typ[6..] typ = typ[6..]
} }

View File

@ -92,12 +92,12 @@ fn (p mut Parser) for_st() {
p.gen_for_varg_header(i, expr, typ, val) p.gen_for_varg_header(i, expr, typ, val)
} }
else if is_arr { else if is_arr {
typ = typ[6..].replace('_ptr', '*') typ = parse_pointer(typ[6..])
p.gen_for_header(i, tmp, typ, val) p.gen_for_header(i, tmp, typ, val)
} }
else if is_map { else if is_map {
i_var_type = 'string' i_var_type = 'string'
typ = typ[4..] typ = parse_pointer(typ[4..])
p.gen_for_map_header(i, tmp, typ, val, typ) p.gen_for_map_header(i, tmp, typ, val, typ)
} }
else if is_str { else if is_str {
@ -178,7 +178,7 @@ fn (p mut Parser) for_st() {
p.gen_for_range_header(i, range_end, tmp, typ, val) p.gen_for_range_header(i, range_end, tmp, typ, val)
} }
else if is_arr { else if is_arr {
typ = typ[6..].replace('_ptr', '*') // all after `array_` typ = parse_pointer(typ[6..]) // all after `array_`
p.gen_for_header(i, tmp, typ, val) p.gen_for_header(i, tmp, typ, val)
} }
else if is_str { else if is_str {

View File

@ -96,7 +96,7 @@ fn (p mut Parser) gen_handle_option_or_else(_typ, name string, fn_call_ph int) s
is_assign := name.len > 0 is_assign := name.len > 0
tmp := p.get_tmp() tmp := p.get_tmp()
p.cgen.set_placeholder(fn_call_ph, '$typ $tmp = ') p.cgen.set_placeholder(fn_call_ph, '$typ $tmp = ')
typ = typ[7..] typ = parse_pointer(typ[7..])
p.genln(';') p.genln(';')
or_tok_idx := p.token_idx or_tok_idx := p.token_idx
p.check(.key_orelse) p.check(.key_orelse)
@ -324,7 +324,7 @@ fn (p mut Parser) gen_method_call(receiver &Var, receiver_type string, cgen_name
if ftyp == 'void*' { if ftyp == 'void*' {
if receiver_type.starts_with('array_') { if receiver_type.starts_with('array_') {
// array_int => int // array_int => int
cast = receiver_type.all_after('array_') cast = parse_pointer(receiver_type.all_after('array_'))
cast = '*($cast*) ' cast = '*($cast*) '
} }
else { else {
@ -611,7 +611,7 @@ fn (p mut Parser) cast(typ string) {
fn type_default(typ string) string { fn type_default(typ string) string {
if typ.starts_with('array_') { if typ.starts_with('array_') {
return 'new_array(0, 1, sizeof( ${typ[6..]} ))' return 'new_array(0, 1, sizeof( ${parse_pointer(typ[6..])} ))'
} }
// Always set pointers to 0 // Always set pointers to 0
if typ.ends_with('*') { if typ.ends_with('*') {

View File

@ -102,7 +102,7 @@ fn (p mut Parser) get_type2() Type {
p.error('maps only support string keys for now') p.error('maps only support string keys for now')
} }
p.check(.rsbr) p.check(.rsbr)
val_type := p.get_type() // p.check_name() val_type := stringify_pointer(p.get_type()) // p.check_name()
typ = 'map_$val_type' typ = 'map_$val_type'
p.register_map(typ) p.register_map(typ)
return Type{ return Type{
@ -190,6 +190,7 @@ fn (p mut Parser) get_type2() Type {
if arr_level > 0 { if arr_level > 0 {
// p.log('ARR TYPE="$typ" run=$p.pass') // p.log('ARR TYPE="$typ" run=$p.pass')
// We come across "[]User" etc ? // We come across "[]User" etc ?
typ = stringify_pointer(typ)
for i := 0; i < arr_level; i++ { for i := 0; i < arr_level; i++ {
typ = 'array_$typ' typ = 'array_$typ'
} }
@ -197,6 +198,7 @@ fn (p mut Parser) get_type2() Type {
} }
p.next() p.next()
if is_question { if is_question {
typ = stringify_pointer(typ)
typ = 'Option_$typ' typ = 'Option_$typ'
p.table.register_type_with_parent(typ, 'Option') p.table.register_type_with_parent(typ, 'Option')
} }
@ -223,3 +225,21 @@ fn (p mut Parser) get_type2() Type {
} }
} }
fn parse_pointer(_typ string) string {
if !_typ.starts_with('ptr_') {
return _typ
}
mut typ := _typ.clone()
for typ.starts_with('ptr_') {
typ = typ[4..] + '*'
}
return typ
}
fn stringify_pointer(typ string) string {
if !typ.ends_with('*') {
return typ
}
count := typ.count('*')
return 'ptr_'.repeat(count) + typ.trim_right('*')
}

View File

@ -215,7 +215,7 @@ fn (p mut Parser) if_statement(is_expr bool, elif_depth int) string {
p.error('`if x := opt() {` syntax requires a function that returns an optional value') p.error('`if x := opt() {` syntax requires a function that returns an optional value')
} }
p.is_var_decl = false p.is_var_decl = false
typ := option_type[7..] typ := parse_pointer(option_type[7..])
// Option_User tmp = get_user(1); // Option_User tmp = get_user(1);
// if (tmp.ok) { // if (tmp.ok) {
// User user = *(User*)tmp.data; // User user = *(User*)tmp.data;

View File

@ -1019,7 +1019,7 @@ fn (p mut Parser) get_type() string {
} }
p.check(.rsbr) p.check(.rsbr)
val_type := p.get_type() // p.check_name() val_type := p.get_type() // p.check_name()
typ = 'map_$val_type' typ = 'map_${stringify_pointer(val_type)}'
p.register_map(typ) p.register_map(typ)
return typ return typ
} }
@ -1123,13 +1123,13 @@ fn (p mut Parser) get_type() string {
// p.log('ARR TYPE="$typ" run=$p.pass') // p.log('ARR TYPE="$typ" run=$p.pass')
// We come across "[]User" etc ? // We come across "[]User" etc ?
for i := 0; i < arr_level; i++ { for i := 0; i < arr_level; i++ {
typ = 'array_$typ' typ = 'array_${stringify_pointer(typ)}'
} }
p.register_array(typ) p.register_array(typ)
} }
p.next() p.next()
if is_question { if is_question {
typ = 'Option_$typ' typ = 'Option_${stringify_pointer(typ)}'
p.table.register_type_with_parent(typ, 'Option') p.table.register_type_with_parent(typ, 'Option')
} }
// Because the code uses * to see if it's a pointer // Because the code uses * to see if it's a pointer
@ -1511,13 +1511,13 @@ fn ($v.name mut $v.typ) ${p.cur_fn.name}(...) {
p.error_with_token_index('${fn_name}() $err_used_as_value', p.token_idx - 2) p.error_with_token_index('${fn_name}() $err_used_as_value', p.token_idx - 2)
} }
// Allow `num = 4` where `num` is an `?int` // Allow `num = 4` where `num` is an `?int`
if p.assigned_type.starts_with('Option_') && expr_type == p.assigned_type['Option_'.len..] { if p.assigned_type.starts_with('Option_') && expr_type == parse_pointer(p.assigned_type['Option_'.len..]) {
expr := p.cgen.cur_line[pos..] expr := p.cgen.cur_line[pos..]
left := p.cgen.cur_line[..pos] left := p.cgen.cur_line[..pos]
typ := expr_type.replace('Option_', '') typ := parse_pointer(expr_type.replace('Option_', ''))
p.cgen.resetln(left + 'opt_ok(($typ[]){ $expr }, sizeof($typ))') p.cgen.resetln(left + 'opt_ok(($typ[]){ $expr }, sizeof($typ))')
} }
else if expr_type.starts_with('Option_') && p.assigned_type == expr_type['Option_'.len..] && p.tok == .key_orelse { else if expr_type.starts_with('Option_') && p.assigned_type == parse_pointer(expr_type['Option_'.len..]) && p.tok == .key_orelse {
line := p.cgen.cur_line line := p.cgen.cur_line
vname := line[..pos].replace('=', '') // TODO cgen line hack vname := line[..pos].replace('=', '') // TODO cgen line hack
if idx:=line.index('='){ if idx:=line.index('='){
@ -1976,7 +1976,7 @@ fn (p mut Parser) dot(str_typ_ string, method_ph int) string {
} }
if !typ.is_c && !p.is_c_fn_call && !has_field && !has_method && !p.first_pass() { if !typ.is_c && !p.is_c_fn_call && !has_field && !has_method && !p.first_pass() {
if typ.name.starts_with('Option_') { if typ.name.starts_with('Option_') {
opt_type := typ.name[7..] opt_type := typ.name[7..].replace('ptr_', '&')
p.error('unhandled option type: `?$opt_type`') p.error('unhandled option type: `?$opt_type`')
} }
// println('error in dot():') // println('error in dot():')
@ -2064,7 +2064,7 @@ pub:
method.typ = p.gen_handle_option_or_else(method.typ, '', method_ph) method.typ = p.gen_handle_option_or_else(method.typ, '', method_ph)
} }
else if !p.is_var_decl && !is_or_else && !p.inside_return_expr && method.typ.starts_with('Option_') { else if !p.is_var_decl && !is_or_else && !p.inside_return_expr && method.typ.starts_with('Option_') {
opt_type := method.typ[7..] opt_type := method.typ[7..].replace('ptr_', '&')
p.error('unhandled option type: `?$opt_type`') p.error('unhandled option type: `?$opt_type`')
} }
// Methods returning `array` should return `array_string` etc // Methods returning `array` should return `array_string` etc
@ -2073,7 +2073,7 @@ pub:
} }
// Array methods returning `voidptr` (like `last()`) should return element type // Array methods returning `voidptr` (like `last()`) should return element type
if method.typ == 'void*' && typ.name.starts_with('array_') { if method.typ == 'void*' && typ.name.starts_with('array_') {
return typ.name[6..] return parse_pointer(typ.name[6..])
} }
// if false && p.tok == .lsbr { // if false && p.tok == .lsbr {
// if is_indexer { // if is_indexer {
@ -2177,7 +2177,7 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string {
} }
if is_arr { if is_arr {
if is_arr0 { if is_arr0 {
typ = typ[6..].replace('_ptr', '*') typ = parse_pointer(typ[6..])
} }
p.gen_array_at(typ, is_arr0, fn_ph) p.gen_array_at(typ, is_arr0, fn_ph)
} }
@ -2187,6 +2187,7 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string {
// can only do that later once we know whether there's an "=" or not // can only do that later once we know whether there's an "=" or not
if is_map { if is_map {
typ = typ.replace('map_', '') typ = typ.replace('map_', '')
typ = parse_pointer(typ)
if typ == 'map' { if typ == 'map' {
typ = 'void*' typ = 'void*'
} }
@ -2212,7 +2213,7 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string {
} }
if p.tok == .dotdot { if p.tok == .dotdot {
if is_arr { if is_arr {
typ = 'array_' + typ typ = 'array_' + stringify_pointer(typ)
} }
else if is_str { else if is_str {
typ = 'string' typ = 'string'
@ -2292,7 +2293,7 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string {
// } // }
// `m[key]`. no =, just a getter // `m[key]`. no =, just a getter
else if (is_map || is_arr || (is_str && !p.builtin_mod)) && is_indexer { else if (is_map || is_arr || (is_str && !p.builtin_mod)) && is_indexer {
typ = typ.replace('_ptr', '*') typ = parse_pointer(typ)
p.index_get(typ, fn_ph, IndexConfig{ p.index_get(typ, fn_ph, IndexConfig{
is_arr: is_arr is_arr: is_arr
is_map: is_map is_map: is_map
@ -2349,7 +2350,7 @@ fn (p mut Parser) indot_expr() string {
if !is_arr && !is_map { if !is_arr && !is_map {
p.error('`in` requires an array/map') p.error('`in` requires an array/map')
} }
if is_arr && arr_typ[6..] != typ { if is_arr && parse_pointer(arr_typ[6..]) != typ {
p.error('bad element type: `$typ` in `$arr_typ`') p.error('bad element type: `$typ` in `$arr_typ`')
} }
if is_map && typ != 'string' { if is_map && typ != 'string' {
@ -2471,7 +2472,7 @@ fn (p mut Parser) map_init() string {
p.fgen_nl() p.fgen_nl()
} }
p.gen('new_map_init($i, sizeof($val_type), ' + '(string[$i]){ $keys_gen }, ($val_type [$i]){ $vals_gen } )') p.gen('new_map_init($i, sizeof($val_type), ' + '(string[$i]){ $keys_gen }, ($val_type [$i]){ $vals_gen } )')
typ := 'map_$val_type' typ := 'map_${stringify_pointer(val_type)}'
p.register_map(typ) p.register_map(typ)
return typ return typ
} }
@ -2486,7 +2487,7 @@ fn (p mut Parser) map_init() string {
// if !p.table.known_type(val_type) { // if !p.table.known_type(val_type) {
// p.error('map init unknown type "$val_type"') // p.error('map init unknown type "$val_type"')
// } // }
typ := 'map_$val_type' typ := 'map_${stringify_pointer(val_type)}'
p.register_map(typ) p.register_map(typ)
p.gen('new_map(1, sizeof($val_type))') p.gen('new_map(1, sizeof($val_type))')
if p.tok == .lcbr { if p.tok == .lcbr {
@ -2593,16 +2594,17 @@ fn (p mut Parser) array_init() string {
p.check(.rsbr) p.check(.rsbr)
// type after `]`? (e.g. "[]string") // type after `]`? (e.g. "[]string")
exp_array := p.expected_type.starts_with('array_') exp_array := p.expected_type.starts_with('array_')
if p.tok != .name && p.tok != .mul && p.tok != .lsbr && i == 0 && !exp_array { if p.tok != .name && p.tok != .mul && p.tok != .lsbr && p.tok != .amp && i == 0 && !exp_array {
p.error('specify array type: `[]typ` instead of `[]`') p.error('specify array type: `[]typ` instead of `[]`')
} }
if i == 0 && (p.tok == .name || p.tok == .mul) && p.tokens[p.token_idx - 2].line_nr == p.tokens[p.token_idx - 1].line_nr { if i == 0 && (p.tok == .name || p.tok == .mul || p.tok == .amp) && p.tokens[p.token_idx - 2].line_nr == p.tokens[p.token_idx - 1].line_nr {
// TODO // TODO
// vals.len == 0 { // vals.len == 0 {
if exp_array { if exp_array {
p.error('no need to specify the full array type here, use `[]` instead of `[]${p.expected_type[6..]}`') type_expected := p.expected_type[6..].replace('ptr_', '&')
p.error('no need to specify the full array type here, use `[]` instead of `[]$type_expected`')
} }
typ = p.get_type().replace('*', '_ptr') typ = p.get_type()
} }
else if exp_array && i == 0 { else if exp_array && i == 0 {
// allow `known_array = []` // allow `known_array = []`
@ -2633,9 +2635,9 @@ fn (p mut Parser) array_init() string {
// if ptr { // if ptr {
// typ += '_ptr" // typ += '_ptr"
// } // }
real := typ.replace('_ptr', '*') real := parse_pointer(typ)
p.gen_array_init(real, no_alloc, new_arr_ph, i) p.gen_array_init(real, no_alloc, new_arr_ph, i)
typ = 'array_$typ' typ = 'array_${stringify_pointer(typ)}'
p.register_array(typ) p.register_array(typ)
return typ return typ
} }
@ -2749,10 +2751,10 @@ fn (p mut Parser) return_st() {
// Automatically wrap an object inside an option if the function // Automatically wrap an object inside an option if the function
// returns an option: // returns an option:
// `return val` => `return opt_ok(val)` // `return val` => `return opt_ok(val)`
if p.cur_fn.typ.ends_with(expr_type) && !is_none && p.cur_fn.typ.starts_with('Option_') { if p.cur_fn.typ.ends_with(stringify_pointer(expr_type)) && !is_none && p.cur_fn.typ.starts_with('Option_') {
tmp := p.get_tmp() tmp := p.get_tmp()
ret := p.cgen.cur_line[ph..] ret := p.cgen.cur_line[ph..]
typ := expr_type.replace('Option_', '') typ := parse_pointer(expr_type.replace('Option_', ''))
p.cgen.resetln('$expr_type $tmp = OPTION_CAST($expr_type)($ret);') p.cgen.resetln('$expr_type $tmp = OPTION_CAST($expr_type)($ret);')
p.genln(deferred_text) p.genln(deferred_text)
p.gen('return opt_ok(&$tmp, sizeof($typ))') p.gen('return opt_ok(&$tmp, sizeof($typ))')

View File

@ -428,7 +428,7 @@ fn (p mut Parser) struct_init(typ_ string) string {
// init map fields // init map fields
if field_typ.starts_with('map_') { if field_typ.starts_with('map_') {
p.gen_struct_field_init(sanitized_name) p.gen_struct_field_init(sanitized_name)
p.gen_empty_map(field_typ[4..]) p.gen_empty_map(parse_pointer(field_typ[4..]))
inited_fields << sanitized_name inited_fields << sanitized_name
if i != t.fields.len - 1 { if i != t.fields.len - 1 {
p.gen(',') p.gen(',')

View File

@ -679,7 +679,7 @@ fn (p mut Parser) check_types2(got_, expected_ string, throw bool) bool {
return true return true
} }
// Expected type "Option_os__File", got "os__File" // Expected type "Option_os__File", got "os__File"
if expected.starts_with('Option_') && expected.ends_with(got) { if expected.starts_with('Option_') && expected.ends_with(stringify_pointer(got)) {
return true return true
} }
// NsColor* return 0 // NsColor* return 0

View File

@ -124,3 +124,23 @@ fn test_opt_field() {
val := t.opt or { return } val := t.opt or { return }
assert val == 5 assert val == 5
} }
fn opt_ptr(a &int) ?&int {
if isnil(a) {
return none
}
return a
}
fn test_opt_ptr() {
a := 3
r1 := opt_ptr(&a) or {
&int(0)
}
assert r1 == &a
r2 := opt_ptr(&int(0)) or {
return
}
println('`$r2` should be none')
assert false
}