Skip to content

Commit 94c165d

Browse files
johnniwintherCommit Queue
authored and
Commit Queue
committed
[cfe] Implement primitive equality
Closes #51045 Closes #51565 Closes #51566 Closes #51688 Closes #51800 Change-Id: I4a679ef9cf496a22f4fdc2047a2dc4753e796e2b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/290320 Commit-Queue: Johnni Winther <[email protected]> Reviewed-by: Chloe Stefantsova <[email protected]>
1 parent f39f8f2 commit 94c165d

File tree

48 files changed

+2886
-250
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2886
-250
lines changed

pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,72 @@ Message _withArgumentsConstEvalElementImplementsEqual(
467467
arguments: {'constant': _constant});
468468
}
469469

470+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
471+
const Template<
472+
Message Function(Constant _constant, bool isNonNullableByDefault)>
473+
templateConstEvalElementNotPrimitiveEquality = const Template<
474+
Message Function(Constant _constant, bool isNonNullableByDefault)>(
475+
problemMessageTemplate:
476+
r"""The element '#constant' does not have a primitive equality.""",
477+
withArguments: _withArgumentsConstEvalElementNotPrimitiveEquality);
478+
479+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
480+
const Code<Message Function(Constant _constant, bool isNonNullableByDefault)>
481+
codeConstEvalElementNotPrimitiveEquality = const Code<
482+
Message Function(Constant _constant, bool isNonNullableByDefault)>(
483+
"ConstEvalElementNotPrimitiveEquality",
484+
);
485+
486+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
487+
Message _withArgumentsConstEvalElementNotPrimitiveEquality(
488+
Constant _constant, bool isNonNullableByDefault) {
489+
TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
490+
List<Object> constantParts = labeler.labelConstant(_constant);
491+
String constant = constantParts.join();
492+
return new Message(codeConstEvalElementNotPrimitiveEquality,
493+
problemMessage:
494+
"""The element '${constant}' does not have a primitive equality.""" +
495+
labeler.originMessages,
496+
arguments: {'constant': _constant});
497+
}
498+
499+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
500+
const Template<
501+
Message Function(
502+
Constant _constant, DartType _type, bool isNonNullableByDefault)>
503+
templateConstEvalEqualsOperandNotPrimitiveEquality = const Template<
504+
Message Function(Constant _constant, DartType _type,
505+
bool isNonNullableByDefault)>(
506+
problemMessageTemplate:
507+
r"""Binary operator '==' requires receiver constant '#constant' of a type with primitive equality or type 'double', but was of type '#type'.""",
508+
withArguments:
509+
_withArgumentsConstEvalEqualsOperandNotPrimitiveEquality);
510+
511+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
512+
const Code<
513+
Message Function(
514+
Constant _constant, DartType _type, bool isNonNullableByDefault)>
515+
codeConstEvalEqualsOperandNotPrimitiveEquality = const Code<
516+
Message Function(
517+
Constant _constant, DartType _type, bool isNonNullableByDefault)>(
518+
"ConstEvalEqualsOperandNotPrimitiveEquality",
519+
);
520+
521+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
522+
Message _withArgumentsConstEvalEqualsOperandNotPrimitiveEquality(
523+
Constant _constant, DartType _type, bool isNonNullableByDefault) {
524+
TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
525+
List<Object> constantParts = labeler.labelConstant(_constant);
526+
List<Object> typeParts = labeler.labelType(_type);
527+
String constant = constantParts.join();
528+
String type = typeParts.join();
529+
return new Message(codeConstEvalEqualsOperandNotPrimitiveEquality,
530+
problemMessage:
531+
"""Binary operator '==' requires receiver constant '${constant}' of a type with primitive equality or type 'double', but was of type '${type}'.""" +
532+
labeler.originMessages,
533+
arguments: {'constant': _constant, 'type': _type});
534+
}
535+
470536
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
471537
const Template<Message Function(DartType _type, bool isNonNullableByDefault)>
472538
templateConstEvalFreeTypeParameter = const Template<
@@ -865,6 +931,35 @@ Message _withArgumentsConstEvalKeyImplementsEqual(
865931
arguments: {'constant': _constant});
866932
}
867933

