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('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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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