[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:
committed by
Commit Queue
parent
4af8469493
commit
9b42e2d166
@@ -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";
|
||||
}
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user