diff --git a/cmd/tools/fast/fast_job.v b/cmd/tools/fast/fast_job.v index 01c5a0bb69..c3eebde9b0 100644 --- a/cmd/tools/fast/fast_job.v +++ b/cmd/tools/fast/fast_job.v @@ -22,7 +22,7 @@ fn main() { println('failed to git pull. uncommitted changes?') return } - // println('running fast') + // println('running ./fast') resp := os.execute('./fast') if resp.exit_code < 0 { println(resp.output) @@ -38,6 +38,6 @@ fn main() { os.system('git push origin gh-pages') os.chdir('..') } - time.sleep(60 * time.second) + time.sleep(180 * time.second) } } diff --git a/examples/sokol/particles/particles.v b/examples/sokol/particles/particles.v index a587802c70..05b0cb3140 100644 --- a/examples/sokol/particles/particles.v +++ b/examples/sokol/particles/particles.v @@ -117,8 +117,7 @@ fn frame(user_data voidptr) { app.last = t } -fn event(ev &C.sapp_event, user_data voidptr) { - mut app := &App(user_data) +fn event(ev &C.sapp_event, mut app App) { if ev.@type == .mouse_move { app.ps.explode(ev.mouse_x, ev.mouse_y) } diff --git a/examples/vweb/vweb_example.v b/examples/vweb/vweb_example.v index 9c066dd1eb..f9865222d0 100644 --- a/examples/vweb/vweb_example.v +++ b/examples/vweb/vweb_example.v @@ -15,7 +15,7 @@ mut: fn main() { println('vweb example') - vweb.run(port) + vweb.run(&App{}, port) } pub fn (mut app App) init_server() { diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 5225b804e1..d14f995454 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -714,6 +714,7 @@ pub mut: is_expr bool typ Type has_else bool + // implements bool // comptime $if implements interface } pub struct IfBranch { diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index fb35262beb..b1aefa93ac 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -28,6 +28,7 @@ pub mut: panic_handler FnPanicHandler = default_table_panic_handler panic_userdata voidptr = voidptr(0) // can be used to pass arbitrary data to panic_handler; panic_npanics int + cur_fn &FnDecl // previously stored in Checker.cur_fn and Gen.cur_fn } [unsafe] @@ -150,6 +151,7 @@ mut: pub fn new_table() &Table { mut t := &Table{ type_symbols: []TypeSymbol{cap: 64000} + cur_fn: 0 } t.register_builtin_type_symbols() t.is_fmt = true @@ -326,9 +328,9 @@ fn (t &Table) register_aggregate_field(mut sym TypeSymbol, name string) ?StructF return new_field } -pub fn (t &Table) struct_has_field(s &TypeSymbol, name string) bool { +pub fn (t &Table) struct_has_field(struct_ &TypeSymbol, name string) bool { // println('struct_has_field($s.name, $name) types.len=$t.types.len s.parent_idx=$s.parent_idx') - if _ := t.find_field(s, name) { + if _ := t.find_field(struct_, name) { return true } return false @@ -985,6 +987,30 @@ pub fn (t &Table) has_deep_child_no_ref(ts &TypeSymbol, name string) bool { return false } +// complete_interface_check does a MxN check for all M interfaces vs all N types, to determine what types implement what interfaces. +// It short circuits most checks when an interface can not possibly be implemented by a type. +pub fn (mut table Table) complete_interface_check() { + util.timing_start(@METHOD) + defer { + util.timing_measure(@METHOD) + } + for tk, mut tsym in table.type_symbols { + if tsym.kind != .struct_ { + continue + } + info := tsym.info as Struct + for _, mut idecl in table.interfaces { + if idecl.methods.len > tsym.methods.len { + continue + } + if idecl.fields.len > info.fields.len { + continue + } + table.does_type_implement_interface(tk, idecl.typ) + } + } +} + // bitsize_to_type returns a type corresponding to the bit_size // Examples: // @@ -1020,6 +1046,66 @@ pub fn (mut t Table) bitsize_to_type(bit_size int) Type { } } +fn (mut table Table) does_type_implement_interface(typ Type, inter_typ Type) bool { + // TODO: merge with c.type_implements, which also does error reporting in addition + // to checking. + utyp := typ + if utyp.idx() == inter_typ.idx() { + // same type -> already casted to the interface + return true + } + if inter_typ.idx() == error_type_idx && utyp.idx() == none_type_idx { + // `none` "implements" the Error interface + return true + } + typ_sym := table.get_type_symbol(utyp) + if typ_sym.language != .v { + return false + } + mut inter_sym := table.get_type_symbol(inter_typ) + if typ_sym.kind == .interface_ && inter_sym.kind == .interface_ { + return false + } + // do not check the same type more than once + if mut inter_sym.info is Interface { + for t in inter_sym.info.types { + if t.idx() == utyp.idx() { + return true + } + } + } + imethods := if inter_sym.kind == .interface_ { + (inter_sym.info as Interface).methods + } else { + inter_sym.methods + } + for imethod in imethods { + if method := typ_sym.find_method(imethod.name) { + msg := table.is_same_method(imethod, method) + if msg.len > 0 { + return false + } + continue + } + return false + } + if mut inter_sym.info is Interface { + for ifield in inter_sym.info.fields { + if field := table.find_field_with_embeds(typ_sym, ifield.name) { + if ifield.typ != field.typ { + return false + } else if ifield.is_mut && !(field.is_mut || field.is_global) { + return false + } + continue + } + return false + } + inter_sym.info.types << utyp + } + return true +} + // resolve_generic_to_concrete resolves generics to real types T => int. // Even map[string]map[string]T can be resolved. // This is used for resolving the generic return type of CallExpr white `unwrap_generic` is used to resolve generic usage in FnDecl. @@ -1162,50 +1248,13 @@ pub fn (mut t Table) generic_struct_insts_to_concrete() { } } -// complete_interface_check does a MxN check for all M interfaces vs all N types, to determine what types implement what interfaces. -// It short circuits most checks when an interface can not possibly be implemented by a type. -pub fn (mut table Table) complete_interface_check() { - util.timing_start(@METHOD) - defer { - util.timing_measure(@METHOD) - } - for tk, mut tsym in table.type_symbols { - if tsym.kind != .struct_ { - continue - } - info := tsym.info as Struct - for _, mut idecl in table.interfaces { - if idecl.methods.len > tsym.methods.len { - continue - } - if idecl.fields.len > info.fields.len { - continue - } - table.does_type_implement_interface(tk, idecl.typ) - } - } -} - -fn (mut table Table) does_type_implement_interface(typ Type, inter_typ Type) bool { - // TODO: merge with c.type_implements, which also does error reporting in addition - // to checking. - utyp := typ - if utyp.idx() == inter_typ.idx() { - // same type -> already casted to the interface - return true - } - if inter_typ.idx() == error_type_idx && utyp.idx() == none_type_idx { - // `none` "implements" the Error interface - return true +pub fn (mut table Table) type_implements_interface(utyp Type, interface_type Type) bool { + $if debug_interface_type_implements ? { + eprintln('> Table.type_implements_inteface typ: $utyp.debug() | interface_typ: $interface_type.debug()') } + // utyp := c.unwrap_generic(typ) typ_sym := table.get_type_symbol(utyp) - if typ_sym.language != .v { - return false - } - mut inter_sym := table.get_type_symbol(inter_typ) - if typ_sym.kind == .interface_ && inter_sym.kind == .interface_ { - return false - } + mut inter_sym := table.get_type_symbol(interface_type) // do not check the same type more than once if mut inter_sym.info is Interface { for t in inter_sym.info.types { @@ -1214,32 +1263,70 @@ fn (mut table Table) does_type_implement_interface(typ Type, inter_typ Type) boo } } } + // styp := table.type_to_str(utyp) + if utyp.idx() == interface_type.idx() { + // same type -> already casted to the interface + return true + } + if interface_type.idx() == error_type_idx && utyp.idx() == none_type_idx { + // `none` "implements" the Error interface + return true + } + if typ_sym.kind == .interface_ && inter_sym.kind == .interface_ { + return false + // error('cannot implement interface `$inter_sym.name` with a different interface `$styp`', + // pos) + } imethods := if inter_sym.kind == .interface_ { (inter_sym.info as Interface).methods } else { inter_sym.methods } + // Verify methods for imethod in imethods { if method := typ_sym.find_method(imethod.name) { msg := table.is_same_method(imethod, method) if msg.len > 0 { + // sig := table.fn_signature(imethod, skip_receiver: true) + // add_error_detail('$inter_sym.name has `$sig`') + // c.error('`$styp` incorrectly implements method `$imethod.name` of interface `$inter_sym.name`: $msg', + // pos) return false } continue } + // c.error("`$styp` doesn't implement method `$imethod.name` of interface `$inter_sym.name`", + // pos) return false } + // Verify fields if mut inter_sym.info is Interface { for ifield in inter_sym.info.fields { + if ifield.typ == voidptr_type { + // Allow `voidptr` fields in interfaces for now. (for example + // to enable .db check in vweb) + if table.struct_has_field(typ_sym, ifield.name) { + continue + } else { + return false + } + } if field := table.find_field_with_embeds(typ_sym, ifield.name) { if ifield.typ != field.typ { + // exp := table.type_to_str(ifield.typ) + // got := table.type_to_str(field.typ) + // c.error('`$styp` incorrectly implements field `$ifield.name` of interface `$inter_sym.name`, expected `$exp`, got `$got`', + // pos) return false } else if ifield.is_mut && !(field.is_mut || field.is_global) { + // c.error('`$styp` incorrectly implements interface `$inter_sym.name`, field `$ifield.name` must be mutable', + // pos) return false } continue } - return false + // c.error("`$styp` doesn't implement field `$ifield.name` of interface `$inter_sym.name`", + // pos) } inter_sym.info.types << utyp } diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index ddeba5341b..70d056d561 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -741,7 +741,7 @@ pub mut: pub struct Interface { pub mut: - types []Type + types []Type // all types that implement this interface fields []StructField methods []Fn ifaces []Type diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index c43cb7e0a0..010efc8b62 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -467,7 +467,8 @@ pub fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) ast.Typ node.need_fmts[i] = fmt != c.get_default_fmt(ftyp, typ) } // check recursive str - if c.cur_fn.is_method && c.cur_fn.name == 'str' && c.cur_fn.receiver.name == expr.str() { + if c.table.cur_fn.is_method && c.table.cur_fn.name == 'str' + && c.table.cur_fn.receiver.name == expr.str() { c.error('cannot call `str()` method recursively', expr.position()) } } @@ -527,7 +528,7 @@ pub fn (mut c Checker) infer_fn_generic_types(f ast.Fn, mut call_expr ast.CallEx mut param_elem_sym := c.table.get_type_symbol(param_elem_info.elem_type) for { if arg_elem_sym.kind == .array && param_elem_sym.kind == .array - && param_elem_sym.name !in c.cur_fn.generic_names { + && param_elem_sym.name !in c.table.cur_fn.generic_names { arg_elem_info = arg_elem_sym.info as ast.Array arg_elem_sym = c.table.get_type_symbol(arg_elem_info.elem_type) param_elem_info = param_elem_sym.info as ast.Array diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 9263be5bb3..cc58d4e24a 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -47,15 +47,15 @@ pub mut: notices []errors.Notice error_lines []int // to avoid printing multiple errors for the same line expected_type ast.Type - expected_or_type ast.Type // fn() or { 'this type' } eg. string. expected or block type - cur_fn &ast.FnDecl // current function - const_decl string - const_deps []string - const_names []string - global_names []string - locked_names []string // vars that are currently locked - rlocked_names []string // vars that are currently read-locked - in_for_count int // if checker is currently in a for loop + expected_or_type ast.Type // fn() or { 'this type' } eg. string. expected or block type + // cur_fn &ast.FnDecl // current function + const_decl string + const_deps []string + const_names []string + global_names []string + locked_names []string // vars that are currently locked + rlocked_names []string // vars that are currently read-locked + in_for_count int // if checker is currently in a for loop // checked_ident string // to avoid infinite checker loops returns bool scope_returns bool @@ -99,7 +99,6 @@ pub fn new_checker(table &ast.Table, pref &pref.Preferences) Checker { return Checker{ table: table pref: pref - cur_fn: 0 timers: util.new_timers(timers_should_print) match_exhaustive_cutoff_limit: pref.checker_match_exhaustive_cutoff_limit } @@ -666,7 +665,7 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) ast.Type { 'it cannot be initialized with `$type_sym.name{}`', struct_init.pos) } } - if type_sym.name.len == 1 && c.cur_fn.generic_names.len == 0 { + if type_sym.name.len == 1 && c.table.cur_fn.generic_names.len == 0 { c.error('unknown struct `$type_sym.name`', struct_init.pos) return 0 } @@ -1531,10 +1530,10 @@ pub fn (mut c Checker) call_expr(mut call_expr ast.CallExpr) ast.Type { c.expected_or_type = call_expr.return_type.clear_flag(.optional) c.stmts(call_expr.or_block.stmts) c.expected_or_type = ast.void_type - if call_expr.or_block.kind == .propagate && !c.cur_fn.return_type.has_flag(.optional) + if call_expr.or_block.kind == .propagate && !c.table.cur_fn.return_type.has_flag(.optional) && !c.inside_const { - if !c.cur_fn.is_main { - c.error('to propagate the optional call, `$c.cur_fn.name` must return an optional', + if !c.table.cur_fn.is_main { + c.error('to propagate the optional call, `$c.table.cur_fn.name` must return an optional', call_expr.or_block.pos) } } @@ -1916,7 +1915,7 @@ pub fn (mut c Checker) method_call(mut call_expr ast.CallExpr) ast.Type { c.warn('method `${left_type_sym.name}.$method_name` must be called from an `unsafe` block', call_expr.pos) } - if !c.cur_fn.is_deprecated && method.is_deprecated { + if !c.table.cur_fn.is_deprecated && method.is_deprecated { c.deprecate_fnmethod('method', '${left_type_sym.name}.$method.name', method, call_expr) } @@ -2160,7 +2159,7 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type { concrete_types << concrete_type } } - if !isnil(c.cur_fn) && c.cur_concrete_types.len == 0 && has_generic { + if !isnil(c.table.cur_fn) && c.cur_concrete_types.len == 0 && has_generic { c.error('generic fn using generic types cannot be called outside of generic fn', call_expr.pos) } @@ -2312,7 +2311,7 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type { && func.mod != c.mod { c.error('function `$func.name` is private', call_expr.pos) } - if !isnil(c.cur_fn) && !c.cur_fn.is_deprecated && func.is_deprecated { + if !isnil(c.table.cur_fn) && !c.table.cur_fn.is_deprecated && func.is_deprecated { c.deprecate_fnmethod('function', func.name, func, call_expr) } if func.is_unsafe && !c.inside_unsafe @@ -2562,13 +2561,13 @@ fn semicolonize(main string, details string) string { return '$main; $details' } -fn (mut c Checker) type_implements(typ ast.Type, inter_typ ast.Type, pos token.Position) bool { +fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos token.Position) bool { $if debug_interface_type_implements ? { eprintln('> type_implements typ: $typ.debug() | inter_typ: $inter_typ.debug()') } utyp := c.unwrap_generic(typ) typ_sym := c.table.get_type_symbol(utyp) - mut inter_sym := c.table.get_type_symbol(inter_typ) + mut inter_sym := c.table.get_type_symbol(interface_type) // do not check the same type more than once if mut inter_sym.info is ast.Interface { for t in inter_sym.info.types { @@ -2578,11 +2577,11 @@ fn (mut c Checker) type_implements(typ ast.Type, inter_typ ast.Type, pos token.P } } styp := c.table.type_to_str(utyp) - if utyp.idx() == inter_typ.idx() { + if utyp.idx() == interface_type.idx() { // same type -> already casted to the interface return true } - if inter_typ.idx() == ast.error_type_idx && utyp.idx() == ast.none_type_idx { + if interface_type.idx() == ast.error_type_idx && utyp.idx() == ast.none_type_idx { // `none` "implements" the Error interface return true } @@ -2595,6 +2594,7 @@ fn (mut c Checker) type_implements(typ ast.Type, inter_typ ast.Type, pos token.P } else { inter_sym.methods } + // Verify methods for imethod in imethods { if method := typ_sym.find_method(imethod.name) { msg := c.table.is_same_method(imethod, method) @@ -2610,6 +2610,7 @@ fn (mut c Checker) type_implements(typ ast.Type, inter_typ ast.Type, pos token.P c.error("`$styp` doesn't implement method `$imethod.name` of interface `$inter_sym.name`", pos) } + // Verify fields if mut inter_sym.info is ast.Interface { for ifield in inter_sym.info.fields { if field := c.table.find_field_with_embeds(typ_sym, ifield.name) { @@ -2662,9 +2663,9 @@ pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast pub fn (mut c Checker) check_or_expr(or_expr ast.OrExpr, ret_type ast.Type, expr_return_type ast.Type) { if or_expr.kind == .propagate { - if !c.cur_fn.return_type.has_flag(.optional) && c.cur_fn.name != 'main.main' + if !c.table.cur_fn.return_type.has_flag(.optional) && c.table.cur_fn.name != 'main.main' && !c.inside_const { - c.error('to propagate the optional call, `$c.cur_fn.name` must return an optional', + c.error('to propagate the optional call, `$c.table.cur_fn.name` must return an optional', or_expr.pos) } return @@ -2763,7 +2764,7 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) ast.Typ match mut selector_expr.expr { ast.Ident { name := selector_expr.expr.name - valid_generic := util.is_generic_type_name(name) && name in c.cur_fn.generic_names + valid_generic := util.is_generic_type_name(name) && name in c.table.cur_fn.generic_names if valid_generic { name_type = ast.Type(c.table.find_type_idx(name)).set_flag(.generic) } @@ -2901,10 +2902,10 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) ast.Typ // TODO: non deferred pub fn (mut c Checker) return_stmt(mut return_stmt ast.Return) { - c.expected_type = c.cur_fn.return_type + c.expected_type = c.table.cur_fn.return_type expected_type := c.unwrap_generic(c.expected_type) expected_type_sym := c.table.get_type_symbol(expected_type) - if return_stmt.exprs.len > 0 && c.cur_fn.return_type == ast.void_type { + if return_stmt.exprs.len > 0 && c.table.cur_fn.return_type == ast.void_type { c.error('unexpected argument, current function does not return anything', return_stmt.exprs[0].position()) return } else if return_stmt.exprs.len == 0 && !(c.expected_type == ast.void_type @@ -2979,7 +2980,7 @@ pub fn (mut c Checker) return_stmt(mut return_stmt ast.Return) { if return_stmt.exprs[i].is_auto_deref_var() { continue } - c.error('fn `$c.cur_fn.name` expects you to return a non reference type `${c.table.type_to_str(exp_type)}`, but you are returning `${c.table.type_to_str(got_typ)}` instead', + c.error('fn `$c.table.cur_fn.name` expects you to return a non reference type `${c.table.type_to_str(exp_type)}`, but you are returning `${c.table.type_to_str(got_typ)}` instead', pos) } if (exp_type.is_ptr() || exp_type.is_pointer()) @@ -2988,7 +2989,7 @@ pub fn (mut c Checker) return_stmt(mut return_stmt ast.Return) { if return_stmt.exprs[i].is_auto_deref_var() { continue } - c.error('fn `$c.cur_fn.name` expects you to return a reference type `${c.table.type_to_str(exp_type)}`, but you are returning `${c.table.type_to_str(got_typ)}` instead', + c.error('fn `$c.table.cur_fn.name` expects you to return a reference type `${c.table.type_to_str(exp_type)}`, but you are returning `${c.table.type_to_str(got_typ)}` instead', pos) } if exp_type.is_ptr() && got_typ.is_ptr() { @@ -3839,8 +3840,8 @@ fn (mut c Checker) stmt(node ast.Stmt) { } ast.DeferStmt { if node.idx_in_fn < 0 { - node.idx_in_fn = c.cur_fn.defer_stmts.len - c.cur_fn.defer_stmts << unsafe { &node } + node.idx_in_fn = c.table.cur_fn.defer_stmts.len + c.table.cur_fn.defer_stmts << unsafe { &node } } c.stmts(node.stmts) } @@ -3891,7 +3892,7 @@ fn (mut c Checker) stmt(node ast.Stmt) { c.warn('`goto` requires `unsafe` (consider using labelled break/continue)', node.pos) } - if node.name !in c.cur_fn.label_names { + if node.name !in c.table.cur_fn.label_names { c.error('unknown label `$node.name`', node.pos) } // TODO: check label doesn't bypass variable declarations @@ -4424,8 +4425,8 @@ fn (mut c Checker) stmts(stmts []ast.Stmt) { pub fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type { if typ.has_flag(.generic) { - if t_typ := c.table.resolve_generic_to_concrete(typ, c.cur_fn.generic_names, c.cur_concrete_types, - false) + if t_typ := c.table.resolve_generic_to_concrete(typ, c.table.cur_fn.generic_names, + c.cur_concrete_types, false) { return t_typ } @@ -4453,11 +4454,11 @@ pub fn (mut c Checker) expr(node ast.Expr) ast.Type { } ast.AnonFn { c.inside_anon_fn = true - keep_fn := c.cur_fn - c.cur_fn = unsafe { &node.decl } + keep_fn := c.table.cur_fn + c.table.cur_fn = unsafe { &node.decl } c.stmts(node.decl.stmts) c.fn_decl(mut node.decl) - c.cur_fn = keep_fn + c.table.cur_fn = keep_fn c.inside_anon_fn = false return node.typ } @@ -4939,23 +4940,23 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type { fn (mut c Checker) at_expr(mut node ast.AtExpr) ast.Type { match node.kind { .fn_name { - node.val = c.cur_fn.name.all_after_last('.') + node.val = c.table.cur_fn.name.all_after_last('.') } .method_name { - fname := c.cur_fn.name.all_after_last('.') - if c.cur_fn.is_method { - node.val = c.table.type_to_str(c.cur_fn.receiver.typ).all_after_last('.') + '.' + - fname + fname := c.table.cur_fn.name.all_after_last('.') + if c.table.cur_fn.is_method { + node.val = c.table.type_to_str(c.table.cur_fn.receiver.typ).all_after_last('.') + + '.' + fname } else { node.val = fname } } .mod_name { - node.val = c.cur_fn.mod + node.val = c.table.cur_fn.mod } .struct_name { - if c.cur_fn.is_method { - node.val = c.table.type_to_str(c.cur_fn.receiver.typ).all_after_last('.') + if c.table.cur_fn.is_method { + node.val = c.table.type_to_str(c.table.cur_fn.receiver.typ).all_after_last('.') } else { node.val = '' } @@ -5748,6 +5749,11 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { comptime_field_name = left.expr.str() c.comptime_fields_type[comptime_field_name] = got_type is_comptime_type_is_expr = true + } else if branch.cond.right is ast.TypeNode && left is ast.TypeNode { + // is interface + checked_type := c.unwrap_generic((left as ast.TypeNode).typ) + should_skip = !c.table.type_implements_interface(checked_type, + got_type) } else if left is ast.TypeNode { is_comptime_type_is_expr = true left_type := c.unwrap_generic(left.typ) @@ -5932,8 +5938,8 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { return node.typ } -// comp_if_branch checks the condition of a compile-time `if` branch. It returns a `bool` that -// saying whether that branch's contents should be skipped (targets a different os for example) +// comp_if_branch checks the condition of a compile-time `if` branch. It returns `true` +// if that branch's contents should be skipped (targets a different os for example) fn (mut c Checker) comp_if_branch(cond ast.Expr, pos token.Position) bool { // TODO: better error messages here match cond { @@ -5971,12 +5977,20 @@ fn (mut c Checker) comp_if_branch(cond ast.Expr, pos token.Position) bool { return l && r // skip (return true) only if both should be skipped } .key_is, .not_is { - if cond.left is ast.SelectorExpr || cond.left is ast.TypeNode { - // $if method.@type is string + if cond.left is ast.TypeNode && cond.right is ast.TypeNode { + // `$if Foo is Interface {` + type_node := cond.right as ast.TypeNode + sym := c.table.get_type_symbol(type_node.typ) + if sym.kind != .interface_ { + c.error('`$sym.name` is not an interface', cond.right.position()) + } + return false + } else if cond.left is ast.SelectorExpr || cond.left is ast.TypeNode { + // `$if method.@type is string` c.expr(cond.left) return false } else { - c.error('invalid `\$if` condition: expected a type or selector expression', + c.error('invalid `\$if` condition: expected a type or a selector expression or an interface check', cond.left.position()) } } @@ -6868,7 +6882,7 @@ fn (mut c Checker) post_process_generic_fns() { for concrete_types in c.table.fn_generic_types[node.name] { c.cur_concrete_types = concrete_types c.fn_decl(mut node) - if node.name in ['vweb.run_app', 'vweb.run'] { + if node.name == 'vweb.run' { for ct in concrete_types { if ct !in c.vweb_gen_types { c.vweb_gen_types << ct @@ -7033,7 +7047,8 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { } } c.expected_type = ast.void_type - c.cur_fn = unsafe { node } + c.table.cur_fn = unsafe { node } + // c.table.cur_fn = node // Add return if `fn(...) ? {...}` have no return at end if node.return_type != ast.void_type && node.return_type.has_flag(.optional) && (node.stmts.len == 0 || node.stmts[node.stmts.len - 1] !is ast.Return) { diff --git a/vlib/v/checker/tests/unknown_comptime_expr.out b/vlib/v/checker/tests/unknown_comptime_expr.out index a778c309fe..f987dd4fc9 100644 --- a/vlib/v/checker/tests/unknown_comptime_expr.out +++ b/vlib/v/checker/tests/unknown_comptime_expr.out @@ -19,7 +19,7 @@ vlib/v/checker/tests/unknown_comptime_expr.vv:13:6: error: undefined ident: `huh | ~~~ 14 | $if s is int {} 15 | $if s.i is 5 {} -vlib/v/checker/tests/unknown_comptime_expr.vv:14:6: error: invalid `$if` condition: expected a type or selector expression +vlib/v/checker/tests/unknown_comptime_expr.vv:14:6: error: invalid `$if` condition: expected a type or selector expression or an interface check 12 | s := S1{} 13 | $if huh.typ is T {} 14 | $if s is int {} diff --git a/vlib/v/checker/tests/vweb_routing_checks.vv b/vlib/v/checker/tests/vweb_routing_checks.vv index d48c8a69fd..53f1d09bbc 100644 --- a/vlib/v/checker/tests/vweb_routing_checks.vv +++ b/vlib/v/checker/tests/vweb_routing_checks.vv @@ -43,6 +43,5 @@ pub fn (mut app App) index() { fn main() { port := 8181 - mut app := App{} - vweb.run_app(mut app, port) + vweb.run(&App{}, port) } diff --git a/vlib/v/checker/tests/vweb_tmpl_used_var.vv b/vlib/v/checker/tests/vweb_tmpl_used_var.vv index 636f745375..13cdbec9f9 100644 --- a/vlib/v/checker/tests/vweb_tmpl_used_var.vv +++ b/vlib/v/checker/tests/vweb_tmpl_used_var.vv @@ -10,6 +10,5 @@ pub fn (mut app App) index() vweb.Result { } fn main() { - mut app := App{} - vweb.run_app(mut app, 8181) + vweb.run(&App{}, 8181) } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index d47fb34c60..c603778906 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -120,22 +120,22 @@ mut: is_builtin_mod bool hotcode_fn_names []string embedded_files []ast.EmbeddedFile - cur_fn ast.FnDecl - cur_concrete_types []ast.Type // current concrete types, e.g. - sql_i int - sql_stmt_name string - sql_bind_name string - sql_idents []string - sql_idents_types []ast.Type - sql_left_type ast.Type - sql_table_name string - sql_fkey string - sql_parent_id string - sql_side SqlExprSide // left or right, to distinguish idents in `name == name` - inside_vweb_tmpl bool - inside_return bool - inside_or_block bool - strs_to_free0 []string // strings.Builder + // cur_fn ast.FnDecl + cur_concrete_types []ast.Type // current concrete types, e.g. + sql_i int + sql_stmt_name string + sql_bind_name string + sql_idents []string + sql_idents_types []ast.Type + sql_left_type ast.Type + sql_table_name string + sql_fkey string + sql_parent_id string + sql_side SqlExprSide // left or right, to distinguish idents in `name == name` + inside_vweb_tmpl bool + inside_return bool + inside_or_block bool + strs_to_free0 []string // strings.Builder // strs_to_free []string // strings.Builder inside_call bool has_main bool @@ -6369,38 +6369,40 @@ fn (mut g Gen) interface_table() string { } already_generated_mwrappers[interface_index_name] = current_iinidx current_iinidx++ - // eprintln('>>> current_iinidx: ${current_iinidx-iinidx_minimum_base} | interface_index_name: $interface_index_name') - sb.writeln('$staticprefix $interface_name I_${cctype}_to_Interface_${interface_name}($cctype* x);') - mut cast_struct := strings.new_builder(100) - cast_struct.writeln('($interface_name) {') - cast_struct.writeln('\t\t._$cctype = x,') - cast_struct.writeln('\t\t._typ = $interface_index_name,') - for field in inter_info.fields { - cname := c_name(field.name) - field_styp := g.typ(field.typ) - if _ := st_sym.find_field(field.name) { - cast_struct.writeln('\t\t.$cname = ($field_styp*)((char*)x + __offsetof_ptr(x, $cctype, $cname)),') - } else { - // the field is embedded in another struct - cast_struct.write_string('\t\t.$cname = ($field_styp*)((char*)x') - for embed_type in st_sym.struct_info().embeds { - embed_sym := g.table.get_type_symbol(embed_type) - if _ := embed_sym.find_field(field.name) { - cast_struct.write_string(' + __offsetof_ptr(x, $cctype, $embed_sym.embed_name()) + __offsetof_ptr(x, $embed_sym.cname, $cname)') - break + if ityp.name != 'vweb.DbInterface' { // TODO remove this + // eprintln('>>> current_iinidx: ${current_iinidx-iinidx_minimum_base} | interface_index_name: $interface_index_name') + sb.writeln('$staticprefix $interface_name I_${cctype}_to_Interface_${interface_name}($cctype* x);') + mut cast_struct := strings.new_builder(100) + cast_struct.writeln('($interface_name) {') + cast_struct.writeln('\t\t._$cctype = x,') + cast_struct.writeln('\t\t._typ = $interface_index_name,') + for field in inter_info.fields { + cname := c_name(field.name) + field_styp := g.typ(field.typ) + if _ := st_sym.find_field(field.name) { + cast_struct.writeln('\t\t.$cname = ($field_styp*)((char*)x + __offsetof_ptr(x, $cctype, $cname)),') + } else { + // the field is embedded in another struct + cast_struct.write_string('\t\t.$cname = ($field_styp*)((char*)x') + for embed_type in st_sym.struct_info().embeds { + embed_sym := g.table.get_type_symbol(embed_type) + if _ := embed_sym.find_field(field.name) { + cast_struct.write_string(' + __offsetof_ptr(x, $cctype, $embed_sym.embed_name()) + __offsetof_ptr(x, $embed_sym.cname, $cname)') + break + } } + cast_struct.writeln('),') } - cast_struct.writeln('),') } - } - cast_struct.write_string('\t}') - cast_struct_str := cast_struct.str() + cast_struct.write_string('\t}') + cast_struct_str := cast_struct.str() - cast_functions.writeln(' + cast_functions.writeln(' // Casting functions for converting "$cctype" to interface "$interface_name" $staticprefix inline $interface_name I_${cctype}_to_Interface_${interface_name}($cctype* x) { return $cast_struct_str; }') + } if g.pref.build_mode != .build_module { methods_struct.writeln('\t{') diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 88b11d8e8c..393e9424a9 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -192,6 +192,7 @@ fn (mut g Gen) comp_if(node ast.IfExpr) { if !node.is_expr && !node.has_else && node.branches.len == 1 { if node.branches[0].stmts.len == 0 { // empty ifdef; result of target OS != conditional => skip + g.write('/*empty $ if*/') return } if !g.pref.output_cross_c { @@ -217,7 +218,10 @@ fn (mut g Gen) comp_if(node ast.IfExpr) { } else { '' } - mut comp_if_stmts_skip := false + mut comp_if_stmts_skip := false // don't write any statements if the condition is false + // (so that for example windows calls don't get generated inside `$if macos` which + // will lead to compilation errors) + for i, branch in node.branches { start_pos := g.out.len if i == node.branches.len - 1 && node.has_else { @@ -273,6 +277,10 @@ fn (mut g Gen) comp_if(node ast.IfExpr) { g.writeln('#endif') } +/* +// returning `false` means the statements inside the $if can be skipped +*/ +// returns the value of the bool comptime expression fn (mut g Gen) comp_if_cond(cond ast.Expr) bool { match cond { ast.BoolLiteral { @@ -310,7 +318,38 @@ fn (mut g Gen) comp_if_cond(cond ast.Expr) bool { mut name := '' mut exp_type := ast.Type(0) got_type := (cond.right as ast.TypeNode).typ - if left is ast.SelectorExpr { + // Handle `$if x is Interface {` + // mut matches_interface := 'false' + if left is ast.TypeNode && cond.right is ast.TypeNode { + // `$if Foo is Interface {` + interface_sym := g.table.get_type_symbol(got_type) + if interface_sym.info is ast.Interface { + // q := g.table.get_type_symbol(interface_sym.info.types[0]) + checked_type := g.unwrap_generic((left as ast.TypeNode).typ) + // TODO PERF this check is run twice (also in the checker) + // store the result in a field + is_true := g.table.type_implements_interface(checked_type, + got_type) + // true // exp_type in interface_sym.info.types + if cond.op == .key_is { + if is_true { + g.write('1') + } else { + g.write('0') + } + return is_true + } else if cond.op == .not_is { + if is_true { + g.write('0') + } else { + g.write('1') + } + return !is_true + } + // matches_interface = '/*iface:$got_type $exp_type*/ true' + //} + } + } else if left is ast.SelectorExpr { name = '${left.expr}.$left.field_name' exp_type = g.comptime_var_type_map[name] } else if left is ast.TypeNode { @@ -323,7 +362,7 @@ fn (mut g Gen) comp_if_cond(cond ast.Expr) bool { g.write('$exp_type == $got_type') return exp_type == got_type } else { - g.write('$exp_type !=$got_type') + g.write('$exp_type != $got_type') return exp_type != got_type } } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 37e21e5765..fcbdf871d5 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -95,7 +95,7 @@ fn (mut g Gen) process_fn_decl(node ast.FnDecl) { } } -fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) { +fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { // TODO For some reason, build fails with autofree with this line // as it's only informative, comment it for now // g.gen_attrs(it.attrs) @@ -143,11 +143,14 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) { g.cur_concrete_types = [] return } - cur_fn_save := g.cur_fn + cur_fn_save := g.table.cur_fn defer { - g.cur_fn = cur_fn_save + g.table.cur_fn = cur_fn_save + } + unsafe { + // TODO remove unsafe + g.table.cur_fn = node } - g.cur_fn = node fn_start_pos := g.out.len g.write_v_source_line_info(node.pos) msvc_attrs := g.write_fn_attrs(node.attrs) @@ -474,8 +477,8 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { pub fn (mut g Gen) unwrap_generic(typ ast.Type) ast.Type { if typ.has_flag(.generic) { - if t_typ := g.table.resolve_generic_to_concrete(typ, g.cur_fn.generic_names, g.cur_concrete_types, - false) + if t_typ := g.table.resolve_generic_to_concrete(typ, g.table.cur_fn.generic_names, + g.cur_concrete_types, false) { return t_typ } diff --git a/vlib/v/gen/native/macho_test.v b/vlib/v/gen/native/macho_test.v index 8eff1775b1..469e8ea973 100644 --- a/vlib/v/gen/native/macho_test.v +++ b/vlib/v/gen/native/macho_test.v @@ -6,7 +6,9 @@ fn test_macho() { mut g := native.Gen{ pref: &pref.Preferences{} out_name: 'test.bin' - table: &ast.Table{} + table: &ast.Table{ + cur_fn: 0 + } } g.generate_macho_header() g.generate_macho_footer() diff --git a/vlib/vweb/tests/vweb_test_server.v b/vlib/vweb/tests/vweb_test_server.v index 576d58022a..7782952591 100644 --- a/vlib/vweb/tests/vweb_test_server.v +++ b/vlib/vweb/tests/vweb_test_server.v @@ -30,16 +30,17 @@ fn main() { assert timeout > 0 go exit_after_timeout(timeout) // - mut app := App{ + mut app := &App{ port: http_port timeout: timeout } - vweb.run_app(mut app, http_port) + eprintln('>> webserver: started on http://127.0.0.1:$app.port/ , with maximum runtime of $app.timeout milliseconds.') + // vweb.run(mut app, http_port) + vweb.run(mut app, http_port) } -pub fn (mut app App) init_server() { - eprintln('>> webserver: started on http://127.0.0.1:$app.port/ , with maximum runtime of $app.timeout milliseconds.') -} +// pub fn (mut app App) init_server() { +//} pub fn (mut app App) index() vweb.Result { return app.text('Welcome to VWeb') diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v index 6840437eb6..0d6f762205 100644 --- a/vlib/vweb/vweb.v +++ b/vlib/vweb/vweb.v @@ -284,27 +284,49 @@ pub fn (ctx &Context) get_header(key string) string { return ctx.req.header.get_custom(key) or { '' } } +/* pub fn run(port int) { - mut app := T{} - run_app(mut app, port) + mut x := &T{} + run_app(mut x, port) +} +*/ + +interface DbInterface { + db voidptr } -pub fn run_app(mut app T, port int) { +// run_app +pub fn run(global_app &T, port int) { + // x := global_app.clone() + // mut global_app := &T{} + // mut app := &T{} + // run_app(mut app, port) + mut l := net.listen_tcp(port) or { panic('failed to listen') } println('[Vweb] Running app on http://localhost:$port') - app.Context = Context{ - conn: 0 - } - app.init_server() - $for method in T.methods { - $if method.return_type is Result { - // check routes for validity - } - } + // app.Context = Context{ + // conn: 0 + //} + // app.init_server() + // global_app.init_server() + //$for method in T.methods { + //$if method.return_type is Result { + // check routes for validity + //} + //} for { + // Create a new app object for each connection, copy global data like db connections + mut request_app := T{} + $if T is DbInterface { + request_app.db = global_app.db + } $else { + // println('vweb no db') + } + // request_app.Context = Context{ + // conn: 0 + //} mut conn := l.accept() or { panic('accept() failed') } - // TODO: running handle_conn concurrently results in a race-condition - handle_conn(mut conn, mut app) + handle_conn(mut conn, mut request_app) } }