]> git.ipfire.org Git - thirdparty/vala.git/commitdiff
vala: Add support for type narrowing
authorRico Tzschichholz <ricotz@ubuntu.com>
Wed, 11 Nov 2020 16:04:32 +0000 (17:04 +0100)
committerRico Tzschichholz <ricotz@ubuntu.com>
Wed, 11 Nov 2020 17:23:23 +0000 (18:23 +0100)
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 non-null 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
tests/Makefile.am
tests/nullability/member-access-narrowed-instance.vala [new file with mode: 0644]
tests/objects/type-narrowing-fallback.vala [new file with mode: 0644]
tests/objects/type-narrowing.vala [new file with mode: 0644]
vala/valadatatype.vala
vala/valamemberaccess.vala

index 6d455738b412d7e92f06d2658445d3c588bafbf0..1f2231c55f06e669ca74bdc4aa9fdc093570e12a 100644 (file)
@@ -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 */
index 0c5f059e6959aca4eb20c9ca4dc5e5f0b397f8be..50816d39c393788da874a2316f974e71708072b7 100644 (file)
@@ -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 \
@@ -1039,6 +1041,7 @@ TESTS = \
        semantic/yield-statement-requires-async-context.test \
        nullability/bug611223.vala \
        nullability/local-variable-invalid-convert.test \
+       nullability/member-access-narrowed-instance.vala \
        nullability/member-access-nullable-instance.test \
        nullability/method-parameter-invalid-convert.test \
        nullability/method-return-invalid-convert.test \
diff --git a/tests/nullability/member-access-narrowed-instance.vala b/tests/nullability/member-access-narrowed-instance.vala
new file mode 100644 (file)
index 0000000..f83ff5d
--- /dev/null
@@ -0,0 +1,13 @@
+class Foo {
+       public int i;
+}
+
+void main () {
+       Foo? foo = new Foo ();
+       if (foo is Foo) {
+               foo.i = 42;
+               assert (foo.i == 42);
+       } else {
+               assert_not_reached ();
+       }
+}
diff --git a/tests/objects/type-narrowing-fallback.vala b/tests/objects/type-narrowing-fallback.vala
new file mode 100644 (file)
index 0000000..17c9a23
--- /dev/null
@@ -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 (file)
index 0000000..47997d1
--- /dev/null
@@ -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");
+       }
+}
index ada2c1dd4bdde3ed59742eaa0c28d7776c2acfe3..04ba297006284ae2c63d03e79c988a53a648e60e 100644 (file)
@@ -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) {
index 54caa3a2327cbed3609dd97768d8f988e01e3a1e..06cf64ce4a430e0eb8909e9eb45d93ca46f9362b 100644 (file)
@@ -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 && context.profile == Profile.GOBJECT) {
+                               check_narrowed_value_type ();
+                       }
                }
 
                if (value_type != null) {
@@ -1112,4 +1116,49 @@ public class Vala.MemberAccess : Expression {
 
                return found;
        }
+
+       void check_narrowed_value_type () {
+               unowned Variable? variable = symbol_reference as Variable;
+               if (variable == null) {
+                       return;
+               }
+
+               if (!(parent_node is MemberAccess)) {
+                       return;
+               }
+
+               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) {
+                                       if (narrowed_symnol != value_type.type_symbol) {
+                                               value_type.context_symbol = narrowed_symnol;
+                                       }
+                                       value_type.nullable = false;
+                               }
+                       }
+               }
+       }
 }