v: support for `[if expr]`, part 2 (old `[if ident]` is not yet deprecated)

pull/10498/head
Delyan Angelov 2021-06-17 11:38:42 +03:00
parent 0918c130ee
commit 39e7290416
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
9 changed files with 149 additions and 44 deletions

View File

@ -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('no_body', t.bool_node(node.no_body))
obj.add('is_builtin', t.bool_node(node.is_builtin)) obj.add('is_builtin', t.bool_node(node.is_builtin))
obj.add('is_direct_arr', t.bool_node(node.is_direct_arr)) 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('pos', t.position(node.pos))
obj.add('body_pos', t.position(node.body_pos)) obj.add('body_pos', t.position(node.body_pos))
obj.add('return_type_pos', t.position(node.return_type_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('return_type', t.type_node(node.return_type))
obj.add('source_file', t.number_node(int(node.source_file))) obj.add('source_file', t.number_node(int(node.source_file)))
obj.add('scope', t.number_node(int(node.scope))) 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('attrs', t.array_node_attr(node.attrs))
obj.add('params', t.array_node_arg(node.params)) obj.add('params', t.array_node_arg(node.params))
obj.add('generic_names', t.array_node_string(node.generic_names)) 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('name', t.string_node(node.name))
obj.add('has_arg', t.bool_node(node.has_arg)) obj.add('has_arg', t.bool_node(node.has_arg))
obj.add('kind', t.enum_node(node.kind)) 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)) obj.add('arg', t.string_node(node.arg))
return obj return obj
} }

View File

@ -384,7 +384,7 @@ pub:
generic_names []string generic_names []string
is_direct_arr bool // direct array access is_direct_arr bool // direct array access
attrs []Attr 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: pub mut:
params []Param params []Param
stmts []Stmt stmts []Stmt
@ -392,12 +392,14 @@ pub mut:
return_type Type return_type Type
return_type_pos token.Position // `string` in `fn (u User) name() string` position return_type_pos token.Position // `string` in `fn (u User) name() string` position
has_return bool 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 comments []Comment // comments *after* the header, but *before* `{`; used for InterfaceDecl
source_file &File = 0 next_comments []Comment // coments that are one line after the decl; used for InterfaceDecl
scope &Scope //
label_names []string source_file &File = 0
pos token.Position // function declaration position scope &Scope
label_names []string
pos token.Position // function declaration position
} }
// break, continue // break, continue

View File

