[vala/wip/issue/894: 1/3] vala: Add support for type narrowing




commit 59bdca8dcaaef16b441ed14f341a746d74bab1a3
Author: Rico Tzschichholz <ricotz ubuntu com>
Date:   Wed Nov 11 17:04:32 2020 +0100

    vala: Add support for type narrowing
    
    This causes type of given variable to be narrowed for the correspoding
    child block of an if-statement.
    
    Foo foo = ...;
    if (foo is Bar) {
        // foo represents a Bar instance inside this block
    }
    
    This makes conditional-expressions behaving similar.
    
    ... = (foo is Bar) ? "foo is instance of Bar here" : "...";
    
    Fixes https://gitlab.gnome.org/GNOME/vala/issues/894

 codegen/valaccodememberaccessmodule.vala   |  8 +++++
 tests/Makefile.am                          |  2 ++
 tests/objects/type-narrowing-fallback.vala | 25 +++++++++++++
 tests/objects/type-narrowing.vala          | 58 ++++++++++++++++++++++++++++++
 vala/valadatatype.vala                     | 15 ++++++--
 vala/valamemberaccess.vala                 | 49 +++++++++++++++++++++++++
 6 files changed, 154 insertions(+), 3 deletions(-)
---
diff --git a/codegen/valaccodememberaccessmodule.vala b/codegen/valaccodememberaccessmodule.vala
index 6d455738b..1f2231c55 100644
--- a/codegen/valaccodememberaccessmodule.vala
+++ b/codegen/valaccodememberaccessmodule.vala
@@ -404,6 +404,14 @@ public abstract class Vala.CCodeMemberAccessModule : CCodeControlFlowModule {
                                expr.target_value = load_parameter (param, expr);
                        }
                }
+
+               // Add cast for narrowed type access of variables if needed
+               if (expr.symbol_reference is Variable) {
+                       unowned GLibValue cvalue = (GLibValue) expr.target_value;
+                       if (cvalue.value_type.type_symbol != expr.value_type.type_symbol) {
+                               cvalue.cvalue = new CCodeCastExpression (cvalue.cvalue, get_ccode_name 
(expr.value_type));
+                       }
+               }
        }
 
        /* Returns lvalue access to the given local variable */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0c5f059e6..558766116 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -475,6 +475,8 @@ TESTS = \
        objects/signals-prototype-access-invalid-2.test \
        objects/signals-struct-return.vala \
        objects/singleton.vala \
+       objects/type-narrowing.vala \
+       objects/type-narrowing-fallback.vala \
        objects/test-025.vala \
        objects/test-026.vala \
        objects/test-029.vala \
