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 Expr = AnonFn | ArrayDecompose | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral |
|
||||
CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ConcatExpr |
|
||||
EnumVal | FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral |
|
||||
Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr |
|
||||
RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral |
|
||||
StructInit | Type | TypeOf | UnsafeExpr
|
||||
CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ComptimeSelector |
|
||||
ConcatExpr | EnumVal | FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr |
|
||||
IntegerLiteral | Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr |
|
||||
PrefixExpr | RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral |
|
||||
StringLiteral | StructInit | Type | TypeOf | UnsafeExpr
|
||||
|
||||
pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt |
|
||||
EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt |
|
||||
|
@ -1056,8 +1056,19 @@ pub mut:
|
|||
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:
|
||||
has_parens bool // if $() is used, for vfmt
|
||||
method_name string
|
||||
left Expr
|
||||
is_vweb bool
|
||||
|
@ -1147,7 +1158,7 @@ pub fn (expr Expr) position() token.Position {
|
|||
IfGuardExpr {
|
||||
return expr.expr.position()
|
||||
}
|
||||
ComptimeCall {
|
||||
ComptimeCall, ComptimeSelector {
|
||||
return expr.left.position()
|
||||
}
|
||||
InfixExpr {
|
||||
|
|
|
@ -212,6 +212,9 @@ pub fn (x Expr) str() string {
|
|||
CharLiteral {
|
||||
return '`$x.val`'
|
||||
}
|
||||
ComptimeSelector {
|
||||
return '${x.left}.$$x.field_expr'
|
||||
}
|
||||
EnumVal {
|
||||
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
|
||||
loop_label string // set when inside a labelled for loop
|
||||
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 {
|
||||
|
@ -979,6 +980,9 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) {
|
|||
// TODO
|
||||
return '', pos
|
||||
}
|
||||
ast.ComptimeSelector {
|
||||
return '', pos
|
||||
}
|
||||
ast.Ident {
|
||||
if expr.obj is 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' {
|
||||
return c.table.find_type_idx('vweb.Result')
|
||||
} else {
|
||||
return table.string_type
|
||||
}
|
||||
// return table.void_type
|
||||
return table.string_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 {
|
||||
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
|
||||
// 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
|
||||
if found_branch {
|
||||
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,9 +924,18 @@ pub fn (mut f Fmt) expr(node ast.Expr) {
|
|||
f.write("\$tmpl('$node.args_var')")
|
||||
}
|
||||
} 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 {
|
||||
for i, val in node.vals {
|
||||
if i != 0 {
|
||||
|
|
|
@ -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
|
||||
has_main 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
|
||||
// tmp_arg_vars_to_free []string
|
||||
// autofree_pregen map[string]string
|
||||
|
@ -2491,6 +2494,9 @@ fn (mut g Gen) expr(node ast.Expr) {
|
|||
ast.ComptimeCall {
|
||||
g.comptime_call(node)
|
||||
}
|
||||
ast.ComptimeSelector {
|
||||
g.comptime_selector(node)
|
||||
}
|
||||
ast.Comment {}
|
||||
ast.ConcatExpr {
|
||||
g.concat_expr(node)
|
||||
|
|
|
@ -7,6 +7,26 @@ import v.ast
|
|||
import v.table
|
||||
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) {
|
||||
if node.is_vweb {
|
||||
is_html := node.method_name == 'html'
|
||||
|
@ -314,17 +334,17 @@ fn (mut g Gen) comp_for(node ast.CompFor) {
|
|||
}
|
||||
} else if node.kind == .fields {
|
||||
// TODO add fields
|
||||
// TODO: temporary, remove this
|
||||
sym_info := sym.info
|
||||
if sym_info is table.Struct {
|
||||
mut fields := sym_info.fields.filter(it.attrs.len == 0)
|
||||
fields_with_attrs := sym_info.fields.filter(it.attrs.len > 0)
|
||||
if sym.info is table.Struct {
|
||||
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
|
||||
if fields.len > 0 {
|
||||
g.writeln('\tFieldData $node.val_var;')
|
||||
g.writeln('\tmemset(&$node.val_var, 0, sizeof(FieldData));')
|
||||
}
|
||||
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${node.val_var}.name = _SLIT("$field.name");')
|
||||
if field.attrs.len == 0 {
|
||||
|
|
|
@ -573,6 +573,9 @@ fn (mut g JsGen) expr(node ast.Expr) {
|
|||
ast.ComptimeCall {
|
||||
// TODO
|
||||
}
|
||||
ast.ComptimeSelector {
|
||||
// TODO
|
||||
}
|
||||
ast.UnsafeExpr {
|
||||
g.expr(node.expr)
|
||||
}
|
||||
|
|
|
@ -263,53 +263,44 @@ fn os_from_string(os string) pref.OS {
|
|||
return .linux
|
||||
}
|
||||
|
||||
// `app.$action()` (`action` is a string)
|
||||
// `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 {
|
||||
fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr {
|
||||
p.check(.dollar)
|
||||
method_name := p.check_name()
|
||||
/*
|
||||
mut j := 0
|
||||
sym := p.table.get_type_symbol(typ)
|
||||
if sym.kind != .struct_ {
|
||||
p.error('not a struct')
|
||||
mut has_parens := false
|
||||
if p.tok.kind == .lpar {
|
||||
p.check(.lpar)
|
||||
has_parens = true
|
||||
}
|
||||
// info := sym.info as table.Struct
|
||||
for method in sym.methods {
|
||||
if method.return_type != table.void_type {
|
||||
continue
|
||||
if p.peek_tok.kind == .lpar {
|
||||
method_name := p.check_name()
|
||||
// `app.$action()` (`action` is a string)
|
||||
if has_parens {
|
||||
p.check(.rpar)
|
||||
}
|
||||
/*
|
||||
receiver := method.args[0]
|
||||
if !p.expr_var.ptr {
|
||||
p.error('`$p.expr_var.name` needs to be a reference')
|
||||
p.check(.lpar)
|
||||
mut args_var := ''
|
||||
if p.tok.kind == .name {
|
||||
args_var = p.tok.lit
|
||||
p.next()
|
||||
}
|
||||
amp := if receiver.is_mut && !p.expr_var.ptr { '&' } else { '' }
|
||||
if j > 0 {
|
||||
p.gen(' else ')
|
||||
p.check(.rpar)
|
||||
if p.tok.kind == .key_orelse {
|
||||
p.check(.key_orelse)
|
||||
p.check(.lcbr)
|
||||
}
|
||||
return ast.ComptimeCall{
|
||||
has_parens: has_parens
|
||||
left: left
|
||||
method_name: method_name
|
||||
args_var: args_var
|
||||
}
|
||||
p.genln('if (string_eq($method_name, _STR("$method.name")) ) ' + '${typ.name}_$method.name ($amp $p.expr_var.name);')
|
||||
*/
|
||||
j++
|
||||
}
|
||||
*/
|
||||
p.check(.lpar)
|
||||
mut args_var := ''
|
||||
if p.tok.kind == .name {
|
||||
args_var = p.tok.lit
|
||||
p.next()
|
||||
expr := p.expr(0)
|
||||
if has_parens {
|
||||
p.check(.rpar)
|
||||
}
|
||||
p.check(.rpar)
|
||||
if p.tok.kind == .key_orelse {
|
||||
p.check(.key_orelse)
|
||||
// p.genln('else {')
|
||||
p.check(.lcbr)
|
||||
// p.statements()
|
||||
}
|
||||
return ast.ComptimeCall{
|
||||
return ast.ComptimeSelector{
|
||||
has_parens: has_parens
|
||||
left: left
|
||||
method_name: method_name
|
||||
args_var: args_var
|
||||
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 {
|
||||
p.next()
|
||||
if p.tok.kind == .dollar {
|
||||
return p.comptime_method_call(left)
|
||||
return p.comptime_selector(left)
|
||||
}
|
||||
is_generic_call := p.is_generic_call()
|
||||
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