ast, cgen, checker, parser: support method arguments in comptime $for (#9208)

pull/9249/head
Miccah 2021-03-11 07:04:34 -06:00 committed by GitHub
parent 8de6511056
commit f26d2f02b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 112 additions and 53 deletions

View File

@ -1184,6 +1184,7 @@ pub mut:
sym table.TypeSymbol
result_type table.Type
env_value string
args []CallArg
}
pub struct None {

View File

@ -4072,15 +4072,9 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) table.Type {
return rtyp
}
if node.method_name == 'method' {
if node.args_var.len > 0 {
v := node.scope.find_var(node.args_var) or {
c.error('unknown identifier `$node.args_var`', node.method_pos)
return table.void_type
}
s := c.table.type_to_str(c.expr(v.expr))
if s != '[]string' {
c.error('expected `[]string`, not s', node.method_pos)
}
for i, arg in node.args {
// check each arg expression
node.args[i].typ = c.expr(arg.expr)
}
// assume string for now
return table.string_type

View File

@ -1,14 +1,7 @@
vlib/v/checker/tests/comptime_call_method.vv:10:7: error: unknown identifier `wrong`
vlib/v/checker/tests/comptime_call_method.vv:10:14: error: undefined ident: `wrong`
8 | s1 := S1{}
9 | $for method in S1.methods {
10 | s1.$method(wrong)
| ~~~~~~
11 | arg := 7
12 | s1.$method(arg)
vlib/v/checker/tests/comptime_call_method.vv:12:7: error: expected `[]string`, not s
10 | s1.$method(wrong)
11 | arg := 7
12 | s1.$method(arg)
| ~~~~~~
13 | }
14 | }
| ~~~~~
11 | }
12 | }

View File

@ -8,7 +8,5 @@ fn test_methods_arg() {
s1 := S1{}
$for method in S1.methods {
s1.$method(wrong)
arg := 7
s1.$method(arg)
}
}

View File