diff --git a/tests/objects/type-narrowing-fallback.vala b/tests/objects/type-narrowing-fallback.vala
new file mode 100644
index 000000000..17c9a23fd
--- /dev/null
+++ b/tests/objects/type-narrowing-fallback.vala
@@ -0,0 +1,25 @@
+interface Foo : Object {
+       public abstract int get_foo ();
+}
+
+class Bar : Object {
+       public int get_bar () {
+               return 23;
+       }
+}
+
+class Manam : Bar, Foo {
+       public virtual int get_foo () {
+               return 42;
+       }
+}
+
+void main () {
+       Foo foo = new Manam ();
+       if (foo is Bar) {
+               assert (foo.get_bar () == 23);
+               assert (foo.get_foo () == 42);
+       } else {
+               assert_not_reached ();
+       }
+}
diff --git a/tests/objects/type-narrowing.vala b/tests/objects/type-narrowing.vala
new file mode 100644
index 000000000..47997d141
--- /dev/null
+++ b/tests/objects/type-narrowing.vala
@@ -0,0 +1,58 @@
+class Foo {
+       public void manam () {
+               if (this is Bar) {
+                       assert (this.str == "bar");
+               }
+               assert (((this is Bar) ? this.str : "foo") == "bar");
+
+               if (!(this is Bar)) {
+                       assert_not_reached ();
+               } else {
+                       assert (this.str == "bar");
+               }
+               assert ((!(this is Bar) ? "foo" : this.str) == "bar");
+       }
+}
+
+class Bar : Foo {
+       public string str;
+       public Bar (string s) {
+               str = s;
+       }
+}
+
+class Manam : Bar {
+       public Manam (string s) {
+               base (s);
+       }
+}
+
+void manam (Foo foo) {
+       if (foo is Bar) {
+               assert (foo.str == "bar");
+       }
+       assert (((foo is Bar) ? foo.str : "foo") == "bar");
+
+       if (!(foo is Bar)) {
+               assert_not_reached ();
+       } else {
+               assert (foo.str == "bar");
+       }
+       assert ((!(foo is Bar) ? "foo" : foo.str) == "bar");
+}
+
+void main() {
+       {
+               var bar = new Bar ("bar");
+               bar.manam ();
+               manam (bar);
+       }
+       {
+               Bar bar = new Manam ("manam");
+               if (bar is Manam) {
+                       assert (bar.str == "manam");
+                       bar = new Bar ("bar");
+               }
+               assert (bar.str == "bar");
+       }
+}
diff --git a/vala/valadatatype.vala b/vala/valadatatype.vala
index ada2c1dd4..04ba29700 100644
--- a/vala/valadatatype.vala
+++ b/vala/valadatatype.vala
@@ -44,6 +44,11 @@ public abstract class Vala.DataType : CodeNode {
         */
        public weak Symbol? symbol { get; private set; }
 
+       /**
+        * The referred symbol in the current context.
+        */
+       public weak Symbol? context_symbol { get; set; }
+
        /**
         * The referred type symbol.
         */
@@ -428,10 +433,14 @@ public abstract class Vala.DataType : CodeNode {
        }
 
        public virtual Symbol? get_member (string member_name) {
-               if (type_symbol != null) {
-                       return SemanticAnalyzer.symbol_lookup_inherited (type_symbol, member_name);
+               Symbol? member = null;
+               if (context_symbol != null) {
+                       member = SemanticAnalyzer.symbol_lookup_inherited (context_symbol, member_name);
                }
-               return null;
+               if (member == null && type_symbol != null) {
+                       member = SemanticAnalyzer.symbol_lookup_inherited (type_symbol, member_name);
+               }
+               return member;
        }
 
        public virtual Symbol? get_pointer_member (string member_name) {
diff --git a/vala/valamemberaccess.vala b/vala/valamemberaccess.vala
index 54caa3a23..fb2a83f06 100644
--- a/vala/valamemberaccess.vala
+++ b/vala/valamemberaccess.vala
@@ -986,6 +986,10 @@ public class Vala.MemberAccess : Expression {
                                var parent_type = SemanticAnalyzer.get_data_type_for_symbol 
(symbol_reference.parent_symbol);
                                inner.target_type = parent_type.get_actual_type (inner.value_type, null, 
this);
                        }
+
+                       if (inner == null && value_type != null) {
+                               value_type.context_symbol = get_narrowed_symbol ();
+                       }
                }
 
                if (value_type != null) {
@@ -1112,4 +1116,49 @@ public class Vala.MemberAccess : Expression {
 
                return found;
        }
+
+       unowned Symbol? get_narrowed_symbol () {
+               unowned Variable? variable = symbol_reference as Variable;
+               if (variable == null) {
+                       return null;
+               }
+
+               if (!(parent_node is MemberAccess)) {
+                       return null;
+               }
+
+               bool is_negation = false;
+               unowned CodeNode? parent = parent_node;
+               unowned IfStatement? if_statement = null;
+               while (parent != null && !(parent is Method)) {
+                       if (parent is TypeCheck) {
+                               parent = null;
+                               break;
+                       }
+                       if (parent.parent_node is IfStatement) {
+                               if_statement = (IfStatement) parent.parent_node;
+                               is_negation = if_statement.false_statement == parent;
+                               break;
+                       }
+                       parent = parent.parent_node;
+               }
+
+               if (if_statement != null) {
+                       unowned Expression expr = if_statement.condition;
+                       if (expr is UnaryExpression && ((UnaryExpression) expr).operator == 
UnaryOperator.LOGICAL_NEGATION) {
+                               expr = ((UnaryExpression) expr).inner;
+                               is_negation = !is_negation;
+                       }
+                       unowned TypeCheck? type_check = expr as TypeCheck;
+                       if (!is_negation && type_check != null) {
+                               unowned TypeSymbol? narrowed_symnol = type_check.type_reference.type_symbol;
+                               if (variable == type_check.expression.symbol_reference
+                                   && narrowed_symnol != value_type.type_symbol) {
+                                       return narrowed_symnol;
+                               }
+                       }
+               }
+
+               return null;
+       }
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]