[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>
This commit is contained in:
Johnni Winther
2023-05-09 11:02:45 +00:00
committed by Commit Queue
parent 4af8469493
commit 9b42e2d166
16 changed files with 470 additions and 2 deletions
@@ -424,18 +424,25 @@ class DelayedAsExpression implements DelayedExpression {
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)) {
@@ -253,11 +253,13 @@ IsExpression createIsExpression(Expression operand, DartType type,
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].
@@ -10475,6 +10475,22 @@ class InferenceVisitorImpl extends InferenceVisitorBase
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);
@@ -908,6 +908,56 @@ class PromotedCacheableExpression implements CacheableExpression {
}
}
/// 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
@@ -475,6 +475,11 @@ class MatchingExpressionVisitor
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);
@@ -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';
}
@@ -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";
}
@@ -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";
}
@@ -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) {}
@@ -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() {}
@@ -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";
}
@@ -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";
}
@@ -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
;
@@ -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";
}
+5
View File
@@ -1088,6 +1088,11 @@ class NamedPattern extends Pattern {
/// 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.
///
+9
View File
@@ -4917,6 +4917,9 @@ class EquivalenceStrategy {
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 @@ class EquivalenceStrategy {
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');