all: remove `it` smartcast and replace with original variable name (#5764)

pull/5775/head
Daniel Däschle 2020-07-09 17:14:14 +02:00 committed by GitHub
parent 9511b7d0a1
commit c5dc1a33b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 210 additions and 94 deletions

View File

@ -98,9 +98,9 @@ fn (app App) gen_api_for_module_in_os(mod_name, os_name string) string {
for f in b.parsed_files { for f in b.parsed_files {
for s in f.stmts { for s in f.stmts {
if s is ast.FnDecl { if s is ast.FnDecl {
if it.is_pub { if s.is_pub {
fn_signature := it.stringify(b.table) fn_signature := s.stringify(b.table)
fn_mod := it.modname() fn_mod := s.modname()
if fn_mod == mod_name { if fn_mod == mod_name {
fline := '${fn_mod}: $fn_signature' fline := '${fn_mod}: $fn_signature'
res << fline res << fline

View File

@ -541,6 +541,32 @@ else {
println(s) // "odd" println(s) // "odd"
``` ```
#### Is check
You can check sum types using `if` like `match`ing them.
```v
struct Abc {
val string
}
struct Xyz {
foo string
}
type Alphabet = Abc | Xyz
x := Alphabet(Abc{'test'}) // sum type
if x is Abc {
// x is automatically castet to Abc and can be used here
println(x)
}
```
If you have a struct field which should be checked, there is also a way to name a alias.
```
if x.bar is MyStruct as bar {
// x.bar cannot be castet automatically, instead you say "as bar" which creates a variable with the MyStruct typing
println(bar)
}
```
### In operator ### In operator
`in` allows to check whether an array or a map contains an element. `in` allows to check whether an array or a map contains an element.

View File

@ -33,7 +33,7 @@ fn wait() {
fn (l Lander) land(w World) { fn (l Lander) land(w World) {
if w is Mars { if w is Mars {
for it.dust_storm() { for w.dust_storm() {
wait() wait()
} }
} }

View File

@ -389,13 +389,13 @@ pub fn (i &Ident) var_info() IdentVar {
pub struct InfixExpr { pub struct InfixExpr {
pub: pub:
op token.Kind op token.Kind
pos token.Position pos token.Position
left Expr left Expr
right Expr right Expr
pub mut: pub mut:
left_type table.Type left_type table.Type
right_type table.Type right_type table.Type
} }
pub struct PostfixExpr { pub struct PostfixExpr {
@ -443,6 +443,7 @@ pub:
comments []Comment comments []Comment
pub mut: pub mut:
smartcast bool // should only be true if cond is `x is sumtype`, it will be set in checker - if_expr smartcast bool // should only be true if cond is `x is sumtype`, it will be set in checker - if_expr
left_as_name string // only used in x is SumType check
} }
pub struct LockExpr { pub struct LockExpr {

View File

@ -279,7 +279,7 @@ pub fn (node Stmt) str() string {
mut out := '' mut out := ''
for i, left in it.left { for i, left in it.left {
if left is Ident { if left is Ident {
var_info := it.var_info() var_info := left.var_info()
if var_info.is_mut { if var_info.is_mut {
out += 'mut ' out += 'mut '
} }

View File

@ -288,8 +288,8 @@ fn (b &Builder) print_warnings_and_errors() {
for file in b.parsed_files { for file in b.parsed_files {
for stmt in file.stmts { for stmt in file.stmts {
if stmt is ast.FnDecl { if stmt is ast.FnDecl {
if it.name == fn_name { if stmt.name == fn_name {
fline := it.pos.line_nr fline := stmt.pos.line_nr
println('$file.path:$fline:') println('$file.path:$fline:')
} }
} }

View File

@ -207,17 +207,17 @@ fn (mut c Checker) check_file_in_main(file ast.File) bool {
} }
ast.TypeDecl { ast.TypeDecl {
if stmt is ast.AliasTypeDecl { if stmt is ast.AliasTypeDecl {
if it.is_pub { if stmt.is_pub {
c.warn('type alias `$it.name` $no_pub_in_main_warning', c.warn('type alias `$stmt.name` $no_pub_in_main_warning',
it.pos) stmt.pos)
} }
} else if stmt is ast.SumTypeDecl { } else if stmt is ast.SumTypeDecl {
if it.is_pub { if stmt.is_pub {
c.warn('sum type `$it.name` $no_pub_in_main_warning', it.pos) c.warn('sum type `$stmt.name` $no_pub_in_main_warning', stmt.pos)
} }
} else if stmt is ast.FnTypeDecl { } else if stmt is ast.FnTypeDecl {
if it.is_pub { if stmt.is_pub {
c.warn('type alias `$it.name` $no_pub_in_main_warning', it.pos) c.warn('type alias `$stmt.name` $no_pub_in_main_warning', stmt.pos)
} }
} }
} }
@ -1266,25 +1266,25 @@ fn (mut c Checker) type_implements(typ, inter_typ table.Type, pos token.Position
// return the actual type of the expression, once the optional is handled // return the actual type of the expression, once the optional is handled
pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type table.Type) table.Type { pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type table.Type) table.Type {
if expr is ast.CallExpr { if expr is ast.CallExpr {
if it.return_type.has_flag(.optional) { if expr.return_type.has_flag(.optional) {
if it.or_block.kind == .absent { if expr.or_block.kind == .absent {
if ret_type != table.void_type { if ret_type != table.void_type {
c.error('${it.name}() returns an option, but you missed to add an `or {}` block to it', c.error('${expr.name}() returns an option, but you missed to add an `or {}` block to it',
it.pos) expr.pos)
} }
} else { } else {
c.check_or_expr(it.or_block, ret_type) c.check_or_expr(expr.or_block, ret_type)
} }
// remove optional flag // remove optional flag
// return ret_type.clear_flag(.optional) // return ret_type.clear_flag(.optional)
// TODO: currently unwrapped in assign, would need to refactor assign to unwrap here // TODO: currently unwrapped in assign, would need to refactor assign to unwrap here
return ret_type return ret_type
} else if it.or_block.kind == .block { } else if expr.or_block.kind == .block {
c.error('unexpected `or` block, the function `$it.name` does not return an optional', c.error('unexpected `or` block, the function `$expr.name` does not return an optional',
it.pos) expr.pos)
} else if it.or_block.kind == .propagate { } else if expr.or_block.kind == .propagate {
c.error('unexpected `?`, the function `$it.name`, does not return an optional', c.error('unexpected `?`, the function `$expr.name`, does not return an optional',
it.pos) expr.pos)
} }
} }
return ret_type return ret_type
@ -1522,7 +1522,7 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) {
} }
if assign_stmt.left.len != right_len { if assign_stmt.left.len != right_len {
if right_first is ast.CallExpr { if right_first is ast.CallExpr {
c.error('assignment mismatch: $assign_stmt.left.len variable(s) but `${it.name}()` returns $right_len value(s)', c.error('assignment mismatch: $assign_stmt.left.len variable(s) but `${right_first.name}()` returns $right_len value(s)',
assign_stmt.pos) assign_stmt.pos)
} else { } else {
c.error('assignment mismatch: $assign_stmt.left.len variable(s) $right_len value(s)', c.error('assignment mismatch: $assign_stmt.left.len variable(s) $right_len value(s)',
@ -2620,27 +2620,30 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
// smartcast sumtypes when using `is` // smartcast sumtypes when using `is`
if branch.cond is ast.InfixExpr { if branch.cond is ast.InfixExpr {
infix := branch.cond as ast.InfixExpr infix := branch.cond as ast.InfixExpr
if infix.op == .key_is && infix.left is ast.Ident && infix.right is ast.Type { if infix.op == .key_is && (infix.left is ast.Ident || infix.left is ast.SelectorExpr) && infix.right is ast.Type {
left_expr := infix.left as ast.Ident
right_expr := infix.right as ast.Type right_expr := infix.right as ast.Type
if left_expr.kind == .variable { is_variable := if infix.left is ast.Ident {
// Register shadow variable or `as` variable with actual type (infix.left as ast.Ident).kind == .variable
} else { true }
// Register shadow variable or `as` variable with actual type
if is_variable {
left_sym := c.table.get_type_symbol(infix.left_type) left_sym := c.table.get_type_symbol(infix.left_type)
if left_sym.kind == .sum_type { if left_sym.kind == .sum_type && branch.left_as_name.len > 0 {
mut is_mut := false
if infix.left is ast.Ident {
is_mut = (infix.left as ast.Ident).is_mut
} else if infix.left is ast.SelectorExpr {
selector := infix.left as ast.SelectorExpr
field := c.table.struct_find_field(left_sym, selector.field_name) or { table.Field{} }
is_mut = field.is_mut
}
mut scope := c.file.scope.innermost(branch.body_pos.pos) mut scope := c.file.scope.innermost(branch.body_pos.pos)
scope.register('it', ast.Var{ scope.register(branch.left_as_name, ast.Var{
name: 'it' name: branch.left_as_name
typ: right_expr.typ.to_ptr() typ: right_expr.typ.to_ptr()
pos: left_expr.pos pos: infix.left.position()
is_used: true is_used: true
is_mut: left_expr.is_mut is_mut: is_mut
})
scope.register(left_expr.name, ast.Var{
name: left_expr.name
typ: right_expr.typ.to_ptr()
pos: left_expr.pos
is_used: true
is_mut: left_expr.is_mut
}) })
node.branches[i].smartcast = true node.branches[i].smartcast = true
} }

View File

@ -0,0 +1,7 @@
fn foo() string {
if true {
return ''
} else {
}
}

View File

@ -45,7 +45,7 @@ pub fn merge_comments(stmts []ast.Stmt) string {
mut res := []string{} mut res := []string{}
for s in stmts { for s in stmts {
if s is ast.Comment { if s is ast.Comment {
res << it.text.trim_left('|') res << s.text.trim_left('|')
} }
} }
return res.join('\n') return res.join('\n')
@ -374,11 +374,11 @@ fn (mut d Doc) generate() ?Doc {
continue continue
} }
if stmt is ast.FnDecl { if stmt is ast.FnDecl {
if it.is_deprecated { if stmt.is_deprecated {
continue continue
} }
if it.receiver.typ != 0 { if stmt.receiver.typ != 0 {
node.attrs['parent'] = d.fmt.type_to_str(it.receiver.typ).trim_left('&') node.attrs['parent'] = d.fmt.type_to_str(stmt.receiver.typ).trim_left('&')
p_idx := d.contents.index_by_name(node.attrs['parent']) p_idx := d.contents.index_by_name(node.attrs['parent'])
if p_idx == -1 && node.attrs['parent'] != 'void' { if p_idx == -1 && node.attrs['parent'] != 'void' {
d.contents << DocNode{ d.contents << DocNode{

View File

@ -246,7 +246,7 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) {
ast.AssignStmt { ast.AssignStmt {
for i, left in node.left { for i, left in node.left {
if left is ast.Ident { if left is ast.Ident {
var_info := it.var_info() var_info := left.var_info()
if var_info.is_mut { if var_info.is_mut {
f.write('mut ') f.write('mut ')
} }
@ -660,8 +660,8 @@ pub fn (mut f Fmt) prefix_expr_cast_expr(fexpr ast.Expr) {
mut is_pe_amp_ce := false mut is_pe_amp_ce := false
mut ce := ast.CastExpr{} mut ce := ast.CastExpr{}
if fexpr is ast.PrefixExpr { if fexpr is ast.PrefixExpr {
if it.right is ast.CastExpr && it.op == .amp { if fexpr.right is ast.CastExpr && fexpr.op == .amp {
ce = it.right as ast.CastExpr ce = fexpr.right as ast.CastExpr
ce.typname = f.table.get_type_symbol(ce.typ).name ce.typname = f.table.get_type_symbol(ce.typ).name
is_pe_amp_ce = true is_pe_amp_ce = true
f.expr(ce) f.expr(ce)
@ -1248,16 +1248,16 @@ pub fn (mut f Fmt) call_expr(node ast.CallExpr) {
} }
*/ */
if node.left is ast.Ident { if node.left is ast.Ident {
it := node.left as ast.Ident left := node.left as ast.Ident
// `time.now()` without `time imported` is processed as a method call with `time` being // `time.now()` without `time imported` is processed as a method call with `time` being
// a `node.left` expression. Import `time` automatically. // a `node.left` expression. Import `time` automatically.
// TODO fetch all available modules // TODO fetch all available modules
if it.name in ['time', 'os', 'strings', 'math', 'json', 'base64'] { if left.name in ['time', 'os', 'strings', 'math', 'json', 'base64'] {
if it.name !in f.auto_imports { if left.name !in f.auto_imports {
f.auto_imports << it.name f.auto_imports << left.name
f.file.imports << ast.Import{ f.file.imports << ast.Import{
mod: it.name mod: left.name
alias: it.name alias: left.name
} }
} }
// for imp in f.file.imports { // for imp in f.file.imports {
@ -1321,7 +1321,7 @@ pub fn (mut f Fmt) match_expr(it ast.MatchExpr) {
stmt := branch.stmts[0] stmt := branch.stmts[0]
if stmt is ast.ExprStmt { if stmt is ast.ExprStmt {
// If expressions inside match branches can't be one a single line // If expressions inside match branches can't be one a single line
if !expr_is_single_line(it.expr) { if !expr_is_single_line(stmt.expr) {
single_line = false single_line = false
break break
} }

View File

@ -1213,12 +1213,12 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
mut blank_assign := false mut blank_assign := false
mut ident := ast.Ident{} mut ident := ast.Ident{}
if left is ast.Ident { if left is ast.Ident {
ident = it ident = *left
// id_info := ident.var_info() // id_info := ident.var_info()
// var_type = id_info.typ // var_type = id_info.typ
blank_assign = ident.kind == .blank_ident blank_assign = left.kind == .blank_ident
if ident.info is ast.IdentVar { if left.info is ast.IdentVar {
share := (ident.info as ast.IdentVar).share share := (left.info as ast.IdentVar).share
if share == .shared_t { if share == .shared_t {
var_type = var_type.set_flag(.shared_f) var_type = var_type.set_flag(.shared_f)
} }
@ -1390,9 +1390,9 @@ fn (mut g Gen) gen_cross_tmp_variable(left []ast.Expr, val ast.Expr) {
mut has_var := false mut has_var := false
for lx in left { for lx in left {
if lx is ast.Ident { if lx is ast.Ident {
if val.name == it.name { if val.name == lx.name {
g.write('_var_') g.write('_var_')
g.write(it.pos.pos.str()) g.write(lx.pos.pos.str())
has_var = true has_var = true
break break
} }
@ -2322,9 +2322,8 @@ fn (mut g Gen) if_expr(node ast.IfExpr) {
infix := branch.cond as ast.InfixExpr infix := branch.cond as ast.InfixExpr
right_type := infix.right as ast.Type right_type := infix.right as ast.Type
left_type := infix.left_type left_type := infix.left_type
left_expr := infix.left as ast.Ident
it_type := g.typ(right_type.typ) it_type := g.typ(right_type.typ)
g.write('\t$it_type* it = ($it_type*)') g.write('\t$it_type* _sc_tmp_$branch.pos.pos = ($it_type*)')
g.expr(infix.left) g.expr(infix.left)
if left_type.is_ptr() { if left_type.is_ptr() {
g.write('->') g.write('->')
@ -2332,7 +2331,7 @@ fn (mut g Gen) if_expr(node ast.IfExpr) {
g.write('.') g.write('.')
} }
g.writeln('obj;') g.writeln('obj;')
g.writeln('\t$it_type* $left_expr.name = it;') g.writeln('\t$it_type* $branch.left_as_name = _sc_tmp_$branch.pos.pos;')
} }
g.stmts(branch.stmts) g.stmts(branch.stmts)
} }

View File

@ -12,9 +12,9 @@ fn (g &Gen) comptime_call(node ast.ComptimeCall) {
for stmt in node.vweb_tmpl.stmts { for stmt in node.vweb_tmpl.stmts {
if stmt is ast.FnDecl { if stmt is ast.FnDecl {
// insert stmts from vweb_tmpl fn // insert stmts from vweb_tmpl fn
if it.name.starts_with('main.vweb_tmpl') { if stmt.name.starts_with('main.vweb_tmpl') {
g.inside_vweb_tmpl = true g.inside_vweb_tmpl = true
g.stmts(it.stmts) g.stmts(stmt.stmts)
g.inside_vweb_tmpl = false g.inside_vweb_tmpl = false
break break
} }

View File

@ -686,8 +686,8 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt) {
val := stmt.right[i] val := stmt.right[i]
mut is_mut := false mut is_mut := false
if left is ast.Ident { if left is ast.Ident {
is_mut = it.is_mut is_mut = left.is_mut
if it.kind == .blank_ident || it.name in ['', '_'] { if left.kind == .blank_ident || left.name in ['', '_'] {
tmp_var := g.new_tmp_var() tmp_var := g.new_tmp_var()
// TODO: Can the tmp_var declaration be omitted? // TODO: Can the tmp_var declaration be omitted?
g.write('const $tmp_var = ') g.write('const $tmp_var = ')

View File

@ -15,7 +15,7 @@ fn (mut p Parser) check_undefined_variables(exprs []ast.Expr, val ast.Expr) {
ast.Ident { ast.Ident {
for expr in exprs { for expr in exprs {
if expr is ast.Ident { if expr is ast.Ident {
if it.name == val.name { if expr.name == val.name {
p.error_with_pos('undefined variable: `$val.name`', val.pos) p.error_with_pos('undefined variable: `$val.name`', val.pos)
} }
} }
@ -49,7 +49,7 @@ fn (mut p Parser) check_cross_variables(exprs []ast.Expr, val ast.Expr) bool {
ast.Ident { ast.Ident {
for expr in exprs { for expr in exprs {
if expr is ast.Ident { if expr is ast.Ident {
if it.name == val_.name { if expr.name == val_.name {
return true return true
} }
} }

View File

@ -129,13 +129,12 @@ fn (mut p Parser) vweb() ast.ComptimeCall {
// copy vars from current fn scope into vweb_tmpl scope // copy vars from current fn scope into vweb_tmpl scope
for stmt in file.stmts { for stmt in file.stmts {
if stmt is ast.FnDecl { if stmt is ast.FnDecl {
if it.name == 'main.vweb_tmpl_$p.cur_fn_name' { if stmt.name == 'main.vweb_tmpl_$p.cur_fn_name' {
fn_decl := it tmpl_scope := file.scope.innermost(stmt.body_pos.pos)
tmpl_scope := file.scope.innermost(it.body_pos.pos)
for _, obj in p.scope.objects { for _, obj in p.scope.objects {
if obj is ast.Var { if obj is ast.Var {
mut v := it mut v := obj
v.pos = fn_decl.body_pos v.pos = stmt.body_pos
tmpl_scope.register(v.name, *v) tmpl_scope.register(v.name, *v)
// set the controller action var to used // set the controller action var to used
// if its unused in the template it will warn // if its unused in the template it will warn

View File

@ -555,7 +555,7 @@ fn (mut p Parser) fn_redefinition_error(name string) {
fn have_fn_main(stmts []ast.Stmt) bool { fn have_fn_main(stmts []ast.Stmt) bool {
for stmt in stmts { for stmt in stmts {
if stmt is ast.FnDecl { if stmt is ast.FnDecl {
if it.name == 'main.main' && it.mod == 'main' { if stmt.name == 'main.main' && stmt.mod == 'main' {
return true return true
} }
} }

View File

@ -64,6 +64,22 @@ fn (mut p Parser) if_expr() ast.IfExpr {
} else { } else {
cond = p.expr(0) cond = p.expr(0)
} }
mut left_as_name := ''
is_infix := cond is ast.InfixExpr
if is_infix {
infix := cond as ast.InfixExpr
is_is_cast := infix.op == .key_is
is_ident := infix.left is ast.Ident
left_as_name = if is_is_cast && p.tok.kind == .key_as {
p.next()
p.check_name()
} else if is_ident {
ident := infix.left as ast.Ident
ident.name
} else {
''
}
}
end_pos := p.prev_tok.position() end_pos := p.prev_tok.position()
body_pos := p.tok.position() body_pos := p.tok.position()
p.inside_if = false p.inside_if = false
@ -77,6 +93,7 @@ fn (mut p Parser) if_expr() ast.IfExpr {
pos: start_pos.extend(end_pos) pos: start_pos.extend(end_pos)
body_pos: body_pos.extend(p.tok.position()) body_pos: body_pos.extend(p.tok.position())
comments: comments comments: comments
left_as_name: left_as_name
} }
comments = [] comments = []
if p.tok.kind != .key_else { if p.tok.kind != .key_else {

View File

@ -230,6 +230,10 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr {
} }
// continue on infix expr // continue on infix expr
node = p.infix_expr(node) node = p.infix_expr(node)
// return early `if bar is SumType as b {`
if p.tok.kind == .key_as && p.inside_if {
return node
}
} else if p.tok.kind in [.inc, .dec] { } else if p.tok.kind in [.inc, .dec] {
// Postfix // Postfix
node = ast.PostfixExpr{ node = ast.PostfixExpr{

View File

@ -0,0 +1,60 @@
struct Abc {
mut:
val string
}
struct Xyz {
name string
}
type Alphabet = Abc | Xyz
fn test_if_smartcast() {
x := Alphabet(Abc{'test'})
if x is Abc {
assert x.val == 'test'
}
}
fn test_mutable() {
mut x := Alphabet(Abc{'test'})
if x is Abc {
y := Abc{}
x = &y
assert x == &y
}
}
fn test_nested_if_smartcast() {
x := Alphabet(Abc{'test'})
y := Alphabet(Xyz{'foo'})
if x is Abc {
if y is Xyz {
assert y.name == 'foo'
}
}
}
fn test_as_cast() {
x := Alphabet(Abc{'test'})
if x is Abc as test {
assert test.val == 'test'
}
}
struct Test {
abc Alphabet
}
fn test_mutable_with_struct() {
mut x := Test{Abc{'test'}}
if x.abc is Abc as test {
test.val = 'test'
assert test.val == 'test'
}
}
fn test_as_cast_with_struct() {
x := Test{Abc{'test'}}
if x.abc is Abc as test {
assert test.val == 'test'
}
}