parser, checker, cgen: minor cleanup of go_expr() (#13875)

pull/13861/head
yuyi 2022-04-01 00:58:04 +08:00 committed by GitHub
parent db3bbb58cf
commit bc98c11d9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 299 additions and 299 deletions

View File

@ -2111,32 +2111,6 @@ fn (mut c Checker) global_decl(mut node ast.GlobalDecl) {
} }
} }
fn (mut c Checker) go_expr(mut node ast.GoExpr) ast.Type {
ret_type := c.call_expr(mut node.call_expr)
if node.call_expr.or_block.kind != .absent {
c.error('optional handling cannot be done in `go` call. Do it when calling `.wait()`',
node.call_expr.or_block.pos)
}
// Make sure there are no mutable arguments
for arg in node.call_expr.args {
if arg.is_mut && !arg.typ.is_ptr() {
c.error('function in `go` statement cannot contain mutable non-reference arguments',
arg.expr.pos())
}
}
if node.call_expr.is_method && node.call_expr.receiver_type.is_ptr()
&& !node.call_expr.left_type.is_ptr() {
c.error('method in `go` statement cannot have non-reference mutable receiver',
node.call_expr.left.pos())
}
if c.pref.backend.is_js() {
return c.table.find_or_register_promise(ret_type)
} else {
return c.table.find_or_register_thread(ret_type)
}
}
fn (mut c Checker) asm_stmt(mut stmt ast.AsmStmt) { fn (mut c Checker) asm_stmt(mut stmt ast.AsmStmt) {
if stmt.is_goto { if stmt.is_goto {
c.warn('inline assembly goto is not supported, it will most likely not work', c.warn('inline assembly goto is not supported, it will most likely not work',

View File

@ -1484,6 +1484,32 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type {
return ast.void_type return ast.void_type
} }
fn (mut c Checker) go_expr(mut node ast.GoExpr) ast.Type {
ret_type := c.call_expr(mut node.call_expr)
if node.call_expr.or_block.kind != .absent {
c.error('optional handling cannot be done in `go` call. Do it when calling `.wait()`',
node.call_expr.or_block.pos)
}
// Make sure there are no mutable arguments
for arg in node.call_expr.args {
if arg.is_mut && !arg.typ.is_ptr() {
c.error('function in `go` statement cannot contain mutable non-reference arguments',
arg.expr.pos())
}
}
if node.call_expr.is_method && node.call_expr.receiver_type.is_ptr()
&& !node.call_expr.left_type.is_ptr() {
c.error('method in `go` statement cannot have non-reference mutable receiver',
node.call_expr.left.pos())
}
if c.pref.backend.is_js() {
return c.table.find_or_register_promise(ret_type)
} else {
return c.table.find_or_register_thread(ret_type)
}
}
fn (mut c Checker) deprecate_fnmethod(kind string, name string, the_fn ast.Fn, node ast.CallExpr) { fn (mut c Checker) deprecate_fnmethod(kind string, name string, the_fn ast.Fn, node ast.CallExpr) {
mut deprecation_message := '' mut deprecation_message := ''
now := time.now() now := time.now()

View File

@ -5146,258 +5146,6 @@ fn (mut g Gen) enum_val(node ast.EnumVal) {
g.write('${styp}__$node.val') g.write('${styp}__$node.val')
} }
fn (mut g Gen) go_expr(node ast.GoExpr) {
line := g.go_before_stmt(0)
mut handle := ''
tmp := g.new_tmp_var()
mut expr := node.call_expr
mut name := expr.name // util.no_dots(expr.name)
// TODO: fn call is duplicated. merge with fn_call().
for i, concrete_type in expr.concrete_types {
if concrete_type != ast.void_type && concrete_type != 0 {
// Using _T_ to differentiate between get<string> and get_string
// `foo<int>()` => `foo_T_int()`
if i == 0 {
name += '_T'
}
name += '_' + g.typ(concrete_type)
}
}
if expr.is_method {
receiver_sym := g.table.sym(expr.receiver_type)
name = receiver_sym.name + '_' + name
} else if mut expr.left is ast.AnonFn {
g.gen_anon_fn_decl(mut expr.left)
name = expr.left.decl.name
} else if expr.is_fn_var {
name = g.table.sym(expr.fn_var_type).name
}
name = util.no_dots(name)
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') {
mut key := expr.name
if expr.is_method {
sym := g.table.sym(expr.receiver_type)
key = sym.name + '.' + expr.name
}
g.write('/* obf go: $key */')
name = g.obf_table[key] or {
panic('cgen: obf name "$key" not found, this should never happen')
}
}
g.empty_line = true
g.writeln('// start go')
wrapper_struct_name := 'thread_arg_' + name
wrapper_fn_name := name + '_thread_wrapper'
arg_tmp_var := 'arg_' + tmp
g.writeln('$wrapper_struct_name *$arg_tmp_var = malloc(sizeof(thread_arg_$name));')
if expr.is_method {
g.write('$arg_tmp_var->arg0 = ')
// TODO is this needed?
/*
if false && !expr.return_type.is_ptr() {
g.write('&')
}
*/
g.expr(expr.left)
g.writeln(';')
}
for i, arg in expr.args {
g.write('$arg_tmp_var->arg${i + 1} = ')
g.expr(arg.expr)
g.writeln(';')
}
s_ret_typ := g.typ(node.call_expr.return_type)
if g.pref.os == .windows && node.call_expr.return_type != ast.void_type {
g.writeln('$arg_tmp_var->ret_ptr = malloc(sizeof($s_ret_typ));')
}
is_opt := node.call_expr.return_type.has_flag(.optional)
mut gohandle_name := ''
if node.call_expr.return_type == ast.void_type {
gohandle_name = if is_opt { '__v_thread_Option_void' } else { '__v_thread' }
} else {
opt := if is_opt { 'Option_' } else { '' }
gohandle_name = '__v_thread_$opt${g.table.sym(g.unwrap_generic(node.call_expr.return_type)).cname}'
}
if g.pref.os == .windows {
simple_handle := if node.is_expr && node.call_expr.return_type != ast.void_type {
'thread_handle_$tmp'
} else {
'thread_$tmp'
}
g.writeln('HANDLE $simple_handle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)$wrapper_fn_name, $arg_tmp_var, 0, 0);')
g.writeln('if (!$simple_handle) panic_lasterr(tos3("`go ${name}()`: "));')
if node.is_expr && node.call_expr.return_type != ast.void_type {
g.writeln('$gohandle_name thread_$tmp = {')
g.writeln('\t.ret_ptr = $arg_tmp_var->ret_ptr,')
g.writeln('\t.handle = thread_handle_$tmp')
g.writeln('};')
}
if !node.is_expr {
g.writeln('CloseHandle(thread_$tmp);')
}
} else {
g.writeln('pthread_t thread_$tmp;')
g.writeln('int ${tmp}_thr_res = pthread_create(&thread_$tmp, NULL, (void*)$wrapper_fn_name, $arg_tmp_var);')
g.writeln('if (${tmp}_thr_res) panic_error_number(tos3("`go ${name}()`: "), ${tmp}_thr_res);')
if !node.is_expr {
g.writeln('pthread_detach(thread_$tmp);')
}
}
g.writeln('// end go')
if node.is_expr {
handle = 'thread_$tmp'
// create wait handler for this return type if none exists
waiter_fn_name := gohandle_name + '_wait'
mut should_register := false
lock g.waiter_fns {
if waiter_fn_name !in g.waiter_fns {
g.waiter_fns << waiter_fn_name
should_register = true
}
}
if should_register {
g.gowrappers.writeln('\n$s_ret_typ ${waiter_fn_name}($gohandle_name thread) {')
mut c_ret_ptr_ptr := 'NULL'
if node.call_expr.return_type != ast.void_type {
g.gowrappers.writeln('\t$s_ret_typ* ret_ptr;')
c_ret_ptr_ptr = '&ret_ptr'
}
if g.pref.os == .windows {
if node.call_expr.return_type == ast.void_type {
g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread, INFINITE);')
} else {
g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread.handle, INFINITE);')
g.gowrappers.writeln('\tret_ptr = thread.ret_ptr;')
}
} else {
g.gowrappers.writeln('\tint stat = pthread_join(thread, (void **)$c_ret_ptr_ptr);')
}
g.gowrappers.writeln('\tif (stat != 0) { _v_panic(_SLIT("unable to join thread")); }')
if g.pref.os == .windows {
if node.call_expr.return_type == ast.void_type {
g.gowrappers.writeln('\tCloseHandle(thread);')
} else {
g.gowrappers.writeln('\tCloseHandle(thread.handle);')
}
}
if node.call_expr.return_type != ast.void_type {
g.gowrappers.writeln('\t$s_ret_typ ret = *ret_ptr;')
g.gowrappers.writeln('\tfree(ret_ptr);')
g.gowrappers.writeln('\treturn ret;')
}
g.gowrappers.writeln('}')
}
}
// Register the wrapper type and function
mut should_register := false
lock g.threaded_fns {
if name !in g.threaded_fns {
g.threaded_fns << name
should_register = true
}
}
if should_register {
g.type_definitions.writeln('\ntypedef struct $wrapper_struct_name {')
if expr.is_method {
styp := g.typ(expr.receiver_type)
g.type_definitions.writeln('\t$styp arg0;')
}
need_return_ptr := g.pref.os == .windows && node.call_expr.return_type != ast.void_type
if expr.args.len == 0 && !need_return_ptr {
g.type_definitions.writeln('EMPTY_STRUCT_DECLARATION;')
} else {
for i, arg in expr.args {
styp := g.typ(arg.typ)
g.type_definitions.writeln('\t$styp arg${i + 1};')
}
}
if need_return_ptr {
g.type_definitions.writeln('\tvoid* ret_ptr;')
}
g.type_definitions.writeln('} $wrapper_struct_name;')
thread_ret_type := if g.pref.os == .windows { 'u32' } else { 'void*' }
g.type_definitions.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg);')
g.gowrappers.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg) {')
if node.call_expr.return_type != ast.void_type {
if g.pref.os == .windows {
g.gowrappers.write_string('\t*(($s_ret_typ*)(arg->ret_ptr)) = ')
} else {
g.gowrappers.writeln('\t$s_ret_typ* ret_ptr = malloc(sizeof($s_ret_typ));')
g.gowrappers.write_string('\t*ret_ptr = ')
}
} else {
g.gowrappers.write_string('\t')
}
if expr.is_method {
unwrapped_rec_type := g.unwrap_generic(expr.receiver_type)
typ_sym := g.table.sym(unwrapped_rec_type)
if typ_sym.kind == .interface_
&& (typ_sym.info as ast.Interface).defines_method(expr.name) {
rec_cc_type := g.cc_type(unwrapped_rec_type, false)
receiver_type_name := util.no_dots(rec_cc_type)
g.gowrappers.write_string('${c_name(receiver_type_name)}_name_table[')
g.gowrappers.write_string('arg->arg0')
dot := if expr.left_type.is_ptr() { '->' } else { '.' }
mname := c_name(expr.name)
g.gowrappers.write_string('${dot}_typ]._method_${mname}(')
g.gowrappers.write_string('arg->arg0')
g.gowrappers.write_string('${dot}_object')
} else {
g.gowrappers.write_string('${name}(')
g.gowrappers.write_string('arg->arg0')
}
if expr.args.len > 0 {
g.gowrappers.write_string(', ')
}
} else {
g.gowrappers.write_string('${name}(')
}
if expr.args.len > 0 {
mut has_cast := false
for i in 0 .. expr.args.len {
if g.table.sym(expr.expected_arg_types[i]).kind == .interface_
&& g.table.sym(expr.args[i].typ).kind != .interface_ {
has_cast = true
break
}
}
if has_cast {
pos := g.out.len
g.call_args(expr)
mut call_args_str := g.out.after(pos)
g.out.go_back(call_args_str.len)
mut rep_group := []string{cap: 2 * expr.args.len}
for i in 0 .. expr.args.len {
rep_group << g.expr_string(expr.args[i].expr)
rep_group << 'arg->arg${i + 1}'
}
call_args_str = call_args_str.replace_each(rep_group)
g.gowrappers.write_string(call_args_str)
} else {
for i in 0 .. expr.args.len {
g.gowrappers.write_string('arg->arg${i + 1}')
if i != expr.args.len - 1 {
g.gowrappers.write_string(', ')
}
}
}
}
g.gowrappers.writeln(');')
g.gowrappers.writeln('\tfree(arg);')
if g.pref.os != .windows && node.call_expr.return_type != ast.void_type {
g.gowrappers.writeln('\treturn ret_ptr;')
} else {
g.gowrappers.writeln('\treturn 0;')
}
g.gowrappers.writeln('}')
}
if node.is_expr {
g.empty_line = false
g.write(line)
g.write(handle)
}
}
fn (mut g Gen) as_cast(node ast.AsCast) { fn (mut g Gen) as_cast(node ast.AsCast) {
// Make sure the sum type can be cast to this type (the types // Make sure the sum type can be cast to this type (the types
// are the same), otherwise panic. // are the same), otherwise panic.

View File

@ -1667,6 +1667,258 @@ fn (mut g Gen) call_args(node ast.CallExpr) {
} }
} }
fn (mut g Gen) go_expr(node ast.GoExpr) {
line := g.go_before_stmt(0)
mut handle := ''
tmp := g.new_tmp_var()
mut expr := node.call_expr
mut name := expr.name // util.no_dots(expr.name)
// TODO: fn call is duplicated. merge with fn_call().
for i, concrete_type in expr.concrete_types {
if concrete_type != ast.void_type && concrete_type != 0 {
// Using _T_ to differentiate between get<string> and get_string
// `foo<int>()` => `foo_T_int()`
if i == 0 {
name += '_T'
}
name += '_' + g.typ(concrete_type)
}
}
if expr.is_method {
receiver_sym := g.table.sym(expr.receiver_type)
name = receiver_sym.name + '_' + name
} else if mut expr.left is ast.AnonFn {
g.gen_anon_fn_decl(mut expr.left)
name = expr.left.decl.name
} else if expr.is_fn_var {
name = g.table.sym(expr.fn_var_type).name
}
name = util.no_dots(name)
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') {
mut key := expr.name
if expr.is_method {
sym := g.table.sym(expr.receiver_type)
key = sym.name + '.' + expr.name
}
g.write('/* obf go: $key */')
name = g.obf_table[key] or {
panic('cgen: obf name "$key" not found, this should never happen')
}
}
g.empty_line = true
g.writeln('// start go')
wrapper_struct_name := 'thread_arg_' + name
wrapper_fn_name := name + '_thread_wrapper'
arg_tmp_var := 'arg_' + tmp
g.writeln('$wrapper_struct_name *$arg_tmp_var = malloc(sizeof(thread_arg_$name));')
if expr.is_method {
g.write('$arg_tmp_var->arg0 = ')
// TODO is this needed?
/*
if false && !expr.return_type.is_ptr() {
g.write('&')
}
*/
g.expr(expr.left)
g.writeln(';')
}
for i, arg in expr.args {
g.write('$arg_tmp_var->arg${i + 1} = ')
g.expr(arg.expr)
g.writeln(';')
}
s_ret_typ := g.typ(node.call_expr.return_type)
if g.pref.os == .windows && node.call_expr.return_type != ast.void_type {
g.writeln('$arg_tmp_var->ret_ptr = malloc(sizeof($s_ret_typ));')
}
is_opt := node.call_expr.return_type.has_flag(.optional)
mut gohandle_name := ''
if node.call_expr.return_type == ast.void_type {
gohandle_name = if is_opt { '__v_thread_Option_void' } else { '__v_thread' }
} else {
opt := if is_opt { 'Option_' } else { '' }
gohandle_name = '__v_thread_$opt${g.table.sym(g.unwrap_generic(node.call_expr.return_type)).cname}'
}
if g.pref.os == .windows {
simple_handle := if node.is_expr && node.call_expr.return_type != ast.void_type {
'thread_handle_$tmp'
} else {
'thread_$tmp'
}
g.writeln('HANDLE $simple_handle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)$wrapper_fn_name, $arg_tmp_var, 0, 0);')
g.writeln('if (!$simple_handle) panic_lasterr(tos3("`go ${name}()`: "));')
if node.is_expr && node.call_expr.return_type != ast.void_type {
g.writeln('$gohandle_name thread_$tmp = {')
g.writeln('\t.ret_ptr = $arg_tmp_var->ret_ptr,')
g.writeln('\t.handle = thread_handle_$tmp')
g.writeln('};')
}
if !node.is_expr {
g.writeln('CloseHandle(thread_$tmp);')
}
} else {
g.writeln('pthread_t thread_$tmp;')
g.writeln('int ${tmp}_thr_res = pthread_create(&thread_$tmp, NULL, (void*)$wrapper_fn_name, $arg_tmp_var);')
g.writeln('if (${tmp}_thr_res) panic_error_number(tos3("`go ${name}()`: "), ${tmp}_thr_res);')
if !node.is_expr {
g.writeln('pthread_detach(thread_$tmp);')
}
}
g.writeln('// end go')
if node.is_expr {
handle = 'thread_$tmp'
// create wait handler for this return type if none exists
waiter_fn_name := gohandle_name + '_wait'
mut should_register := false
lock g.waiter_fns {
if waiter_fn_name !in g.waiter_fns {
g.waiter_fns << waiter_fn_name
should_register = true
}
}
if should_register {
g.gowrappers.writeln('\n$s_ret_typ ${waiter_fn_name}($gohandle_name thread) {')
mut c_ret_ptr_ptr := 'NULL'
if node.call_expr.return_type != ast.void_type {
g.gowrappers.writeln('\t$s_ret_typ* ret_ptr;')
c_ret_ptr_ptr = '&ret_ptr'
}
if g.pref.os == .windows {
if node.call_expr.return_type == ast.void_type {
g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread, INFINITE);')
} else {
g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread.handle, INFINITE);')
g.gowrappers.writeln('\tret_ptr = thread.ret_ptr;')
}
} else {
g.gowrappers.writeln('\tint stat = pthread_join(thread, (void **)$c_ret_ptr_ptr);')
}
g.gowrappers.writeln('\tif (stat != 0) { _v_panic(_SLIT("unable to join thread")); }')
if g.pref.os == .windows {
if node.call_expr.return_type == ast.void_type {
g.gowrappers.writeln('\tCloseHandle(thread);')
} else {
g.gowrappers.writeln('\tCloseHandle(thread.handle);')
}
}
if node.call_expr.return_type != ast.void_type {
g.gowrappers.writeln('\t$s_ret_typ ret = *ret_ptr;')
g.gowrappers.writeln('\tfree(ret_ptr);')
g.gowrappers.writeln('\treturn ret;')
}
g.gowrappers.writeln('}')
}
}
// Register the wrapper type and function
mut should_register := false
lock g.threaded_fns {
if name !in g.threaded_fns {
g.threaded_fns << name
should_register = true
}
}
if should_register {
g.type_definitions.writeln('\ntypedef struct $wrapper_struct_name {')
if expr.is_method {
styp := g.typ(expr.receiver_type)
g.type_definitions.writeln('\t$styp arg0;')
}
need_return_ptr := g.pref.os == .windows && node.call_expr.return_type != ast.void_type
if expr.args.len == 0 && !need_return_ptr {
g.type_definitions.writeln('EMPTY_STRUCT_DECLARATION;')
} else {
for i, arg in expr.args {
styp := g.typ(arg.typ)
g.type_definitions.writeln('\t$styp arg${i + 1};')
}
}
if need_return_ptr {
g.type_definitions.writeln('\tvoid* ret_ptr;')
}
g.type_definitions.writeln('} $wrapper_struct_name;')
thread_ret_type := if g.pref.os == .windows { 'u32' } else { 'void*' }
g.type_definitions.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg);')
g.gowrappers.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg) {')
if node.call_expr.return_type != ast.void_type {
if g.pref.os == .windows {
g.gowrappers.write_string('\t*(($s_ret_typ*)(arg->ret_ptr)) = ')
} else {
g.gowrappers.writeln('\t$s_ret_typ* ret_ptr = malloc(sizeof($s_ret_typ));')
g.gowrappers.write_string('\t*ret_ptr = ')
}
} else {
g.gowrappers.write_string('\t')
}
if expr.is_method {
unwrapped_rec_type := g.unwrap_generic(expr.receiver_type)
typ_sym := g.table.sym(unwrapped_rec_type)
if typ_sym.kind == .interface_
&& (typ_sym.info as ast.Interface).defines_method(expr.name) {
rec_cc_type := g.cc_type(unwrapped_rec_type, false)
receiver_type_name := util.no_dots(rec_cc_type)
g.gowrappers.write_string('${c_name(receiver_type_name)}_name_table[')
g.gowrappers.write_string('arg->arg0')
dot := if expr.left_type.is_ptr() { '->' } else { '.' }
mname := c_name(expr.name)
g.gowrappers.write_string('${dot}_typ]._method_${mname}(')
g.gowrappers.write_string('arg->arg0')
g.gowrappers.write_string('${dot}_object')
} else {
g.gowrappers.write_string('${name}(')
g.gowrappers.write_string('arg->arg0')
}
if expr.args.len > 0 {
g.gowrappers.write_string(', ')
}
} else {
g.gowrappers.write_string('${name}(')
}
if expr.args.len > 0 {
mut has_cast := false
for i in 0 .. expr.args.len {
if g.table.sym(expr.expected_arg_types[i]).kind == .interface_
&& g.table.sym(expr.args[i].typ).kind != .interface_ {
has_cast = true
break
}
}
if has_cast {
pos := g.out.len
g.call_args(expr)
mut call_args_str := g.out.after(pos)
g.out.go_back(call_args_str.len)
mut rep_group := []string{cap: 2 * expr.args.len}
for i in 0 .. expr.args.len {
rep_group << g.expr_string(expr.args[i].expr)
rep_group << 'arg->arg${i + 1}'
}
call_args_str = call_args_str.replace_each(rep_group)
g.gowrappers.write_string(call_args_str)
} else {
for i in 0 .. expr.args.len {
g.gowrappers.write_string('arg->arg${i + 1}')
if i != expr.args.len - 1 {
g.gowrappers.write_string(', ')
}
}
}
}
g.gowrappers.writeln(');')
g.gowrappers.writeln('\tfree(arg);')
if g.pref.os != .windows && node.call_expr.return_type != ast.void_type {
g.gowrappers.writeln('\treturn ret_ptr;')
} else {
g.gowrappers.writeln('\treturn 0;')
}
g.gowrappers.writeln('}')
}
if node.is_expr {
g.empty_line = false
g.write(line)
g.write(handle)
}
}
// similar to `autofree_call_pregen()` but only to to handle [keep_args_alive] for C functions // similar to `autofree_call_pregen()` but only to to handle [keep_args_alive] for C functions
fn (mut g Gen) keep_alive_call_pregen(node ast.CallExpr) int { fn (mut g Gen) keep_alive_call_pregen(node ast.CallExpr) int {
g.empty_line = true g.empty_line = true

View File

@ -545,27 +545,6 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr {
} }
} }
fn (mut p Parser) go_expr() ast.GoExpr {
p.next()
spos := p.tok.pos()
expr := p.expr(0)
call_expr := if expr is ast.CallExpr {
expr
} else {
p.error_with_pos('expression in `go` must be a function call', expr.pos())
ast.CallExpr{
scope: p.scope
}
}
pos := spos.extend(p.prev_tok.pos())
p.register_auto_import('sync.threads')
p.table.gostmts++
return ast.GoExpr{
call_expr: call_expr
pos: pos
}
}
fn (p &Parser) fileis(s string) bool { fn (p &Parser) fileis(s string) bool {
return p.file_name.contains(s) return p.file_name.contains(s)
} }

View File

@ -928,6 +928,27 @@ fn (mut p Parser) fn_args() ([]ast.Param, bool, bool) {
return args, types_only, is_variadic return args, types_only, is_variadic
} }
fn (mut p Parser) go_expr() ast.GoExpr {
p.next()
spos := p.tok.pos()
expr := p.expr(0)
call_expr := if expr is ast.CallExpr {
expr
} else {
p.error_with_pos('expression in `go` must be a function call', expr.pos())
ast.CallExpr{
scope: p.scope
}
}
pos := spos.extend(p.prev_tok.pos())
p.register_auto_import('sync.threads')
p.table.gostmts++
return ast.GoExpr{
call_expr: call_expr
pos: pos
}
}
fn (mut p Parser) closure_vars() []ast.Param { fn (mut p Parser) closure_vars() []ast.Param {
p.check(.lsbr) p.check(.lsbr)
mut vars := []ast.Param{cap: 5} mut vars := []ast.Param{cap: 5}