[vala/wip/issue/894: 75/76] vala: Add support for type narrowing




commit 6d827062bd4d6cc00ad8c0f18492e8497e70dba3
Author: Rico Tzschichholz <ricotz ubuntu com>
Date:   Tue Aug 11 15:22:21 2020 +0200

    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                        |  1 +
 tests/objects/type-narrowing.vala        | 58 ++++++++++++++++++++++++++++++++
 vala/valamemberaccess.vala               |  4 +++
 vala/valasemanticanalyzer.vala           | 46 +++++++++++++++++++++++++
 5 files changed, 117 insertions(+)
---
diff --git a/codegen/valaccodememberaccessmodule.vala b/codegen/valaccodememberaccessmodule.vala
index f46ea9dfd..bf44ccb74 100644
--- a/codegen/valaccodememberaccessmodule.vala
+++ b/codegen/valaccodememberaccessmodule.vala
@@ -400,6 +400,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 c6865db09..9e22e1bf3 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -454,6 +454,7 @@ TESTS = \
        objects/signals-prototype-access-invalid-2.test \
        objects/signals-struct-return.vala \
        objects/singleton.vala \
+       objects/type-narrowing.vala \
        objects/test-025.vala \
        objects/test-026.vala \
        objects/test-029.vala \
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/valamemberaccess.vala b/vala/valamemberaccess.vala
index b1cc20ada..ffde65018 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 = SemanticAnalyzer.get_narrowed_type (this);
+                       }
                }
 
                if (value_type != null) {
diff --git a/vala/valasemanticanalyzer.vala b/vala/valasemanticanalyzer.vala
index 8242efd35..7fde2a23a 100644
--- a/vala/valasemanticanalyzer.vala
+++ b/vala/valasemanticanalyzer.vala
@@ -942,6 +942,52 @@ public class Vala.SemanticAnalyzer : CodeVisitor {
                return null;
        }
 
+       public static DataType get_narrowed_type (MemberAccess member_access) {
+               unowned Variable? variable = member_access.symbol_reference as Variable;
+               if (variable == null) {
+                       return member_access.value_type;
+               }
+
+               if (!(member_access.parent_node is MemberAccess)) {
+                       return member_access.value_type;
+               }
+
+               bool is_negation = false;
+               unowned CodeNode? parent = member_access.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) {
+                               var narrowed_type = type_check.type_reference.copy ();
+                               narrowed_type.value_owned = member_access.value_type.value_owned;
+                               if (variable == type_check.expression.symbol_reference
+                                   && narrowed_type.type_symbol != member_access.value_type.type_symbol) {
+                                       return narrowed_type;
+                               }
+                       }
+               }
+
+               return member_access.value_type;
+       }
+
        public static DataType get_actual_type (DataType? derived_instance_type, List<DataType>? 
method_type_arguments, GenericType generic_type, CodeNode? node_reference) {
                DataType actual_type = null;
                if (generic_type.type_parameter.parent_symbol is TypeSymbol) {


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