From: Rico Tzschichholz Date: Wed, 11 Nov 2020 16:04:32 +0000 (+0100) Subject: vala: Add support for type narrowing X-Git-Tag: 0.51.1~170 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7ae2f115a702439bd94bf09867b38019f39d010f;p=thirdparty%2Fvala.git 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 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 --- 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..50816d39c 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 \ @@ -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 index 000000000..f83ff5dfd --- /dev/null +++ b/tests/nullability/member-access-narrowed-instance.vala @@ -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 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..06cf64ce4 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 && 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; + } + } + } + } }