[cfe] Generate covariant checks in pattern matching Closes #52192 Change-Id: Iaac816273fb80eaf166fc300b2b3367f1a592d3f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/302223 Commit-Queue: Johnni Winther <johnniwinther@google.com> Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
diff --git a/pkg/front_end/lib/src/fasta/type_inference/delayed_expressions.dart b/pkg/front_end/lib/src/fasta/type_inference/delayed_expressions.dart index 050a5b4..72a4a8b 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/delayed_expressions.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/delayed_expressions.dart
@@ -424,18 +424,25 @@ final DartType _type; final bool isUnchecked; final bool isImplicit; + final bool isCovarianceCheck; final int fileOffset; DelayedAsExpression(this._operand, this._type, {this.isUnchecked = false, this.isImplicit = false, + this.isCovarianceCheck = false, required this.fileOffset}); @override Expression createExpression(TypeEnvironment typeEnvironment, [List<Expression>? effects]) { Expression operand = _operand.createExpression(typeEnvironment, effects); - if (isImplicit) { + if (isCovarianceCheck) { + return createAsExpression(operand, _type, + forNonNullableByDefault: true, + isCovarianceCheck: true, + fileOffset: fileOffset); + } else if (isImplicit) { DartType operandType = _operand.getType(typeEnvironment); if (typeEnvironment.isSubtypeOf( operandType, _type, SubtypeCheckMode.withNullabilities)) {
diff --git a/pkg/front_end/lib/src/fasta/type_inference/external_ast_helper.dart b/pkg/front_end/lib/src/fasta/type_inference/external_ast_helper.dart index 6b19814..f0a77cc 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/external_ast_helper.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/external_ast_helper.dart
@@ -253,11 +253,13 @@ AsExpression createAsExpression(Expression operand, DartType type, {required bool forNonNullableByDefault, bool isUnchecked = false, + bool isCovarianceCheck = false, required int fileOffset}) { return new AsExpression(operand, type) ..fileOffset = fileOffset ..isForNonNullableByDefault = forNonNullableByDefault - ..isUnchecked = isUnchecked; + ..isUnchecked = isUnchecked + ..isCovarianceCheck = isCovarianceCheck; } /// Creates a [NullCheck] of [expression].
diff --git a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart index 218d626..a6f8e26 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart
@@ -10475,6 +10475,22 @@ field.accessKind = ObjectAccessKind.Dynamic; break; } + if (fieldTarget.isInstanceMember || fieldTarget.isObjectMember) { + Member interfaceMember = fieldTarget.member!; + if (interfaceMember is Procedure) { + DartType typeToCheck = isNonNullableByDefault + ? interfaceMember.function + .computeFunctionType(libraryBuilder.nonNullable) + : interfaceMember.function.returnType; + field.checkReturn = + InferenceVisitorBase.returnedTypeParametersOccurNonCovariantly( + interfaceMember.enclosingClass!, typeToCheck); + } else if (interfaceMember is Field) { + field.checkReturn = + InferenceVisitorBase.returnedTypeParametersOccurNonCovariantly( + interfaceMember.enclosingClass!, interfaceMember.type); + } + } } pushRewrite(replacement ?? node);
diff --git a/pkg/front_end/lib/src/fasta/type_inference/matching_cache.dart b/pkg/front_end/lib/src/fasta/type_inference/matching_cache.dart index f5db434..bf2b062 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/matching_cache.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/matching_cache.dart
@@ -908,6 +908,56 @@ } } +/// A cacheable expression that performs a covariant check on the resulting +/// value. +class CovariantCheckCacheableExpression implements CacheableExpression { + final CacheableExpression _expression; + + final DartType _checkedType; + + final int fileOffset; + + CovariantCheckCacheableExpression(this._expression, this._checkedType, + {required this.fileOffset}); + + @override + CacheKey get cacheKey => _expression.cacheKey; + + @override + AccessKey get accessKey => _expression.accessKey; + + @override + Expression createExpression(TypeEnvironment typeEnvironment, + [List<Expression>? effects]) { + Expression result = _expression.createExpression(typeEnvironment, effects); + return createAsExpression(result, _checkedType, + forNonNullableByDefault: true, + fileOffset: fileOffset, + isCovarianceCheck: true); + } + + @override + DartType getType(TypeEnvironment typeEnvironment) { + return _checkedType; + } + + @override + void registerUse() { + _expression.registerUse(); + } + + @override + bool uses(DelayedExpression expression) { + return identical(this, expression) || _expression.uses(expression); + } + + @override + CacheableExpression promote(DartType type) { + if (type == _checkedType) return this; + return new PromotedCacheableExpression(_expression, type); + } +} + /// A [CacheableExpression] created using a potentially shared [Cache]. class CacheExpression implements CacheableExpression { @override
diff --git a/pkg/front_end/lib/src/fasta/type_inference/matching_expressions.dart b/pkg/front_end/lib/src/fasta/type_inference/matching_expressions.dart index 39644ff..1c481ff 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/matching_expressions.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/matching_expressions.dart
@@ -475,6 +475,11 @@ staticTarget: staticTarget, typeArguments: typeArguments, fileOffset: field.fileOffset); + if (field.checkReturn) { + objectExpression = new CovariantCheckCacheableExpression( + objectExpression, field.resultType!, + fileOffset: field.fileOffset); + } DelayedExpression subExpression = visitPattern(field.pattern, objectExpression);
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart b/pkg/front_end/testcases/patterns/issue52192.dart new file mode 100644 index 0000000..26dbdf13 --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue52192.dart
@@ -0,0 +1,44 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +int callCount = 0; + +abstract class A<X> { + void Function(X) get g; +} + +class B implements A<int> { + void Function(int) get g => (int i) => callCount++; +} + +void foo(Object o, num value) { + switch (o) { + case B(g: _) && A<num>(g: var f): + f(value); + } +} + +void main() { + expect(0, callCount); + throws(() => foo(B(), 25.7)); + expect(0, callCount); + throws(() => foo(B(), 1)); + expect(0, callCount); +} + +expect(expected, actual) { + if (expected != actual) { + throw 'Expected $expected, actual $actual'; + } +} + +throws(void Function() f) { + try { + f(); + } catch (e) { + print(e); + return; + } + throw 'No exception thrown'; +}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.strong.expect b/pkg/front_end/testcases/patterns/issue52192.dart.strong.expect new file mode 100644 index 0000000..2c1771b --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue52192.dart.strong.expect
@@ -0,0 +1,55 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class A<X extends core::Object? = dynamic> extends core::Object { + synthetic constructor •() → self::A<self::A::X%> + : super core::Object::•() + ; + abstract get g() → (self::A::X%) → void; +} +class B extends core::Object implements self::A<core::int> { + synthetic constructor •() → self::B + : super core::Object::•() + ; + get g() → (core::int) → void + return (core::int i) → void => let final core::int #t1 = self::callCount in let final core::int #t2 = self::callCount = #t1.{core::num::+}(1){(core::num) → core::int} in #t1; +} +static field core::int callCount = 0; +static method foo(core::Object o, core::num value) → void { + #L1: + { + final synthesized core::Object #0#0 = o; + late final synthesized (core::int) → void #0#2 = #0#0{self::B}.{self::B::g}{(core::int) → void}; + { + hoisted (core::num) → void f; + if(#0#0 is{ForNonNullableByDefault} self::B && (let final dynamic #t3 = #0#2 in true) && (let final dynamic #t4 = f = #0#2 as{CovarianceCheck,ForNonNullableByDefault} (core::num) → void in true)) { + { + f(value){(core::num) → void}; + } + } + } + } +} +static method main() → void { + self::expect(0, self::callCount); + self::throws(() → void => self::foo(new self::B::•(), 25.7)); + self::expect(0, self::callCount); + self::throws(() → void => self::foo(new self::B::•(), 1)); + self::expect(0, self::callCount); +} +static method expect(dynamic expected, dynamic actual) → dynamic { + if(!(expected =={core::Object::==}{(core::Object) → core::bool} actual)) { + throw "Expected ${expected}, actual ${actual}"; + } +} +static method throws(() → void f) → dynamic { + try { + f(){() → void}; + } + on core::Object catch(final core::Object e) { + core::print(e); + return; + } + throw "No exception thrown"; +}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.strong.transformed.expect b/pkg/front_end/testcases/patterns/issue52192.dart.strong.transformed.expect new file mode 100644 index 0000000..42ab55a --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue52192.dart.strong.transformed.expect
@@ -0,0 +1,57 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class A<X extends core::Object? = dynamic> extends core::Object { + synthetic constructor •() → self::A<self::A::X%> + : super core::Object::•() + ; + abstract get g() → (self::A::X%) → void; +} +class B extends core::Object implements self::A<core::int> { + synthetic constructor •() → self::B + : super core::Object::•() + ; + get g() → (core::int) → void + return (core::int i) → void => let final core::int #t1 = self::callCount in let final core::int #t2 = self::callCount = #t1.{core::num::+}(1){(core::num) → core::int} in #t1; +} +static field core::int callCount = 0; +static method foo(core::Object o, core::num value) → void { + #L1: + { + final synthesized core::Object #0#0 = o; + function ##0#2#initializer() → (core::int) → void + return #0#0{self::B}.{self::B::g}{(core::int) → void}; + late final synthesized (core::int) → void #0#2 = ##0#2#initializer(){() → (core::int) → void}; + { + hoisted (core::num) → void f; + if(#0#0 is{ForNonNullableByDefault} self::B && (let final (core::int) → void #t3 = #0#2 in true) && (let final (core::num) → void #t4 = f = #0#2 as{CovarianceCheck,ForNonNullableByDefault} (core::num) → void in true)) { + { + f(value){(core::num) → void}; + } + } + } + } +} +static method main() → void { + self::expect(0, self::callCount); + self::throws(() → void => self::foo(new self::B::•(), 25.7)); + self::expect(0, self::callCount); + self::throws(() → void => self::foo(new self::B::•(), 1)); + self::expect(0, self::callCount); +} +static method expect(dynamic expected, dynamic actual) → dynamic { + if(!(expected =={core::Object::==}{(core::Object) → core::bool} actual)) { + throw "Expected ${expected}, actual ${actual}"; + } +} +static method throws(() → void f) → dynamic { + try { + f(){() → void}; + } + on core::Object catch(final core::Object e) { + core::print(e); + return; + } + throw "No exception thrown"; +}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline.expect b/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline.expect new file mode 100644 index 0000000..213bc96 --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline.expect
@@ -0,0 +1,14 @@ +int callCount = 0; + +abstract class A<X> { + void Function(X) get g; +} + +class B implements A<int> { + void Function(int) get g => (int i) => callCount++; +} + +void foo(Object o, num value) {} +void main() {} +expect(expected, actual) {} +throws(void Function() f) {}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline_modelled.expect new file mode 100644 index 0000000..aa84847 --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline_modelled.expect
@@ -0,0 +1,13 @@ +abstract class A<X> { + void Function(X) get g; +} + +class B implements A<int> { + void Function(int) get g => (int i) => callCount++; +} + +expect(expected, actual) {} +int callCount = 0; +throws(void Function() f) {} +void foo(Object o, num value) {} +void main() {}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.weak.expect b/pkg/front_end/testcases/patterns/issue52192.dart.weak.expect new file mode 100644 index 0000000..2c1771b --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue52192.dart.weak.expect
@@ -0,0 +1,55 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class A<X extends core::Object? = dynamic> extends core::Object { + synthetic constructor •() → self::A<self::A::X%> + : super core::Object::•() + ; + abstract get g() → (self::A::X%) → void; +} +class B extends core::Object implements self::A<core::int> { + synthetic constructor •() → self::B + : super core::Object::•() + ; + get g() → (core::int) → void + return (core::int i) → void => let final core::int #t1 = self::callCount in let final core::int #t2 = self::callCount = #t1.{core::num::+}(1){(core::num) → core::int} in #t1; +} +static field core::int callCount = 0; +static method foo(core::Object o, core::num value) → void { + #L1: + { + final synthesized core::Object #0#0 = o; + late final synthesized (core::int) → void #0#2 = #0#0{self::B}.{self::B::g}{(core::int) → void}; + { + hoisted (core::num) → void f; + if(#0#0 is{ForNonNullableByDefault} self::B && (let final dynamic #t3 = #0#2 in true) && (let final dynamic #t4 = f = #0#2 as{CovarianceCheck,ForNonNullableByDefault} (core::num) → void in true)) { + { + f(value){(core::num) → void}; + } + } + } + } +} +static method main() → void { + self::expect(0, self::callCount); + self::throws(() → void => self::foo(new self::B::•(), 25.7)); + self::expect(0, self::callCount); + self::throws(() → void => self::foo(new self::B::•(), 1)); + self::expect(0, self::callCount); +} +static method expect(dynamic expected, dynamic actual) → dynamic { + if(!(expected =={core::Object::==}{(core::Object) → core::bool} actual)) { + throw "Expected ${expected}, actual ${actual}"; + } +} +static method throws(() → void f) → dynamic { + try { + f(){() → void}; + } + on core::Object catch(final core::Object e) { + core::print(e); + return; + } + throw "No exception thrown"; +}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.weak.modular.expect b/pkg/front_end/testcases/patterns/issue52192.dart.weak.modular.expect new file mode 100644 index 0000000..2c1771b --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue52192.dart.weak.modular.expect
@@ -0,0 +1,55 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class A<X extends core::Object? = dynamic> extends core::Object { + synthetic constructor •() → self::A<self::A::X%> + : super core::Object::•() + ; + abstract get g() → (self::A::X%) → void; +} +class B extends core::Object implements self::A<core::int> { + synthetic constructor •() → self::B + : super core::Object::•() + ; + get g() → (core::int) → void + return (core::int i) → void => let final core::int #t1 = self::callCount in let final core::int #t2 = self::callCount = #t1.{core::num::+}(1){(core::num) → core::int} in #t1; +} +static field core::int callCount = 0; +static method foo(core::Object o, core::num value) → void { + #L1: + { + final synthesized core::Object #0#0 = o; + late final synthesized (core::int) → void #0#2 = #0#0{self::B}.{self::B::g}{(core::int) → void}; + { + hoisted (core::num) → void f; + if(#0#0 is{ForNonNullableByDefault} self::B && (let final dynamic #t3 = #0#2 in true) && (let final dynamic #t4 = f = #0#2 as{CovarianceCheck,ForNonNullableByDefault} (core::num) → void in true)) { + { + f(value){(core::num) → void}; + } + } + } + } +} +static method main() → void { + self::expect(0, self::callCount); + self::throws(() → void => self::foo(new self::B::•(), 25.7)); + self::expect(0, self::callCount); + self::throws(() → void => self::foo(new self::B::•(), 1)); + self::expect(0, self::callCount); +} +static method expect(dynamic expected, dynamic actual) → dynamic { + if(!(expected =={core::Object::==}{(core::Object) → core::bool} actual)) { + throw "Expected ${expected}, actual ${actual}"; + } +} +static method throws(() → void f) → dynamic { + try { + f(){() → void}; + } + on core::Object catch(final core::Object e) { + core::print(e); + return; + } + throw "No exception thrown"; +}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.weak.outline.expect b/pkg/front_end/testcases/patterns/issue52192.dart.weak.outline.expect new file mode 100644 index 0000000..877ce08 --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue52192.dart.weak.outline.expect
@@ -0,0 +1,24 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class A<X extends core::Object? = dynamic> extends core::Object { + synthetic constructor •() → self::A<self::A::X%> + ; + abstract get g() → (self::A::X%) → void; +} +class B extends core::Object implements self::A<core::int> { + synthetic constructor •() → self::B + ; + get g() → (core::int) → void + ; +} +static field core::int callCount; +static method foo(core::Object o, core::num value) → void + ; +static method main() → void + ; +static method expect(dynamic expected, dynamic actual) → dynamic + ; +static method throws(() → void f) → dynamic + ;
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.weak.transformed.expect b/pkg/front_end/testcases/patterns/issue52192.dart.weak.transformed.expect new file mode 100644 index 0000000..42ab55a --- /dev/null +++ b/pkg/front_end/testcases/patterns/issue52192.dart.weak.transformed.expect
@@ -0,0 +1,57 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class A<X extends core::Object? = dynamic> extends core::Object { + synthetic constructor •() → self::A<self::A::X%> + : super core::Object::•() + ; + abstract get g() → (self::A::X%) → void; +} +class B extends core::Object implements self::A<core::int> { + synthetic constructor •() → self::B + : super core::Object::•() + ; + get g() → (core::int) → void + return (core::int i) → void => let final core::int #t1 = self::callCount in let final core::int #t2 = self::callCount = #t1.{core::num::+}(1){(core::num) → core::int} in #t1; +} +static field core::int callCount = 0; +static method foo(core::Object o, core::num value) → void { + #L1: + { + final synthesized core::Object #0#0 = o; + function ##0#2#initializer() → (core::int) → void + return #0#0{self::B}.{self::B::g}{(core::int) → void}; + late final synthesized (core::int) → void #0#2 = ##0#2#initializer(){() → (core::int) → void}; + { + hoisted (core::num) → void f; + if(#0#0 is{ForNonNullableByDefault} self::B && (let final (core::int) → void #t3 = #0#2 in true) && (let final (core::num) → void #t4 = f = #0#2 as{CovarianceCheck,ForNonNullableByDefault} (core::num) → void in true)) { + { + f(value){(core::num) → void}; + } + } + } + } +} +static method main() → void { + self::expect(0, self::callCount); + self::throws(() → void => self::foo(new self::B::•(), 25.7)); + self::expect(0, self::callCount); + self::throws(() → void => self::foo(new self::B::•(), 1)); + self::expect(0, self::callCount); +} +static method expect(dynamic expected, dynamic actual) → dynamic { + if(!(expected =={core::Object::==}{(core::Object) → core::bool} actual)) { + throw "Expected ${expected}, actual ${actual}"; + } +} +static method throws(() → void f) → dynamic { + try { + f(){() → void}; + } + on core::Object catch(final core::Object e) { + core::print(e); + return; + } + throw "No exception thrown"; +}
diff --git a/pkg/kernel/lib/src/ast/patterns.dart b/pkg/kernel/lib/src/ast/patterns.dart index cf1fc07..a5ea7c8 100644 --- a/pkg/kernel/lib/src/ast/patterns.dart +++ b/pkg/kernel/lib/src/ast/patterns.dart
@@ -1088,6 +1088,11 @@ /// This is set during inference. DartType? resultType; + /// When used in an object pattern, this is set to `true` if the field value + /// needs to be checked against the [resultType]. This is needed for fields + /// whose type contain covariant types that occur in non-covariant positions. + bool checkReturn = false; + /// When used in an object pattern, this holds the record on which the /// property for this pattern is read. ///
diff --git a/pkg/kernel/lib/src/equivalence.dart b/pkg/kernel/lib/src/equivalence.dart index 2e193f2..9e2d3d1 100644 --- a/pkg/kernel/lib/src/equivalence.dart +++ b/pkg/kernel/lib/src/equivalence.dart
@@ -4917,6 +4917,9 @@ if (!checkNamedPattern_resultType(visitor, node, other)) { result = visitor.resultOnInequivalence; } + if (!checkNamedPattern_checkReturn(visitor, node, other)) { + result = visitor.resultOnInequivalence; + } if (!checkNamedPattern_recordType(visitor, node, other)) { result = visitor.resultOnInequivalence; } @@ -9149,6 +9152,12 @@ return visitor.checkNodes(node.resultType, other.resultType, 'resultType'); } + bool checkNamedPattern_checkReturn( + EquivalenceVisitor visitor, NamedPattern node, NamedPattern other) { + return visitor.checkValues( + node.checkReturn, other.checkReturn, 'checkReturn'); + } + bool checkNamedPattern_recordType( EquivalenceVisitor visitor, NamedPattern node, NamedPattern other) { return visitor.checkNodes(node.recordType, other.recordType, 'recordType');