v: support for `[if expr]`, part 2 (old `[if ident]` is not yet deprecated)
parent
0918c130ee
commit
39e7290416
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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' {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in New Issue