all: implement basic comptime field selector (#7888)

pull/7897/head
Daniel Däschle 2021-01-05 15:11:43 +01:00 committed by GitHub
parent e19277352b
commit 5841d5d8e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 281 additions and 57 deletions

View File

@ -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 {

View File

@ -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'
}

View File

@ -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

View File

@ -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 |

View File

@ -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>()
}

View File

@ -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 | }

View File

@ -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>()
}

View File

@ -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 {

View File

@ -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>()
}

View File

@ -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>()
}

View File

@ -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)

View File

@ -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 {

View File

@ -573,6 +573,9 @@ fn (mut g JsGen) expr(node ast.Expr) {
ast.ComptimeCall {
// TODO
}
ast.ComptimeSelector {
// TODO
}
ast.UnsafeExpr {
g.expr(node.expr)
}

View File

@ -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
}
}

View File

@ -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()

View File

@ -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'
}