all: implement basic comptime field selector (#7888)
parent
e19277352b
commit
5841d5d8e1
|
@ -10,11 +10,11 @@ import v.errors
|
||||||
pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl
|
pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl
|
||||||
|
|
||||||
pub type Expr = AnonFn | ArrayDecompose | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral |
|
pub type Expr = AnonFn | ArrayDecompose | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral |
|
||||||
CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ConcatExpr |
|
CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ComptimeSelector |
|
||||||
EnumVal | FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral |
|
ConcatExpr | EnumVal | FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr |
|
||||||
Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr |
|
IntegerLiteral | Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr |
|
||||||
RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral |
|
PrefixExpr | RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral |
|
||||||
StructInit | Type | TypeOf | UnsafeExpr
|
StringLiteral | StructInit | Type | TypeOf | UnsafeExpr
|
||||||
|
|
||||||
pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt |
|
pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt |
|
||||||
EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt |
|
EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt |
|
||||||
|
@ -1056,8 +1056,19 @@ pub mut:
|
||||||
val string
|
val string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ComptimeSelector {
|
||||||
|
pub:
|
||||||
|
has_parens bool // if $() is used, for vfmt
|
||||||
|
left Expr
|
||||||
|
field_expr Expr
|
||||||
|
pub mut:
|
||||||
|
left_type table.Type
|
||||||
|
typ table.Type
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ComptimeCall {
|
pub struct ComptimeCall {
|
||||||
pub:
|
pub:
|
||||||
|
has_parens bool // if $() is used, for vfmt
|
||||||
method_name string
|
method_name string
|
||||||
left Expr
|
left Expr
|
||||||
is_vweb bool
|
is_vweb bool
|
||||||
|
@ -1147,7 +1158,7 @@ pub fn (expr Expr) position() token.Position {
|
||||||
IfGuardExpr {
|
IfGuardExpr {
|
||||||
return expr.expr.position()
|
return expr.expr.position()
|
||||||
}
|
}
|
||||||
ComptimeCall {
|
ComptimeCall, ComptimeSelector {
|
||||||
return expr.left.position()
|
return expr.left.position()
|
||||||
}
|
}
|
||||||
InfixExpr {
|
InfixExpr {
|
||||||
|
|
|
@ -212,6 +212,9 @@ pub fn (x Expr) str() string {
|
||||||
CharLiteral {
|
CharLiteral {
|
||||||
return '`$x.val`'
|
return '`$x.val`'
|
||||||
}
|
}
|
||||||
|
ComptimeSelector {
|
||||||
|
return '${x.left}.$$x.field_expr'
|
||||||
|
}
|
||||||
EnumVal {
|
EnumVal {
|
||||||
return '.$x.val'
|
return '.$x.val'
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,7 @@ mut:
|
||||||
prevent_sum_type_unwrapping_once bool // needed for assign new values to sum type, stopping unwrapping then
|
prevent_sum_type_unwrapping_once bool // needed for assign new values to sum type, stopping unwrapping then
|
||||||
loop_label string // set when inside a labelled for loop
|
loop_label string // set when inside a labelled for loop
|
||||||
timers &util.Timers = util.new_timers(false)
|
timers &util.Timers = util.new_timers(false)
|
||||||
|
comptime_fields_type map[string]table.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_checker(table &table.Table, pref &pref.Preferences) Checker {
|
pub fn new_checker(table &table.Table, pref &pref.Preferences) Checker {
|
||||||
|
@ -979,6 +980,9 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) {
|
||||||
// TODO
|
// TODO
|
||||||
return '', pos
|
return '', pos
|
||||||
}
|
}
|
||||||
|
ast.ComptimeSelector {
|
||||||
|
return '', pos
|
||||||
|
}
|
||||||
ast.Ident {
|
ast.Ident {
|
||||||
if expr.obj is ast.Var {
|
if expr.obj is ast.Var {
|
||||||
mut v := expr.obj as ast.Var
|
mut v := expr.obj as ast.Var
|
||||||
|
@ -3201,10 +3205,26 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type {
|
||||||
}
|
}
|
||||||
if node.method_name == 'html' {
|
if node.method_name == 'html' {
|
||||||
return c.table.find_type_idx('vweb.Result')
|
return c.table.find_type_idx('vweb.Result')
|
||||||
} else {
|
}
|
||||||
return table.string_type
|
return table.string_type
|
||||||
}
|
}
|
||||||
// return table.void_type
|
ast.ComptimeSelector {
|
||||||
|
node.left_type = c.unwrap_generic(c.expr(node.left))
|
||||||
|
expr_type := c.unwrap_generic(c.expr(node.field_expr))
|
||||||
|
expr_sym := c.table.get_type_symbol(expr_type)
|
||||||
|
if expr_type != table.string_type {
|
||||||
|
c.error('expected `string` instead of `$expr_sym.name` (e.g. `field.name`)',
|
||||||
|
node.field_expr.position())
|
||||||
|
}
|
||||||
|
if node.field_expr is ast.SelectorExpr {
|
||||||
|
expr_name := node.field_expr.expr.str()
|
||||||
|
if expr_name in c.comptime_fields_type {
|
||||||
|
return c.comptime_fields_type[expr_name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.error('compile time field access can only be used when iterating over `T.fields`',
|
||||||
|
node.field_expr.position())
|
||||||
|
return table.void_type
|
||||||
}
|
}
|
||||||
ast.ConcatExpr {
|
ast.ConcatExpr {
|
||||||
return c.concat_expr(mut node)
|
return c.concat_expr(mut node)
|
||||||
|
@ -4210,6 +4230,14 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if node.is_comptime { // Skip checking if needed
|
if node.is_comptime { // Skip checking if needed
|
||||||
|
// smartcast field type on comptime if
|
||||||
|
if branch.cond is ast.InfixExpr {
|
||||||
|
if branch.cond.op == .key_is {
|
||||||
|
se := branch.cond.left as ast.SelectorExpr
|
||||||
|
got_type := (branch.cond.right as ast.Type).typ
|
||||||
|
c.comptime_fields_type[se.expr.str()] = got_type
|
||||||
|
}
|
||||||
|
}
|
||||||
cur_skip_flags := c.skip_flags
|
cur_skip_flags := c.skip_flags
|
||||||
if found_branch {
|
if found_branch {
|
||||||
c.skip_flags = true
|
c.skip_flags = true
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/comptime_field_selector_not_in_for_err.vv:9:5: error: compile time field access can only be used when iterating over `T.fields`
|
||||||
|
7 | mut t := T{}
|
||||||
|
8 | name := 'test'
|
||||||
|
9 | t.$name = '3'
|
||||||
|
| ~~~~
|
||||||
|
10 | }
|
||||||
|
11 |
|
|
@ -0,0 +1,14 @@
|
||||||
|
struct Foo {
|
||||||
|
test int
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test<T>() {
|
||||||
|
mut t := T{}
|
||||||
|
name := 'test'
|
||||||
|
t.$name = '3'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test<Foo>()
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
vlib/v/checker/tests/comptime_field_selector_not_name_err.vv:10:7: error: expected `string` instead of `FieldData` (e.g. `field.name`)
|
||||||
|
8 | $for f in T.fields {
|
||||||
|
9 | $if f.typ is string {
|
||||||
|
10 | t.$f = '3'
|
||||||
|
| ^
|
||||||
|
11 | }
|
||||||
|
12 | }
|
|
@ -0,0 +1,17 @@
|
||||||
|
struct Foo {
|
||||||
|
test int
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test<T>() {
|
||||||
|
mut t := T{}
|
||||||
|
$for f in T.fields {
|
||||||
|
$if f.typ is string {
|
||||||
|
t.$f = '3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test<Foo>()
|
||||||
|
}
|
|
@ -924,8 +924,17 @@ pub fn (mut f Fmt) expr(node ast.Expr) {
|
||||||
f.write("\$tmpl('$node.args_var')")
|
f.write("\$tmpl('$node.args_var')")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
f.write('${node.left}.\$${node.method_name}($node.args_var)')
|
method_expr := if node.has_parens {
|
||||||
|
'(${node.method_name}($node.args_var))'
|
||||||
|
} else {
|
||||||
|
'${node.method_name}($node.args_var)'
|
||||||
}
|
}
|
||||||
|
f.write('${node.left}.$$method_expr')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast.ComptimeSelector {
|
||||||
|
field_expr := if node.has_parens { '($node.field_expr)' } else { node.field_expr.str() }
|
||||||
|
f.write('${node.left}.$$field_expr')
|
||||||
}
|
}
|
||||||
ast.ConcatExpr {
|
ast.ConcatExpr {
|
||||||
for i, val in node.vals {
|
for i, val in node.vals {
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
struct Foo {
|
||||||
|
mut:
|
||||||
|
test string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (f Foo) print() {
|
||||||
|
println('test')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test<T>() {
|
||||||
|
mut t := T{}
|
||||||
|
t.name = '2'
|
||||||
|
$for f in T.fields {
|
||||||
|
$if f.typ is string {
|
||||||
|
println(t.$f.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test<Foo>()
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
struct Foo {
|
||||||
|
mut:
|
||||||
|
test string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (f Foo) print() {
|
||||||
|
println('test')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test<T>() {
|
||||||
|
mut t := T{}
|
||||||
|
t.name = '2'
|
||||||
|
$for f in T.fields {
|
||||||
|
$if f.typ is string {
|
||||||
|
println(t.$(f.name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test<Foo>()
|
||||||
|
}
|
|
@ -109,7 +109,10 @@ mut:
|
||||||
inside_call bool
|
inside_call bool
|
||||||
has_main bool
|
has_main bool
|
||||||
inside_const bool
|
inside_const bool
|
||||||
comp_for_method string // $for method in T {
|
comp_for_method string // $for method in T.methods {}
|
||||||
|
comp_for_field_var string // $for field in T.fields {}; the variable name
|
||||||
|
comp_for_field_value table.Field // value of the field variable
|
||||||
|
comp_for_field_type table.Type // type of the field variable inferred from `$if field.typ is T {}`
|
||||||
comptime_var_type_map map[string]table.Type
|
comptime_var_type_map map[string]table.Type
|
||||||
// tmp_arg_vars_to_free []string
|
// tmp_arg_vars_to_free []string
|
||||||
// autofree_pregen map[string]string
|
// autofree_pregen map[string]string
|
||||||
|
@ -2491,6 +2494,9 @@ fn (mut g Gen) expr(node ast.Expr) {
|
||||||
ast.ComptimeCall {
|
ast.ComptimeCall {
|
||||||
g.comptime_call(node)
|
g.comptime_call(node)
|
||||||
}
|
}
|
||||||
|
ast.ComptimeSelector {
|
||||||
|
g.comptime_selector(node)
|
||||||
|
}
|
||||||
ast.Comment {}
|
ast.Comment {}
|
||||||
ast.ConcatExpr {
|
ast.ConcatExpr {
|
||||||
g.concat_expr(node)
|
g.concat_expr(node)
|
||||||
|
|
|
@ -7,6 +7,26 @@ import v.ast
|
||||||
import v.table
|
import v.table
|
||||||
import v.util
|
import v.util
|
||||||
|
|
||||||
|
fn (mut g Gen) comptime_selector(node ast.ComptimeSelector) {
|
||||||
|
g.expr(node.left)
|
||||||
|
if node.left_type.is_ptr() {
|
||||||
|
g.write('->')
|
||||||
|
} else {
|
||||||
|
g.write('.')
|
||||||
|
}
|
||||||
|
// check for field.name
|
||||||
|
if node.field_expr is ast.SelectorExpr {
|
||||||
|
if node.field_expr.expr is ast.Ident {
|
||||||
|
if node.field_expr.expr.name == g.comp_for_field_var &&
|
||||||
|
node.field_expr.field_name == 'name' {
|
||||||
|
g.write(g.comp_for_field_value.name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.expr(node.field_expr)
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut g Gen) comptime_call(node ast.ComptimeCall) {
|
fn (mut g Gen) comptime_call(node ast.ComptimeCall) {
|
||||||
if node.is_vweb {
|
if node.is_vweb {
|
||||||
is_html := node.method_name == 'html'
|
is_html := node.method_name == 'html'
|
||||||
|
@ -314,17 +334,17 @@ fn (mut g Gen) comp_for(node ast.CompFor) {
|
||||||
}
|
}
|
||||||
} else if node.kind == .fields {
|
} else if node.kind == .fields {
|
||||||
// TODO add fields
|
// TODO add fields
|
||||||
// TODO: temporary, remove this
|
if sym.info is table.Struct {
|
||||||
sym_info := sym.info
|
mut fields := sym.info.fields.filter(it.attrs.len == 0)
|
||||||
if sym_info is table.Struct {
|
fields_with_attrs := sym.info.fields.filter(it.attrs.len > 0)
|
||||||
mut fields := sym_info.fields.filter(it.attrs.len == 0)
|
|
||||||
fields_with_attrs := sym_info.fields.filter(it.attrs.len > 0)
|
|
||||||
fields << fields_with_attrs
|
fields << fields_with_attrs
|
||||||
if fields.len > 0 {
|
if fields.len > 0 {
|
||||||
g.writeln('\tFieldData $node.val_var;')
|
g.writeln('\tFieldData $node.val_var;')
|
||||||
g.writeln('\tmemset(&$node.val_var, 0, sizeof(FieldData));')
|
g.writeln('\tmemset(&$node.val_var, 0, sizeof(FieldData));')
|
||||||
}
|
}
|
||||||
for field in fields {
|
for field in fields {
|
||||||
|
g.comp_for_field_var = node.val_var
|
||||||
|
g.comp_for_field_value = field
|
||||||
g.writeln('\t// field $i')
|
g.writeln('\t// field $i')
|
||||||
g.writeln('\t${node.val_var}.name = _SLIT("$field.name");')
|
g.writeln('\t${node.val_var}.name = _SLIT("$field.name");')
|
||||||
if field.attrs.len == 0 {
|
if field.attrs.len == 0 {
|
||||||
|
|
|
@ -573,6 +573,9 @@ fn (mut g JsGen) expr(node ast.Expr) {
|
||||||
ast.ComptimeCall {
|
ast.ComptimeCall {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
ast.ComptimeSelector {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
ast.UnsafeExpr {
|
ast.UnsafeExpr {
|
||||||
g.expr(node.expr)
|
g.expr(node.expr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -263,37 +263,19 @@ fn os_from_string(os string) pref.OS {
|
||||||
return .linux
|
return .linux
|
||||||
}
|
}
|
||||||
|
|
||||||
// `app.$action()` (`action` is a string)
|
fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr {
|
||||||
// `typ` is `App` in this example
|
|
||||||
// fn (mut p Parser) comptime_method_call(typ table.Type) ast.ComptimeCall {
|
|
||||||
fn (mut p Parser) comptime_method_call(left ast.Expr) ast.ComptimeCall {
|
|
||||||
p.check(.dollar)
|
p.check(.dollar)
|
||||||
|
mut has_parens := false
|
||||||
|
if p.tok.kind == .lpar {
|
||||||
|
p.check(.lpar)
|
||||||
|
has_parens = true
|
||||||
|
}
|
||||||
|
if p.peek_tok.kind == .lpar {
|
||||||
method_name := p.check_name()
|
method_name := p.check_name()
|
||||||
/*
|
// `app.$action()` (`action` is a string)
|
||||||
mut j := 0
|
if has_parens {
|
||||||
sym := p.table.get_type_symbol(typ)
|
p.check(.rpar)
|
||||||
if sym.kind != .struct_ {
|
|
||||||
p.error('not a struct')
|
|
||||||
}
|
}
|
||||||
// info := sym.info as table.Struct
|
|
||||||
for method in sym.methods {
|
|
||||||
if method.return_type != table.void_type {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
receiver := method.args[0]
|
|
||||||
if !p.expr_var.ptr {
|
|
||||||
p.error('`$p.expr_var.name` needs to be a reference')
|
|
||||||
}
|
|
||||||
amp := if receiver.is_mut && !p.expr_var.ptr { '&' } else { '' }
|
|
||||||
if j > 0 {
|
|
||||||
p.gen(' else ')
|
|
||||||
}
|
|
||||||
p.genln('if (string_eq($method_name, _STR("$method.name")) ) ' + '${typ.name}_$method.name ($amp $p.expr_var.name);')
|
|
||||||
*/
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
p.check(.lpar)
|
p.check(.lpar)
|
||||||
mut args_var := ''
|
mut args_var := ''
|
||||||
if p.tok.kind == .name {
|
if p.tok.kind == .name {
|
||||||
|
@ -303,13 +285,22 @@ fn (mut p Parser) comptime_method_call(left ast.Expr) ast.ComptimeCall {
|
||||||
p.check(.rpar)
|
p.check(.rpar)
|
||||||
if p.tok.kind == .key_orelse {
|
if p.tok.kind == .key_orelse {
|
||||||
p.check(.key_orelse)
|
p.check(.key_orelse)
|
||||||
// p.genln('else {')
|
|
||||||
p.check(.lcbr)
|
p.check(.lcbr)
|
||||||
// p.statements()
|
|
||||||
}
|
}
|
||||||
return ast.ComptimeCall{
|
return ast.ComptimeCall{
|
||||||
|
has_parens: has_parens
|
||||||
left: left
|
left: left
|
||||||
method_name: method_name
|
method_name: method_name
|
||||||
args_var: args_var
|
args_var: args_var
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
expr := p.expr(0)
|
||||||
|
if has_parens {
|
||||||
|
p.check(.rpar)
|
||||||
|
}
|
||||||
|
return ast.ComptimeSelector{
|
||||||
|
has_parens: has_parens
|
||||||
|
left: left
|
||||||
|
field_expr: expr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1413,7 +1413,7 @@ fn (mut p Parser) scope_register_ab() {
|
||||||
fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
|
fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
|
||||||
p.next()
|
p.next()
|
||||||
if p.tok.kind == .dollar {
|
if p.tok.kind == .dollar {
|
||||||
return p.comptime_method_call(left)
|
return p.comptime_selector(left)
|
||||||
}
|
}
|
||||||
is_generic_call := p.is_generic_call()
|
is_generic_call := p.is_generic_call()
|
||||||
name_pos := p.tok.position()
|
name_pos := p.tok.position()
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
struct Foo {
|
||||||
|
immutable int
|
||||||
|
mut:
|
||||||
|
test string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn comptime_field_selector_read<T>() []string {
|
||||||
|
mut t := T{}
|
||||||
|
t.name = '2'
|
||||||
|
t.test = '1'
|
||||||
|
mut value_list := []string{}
|
||||||
|
$for f in T.fields {
|
||||||
|
$if f.typ is string {
|
||||||
|
value_list << t.$f.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value_list
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_comptime_field_selector_read() {
|
||||||
|
assert comptime_field_selector_read<Foo>() == ['1', '2']
|
||||||
|
}
|
||||||
|
|
||||||
|
fn comptime_field_selector_write<T>() T {
|
||||||
|
mut t := T{}
|
||||||
|
$for f in T.fields {
|
||||||
|
$if f.typ is string {
|
||||||
|
t.$f.name = '1'
|
||||||
|
}
|
||||||
|
$if f.typ is int {
|
||||||
|
t.$f.name = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_comptime_field_selector_write() {
|
||||||
|
res := comptime_field_selector_write<Foo>()
|
||||||
|
assert res.immutable == 1
|
||||||
|
assert res.test == '1'
|
||||||
|
assert res.name == '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Foo2 {
|
||||||
|
f Foo
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nested_with_parentheses<T>() T {
|
||||||
|
mut t := T{}
|
||||||
|
$for f in T.fields {
|
||||||
|
$if f.typ is Foo {
|
||||||
|
t.$(f.name).test = '1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_nested_with_parentheses() {
|
||||||
|
res := nested_with_parentheses<Foo2>()
|
||||||
|
assert res.f.test == '1'
|
||||||
|
}
|
Loading…
Reference in New Issue