mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 08:44:27 +00:00
[cfe] Use only field values in equality of record constants
Previously the static type of the record literals was also used, which is incorrect interpretation of the equality on records. Closes https://github.com/dart-lang/sdk/issues/54491 Change-Id: I12fad33271e53279a3d9c8bcfd2a842ac31988a5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/344701 Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Lasse Nielsen <lrn@google.com> Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
This commit is contained in:
parent
28e0ae59ec
commit
1eace401d2
14 changed files with 310 additions and 16 deletions
|
@ -2266,7 +2266,8 @@ class ConstantsTransformer extends RemovingTransformer {
|
|||
return makeConstantExpression(new UnevaluatedConstant(node), node);
|
||||
} else {
|
||||
Constant constant = constantEvaluator.canonicalize(
|
||||
new RecordConstant(positional, named, node.recordType));
|
||||
new RecordConstant.fromTypeContext(
|
||||
positional, named, staticTypeContext));
|
||||
return makeConstantExpression(constant, node);
|
||||
}
|
||||
}
|
||||
|
@ -3110,8 +3111,8 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
|
|||
new NamedExpression(key, _wrap(named[key]!)),
|
||||
], node.recordType, isConst: true));
|
||||
}
|
||||
return canonicalize(new RecordConstant(
|
||||
positional, named, env.substituteType(node.recordType) as RecordType));
|
||||
return canonicalize(new RecordConstant.fromTypeContext(
|
||||
positional, named, staticTypeContext));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -24,7 +24,7 @@ static method main() → void {
|
|||
#C2;
|
||||
#C4;
|
||||
#C4;
|
||||
#C6;
|
||||
#C5;
|
||||
}
|
||||
|
||||
constants {
|
||||
|
@ -32,8 +32,7 @@ constants {
|
|||
#C2 = self::Foo<core::int*> {a:#C1}
|
||||
#C3 = (#C1, #C1)
|
||||
#C4 = self::Generic<core::int*> {record:#C3}
|
||||
#C5 = (#C1, #C1)
|
||||
#C6 = self::NotGeneric {record:#C5}
|
||||
#C5 = self::NotGeneric {record:#C3}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ static method main() → void {
|
|||
#C2;
|
||||
#C4;
|
||||
#C4;
|
||||
#C6;
|
||||
#C5;
|
||||
}
|
||||
|
||||
constants {
|
||||
|
@ -32,8 +32,7 @@ constants {
|
|||
#C2 = self::Foo<core::int*> {a:#C1}
|
||||
#C3 = (#C1, #C1)
|
||||
#C4 = self::Generic<core::int*> {record:#C3}
|
||||
#C5 = (#C1, #C1)
|
||||
#C6 = self::NotGeneric {record:#C5}
|
||||
#C5 = self::NotGeneric {record:#C3}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ static method main() → void {
|
|||
#C2;
|
||||
#C4;
|
||||
#C4;
|
||||
#C6;
|
||||
#C5;
|
||||
}
|
||||
|
||||
constants {
|
||||
|
@ -32,8 +32,7 @@ constants {
|
|||
#C2 = self::Foo<core::int*> {a:#C1}
|
||||
#C3 = (#C1, #C1)
|
||||
#C4 = self::Generic<core::int*> {record:#C3}
|
||||
#C5 = (#C1, #C1)
|
||||
#C6 = self::NotGeneric {record:#C5}
|
||||
#C5 = self::NotGeneric {record:#C3}
|
||||
}
|
||||
|
||||
|
||||
|
|
22
pkg/front_end/testcases/records/issue54491.dart
Normal file
22
pkg/front_end/testcases/records/issue54491.dart
Normal file
|
@ -0,0 +1,22 @@
|
|||
// 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.
|
||||
|
||||
void main() {
|
||||
const Chk((Ex(1),), eq: (1,));
|
||||
const Chk((1,), eq: (1,));
|
||||
const Chk((Ex(1),), eq: (Ex(1),));
|
||||
const Chk((Ex(1),), eq: (1,));
|
||||
const Chk(((1 as Ex),), eq: (1,));
|
||||
const Chk((Ex(1) as int,), eq: (1,));
|
||||
const Chk((Ex(1),) as (int,), eq: (1,));
|
||||
const Chk((1,) as (Ex,), eq: (1,));
|
||||
const Chk(Ex((1,)), eq: (1,));
|
||||
const Chk(Ex((Ex(1),)), eq: (1,));
|
||||
}
|
||||
class Chk {
|
||||
const Chk(Object? v, {required Object? eq}) :
|
||||
assert(v == eq, "Not equal ${(v, eq: eq)}");
|
||||
}
|
||||
|
||||
extension type const Ex(Object? value) {}
|
|
@ -0,0 +1,42 @@
|
|||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Chk extends core::Object /*hasConstConstructor*/ {
|
||||
const constructor •(core::Object? v, {required core::Object? eq}) → self::Chk
|
||||
: assert(v =={core::Object::==}{(core::Object) → core::bool} eq, "Not equal ${(v, {eq: eq})}"), super core::Object::•()
|
||||
;
|
||||
}
|
||||
extension type Ex(core::Object? value) {
|
||||
abstract extension-type-member representation-field get value() → core::Object?;
|
||||
constructor • = self::Ex|constructor#;
|
||||
constructor tearoff • = self::Ex|constructor#_#new#tearOff;
|
||||
}
|
||||
static method main() → void {
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
}
|
||||
static extension-type-member method Ex|constructor#(core::Object? value) → self::Ex /* = core::Object? */ {
|
||||
lowered final self::Ex /* = core::Object? */ #this = value;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method Ex|constructor#_#new#tearOff(core::Object? value) → self::Ex /* = core::Object? */
|
||||
return self::Ex|constructor#(value);
|
||||
|
||||
constants {
|
||||
#C1 = self::Chk {}
|
||||
}
|
||||
|
||||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///issue54491.dart:
|
||||
- Chk. (from org-dartlang-testcase:///issue54491.dart:18:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart)
|
|
@ -0,0 +1,42 @@
|
|||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Chk extends core::Object /*hasConstConstructor*/ {
|
||||
const constructor •(core::Object? v, {required core::Object? eq}) → self::Chk
|
||||
: assert(v =={core::Object::==}{(core::Object) → core::bool} eq, "Not equal ${(v, {eq: eq})}"), super core::Object::•()
|
||||
;
|
||||
}
|
||||
extension type Ex(core::Object? value) {
|
||||
abstract extension-type-member representation-field get value() → core::Object?;
|
||||
constructor • = self::Ex|constructor#;
|
||||
constructor tearoff • = self::Ex|constructor#_#new#tearOff;
|
||||
}
|
||||
static method main() → void {
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
}
|
||||
static extension-type-member method Ex|constructor#(core::Object? value) → self::Ex /* = core::Object? */ {
|
||||
lowered final self::Ex /* = core::Object? */ #this = value;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method Ex|constructor#_#new#tearOff(core::Object? value) → self::Ex /* = core::Object? */
|
||||
return self::Ex|constructor#(value);
|
||||
|
||||
constants {
|
||||
#C1 = self::Chk {}
|
||||
}
|
||||
|
||||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///issue54491.dart:
|
||||
- Chk. (from org-dartlang-testcase:///issue54491.dart:18:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart)
|
|
@ -0,0 +1,8 @@
|
|||
void main() {}
|
||||
|
||||
class Chk {
|
||||
const Chk(Object? v, {required Object? eq})
|
||||
: assert(v == eq, "Not equal ${(v, eq: eq)}");
|
||||
}
|
||||
|
||||
extension type const Ex(Object? value) {}
|
|
@ -0,0 +1,8 @@
|
|||
class Chk {
|
||||
const Chk(Object? v, {required Object? eq})
|
||||
: assert(v == eq, "Not equal ${(v, eq: eq)}");
|
||||
}
|
||||
|
||||
extension type const Ex(Object? value) {}
|
||||
|
||||
void main() {}
|
42
pkg/front_end/testcases/records/issue54491.dart.weak.expect
Normal file
42
pkg/front_end/testcases/records/issue54491.dart.weak.expect
Normal file
|
@ -0,0 +1,42 @@
|
|||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Chk extends core::Object /*hasConstConstructor*/ {
|
||||
const constructor •(core::Object? v, {required core::Object? eq}) → self::Chk
|
||||
: assert(v =={core::Object::==}{(core::Object) → core::bool} eq, "Not equal ${(v, {eq: eq})}"), super core::Object::•()
|
||||
;
|
||||
}
|
||||
extension type Ex(core::Object? value) {
|
||||
abstract extension-type-member representation-field get value() → core::Object?;
|
||||
constructor • = self::Ex|constructor#;
|
||||
constructor tearoff • = self::Ex|constructor#_#new#tearOff;
|
||||
}
|
||||
static method main() → void {
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
}
|
||||
static extension-type-member method Ex|constructor#(core::Object? value) → self::Ex /* = core::Object? */ {
|
||||
lowered final self::Ex /* = core::Object? */ #this = value;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method Ex|constructor#_#new#tearOff(core::Object? value) → self::Ex /* = core::Object? */
|
||||
return self::Ex|constructor#(value);
|
||||
|
||||
constants {
|
||||
#C1 = self::Chk {}
|
||||
}
|
||||
|
||||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///issue54491.dart:
|
||||
- Chk. (from org-dartlang-testcase:///issue54491.dart:18:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart)
|
|
@ -0,0 +1,42 @@
|
|||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Chk extends core::Object /*hasConstConstructor*/ {
|
||||
const constructor •(core::Object? v, {required core::Object? eq}) → self::Chk
|
||||
: assert(v =={core::Object::==}{(core::Object) → core::bool} eq, "Not equal ${(v, {eq: eq})}"), super core::Object::•()
|
||||
;
|
||||
}
|
||||
extension type Ex(core::Object? value) {
|
||||
abstract extension-type-member representation-field get value() → core::Object?;
|
||||
constructor • = self::Ex|constructor#;
|
||||
constructor tearoff • = self::Ex|constructor#_#new#tearOff;
|
||||
}
|
||||
static method main() → void {
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
}
|
||||
static extension-type-member method Ex|constructor#(core::Object? value) → self::Ex /* = core::Object? */ {
|
||||
lowered final self::Ex /* = core::Object? */ #this = value;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method Ex|constructor#_#new#tearOff(core::Object? value) → self::Ex /* = core::Object? */
|
||||
return self::Ex|constructor#(value);
|
||||
|
||||
constants {
|
||||
#C1 = self::Chk {}
|
||||
}
|
||||
|
||||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///issue54491.dart:
|
||||
- Chk. (from org-dartlang-testcase:///issue54491.dart:18:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart)
|
|
@ -0,0 +1,22 @@
|
|||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Chk extends core::Object /*hasConstConstructor*/ {
|
||||
const constructor •(core::Object? v, {required core::Object? eq}) → self::Chk
|
||||
: assert(v =={core::Object::==}{(core::Object) → core::bool} eq, "Not equal ${(v, {eq: eq})}"), super core::Object::•()
|
||||
;
|
||||
}
|
||||
extension type Ex(core::Object? value) {
|
||||
abstract extension-type-member representation-field get value() → core::Object?;
|
||||
constructor • = self::Ex|constructor#;
|
||||
constructor tearoff • = self::Ex|constructor#_#new#tearOff;
|
||||
}
|
||||
static method main() → void
|
||||
;
|
||||
static extension-type-member method Ex|constructor#(core::Object? value) → self::Ex /* = core::Object? */ {
|
||||
lowered final self::Ex /* = core::Object? */ #this = value;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method Ex|constructor#_#new#tearOff(core::Object? value) → self::Ex /* = core::Object? */
|
||||
return self::Ex|constructor#(value);
|
|
@ -0,0 +1,42 @@
|
|||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Chk extends core::Object /*hasConstConstructor*/ {
|
||||
const constructor •(core::Object? v, {required core::Object? eq}) → self::Chk
|
||||
: assert(v =={core::Object::==}{(core::Object) → core::bool} eq, "Not equal ${(v, {eq: eq})}"), super core::Object::•()
|
||||
;
|
||||
}
|
||||
extension type Ex(core::Object? value) {
|
||||
abstract extension-type-member representation-field get value() → core::Object?;
|
||||
constructor • = self::Ex|constructor#;
|
||||
constructor tearoff • = self::Ex|constructor#_#new#tearOff;
|
||||
}
|
||||
static method main() → void {
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
#C1;
|
||||
}
|
||||
static extension-type-member method Ex|constructor#(core::Object? value) → self::Ex /* = core::Object? */ {
|
||||
lowered final self::Ex /* = core::Object? */ #this = value;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method Ex|constructor#_#new#tearOff(core::Object? value) → self::Ex /* = core::Object? */
|
||||
return self::Ex|constructor#(value);
|
||||
|
||||
constants {
|
||||
#C1 = self::Chk {}
|
||||
}
|
||||
|
||||
|
||||
Constructor coverage from constants:
|
||||
org-dartlang-testcase:///issue54491.dart:
|
||||
- Chk. (from org-dartlang-testcase:///issue54491.dart:18:9)
|
||||
- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart)
|
|
@ -13969,7 +13969,24 @@ class RecordConstant extends Constant {
|
|||
/// Named field values, sorted by name.
|
||||
final Map<String, Constant> named;
|
||||
|
||||
/// The static type of the constant.
|
||||
/// The runtime type of the constant.
|
||||
///
|
||||
/// [recordType] is computed from the individual types of the record fields
|
||||
/// and reflects runtime type of the record constant, as opposed to the
|
||||
/// static type of the expression that defined the constant.
|
||||
///
|
||||
/// The following program shows the distinction between the static and the
|
||||
/// runtime types of the constant. The static type of the first record in the
|
||||
/// invocation of `identical` is `(E, String)`, the static type of the second
|
||||
/// — `(int, String)`. The runtime type of both constants is `(int, String)`,
|
||||
/// and the assertion condition should be satisfied.
|
||||
///
|
||||
/// extension type const E(Object? it) {}
|
||||
///
|
||||
/// main() {
|
||||
/// const bool check = identical(const (E(1), "foo"), const (1, "foo"));
|
||||
/// assert(check);
|
||||
/// }
|
||||
final RecordType recordType;
|
||||
|
||||
RecordConstant(this.positional, this.named, this.recordType)
|
||||
|
@ -13993,6 +14010,16 @@ class RecordConstant extends Constant {
|
|||
"Named fields of a RecordConstant aren't sorted lexicographically: "
|
||||
"${named.keys.join(", ")}");
|
||||
|
||||
RecordConstant.fromTypeContext(
|
||||
this.positional, this.named, StaticTypeContext staticTypeContext)
|
||||
: recordType = new RecordType([
|
||||
for (Constant constant in positional)
|
||||
constant.getType(staticTypeContext)
|
||||
], [
|
||||
for (var MapEntry(key: name, value: constant) in named.entries)
|
||||
new NamedType(name, constant.getType(staticTypeContext))
|
||||
], staticTypeContext.nonNullable);
|
||||
|
||||
@override
|
||||
void visitChildren(Visitor v) {
|
||||
recordType.accept(v);
|
||||
|
@ -14048,14 +14075,13 @@ class RecordConstant extends Constant {
|
|||
String toString() => "RecordConstant(${toStringInternal()})";
|
||||
|
||||
@override
|
||||
late final int hashCode = _Hash.combineFinish(recordType.hashCode,
|
||||
_Hash.combineMapHashUnordered(named, _Hash.combineListHash(positional)));
|
||||
late final int hashCode =
|
||||
_Hash.combineMapHashUnordered(named, _Hash.combineListHash(positional));
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is RecordConstant &&
|
||||
other.recordType == recordType &&
|
||||
listEquals(other.positional, positional) &&
|
||||
mapEquals(other.named, named));
|
||||
|
||||
|
|
Loading…
Reference in a new issue