@ -986,10 +986,15 @@ pub fn (mut f Fmt) comptime_call(node ast.ComptimeCall) {
} else if node.is_env {
f.write("\$env('$node.args_var')")
} else {
method_expr := if node.has_parens {
'(${node.method_name}($node.args_var))'
inner_args := if node.args_var != '' {
node.args_var
} else {
'${node.method_name}($node.args_var)'
node.args.map(it.str()).join(', ')
}
method_expr := if node.has_parens {
'(${node.method_name}($inner_args))'
} else {
'${node.method_name}($inner_args)'
}
f.write('${node.left}.$$method_expr')
}

View File

@ -67,12 +67,9 @@ fn (mut a App) my_method(p string) Result {
}
fn handle_conn<T>(mut app T) {
mut vars := []string{cap: 123}
vars << 'abc'
vars << 'def'
$for method in T.methods {
$if method.return_type is Result {
app.$method(vars)
app.$method('abc', 'def')
}
}
}

View File

@ -76,7 +76,34 @@ fn (mut g Gen) comptime_call(node ast.ComptimeCall) {
for val in vals {
}
*/
expand_strs := if node.args.len > 0 && m.params.len - 1 >= node.args.len {
arg := node.args[node.args.len - 1]
param := m.params[node.args.len]
arg.expr is ast.Ident && g.table.type_to_str(arg.typ) == '[]string'
&& g.table.type_to_str(param.typ) != '[]string'
} else {
false
}
// check argument length and types
if m.params.len - 1 != node.args.len && !expand_strs {
// do not generate anything if the argument lengths don't match
g.writeln('/* skipping ${node.sym.name}.$m.name due to mismatched arguments list */')
// verror('expected ${m.params.len-1} arguments to method ${node.sym.name}.$m.name, but got $node.args.len')
return
}
// TODO: check argument types
g.write('${util.no_dots(node.sym.name)}_${g.comp_for_method}(')
// try to see if we need to pass a pointer
if node.left is ast.Ident {
scope := g.file.scope.innermost(node.pos.pos)
if v := scope.find_var(node.left.name) {
if m.params[0].typ.is_ptr() && !v.typ.is_ptr() {
g.write('&')
}
}
}
g.expr(node.left)
if m.params.len > 1 {
g.write(', ')
@ -87,15 +114,25 @@ fn (mut g Gen) comptime_call(node ast.ComptimeCall) {
continue
}
}
if m.params[i].typ.is_int() || m.params[i].typ.idx() == table.bool_type_idx {
// Gets the type name and cast the string to the type with the string_<type> function
type_name := g.table.types[int(m.params[i].typ)].str()
g.write('string_${type_name}(((string*)${node.args_var}.data) [${i - 1}])')
} else {
g.write('((string*)${node.args_var}.data) [${i - 1}] ')
}
if i < m.params.len - 1 {
if i - 1 < node.args.len - 1 {
g.expr(node.args[i - 1].expr)
g.write(', ')
} else if !expand_strs && i == node.args.len {
g.expr(node.args[i - 1].expr)
break
} else {
// last argument; try to expand if it's []string
idx := i - node.args.len
if m.params[i].typ.is_int() || m.params[i].typ.idx() == table.bool_type_idx {
// Gets the type name and cast the string to the type with the string_<type> function
type_name := g.table.types[int(m.params[i].typ)].str()
g.write('string_${type_name}(((string*)${node.args[node.args.len - 1]}.data) [$idx])')
} else {
g.write('((string*)${node.args[node.args.len - 1]}.data) [$idx] ')
}
if i < m.params.len - 1 {
g.write(', ')
}
}
}
g.write(' ); // vweb action call with args')

View File

@ -305,12 +305,7 @@ fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr {
p.mark_var_as_used(method_name)
// `app.$action()` (`action` is a string)
p.check(.lpar)
mut args_var := ''
if p.tok.kind == .name {
args_var = p.tok.lit
p.mark_var_as_used(args_var)
p.next()
}
args := p.call_args()
p.check(.rpar)
if p.tok.kind == .key_orelse {
p.check(.key_orelse)
@ -321,7 +316,8 @@ fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr {
method_name: method_name
method_pos: method_pos
scope: p.scope
args_var: args_var
args_var: ''
args: args
pos: start_pos.extend(p.prev_tok.position())
}
}

View File

@ -1,14 +1,52 @@
struct TestStruct {}
struct TestStruct {
mut:
one_arg_called bool
two_args_called bool
three_args_called bool
}
fn (t TestStruct) test(arg1 string, arg2 string, arg3 string) {}
fn (mut t TestStruct) one_arg(a1 string) {
t.one_arg_called = true
}
fn (mut t TestStruct) two_args(a2 string, b2 int) {
t.two_args_called = true
}
fn (mut t TestStruct) three_args(a3 string, b3 int, c3 []string) {
t.three_args_called = true
}
fn test_comptime_method_names() {
mut num_methods := 0
$for method in TestStruct.methods {
if method.name == 'test' {
args := method.args
assert args[0].name == 'arg1'
assert args[1].name == 'arg2'
assert args[2].name == 'arg3'
if method.name == 'one_arg' {
assert method.args[0].name == 'a1'
num_methods++
} else if method.name == 'two_args' {
assert method.args[0].name == 'a2'
assert method.args[1].name == 'b2'
num_methods++
} else if method.name == 'three_args' {
assert method.args[0].name == 'a3'
assert method.args[1].name == 'b3'
assert method.args[2].name == 'c3'
num_methods++
}
}
assert num_methods == 3
}
fn test_comptime_call_method() {
mut t := TestStruct{}
$for method in TestStruct.methods {
if method.name == 'one_arg' {
t.$method('one')
} else if method.name == 'two_args' {
t.$method('two', 2)
} else if method.name == 'three_args' {
t.$method('three', 3, ['th' 'ree'])
}
}
assert t.one_arg_called
assert t.two_args_called
assert t.three_args_called
}

View File

@ -379,12 +379,12 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T) {
// should be called first.
if !route_path.contains('/:') && url_words == route_words {
// We found a match
app.$method(method_args)
app.$method()
return
}
if url_words.len == 0 && route_words == ['index'] && method.name == 'index' {
app.$method(method_args)
app.$method()
return
}