mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 16:59:47 +00:00
Fix handling of extension types in relational patterns.
In https://dart-review.googlesource.com/c/sdk/+/345082, type erasure was added to the handling of relational patterns, to address some co19 failures, e.g.: extension type const BoolET1(bool _) {} const True1 = BoolET1(true); String testStatement1(bool b) { switch (b) { case == True1: ... } } This was failing because the type of `True1` (`BoolET1`) is not assignable to the argument type of `operator==`, which is `Object`. (This is because extension types do not, by default, extend `Object`; they extend `Object?`). Adding type erasure elimited the co19 failure, but it caused other code to be allowed that shouldn't be allowed, such as: extension type const E(int representation) implements Object {} class A { bool operator <(int other) => ...; } const E0 = E(0); test(A a) { if (a case < E0) ...; } This shouldn't be allowed because the type expected by `A.<` is `int`; allowing `E0` to be passed to this operator breaks extension type encapsulation. The correct fix is for assignability checks for `operator==` to use `S?` rather than `S`, where `S` is the argument type of `operator==`. This is consistent with the patterns specification, and it ensures that `== null` and `!= null` are allowed, while continuing to prohibit relational patterns that break extension type encapsulation. Fixes https://github.com/dart-lang/sdk/issues/54594. Bug: https://github.com/dart-lang/sdk/issues/54594. Change-Id: Id090f432500d75ba694383f1788d58353cd1fc72 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/345860 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
parent
e49f1ccbbe
commit
973eca72f5
|
@ -1630,15 +1630,15 @@ mixin TypeAnalyzer<
|
|||
Error? argumentTypeNotAssignableError;
|
||||
Error? operatorReturnTypeNotAssignableToBoolError;
|
||||
if (operator != null) {
|
||||
Type extensionTypeErasure = operations.extensionTypeErasure(operandType);
|
||||
Type argumentType = isEquality
|
||||
? operations.promoteToNonNull(extensionTypeErasure)
|
||||
: extensionTypeErasure;
|
||||
if (!operations.isAssignableTo(argumentType, operator.parameterType)) {
|
||||
Type parameterType = operator.parameterType;
|
||||
if (isEquality) {
|
||||
parameterType = operations.makeNullable(parameterType);
|
||||
}
|
||||
if (!operations.isAssignableTo(operandType, parameterType)) {
|
||||
argumentTypeNotAssignableError =
|
||||
errors.relationalPatternOperandTypeNotAssignable(
|
||||
pattern: node,
|
||||
operandType: argumentType,
|
||||
operandType: operandType,
|
||||
parameterType: operator.parameterType,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1619,6 +1619,8 @@ class GuardedPattern extends Node with PossiblyGuardedPattern {
|
|||
|
||||
class Harness {
|
||||
static Map<String, Type> _coreMemberTypes = {
|
||||
'int.<': Type('bool Function(num)'),
|
||||
'int.<=': Type('bool Function(num)'),
|
||||
'int.>': Type('bool Function(num)'),
|
||||
'int.>=': Type('bool Function(num)'),
|
||||
'num.sign': Type('num'),
|
||||
|
@ -1684,6 +1686,12 @@ class Harness {
|
|||
operations.addExhaustiveness(type, isExhaustive);
|
||||
}
|
||||
|
||||
/// Updates the harness so that when an extension type erasure query is
|
||||
/// invoked on type [type], [representation] will be returned.
|
||||
void addExtensionTypeErasure(String type, String representation) {
|
||||
operations.addExtensionTypeErasure(type, representation);
|
||||
}
|
||||
|
||||
/// Updates the harness so that when member [memberName] is looked up on type
|
||||
/// [targetType], a member is found having the given [type].
|
||||
///
|
||||
|
@ -2617,6 +2625,7 @@ class MiniAstOperations implements TypeAnalyzerOperations<Var, Type> {
|
|||
'int, num': Type('num'),
|
||||
'Never, int': Type('int'),
|
||||
'Null, int': Type('int?'),
|
||||
'Null, Object': Type('Object?'),
|
||||
'?, int': Type('int'),
|
||||
'?, List<?>': Type('List<?>'),
|
||||
'?, Null': Type('Null'),
|
||||
|
@ -2682,6 +2691,8 @@ class MiniAstOperations implements TypeAnalyzerOperations<Var, Type> {
|
|||
|
||||
final Map<String, bool> _exhaustiveness = Map.of(_coreExhaustiveness);
|
||||
|
||||
final Map<String, Type> _extensionTypeErasure = {};
|
||||
|
||||
final Map<String, Type> _glbs = Map.of(_coreGlbs);
|
||||
|
||||
final Map<String, Type> _lubs = Map.of(_coreLubs);
|
||||
|
@ -2726,6 +2737,12 @@ class MiniAstOperations implements TypeAnalyzerOperations<Var, Type> {
|
|||
_exhaustiveness[type] = isExhaustive;
|
||||
}
|
||||
|
||||
/// Updates the harness so that when an extension type erasure query is
|
||||
/// invoked on type [type], [representation] will be returned.
|
||||
void addExtensionTypeErasure(String type, String representation) {
|
||||
_extensionTypeErasure[type] = Type(representation);
|
||||
}
|
||||
|
||||
void addPromotionException(String from, String to, String result) {
|
||||
(_promotionExceptions[from] ??= {})[to] = result;
|
||||
}
|
||||
|
@ -2783,6 +2800,12 @@ class MiniAstOperations implements TypeAnalyzerOperations<Var, Type> {
|
|||
fail('Unknown downward inference query: $query');
|
||||
}
|
||||
|
||||
@override
|
||||
Type extensionTypeErasure(Type type) {
|
||||
var query = '$type';
|
||||
return _extensionTypeErasure[query] ?? type;
|
||||
}
|
||||
|
||||
@override
|
||||
Type factor(Type from, Type what) {
|
||||
return _typeSystem.factor(from, what);
|
||||
|
@ -2944,11 +2967,6 @@ class MiniAstOperations implements TypeAnalyzerOperations<Var, Type> {
|
|||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Type extensionTypeErasure(Type type) {
|
||||
return type;
|
||||
}
|
||||
|
||||
@override
|
||||
Type streamType(Type elementType) {
|
||||
return PrimaryType('Stream', args: [elementType]);
|
||||
|
|
|
@ -3587,19 +3587,129 @@ main() {
|
|||
'matchedType: Object), variables(), true, block(), noop)')
|
||||
]);
|
||||
});
|
||||
test('argument type not assignable', () {
|
||||
h.run([
|
||||
ifCase(
|
||||
expr('int').checkContext('?'),
|
||||
relationalPattern('>', expr('String'))..errorId = 'PATTERN',
|
||||
[],
|
||||
).checkIR('ifCase(expr(int), >(expr(String), '
|
||||
'matchedType: int), variables(), true, block(), noop)')
|
||||
], expectedErrors: {
|
||||
'relationalPatternOperandTypeNotAssignable(pattern: PATTERN, '
|
||||
'operandType: String, parameterType: num)'
|
||||
|
||||
group('argument type not assignable:', () {
|
||||
test('basic', () {
|
||||
h.run([
|
||||
ifCase(
|
||||
expr('int').checkContext('?'),
|
||||
relationalPattern('>', expr('String'))..errorId = 'PATTERN',
|
||||
[],
|
||||
).checkIR('ifCase(expr(int), >(expr(String), '
|
||||
'matchedType: int), variables(), true, block(), noop)')
|
||||
], expectedErrors: {
|
||||
'relationalPatternOperandTypeNotAssignable(pattern: PATTERN, '
|
||||
'operandType: String, parameterType: num)'
|
||||
});
|
||||
});
|
||||
|
||||
test('> nullable', () {
|
||||
h.run([
|
||||
ifCase(
|
||||
expr('int'),
|
||||
relationalPattern('>', expr('int?'))..errorId = 'PATTERN',
|
||||
[],
|
||||
)
|
||||
], expectedErrors: {
|
||||
'relationalPatternOperandTypeNotAssignable(pattern: PATTERN, '
|
||||
'operandType: int?, parameterType: num)'
|
||||
});
|
||||
});
|
||||
|
||||
test('< nullable', () {
|
||||
h.run([
|
||||
ifCase(
|
||||
expr('int'),
|
||||
relationalPattern('<', expr('int?'))..errorId = 'PATTERN',
|
||||
[],
|
||||
)
|
||||
], expectedErrors: {
|
||||
'relationalPatternOperandTypeNotAssignable(pattern: PATTERN, '
|
||||
'operandType: int?, parameterType: num)'
|
||||
});
|
||||
});
|
||||
|
||||
test('>= nullable', () {
|
||||
h.run([
|
||||
ifCase(
|
||||
expr('int'),
|
||||
relationalPattern('>=', expr('int?'))..errorId = 'PATTERN',
|
||||
[],
|
||||
)
|
||||
], expectedErrors: {
|
||||
'relationalPatternOperandTypeNotAssignable(pattern: PATTERN, '
|
||||
'operandType: int?, parameterType: num)'
|
||||
});
|
||||
});
|
||||
|
||||
test('<= nullable', () {
|
||||
h.run([
|
||||
ifCase(
|
||||
expr('int'),
|
||||
relationalPattern('<=', expr('int?'))..errorId = 'PATTERN',
|
||||
[],
|
||||
)
|
||||
], expectedErrors: {
|
||||
'relationalPatternOperandTypeNotAssignable(pattern: PATTERN, '
|
||||
'operandType: int?, parameterType: num)'
|
||||
});
|
||||
});
|
||||
|
||||
test('extension type to representation', () {
|
||||
h.addSuperInterfaces('E', (_) => [Type('Object?')]);
|
||||
h.addExtensionTypeErasure('E', 'int');
|
||||
h.addMember('C', '>', 'bool Function(int)');
|
||||
h.run([
|
||||
ifCase(
|
||||
expr('C'),
|
||||
relationalPattern('>', expr('E'))..errorId = 'PATTERN',
|
||||
[],
|
||||
)
|
||||
], expectedErrors: {
|
||||
'relationalPatternOperandTypeNotAssignable(pattern: PATTERN, '
|
||||
'operandType: E, parameterType: int)'
|
||||
});
|
||||
});
|
||||
|
||||
test('representation to extension type', () {
|
||||
h.addSuperInterfaces('E', (_) => [Type('Object?')]);
|
||||
h.addExtensionTypeErasure('E', 'int');
|
||||
h.addMember('C', '>', 'bool Function(E)');
|
||||
h.run([
|
||||
ifCase(
|
||||
expr('C'),
|
||||
relationalPattern('>', expr('int'))..errorId = 'PATTERN',
|
||||
[],
|
||||
)
|
||||
], expectedErrors: {
|
||||
'relationalPatternOperandTypeNotAssignable(pattern: PATTERN, '
|
||||
'operandType: int, parameterType: E)'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('argument type assignable:', () {
|
||||
test('== nullable', () {
|
||||
h.run([
|
||||
ifCase(
|
||||
expr('int'),
|
||||
relationalPattern('==', expr('int?')),
|
||||
[],
|
||||
)
|
||||
]);
|
||||
});
|
||||
|
||||
test('!= nullable', () {
|
||||
h.run([
|
||||
ifCase(
|
||||
expr('int'),
|
||||
relationalPattern('!=', expr('int?')),
|
||||
[],
|
||||
)
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('return type is not assignable to bool', () {
|
||||
h.addMember('A', '>', 'int Function(Object)');
|
||||
h.run([
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) 2024, 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.
|
||||
|
||||
// This test verifies that extension type erasure is not performed
|
||||
// when checking the type argument of a relational pattern.
|
||||
|
||||
extension type const E(int representation) implements Object {}
|
||||
|
||||
class A {
|
||||
bool operator ==(covariant int other) => true;
|
||||
bool operator <(int other) => true;
|
||||
bool operator <=(int other) => true;
|
||||
bool operator >(int other) => true;
|
||||
bool operator >=(int other) => true;
|
||||
}
|
||||
|
||||
class B {
|
||||
bool operator ==(covariant E other) => true;
|
||||
bool operator <(E other) => true;
|
||||
bool operator <=(E other) => true;
|
||||
bool operator >(E other) => true;
|
||||
bool operator >=(E other) => true;
|
||||
}
|
||||
|
||||
const E0 = E(0);
|
||||
|
||||
test() {
|
||||
if (A() case == E0) {}
|
||||
// ^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'E' can't be assigned to the parameter type 'int'.
|
||||
if (A() case != E0) {}
|
||||
// ^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'E' can't be assigned to the parameter type 'int'.
|
||||
if (A() case > E0) {}
|
||||
// ^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'E' can't be assigned to the parameter type 'int'.
|
||||
if (A() case >= E0) {}
|
||||
// ^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'E' can't be assigned to the parameter type 'int'.
|
||||
if (A() case < E0) {}
|
||||
// ^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'E' can't be assigned to the parameter type 'int'.
|
||||
if (A() case <= E0) {}
|
||||
// ^^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'E' can't be assigned to the parameter type 'int'.
|
||||
if (B() case == 0) {}
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'int' can't be assigned to the parameter type 'E'.
|
||||
if (B() case != 0) {}
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'int' can't be assigned to the parameter type 'E'.
|
||||
if (B() case > 0) {}
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'int' can't be assigned to the parameter type 'E'.
|
||||
if (B() case >= 0) {}
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'int' can't be assigned to the parameter type 'E'.
|
||||
if (B() case < 0) {}
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'int' can't be assigned to the parameter type 'E'.
|
||||
if (B() case <= 0) {}
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE
|
||||
// [cfe] The argument type 'int' can't be assigned to the parameter type 'E'.
|
||||
}
|
Loading…
Reference in a new issue