diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index a1535812f2..ad094dd080 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -493,6 +493,7 @@ fn (t Tree) fn_decl(node ast.FnDecl) &Node { obj.add('no_body', t.bool_node(node.no_body)) obj.add('is_builtin', t.bool_node(node.is_builtin)) obj.add('is_direct_arr', t.bool_node(node.is_direct_arr)) + obj.add('ctdefine_idx', t.number_node(node.ctdefine_idx)) obj.add('pos', t.position(node.pos)) obj.add('body_pos', t.position(node.body_pos)) obj.add('return_type_pos', t.position(node.return_type_pos)) @@ -501,7 +502,6 @@ fn (t Tree) fn_decl(node ast.FnDecl) &Node { obj.add('return_type', t.type_node(node.return_type)) obj.add('source_file', t.number_node(int(node.source_file))) obj.add('scope', t.number_node(int(node.scope))) - obj.add('skip_gen', t.bool_node(node.skip_gen)) obj.add('attrs', t.array_node_attr(node.attrs)) obj.add('params', t.array_node_arg(node.params)) obj.add('generic_names', t.array_node_string(node.generic_names)) @@ -630,6 +630,10 @@ fn (t Tree) attr(node ast.Attr) &Node { obj.add('name', t.string_node(node.name)) obj.add('has_arg', t.bool_node(node.has_arg)) obj.add('kind', t.enum_node(node.kind)) + obj.add('ct_expr', t.expr(node.ct_expr)) + obj.add('ct_opt', t.bool_node(node.ct_opt)) + obj.add('ct_evaled', t.bool_node(node.ct_evaled)) + obj.add('ct_skip', t.bool_node(node.ct_skip)) obj.add('arg', t.string_node(node.arg)) return obj } diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 88b3b821af..3d50dcdfd1 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -384,7 +384,7 @@ pub: generic_names []string is_direct_arr bool // direct array access attrs []Attr - skip_gen bool // this function doesn't need to be generated (for example [if foo]) + ctdefine_idx int = -1 // the index in fn.attrs of `[if xyz]`, when such attribute exists pub mut: params []Param stmts []Stmt @@ -392,12 +392,14 @@ pub mut: return_type Type return_type_pos token.Position // `string` in `fn (u User) name() string` position has_return bool - comments []Comment // comments *after* the header, but *before* `{`; used for InterfaceDecl - next_comments []Comment // coments that are one line after the decl; used for InterfaceDecl - source_file &File = 0 - scope &Scope - label_names []string - pos token.Position // function declaration position + // + comments []Comment // comments *after* the header, but *before* `{`; used for InterfaceDecl + next_comments []Comment // coments that are one line after the decl; used for InterfaceDecl + // + source_file &File = 0 + scope &Scope + label_names []string + pos token.Position // function declaration position } // break, continue diff --git a/vlib/v/ast/attr.v b/vlib/v/ast/attr.v index 0884ce8de5..f91cdb84ac 100644 --- a/vlib/v/ast/attr.v +++ b/vlib/v/ast/attr.v @@ -19,7 +19,16 @@ pub: has_arg bool arg string // [name: arg] kind AttrKind + ct_expr Expr // .kind == comptime_define, for [if !name] + ct_opt bool // true for [if user_defined_name?] pos token.Position +pub mut: + ct_evaled bool // whether ct_skip has been evaluated already + ct_skip bool // is the comptime expr *false*, filled by checker +} + +pub fn (a Attr) debug() string { + return 'Attr{ name: "$a.name", has_arg: $a.has_arg, arg: "$a.arg", kind: $a.kind, ct_expr: $a.ct_expr, ct_opt: $a.ct_opt, ct_skip: $a.ct_skip}' } // str returns the string representation without square brackets @@ -43,10 +52,10 @@ pub fn (attrs []Attr) contains(str string) bool { return attrs.any(it.name == str) } -pub fn (attrs []Attr) find_comptime_define() ?string { - for a in attrs { - if a.kind == .comptime_define { - return a.name +pub fn (attrs []Attr) find_comptime_define() ?int { + for idx in 0 .. attrs.len { + if attrs[idx].kind == .comptime_define { + return idx } } return none diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 7ecebf5654..bf439549d3 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -339,6 +339,13 @@ pub fn (x Expr) str() string { ParExpr { return '($x.expr)' } + PostfixExpr { + // TODO: uncomment after [if x] is deprecated + // if x.op == .question { + // return '$x.expr ?' + //} + return '$x.expr$x.op' + } PrefixExpr { return x.op.str() + x.right.str() } diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 6da76c1b94..0337c4603d 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -77,12 +77,9 @@ pub: is_placeholder bool is_main bool // `fn main(){}` is_test bool // `fn test_abc(){}` - is_conditional bool // `[if abc]fn(){}` is_keep_alive bool // passed memory must not be freed (by GC) before function returns no_body bool // a pure declaration like `fn abc(x int)`; used in .vh files, C./JS. fns. mod string - ctdefine string // compile time define. "myflag", when [if myflag] tag - attrs []Attr pos token.Position return_type_pos token.Position pub mut: @@ -91,6 +88,10 @@ pub mut: params []Param source_fn voidptr // set in the checker, while processing fn declarations usages int + // + attrs []Attr // all fn attributes + is_conditional bool // true for `[if abc]fn(){}` + ctdefine_idx int // the index of the attribute, containing the compile time define [if mytag] } fn (f &Fn) method_equals(o &Fn) bool { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index cdc8df6954..d7a3f49c7c 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -28,11 +28,22 @@ const ( valid_comp_if_cpu_features = ['x64', 'x32', 'little_endian', 'big_endian'] valid_comp_if_other = ['js', 'debug', 'prod', 'test', 'glibc', 'prealloc', 'no_bounds_checking', 'freestanding', 'threads'] + valid_comp_not_user_defined = all_valid_comptime_idents() array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort', 'contains', 'index', 'wait', 'any', 'all', 'first', 'last', 'pop'] vroot_is_deprecated_message = '@VROOT is deprecated, use @VMODROOT or @VEXEROOT instead' ) +fn all_valid_comptime_idents() []string { + mut res := []string{} + res << checker.valid_comp_if_os + res << checker.valid_comp_if_compilers + res << checker.valid_comp_if_platforms + res << checker.valid_comp_if_cpu_features + res << checker.valid_comp_if_other + return res +} + [heap] pub struct Checker { pref &pref.Preferences // Preferences shared from V struct @@ -66,6 +77,7 @@ pub mut: inside_ref_lit bool inside_fn_arg bool // `a`, `b` in `a.f(b)` inside_c_call bool // true inside C.printf( param ) calls, but NOT in nested calls, unless they are also C. + inside_ct_attr bool // true inside [if expr] skip_flags bool // should `#flag` and `#include` be skipped mut: files []ast.File @@ -1860,9 +1872,8 @@ pub fn (mut c Checker) method_call(mut call_expr ast.CallExpr) ast.Type { && method.no_body { c.error('cannot call a method that does not have a body', call_expr.pos) } - if method.return_type == ast.void_type && method.ctdefine.len > 0 - && method.ctdefine !in c.pref.compile_defines { - call_expr.should_be_skipped = true + if method.return_type == ast.void_type && method.is_conditional && method.ctdefine_idx != -1 { + call_expr.should_be_skipped = c.evaluate_once_comptime_if_attribute(mut method.attrs[method.ctdefine_idx]) } nr_args := if method.params.len == 0 { 0 } else { method.params.len - 1 } min_required_args := method.params.len - if method.is_variadic && method.params.len > 1 { @@ -2408,9 +2419,8 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type { call_expr.pos) return func.return_type } - if func.return_type == ast.void_type && func.ctdefine.len > 0 - && func.ctdefine !in c.pref.compile_defines { - call_expr.should_be_skipped = true + if func.return_type == ast.void_type && func.is_conditional && func.ctdefine_idx != -1 { + call_expr.should_be_skipped = c.evaluate_once_comptime_if_attribute(mut func.attrs[func.ctdefine_idx]) } // dont check number of args for JS functions since arguments are not required if call_expr.language != .js { @@ -5370,7 +5380,12 @@ pub fn (mut c Checker) ident(mut ident ast.Ident) ast.Type { } else if ident.name == 'errcode' { c.error('undefined ident: `errcode`; did you mean `err.code`?', ident.pos) } else { - c.error('undefined ident: `$ident.name`', ident.pos) + if c.inside_ct_attr { + c.note('`[if $ident.name]` is deprecated. Use `[if $ident.name?]` instead', + ident.pos) + } else { + c.error('undefined ident: `$ident.name`', ident.pos) + } } if c.table.known_type(ident.name) { // e.g. `User` in `json.decode(User, '...')` @@ -6273,11 +6288,13 @@ fn (mut c Checker) comp_if_branch(cond ast.Expr, pos token.Position) bool { cond.pos) return false } - // `$if some_var {}` + // `$if some_var {}`, or `[if user_defined_tag] fn abc(){}` typ := c.expr(cond) if cond.obj !is ast.Var && cond.obj !is ast.ConstField && cond.obj !is ast.GlobalField { - c.error('unknown var: `$cname`', pos) + if !c.inside_ct_attr { + c.error('unknown var: `$cname`', pos) + } return false } expr := c.find_obj_definition(cond.obj) or { @@ -7141,6 +7158,45 @@ fn (mut c Checker) post_process_generic_fns() { } } +fn (mut c Checker) evaluate_once_comptime_if_attribute(mut a ast.Attr) bool { + if a.ct_evaled { + return a.ct_skip + } + if a.ct_expr is ast.Ident { + if a.ct_opt { + if a.ct_expr.name in checker.valid_comp_not_user_defined { + c.error('optional `[if expression ?]` tags, can be used only for user defined identifiers', + a.pos) + a.ct_skip = true + } else { + a.ct_skip = a.ct_expr.name !in c.pref.compile_defines + } + a.ct_evaled = true + return a.ct_skip + } else { + if a.ct_expr.name !in checker.valid_comp_not_user_defined { + // TODO: uncomment after [if x] is deprecated in favour of [if x?], see also vlib/v/ast/str.v:343 + // c.note('`[if $a.ct_expr.name]` is deprecated. Use `[if $a.ct_expr.name ?]` instead', a.pos) + a.ct_skip = a.ct_expr.name !in c.pref.compile_defines + a.ct_evaled = true + return a.ct_skip + } else { + if a.ct_expr.name in c.pref.compile_defines { + // explicitly allow custom user overrides with `-d linux` for example, for easier testing: + a.ct_skip = false + a.ct_evaled = true + return a.ct_skip + } + } + } + } + c.inside_ct_attr = true + a.ct_skip = c.comp_if_branch(a.ct_expr, a.pos) + c.inside_ct_attr = false + a.ct_evaled = true + return a.ct_skip +} + fn (mut c Checker) fn_decl(mut node ast.FnDecl) { c.returns = false if node.generic_names.len > 0 && c.table.cur_concrete_types.len == 0 { @@ -7178,8 +7234,9 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { c.main_fn_decl_node = node } if node.return_type != ast.void_type { - if ct_name := node.attrs.find_comptime_define() { - c.error('only functions that do NOT return values can have `[if $ct_name]` tags', + if ct_attr_idx := node.attrs.find_comptime_define() { + sexpr := node.attrs[ct_attr_idx].ct_expr.str() + c.error('only functions that do NOT return values can have `[if $sexpr]` tags', node.pos) } if node.generic_names.len > 0 { @@ -7192,6 +7249,13 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { } } } + if node.return_type == ast.void_type { + for mut a in node.attrs { + if a.kind == .comptime_define { + c.evaluate_once_comptime_if_attribute(mut a) + } + } + } if node.is_method { mut sym := c.table.get_type_symbol(node.receiver.typ) if sym.kind == .array && !c.is_builtin_mod && node.name == 'map' { diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index f541696ccc..89bc039a09 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -197,7 +197,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { else {} } } - conditional_ctdefine := p.attrs.find_comptime_define() or { '' } + conditional_ctdefine_idx := p.attrs.find_comptime_define() or { -1 } is_pub := p.tok.kind == .key_pub if is_pub { p.next() @@ -376,12 +376,14 @@ fn (mut p Parser) fn_decl() ast.FnDecl { is_unsafe: is_unsafe is_main: is_main is_test: is_test - is_conditional: conditional_ctdefine != '' is_keep_alive: is_keep_alive - ctdefine: conditional_ctdefine + // + attrs: p.attrs + is_conditional: conditional_ctdefine_idx != -1 + ctdefine_idx: conditional_ctdefine_idx + // no_body: no_body mod: p.mod - attrs: p.attrs }) } else { if language == .c { @@ -405,12 +407,14 @@ fn (mut p Parser) fn_decl() ast.FnDecl { is_unsafe: is_unsafe is_main: is_main is_test: is_test - is_conditional: conditional_ctdefine != '' is_keep_alive: is_keep_alive - ctdefine: conditional_ctdefine + // + attrs: p.attrs + is_conditional: conditional_ctdefine_idx != -1 + ctdefine_idx: conditional_ctdefine_idx + // no_body: no_body mod: p.mod - attrs: p.attrs language: language }) } @@ -449,8 +453,12 @@ fn (mut p Parser) fn_decl() ast.FnDecl { is_variadic: is_variadic is_main: is_main is_test: is_test - is_conditional: conditional_ctdefine != '' is_keep_alive: is_keep_alive + // + attrs: p.attrs + is_conditional: conditional_ctdefine_idx != -1 + ctdefine_idx: conditional_ctdefine_idx + // receiver: ast.StructField{ name: rec.name typ: rec.typ @@ -467,7 +475,6 @@ fn (mut p Parser) fn_decl() ast.FnDecl { body_pos: body_start_pos file: p.file_name is_builtin: p.builtin_mod || p.mod in util.builtin_module_parts - attrs: p.attrs scope: p.scope label_names: p.label_names } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 60585c69f2..8dcc611b0c 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1520,17 +1520,24 @@ fn (mut p Parser) parse_attr() ast.Attr { mut name := '' mut has_arg := false mut arg := '' + mut comptime_cond := ast.empty_expr() + mut comptime_cond_opt := false if p.tok.kind == .key_if { kind = .comptime_define p.next() - p.check(.name) - // TODO: remove this check after bootstrapping - // it is only for compatibility with the new - // [if user_defined?] syntax. - if p.tok.kind == .question { - p.next() + p.comp_if_cond = true + p.inside_if_expr = true + p.inside_ct_if_expr = true + comptime_cond = p.expr(0) + p.comp_if_cond = false + p.inside_if_expr = false + p.inside_ct_if_expr = false + if comptime_cond is ast.PostfixExpr { + x := comptime_cond as ast.PostfixExpr + comptime_cond_opt = true + comptime_cond = x.expr } - name = p.prev_tok.lit + name = comptime_cond.str() } else if p.tok.kind == .string { name = p.tok.lit kind = .string @@ -1562,6 +1569,8 @@ fn (mut p Parser) parse_attr() ast.Attr { has_arg: has_arg arg: arg kind: kind + ct_expr: comptime_cond + ct_opt: comptime_cond_opt pos: apos.extend(p.tok.position()) } } diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index 9acf319bf0..09849f7e7a 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -447,8 +447,10 @@ pub fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_iden // Postfix // detect `f(x++)`, `a[x++]` if p.peek_tok.kind in [.rpar, .rsbr] { - p.warn_with_pos('`$p.tok.kind` operator can only be used as a statement', - p.peek_tok.position()) + if !p.inside_ct_if_expr { + p.warn_with_pos('`$p.tok.kind` operator can only be used as a statement', + p.peek_tok.position()) + } } if p.tok.kind in [.inc, .dec] && p.prev_tok.line_nr != p.tok.line_nr { p.error_with_pos('$p.tok must be on the same line as the previous token',