vweb: make thread safe; checker: `$if T is Interface {`

pull/10081/head
Alexander Medvednikov 2021-05-11 09:30:01 +03:00
parent e310513a5f
commit dc034d9b16
17 changed files with 348 additions and 178 deletions

View File

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

View File

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

View File

@ -15,7 +15,7 @@ mut:
fn main() {
println('vweb example')
vweb.run<App>(port)
vweb.run(&App{}, port)
}
pub fn (mut app App) init_server() {

View File

@ -714,6 +714,7 @@ pub mut:
is_expr bool
typ Type
has_else bool
// implements bool // comptime $if implements interface
}
pub struct IfBranch {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -43,6 +43,5 @@ pub fn (mut app App) index() {
fn main() {
port := 8181
mut app := App{}
vweb.run_app<App>(mut app, port)
vweb.run<App>(&App{}, port)
}

View File

@ -10,6 +10,5 @@ pub fn (mut app App) index() vweb.Result {
}
fn main() {
mut app := App{}
vweb.run_app<App>(mut app, 8181)
vweb.run<App>(&App{}, 8181)
}

View File

@ -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. <int, string>
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. <int, string>
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{')

View File

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

View File

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

View File

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

View File

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

View File

@ -284,27 +284,49 @@ pub fn (ctx &Context) get_header(key string) string {
return ctx.req.header.get_custom(key) or { '' }
}
/*
pub fn run<T>(port int) {
mut app := T{}
run_app<T>(mut app, port)
mut x := &T{}
run_app(mut x, port)
}
*/
interface DbInterface {
db voidptr
}
pub fn run_app<T>(mut app T, port int) {
// run_app
pub fn run<T>(global_app &T, port int) {
// x := global_app.clone()
// mut global_app := &T{}
// mut app := &T{}
// run_app<T>(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<T>(mut conn, mut app)
handle_conn<T>(mut conn, mut request_app)
}
}