934+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
935+
const Template<
936+
Message Function(Constant _constant, bool isNonNullableByDefault)>
937+
templateConstEvalKeyNotPrimitiveEquality = const Template<
938+
Message Function(Constant _constant, bool isNonNullableByDefault)>(
939+
problemMessageTemplate:
940+
r"""The key '#constant' does not have a primitive equality.""",
941+
withArguments: _withArgumentsConstEvalKeyNotPrimitiveEquality);
942+
943+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
944+
const Code<Message Function(Constant _constant, bool isNonNullableByDefault)>
945+
codeConstEvalKeyNotPrimitiveEquality = const Code<
946+
Message Function(Constant _constant, bool isNonNullableByDefault)>(
947+
"ConstEvalKeyNotPrimitiveEquality",
948+
);
949+
950+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
951+
Message _withArgumentsConstEvalKeyNotPrimitiveEquality(
952+
Constant _constant, bool isNonNullableByDefault) {
953+
TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
954+
List<Object> constantParts = labeler.labelConstant(_constant);
955+
String constant = constantParts.join();
956+
return new Message(codeConstEvalKeyNotPrimitiveEquality,
957+
problemMessage:
958+
"""The key '${constant}' does not have a primitive equality.""" +
959+
labeler.originMessages,
960+
arguments: {'constant': _constant});
961+
}
962+
868963
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
869964
const Template<
870965
Message Function(Constant _constant, bool isNonNullableByDefault)>

