From 9025c3528f0792825b0d1b3f858186b7633537d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=C3=A4schle?= Date: Wed, 6 Jan 2021 18:58:53 +0100 Subject: [PATCH] all: implement comptime if for generic type `$if T is string {` (#7901) --- vlib/v/checker/checker.v | 21 +++++++++++----- vlib/v/gen/comptime.v | 14 ++++++++--- vlib/v/parser/pratt.v | 4 +++ vlib/v/tests/comptime_if_expr_test.v | 37 ++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 264b5c3232..13bdf35538 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -4122,6 +4122,7 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type { mut nbranches_without_return := 0 mut should_skip := false // Whether the current branch should be skipped mut found_branch := false // Whether a matching branch was found- skip the rest + mut is_comptime_t_is_expr := false // if `$if T is string` for i in 0 .. node.branches.len { mut branch := node.branches[i] if branch.cond is ast.ParExpr { @@ -4237,10 +4238,16 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type { // smartcast field type on comptime if if branch.cond is ast.InfixExpr { if branch.cond.op == .key_is { - se := branch.cond.left - if se is ast.SelectorExpr { - got_type := (branch.cond.right as ast.Type).typ - c.comptime_fields_type[se.expr.str()] = got_type + left := branch.cond.left + got_type := (branch.cond.right as ast.Type).typ + if left is ast.SelectorExpr { + c.comptime_fields_type[left.expr.str()] = got_type + } else if left is ast.Type { + is_comptime_t_is_expr = true + left_type := c.unwrap_generic(left.typ) + if left_type != got_type { + should_skip = true + } } } } @@ -4255,7 +4262,7 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type { } if !c.skip_flags || c.pref.output_cross_c { c.stmts(branch.stmts) - } else { + } else if !is_comptime_t_is_expr { node.branches[i].stmts = [] } c.skip_flags = cur_skip_flags @@ -4386,7 +4393,9 @@ fn (mut c Checker) comp_if_branch(cond ast.Expr, pos token.Position) bool { return l && r // skip (return true) only if both should be skipped } .key_is, .not_is { - if cond.left is ast.SelectorExpr && cond.right is ast.Type { + if (cond.left is ast.SelectorExpr || + cond.left is ast.Type) && + cond.right is ast.Type { // $if method.@type is string } else { c.error('invalid `\$if` condition: $cond.left', cond.pos) diff --git a/vlib/v/gen/comptime.v b/vlib/v/gen/comptime.v index 944900e294..9786d3136d 100644 --- a/vlib/v/gen/comptime.v +++ b/vlib/v/gen/comptime.v @@ -231,10 +231,18 @@ fn (mut g Gen) comp_if_expr(cond ast.Expr) { g.comp_if_expr(cond.right) } .key_is, .not_is { - se := cond.left as ast.SelectorExpr - name := '${se.expr}.$se.field_name' - exp_type := g.comptime_var_type_map[name] + left := cond.left + mut name := '' + mut exp_type := table.Type(0) got_type := (cond.right as ast.Type).typ + if left is ast.SelectorExpr { + name = '${left.expr}.$left.field_name' + exp_type = g.comptime_var_type_map[name] + } else if left is ast.Type { + name = left.str() + // this is only allowed for generics currently, otherwise blocked by checker + exp_type = g.unwrap_generic(left.typ) + } g.write('$exp_type == $got_type') } .eq, .ne { diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index 09443df8c8..80538facd7 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -31,6 +31,10 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { node = p.sql_expr() p.inside_match = false } else { + if p.inside_if && p.tok.lit == 'T' { + // $if T is string {} + p.expecting_type = true + } node = p.name_expr() p.is_stmt_ident = is_stmt_ident } diff --git a/vlib/v/tests/comptime_if_expr_test.v b/vlib/v/tests/comptime_if_expr_test.v index 9961b09a38..c9fa4642ad 100644 --- a/vlib/v/tests/comptime_if_expr_test.v +++ b/vlib/v/tests/comptime_if_expr_test.v @@ -28,3 +28,40 @@ fn test_ct_expressions() { assert true } } + +fn generic_t_is() T { + $if T is string { + return 'It\'s a string!' + } $else { + return T{} + } + return T{} +} + +struct GenericTIsTest {} + +fn test_generic_t_is() { + assert generic_t_is() == 'It\'s a string!' + assert generic_t_is() == GenericTIsTest{} +} + +fn generic_t_is2() ?T { + $if T is string { + return 'It\'s a string!' + } $else { + return T{} + } + } + +fn test_generic_t_is2() { + res := generic_t_is2() or { + assert false + '' + } + res2 := generic_t_is2() or { + assert false + GenericTIsTest{} + } + assert res == 'It\'s a string!' + assert res2 == GenericTIsTest{} +}