@ -19,7 +19,16 @@ pub:
has_arg bool has_arg bool
arg string // [name: arg] arg string // [name: arg]
kind AttrKind kind AttrKind
ct_expr Expr // .kind == comptime_define, for [if !name]
ct_opt bool // true for [if user_defined_name?]
pos token.Position 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 // 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) return attrs.any(it.name == str)
} }
pub fn (attrs []Attr) find_comptime_define() ?string { pub fn (attrs []Attr) find_comptime_define() ?int {
for a in attrs { for idx in 0 .. attrs.len {
if a.kind == .comptime_define { if attrs[idx].kind == .comptime_define {
return a.name return idx
} }
} }
return none return none

View File

@ -339,6 +339,13 @@ pub fn (x Expr) str() string {
ParExpr { ParExpr {
return '($x.expr)' return '($x.expr)'
} }
PostfixExpr {
// TODO: uncomment after [if x] is deprecated
// if x.op == .question {
// return '$x.expr ?'
//}
return '$x.expr$x.op'
}
PrefixExpr { PrefixExpr {
return x.op.str() + x.right.str() return x.op.str() + x.right.str()
} }

View File

@ -77,12 +77,9 @@ pub:
is_placeholder bool is_placeholder bool
is_main bool // `fn main(){}` is_main bool // `fn main(){}`
is_test bool // `fn test_abc(){}` 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 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. no_body bool // a pure declaration like `fn abc(x int)`; used in .vh files, C./JS. fns.
mod string mod string
ctdefine string // compile time define. "myflag", when [if myflag] tag
attrs []Attr
pos token.Position pos token.Position
return_type_pos token.Position return_type_pos token.Position
pub mut: pub mut:
@ -91,6 +88,10 @@ pub mut:
params []Param params []Param
source_fn voidptr // set in the checker, while processing fn declarations source_fn voidptr // set in the checker, while processing fn declarations
usages int 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 { fn (f &Fn) method_equals(o &Fn) bool {

View File

@ -28,11 +28,22 @@ const (
valid_comp_if_cpu_features = ['x64', 'x32', 'little_endian', 'big_endian'] valid_comp_if_cpu_features = ['x64', 'x32', 'little_endian', 'big_endian']
valid_comp_if_other = ['js', 'debug', 'prod', 'test', 'glibc', 'prealloc', valid_comp_if_other = ['js', 'debug', 'prod', 'test', 'glibc', 'prealloc',
'no_bounds_checking', 'freestanding', 'threads'] 'no_bounds_checking', 'freestanding', 'threads']
valid_comp_not_user_defined = all_valid_comptime_idents()
array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort', array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort',
'contains', 'index', 'wait', 'any', 'all', 'first', 'last', 'pop'] 'contains', 'index', 'wait', 'any', 'all', 'first', 'last', 'pop']
vroot_is_deprecated_message = '@VROOT is deprecated, use @VMODROOT or @VEXEROOT instead' 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] [heap]
pub struct Checker { pub struct Checker {
pref &pref.Preferences // Preferences shared from V struct pref &pref.Preferences // Preferences shared from V struct
@ -66,6 +77,7 @@ pub mut:
inside_ref_lit bool inside_ref_lit bool
inside_fn_arg bool // `a`, `b` in `a.f(b)` 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_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 skip_flags bool // should `#flag` and `#include` be skipped
mut: mut:
files []ast.File files []ast.File
@ -1860,9 +1872,8 @@ pub fn (mut c Checker) method_call(mut call_expr ast.CallExpr) ast.Type {
&& method.no_body { && method.no_body {
c.error('cannot call a method that does not have a body', call_expr.pos) 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 if method.return_type == ast.void_type && method.is_conditional && method.ctdefine_idx != -1 {
&& method.ctdefine !in c.pref.compile_defines { call_expr.should_be_skipped = c.evaluate_once_comptime_if_attribute(mut method.attrs[method.ctdefine_idx])
call_expr.should_be_skipped = true
} }
nr_args := if method.params.len == 0 { 0 } else { method.params.len - 1 } 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 { 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) call_expr.pos)
return func.return_type return func.return_type
} }
if func.return_type == ast.void_type && func.ctdefine.len > 0 if func.return_type == ast.void_type && func.is_conditional && func.ctdefine_idx != -1 {
&& func.ctdefine !in c.pref.compile_defines { call_expr.should_be_skipped = c.evaluate_once_comptime_if_attribute(mut func.attrs[func.ctdefine_idx])
call_expr.should_be_skipped = true
} }
// dont check number of args for JS functions since arguments are not required // dont check number of args for JS functions since arguments are not required
if call_expr.language != .js { 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' { } else if ident.name == 'errcode' {
c.error('undefined ident: `errcode`; did you mean `err.code`?', ident.pos) c.error('undefined ident: `errcode`; did you mean `err.code`?', ident.pos)
} else { } 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) { if c.table.known_type(ident.name) {
// e.g. `User` in `json.decode(User, '...')` // 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) cond.pos)
return false return false
} }
// `$if some_var {}` // `$if some_var {}`, or `[if user_defined_tag] fn abc(){}`
typ := c.expr(cond) typ := c.expr(cond)
if cond.obj !is ast.Var && cond.obj !is ast.ConstField if cond.obj !is ast.Var && cond.obj !is ast.ConstField
&& cond.obj !is ast.GlobalField { && cond.obj !is ast.GlobalField {
c.error('unknown var: `$cname`', pos) if !c.inside_ct_attr {
c.error('unknown var: `$cname`', pos)
}
return false return false
} }
expr := c.find_obj_definition(cond.obj) or { 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) { fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
c.returns = false c.returns = false
if node.generic_names.len > 0 && c.table.cur_concrete_types.len == 0 { 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 c.main_fn_decl_node = node
} }
if node.return_type != ast.void_type { if node.return_type != ast.void_type {
if ct_name := node.attrs.find_comptime_define() { if ct_attr_idx := node.attrs.find_comptime_define() {
c.error('only functions that do NOT return values can have `[if $ct_name]` tags', 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) node.pos)
} }
if node.generic_names.len > 0 { 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 { if node.is_method {
mut sym := c.table.get_type_symbol(node.receiver.typ) mut sym := c.table.get_type_symbol(node.receiver.typ)
if sym.kind == .array && !c.is_builtin_mod && node.name == 'map' { if sym.kind == .array && !c.is_builtin_mod && node.name == 'map' {

View File

@ -197,7 +197,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
else {} 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 is_pub := p.tok.kind == .key_pub
if is_pub { if is_pub {
p.next() p.next()
@ -376,12 +376,14 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
is_unsafe: is_unsafe is_unsafe: is_unsafe
is_main: is_main is_main: is_main
is_test: is_test is_test: is_test
is_conditional: conditional_ctdefine != ''
is_keep_alive: is_keep_alive 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 no_body: no_body
mod: p.mod mod: p.mod
attrs: p.attrs
}) })
} else { } else {
if language == .c { if language == .c {
@ -405,12 +407,14 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
is_unsafe: is_unsafe is_unsafe: is_unsafe
is_main: is_main is_main: is_main
is_test: is_test is_test: is_test
is_conditional: conditional_ctdefine != ''
is_keep_alive: is_keep_alive 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 no_body: no_body
mod: p.mod mod: p.mod
attrs: p.attrs
language: language language: language
}) })
} }
@ -449,8 +453,12 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
is_variadic: is_variadic is_variadic: is_variadic
is_main: is_main is_main: is_main
is_test: is_test is_test: is_test
is_conditional: conditional_ctdefine != ''
is_keep_alive: is_keep_alive is_keep_alive: is_keep_alive
//
attrs: p.attrs
is_conditional: conditional_ctdefine_idx != -1
ctdefine_idx: conditional_ctdefine_idx
//
receiver: ast.StructField{ receiver: ast.StructField{
name: rec.name name: rec.name
typ: rec.typ typ: rec.typ
@ -467,7 +475,6 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
body_pos: body_start_pos body_pos: body_start_pos
file: p.file_name file: p.file_name
is_builtin: p.builtin_mod || p.mod in util.builtin_module_parts is_builtin: p.builtin_mod || p.mod in util.builtin_module_parts
attrs: p.attrs
scope: p.scope scope: p.scope
label_names: p.label_names label_names: p.label_names
} }

View File

@ -1520,17 +1520,24 @@ fn (mut p Parser) parse_attr() ast.Attr {
mut name := '' mut name := ''
mut has_arg := false mut has_arg := false
mut arg := '' mut arg := ''
mut comptime_cond := ast.empty_expr()
mut comptime_cond_opt := false
if p.tok.kind == .key_if { if p.tok.kind == .key_if {
kind = .comptime_define kind = .comptime_define
p.next() p.next()
p.check(.name) p.comp_if_cond = true
// TODO: remove this check after bootstrapping p.inside_if_expr = true
// it is only for compatibility with the new p.inside_ct_if_expr = true
// [if user_defined?] syntax. comptime_cond = p.expr(0)
if p.tok.kind == .question { p.comp_if_cond = false
p.next() 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 { } else if p.tok.kind == .string {
name = p.tok.lit name = p.tok.lit
kind = .string kind = .string
@ -1562,6 +1569,8 @@ fn (mut p Parser) parse_attr() ast.Attr {
has_arg: has_arg has_arg: has_arg
arg: arg arg: arg
kind: kind kind: kind
ct_expr: comptime_cond
ct_opt: comptime_cond_opt
pos: apos.extend(p.tok.position()) pos: apos.extend(p.tok.position())
} }
} }

View File

@ -447,8 +447,10 @@ pub fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_iden
// Postfix // Postfix
// detect `f(x++)`, `a[x++]` // detect `f(x++)`, `a[x++]`
if p.peek_tok.kind in [.rpar, .rsbr] { if p.peek_tok.kind in [.rpar, .rsbr] {
p.warn_with_pos('`$p.tok.kind` operator can only be used as a statement', if !p.inside_ct_if_expr {
p.peek_tok.position()) 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 { 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', p.error_with_pos('$p.tok must be on the same line as the previous token',