pkg/front_end/lib/src/fasta/kernel/constant_collection_builders.dart

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,17 @@ class SetConstantBuilder extends _ListOrSetConstantBuilder<SetLiteral> {
159159
@override
160160
AbortConstant? addConstant(Constant constant, TreeNode context) {
161161
if (!evaluator.hasPrimitiveEqual(constant)) {
162-
return evaluator.createEvaluationErrorConstant(
163-
context,
164-
templateConstEvalElementImplementsEqual.withArguments(
165-
constant, evaluator.isNonNullableByDefault));
162+
if (evaluator.enablePrimitiveEquality) {
163+
return evaluator.createEvaluationErrorConstant(
164+
context,
165+
templateConstEvalElementNotPrimitiveEquality.withArguments(
166+
constant, evaluator.isNonNullableByDefault));
167+
} else {
168+
return evaluator.createEvaluationErrorConstant(
169+
context,
170+
templateConstEvalElementImplementsEqual.withArguments(
171+
constant, evaluator.isNonNullableByDefault));
172+
}
166173
}
167174
bool unseen = seen.add(constant);
168175
if (!unseen) {
@@ -304,10 +311,17 @@ class MapConstantBuilder {
304311
parts.add(lastPart = <ConstantMapEntry>[]);
305312
}
306313
if (!evaluator.hasPrimitiveEqual(key)) {
307-
return evaluator.createEvaluationErrorConstant(
308-
keyContext,
309-
templateConstEvalKeyImplementsEqual.withArguments(
310-
key, evaluator.isNonNullableByDefault));
314+
if (evaluator.enablePrimitiveEquality) {
315+
return evaluator.createEvaluationErrorConstant(
316+
keyContext,
317+
templateConstEvalKeyNotPrimitiveEquality.withArguments(
318+
key, evaluator.isNonNullableByDefault));
319+
} else {
320+
return evaluator.createEvaluationErrorConstant(
321+
keyContext,
322+
templateConstEvalKeyImplementsEqual.withArguments(
323+
key, evaluator.isNonNullableByDefault));
324+
}
311325
}
312326
bool unseenKey = seenKeys.add(key);
313327
if (!unseenKey) {

pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,8 @@ class ConstantsTransformer extends RemovingTransformer {
442442
/// rewritten.
443443
bool get keepLocals => backend.keepLocals;
444444

445+
Library get currentLibrary => _staticTypeContext!.enclosingLibrary;
446+
445447
// Transform the library/class members:
446448

447449
void convertLibrary(Library library) {
@@ -1517,7 +1519,7 @@ class ConstantsTransformer extends RemovingTransformer {
15171519
reportedErrors = [];
15181520
}
15191521
if (!useFallbackExhaustivenessAlgorithm) {
1520-
Library library = _staticTypeContext!.enclosingLibrary;
1522+
Library library = currentLibrary;
15211523
for (ExhaustivenessError error in errors) {
15221524
if (error is UnreachableCaseError) {
15231525
if (library.importUri.isScheme('dart') &&
@@ -1558,7 +1560,7 @@ class ConstantsTransformer extends RemovingTransformer {
15581560
TreeNode visitSwitchStatement(
15591561
SwitchStatement node, TreeNode? removalSentinel) {
15601562
TreeNode result = super.visitSwitchStatement(node, removalSentinel);
1561-
Library library = _staticTypeContext!.enclosingLibrary;
1563+
Library library = currentLibrary;
15621564
// ignore: unnecessary_null_comparison
15631565
if (library != null) {
15641566
for (SwitchCase switchCase in node.cases) {
@@ -2183,6 +2185,7 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
21832185
final Map<Node, Constant?> nodeCache;
21842186

21852187
late Map<Class, bool> primitiveEqualCache;
2188+
late Map<Class, bool> primitiveHashCodeCache;
21862189

21872190
/// Classes that are considered having a primitive equals but where the
21882191
/// `operator ==` is actually defined through as custom method. For instance
@@ -2210,6 +2213,8 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
22102213
bool get isNonNullableByDefault =>
22112214
_staticTypeContext!.nonNullable == Nullability.nonNullable;
22122215

2216+
Library get currentLibrary => _staticTypeContext!.enclosingLibrary;
2217+
22132218
late ConstantWeakener _weakener;
22142219

22152220
ConstantEvaluator(this.dartLibrarySupport, this.backend, this.component,
@@ -2247,6 +2252,7 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
22472252
coreTypes.symbolClass: true,
22482253
coreTypes.typeClass: true,
22492254
};
2255+
primitiveHashCodeCache = <Class, bool>{...primitiveEqualCache};
22502256
_weakener = new ConstantWeakener(this);
22512257
}
22522258

@@ -3648,20 +3654,37 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
36483654
}
36493655

36503656
Constant _handleEquals(Expression node, Constant left, Constant right) {
3651-
if (left is NullConstant ||
3652-
left is BoolConstant ||
3653-
left is IntConstant ||
3654-
left is DoubleConstant ||
3655-
left is StringConstant ||
3656-
right is NullConstant) {
3657-
// [DoubleConstant] uses [identical] to determine equality, so we need
3658-
// to take the special cases into account.
3659-
return doubleSpecialCases(left, right) ?? makeBoolConstant(left == right);
3657+
if (enablePrimitiveEquality) {
3658+
if (hasPrimitiveEqual(left) ||
3659+
left is DoubleConstant ||
3660+
right is NullConstant) {
3661+
return doubleSpecialCases(left, right) ??
3662+
makeBoolConstant(left == right);
3663+
} else {
3664+
return createEvaluationErrorConstant(
3665+
node,
3666+
templateConstEvalEqualsOperandNotPrimitiveEquality.withArguments(
3667+
left,
3668+
left.getType(_staticTypeContext!),
3669+
isNonNullableByDefault));
3670+
}
36603671
} else {
3661-
return createEvaluationErrorConstant(
3662-
node,
3663-
templateConstEvalInvalidEqualsOperandType.withArguments(
3664-
left, left.getType(_staticTypeContext!), isNonNullableByDefault));
3672+
if (left is NullConstant ||
3673+
left is BoolConstant ||
3674+
left is IntConstant ||
3675+
left is DoubleConstant ||
3676+
left is StringConstant ||
3677+
right is NullConstant) {
3678+
// [DoubleConstant] uses [identical] to determine equality, so we need
3679+
// to take the special cases into account.
3680+
return doubleSpecialCases(left, right) ??
3681+
makeBoolConstant(left == right);
3682+
} else {
3683+
return createEvaluationErrorConstant(
3684+
node,
3685+
templateConstEvalInvalidEqualsOperandType.withArguments(left,
3686+
left.getType(_staticTypeContext!), isNonNullableByDefault));
3687+
}
36653688
}
36663689
}
36673690

@@ -4737,9 +4760,8 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
47374760

47384761
@override
47394762
Constant visitSymbolLiteral(SymbolLiteral node) {
4740-
final Reference? libraryReference = node.value.startsWith('_')
4741-
? _staticTypeContext!.enclosingLibrary.reference
4742-
: null;
4763+
final Reference? libraryReference =
4764+
node.value.startsWith('_') ? currentLibrary.reference : null;
47434765
return canonicalize(new SymbolConstant(node.value, libraryReference));
47444766
}
47454767

@@ -4867,6 +4889,8 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
48674889
return null;
48684890
}
48694891

4892+
bool get enablePrimitiveEquality => currentLibrary.languageVersion.major >= 3;
4893+
48704894
bool hasPrimitiveEqual(Constant constant,
48714895
{bool allowPseudoPrimitive = true}) {
48724896
if (intFolder.isInt(constant)) return true;
@@ -4892,9 +4916,13 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
48924916
}
48934917
DartType type = constant.getType(_staticTypeContext!);
48944918
if (type is InterfaceType) {
4895-
bool result = classHasPrimitiveEqual(type.classNode);
4919+
Class cls = type.classNode;
4920+
bool result = classHasPrimitiveEqual(cls);
48964921
if (result && !allowPseudoPrimitive) {
4897-
result = !pseudoPrimitiveClasses.contains(type.classNode);
4922+
result = !pseudoPrimitiveClasses.contains(cls);
4923+
}
4924+
if (result && enablePrimitiveEquality) {
4925+
result = classHasPrimitiveHashCode(cls);
48984926
}
48994927
return result;
49004928
}
@@ -4917,6 +4945,27 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
49174945
classHasPrimitiveEqual(klass.supertype!.classNode);
49184946
}
49194947

4948+
bool classHasPrimitiveHashCode(Class klass) {
4949+
bool? cached = primitiveHashCodeCache[klass];
4950+
if (cached != null) return cached;
4951+
for (Procedure procedure in klass.procedures) {
4952+
if (procedure.kind == ProcedureKind.Getter &&
4953+
procedure.name.text == 'hashCode' &&
4954+
!procedure.isAbstract &&
4955+
!procedure.isForwardingStub) {
4956+
return primitiveHashCodeCache[klass] = false;
4957+
}
4958+
}
4959+
for (Field field in klass.fields) {
4960+
if (field.name.text == 'hashCode') {
4961+
return primitiveHashCodeCache[klass] = false;
4962+
}
4963+
}
4964+
if (klass.supertype == null) return true; // To be on the safe side
4965+
return primitiveHashCodeCache[klass] =
4966+
classHasPrimitiveHashCode(klass.supertype!.classNode);
4967+
}
4968+
49204969
BoolConstant makeBoolConstant(bool value) =>
49214970
value ? trueConstant : falseConstant;
49224971

@@ -6097,3 +6146,10 @@ class _PatternSwitchStatementInfo {
60976146
_PatternSwitchStatementInfo(this.switchIndexVariable,
60986147
this.innerLabeledStatement, this.switchCaseIndexMap);
60996148
}
6149+
6150+
enum PrimitiveEquality {
6151+
None,
6152+
EqualsOnly,
6153+
HashCodeOnly,
6154+
EqualsAndHashCode,
6155+
}

pkg/front_end/lib/src/fasta/source/source_loader.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3087,7 +3087,7 @@ class MapEntry<K, V> {
30873087
30883088
abstract class Map<K, V> extends Iterable {
30893089
factory Map.unmodifiable(other) => null;
3090-
factory Map.of(o) = Map<E>._of;
3090+
factory Map.of(o) = Map<K, V>._of;
30913091
external factory Map._of(o);
30923092
Iterable<MapEntry<K, V>> get entries;
30933093
void operator []=(K key, V value) {}

pkg/front_end/messages.status

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ ConstEvalDeferredLibrary/example: Fail
107107
ConstEvalDuplicateElement/example: Fail
108108
ConstEvalDuplicateKey/example: Fail
109109
ConstEvalElementImplementsEqual/example: Fail
110+
ConstEvalElementNotPrimitiveEquality/analyzerCode: Fail
111+
ConstEvalElementNotPrimitiveEquality/example: Fail
112+
ConstEvalEqualsOperandNotPrimitiveEquality/analyzerCode: Fail
113+
ConstEvalEqualsOperandNotPrimitiveEquality/example: Fail
110114
ConstEvalError/analyzerCode: Fail
111115
ConstEvalError/example: Fail
112116
ConstEvalExtension/example: Fail
@@ -138,6 +142,8 @@ ConstEvalIterationInConstList/example: Fail
138142
ConstEvalIterationInConstMap/example: Fail
139143
ConstEvalIterationInConstSet/example: Fail
140144
ConstEvalKeyImplementsEqual/example: Fail
145+
ConstEvalKeyNotPrimitiveEquality/analyzerCode: Fail
146+
ConstEvalKeyNotPrimitiveEquality/example: Fail
141147
ConstEvalNegativeShift/analyzerCode: Fail # http://dartbug.com/33481
142148
ConstEvalNegativeShift/example: Fail
143149
ConstEvalNonConstantVariableGet/example: Fail

pkg/front_end/messages.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,16 @@ ConstEvalElementImplementsEqual:
139139
problemMessage: "The element '#constant' does not have a primitive operator '=='."
140140
analyzerCode: CONST_SET_ELEMENT_TYPE_IMPLEMENTS_EQUALS
141141

142+
ConstEvalElementNotPrimitiveEquality:
143+
problemMessage: "The element '#constant' does not have a primitive equality."
144+
142145
ConstEvalKeyImplementsEqual:
143146
problemMessage: "The key '#constant' does not have a primitive operator '=='."
144147
analyzerCode: CONST_MAP_KEY_EXPRESSION_TYPE_IMPLEMENTS_EQUALS
145148

149+
ConstEvalKeyNotPrimitiveEquality:
150+
problemMessage: "The key '#constant' does not have a primitive equality."
151+
146152
ConstEvalCaseImplementsEqual:
147153
problemMessage: "Case expression '#constant' does not have a primitive operator '=='."
148154

@@ -155,6 +161,9 @@ ConstEvalInvalidBinaryOperandType:
155161
ConstEvalInvalidEqualsOperandType:
156162
problemMessage: "Binary operator '==' requires receiver constant '#constant' of type 'Null', 'bool', 'int', 'double', or 'String', but was of type '#type'."
157163

164+
ConstEvalEqualsOperandNotPrimitiveEquality:
165+
problemMessage: "Binary operator '==' requires receiver constant '#constant' of a type with primitive equality or type 'double', but was of type '#type'."
166+
158167
ConstEvalZeroDivisor:
159168
problemMessage: "Binary operator '#string' on '#string2' requires non-zero divisor, but divisor was '0'."
160169
analyzerCode: CONST_EVAL_THROWS_IDBZE

0 commit comments

Comments
 (0)