Skip to content

Commit 243fb5c

Browse files
srawlinscommit-bot@chromium.org
authored andcommitted
analyzer: Support non-proper rename type alias constructor tear-offs
An alternative to https://dart-review.googlesource.com/c/sdk/+/218582 In the case of a constructor tear-off through a non-proper rename type alias, we store the type alias on the FunctionState in constant evaluation. Fixes #47267 Change-Id: I3bcbc11fceafc3a688eb956cf0459ad1dbde6307 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/218746 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent 2f66c07 commit 243fb5c

File tree

7 files changed

+245
-43
lines changed

7 files changed

+245
-43
lines changed

pkg/analyzer/lib/src/dart/constant/evaluation.dart

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -632,39 +632,31 @@ class ConstantVisitor extends UnifyingAstVisitor<DartObjectImpl> {
632632

633633
@override
634634
DartObjectImpl? visitConstructorReference(ConstructorReference node) {
635-
var constructorTearoffResult = DartObjectImpl(
635+
var constructorFunctionType = node.typeOrThrow as FunctionType;
636+
var classType = constructorFunctionType.returnType as InterfaceType;
637+
var typeArguments = classType.typeArguments;
638+
// The result is already instantiated during resolution;
639+
// [_dartObjectComputer.typeInstantiate] is unnecessary.
640+
var typeElement =
641+
node.constructorName.type2.name.staticElement as TypeDefiningElement;
642+
643+
TypeAliasElement? viaTypeAlias;
644+
if (typeElement is TypeAliasElementImpl) {
645+
if (constructorFunctionType.typeFormals.isNotEmpty &&
646+
!typeElement.isProperRename()) {
647+
// The type alias is not a proper rename of the aliased class, so
648+
// the constructor tear-off is distinct from the associated
649+
// constructor function of the aliased class.
650+
viaTypeAlias = typeElement;
651+
}
652+
}
653+
654+
return DartObjectImpl(
636655
typeSystem,
637656
node.typeOrThrow,
638-
FunctionState(node.constructorName.staticElement),
657+
FunctionState(node.constructorName.staticElement,
658+
typeArguments: typeArguments, viaTypeAlias: viaTypeAlias),
639659
);
640-
var typeArgumentList = node.constructorName.type2.typeArguments;
641-
if (typeArgumentList == null) {
642-
return constructorTearoffResult;
643-
} else {
644-
var typeArguments = <DartType>[];
645-
for (var typeArgument in typeArgumentList.arguments) {
646-
var object = typeArgument.accept(this);
647-
if (object == null) {
648-
return null;
649-
}
650-
var typeArgumentType = object.toTypeValue();
651-
if (typeArgumentType == null) {
652-
return null;
653-
}
654-
// TODO(srawlins): Test type alias types (`typedef i = int`) used as
655-
// type arguments. Possibly change implementation based on
656-
// canonicalization rules.
657-
typeArguments.add(typeArgumentType);
658-
}
659-
// The result is already instantiated during resolution;
660-
// [_dartObjectComputer.typeInstantiate] is unnecessary.
661-
return DartObjectImpl(
662-
typeSystem,
663-
node.typeOrThrow,
664-
FunctionState(node.constructorName.staticElement,
665-
typeArguments: typeArguments),
666-
);
667-
}
668660
}
669661

670662
@override

pkg/analyzer/lib/src/dart/constant/value.dart

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -920,11 +920,14 @@ class DartObjectImpl implements DartObject {
920920
List<DartType> typeArguments,
921921
) {
922922
var functionState = _state as FunctionState;
923-
var element = functionState._element;
924923
return DartObjectImpl(
925924
typeSystem,
926925
type,
927-
FunctionState(element, typeArguments: typeArguments),
926+
FunctionState(
927+
functionState._element,
928+
typeArguments: typeArguments,
929+
viaTypeAlias: functionState._viaTypeAlias,
930+
),
928931
);
929932
}
930933

@@ -1245,10 +1248,22 @@ class FunctionState extends InstanceState {
12451248

12461249
final List<DartType>? _typeArguments;
12471250

1251+
/// The type alias which was referenced when tearing off a constructor,
1252+
/// if this function is a constructor tear-off, referenced via a type alias,
1253+
/// and the type alias is not a proper rename for the class, and the
1254+
/// constructor tear-off is generic, so the tear-off cannot be considered
1255+
/// equivalent to tearing off the associated constructor function of the
1256+
/// aliased class.
1257+
///
1258+
/// Otherwise null.
1259+
final TypeDefiningElement? _viaTypeAlias;
1260+
12481261
/// Initialize a newly created state to represent the function with the given
12491262
/// [element].
1250-
FunctionState(this._element, {List<DartType>? typeArguments})
1251-
: _typeArguments = typeArguments;
1263+
FunctionState(this._element,
1264+
{List<DartType>? typeArguments, TypeDefiningElement? viaTypeAlias})
1265+
: _typeArguments = typeArguments,
1266+
_viaTypeAlias = viaTypeAlias;
12521267

12531268
@override
12541269
int get hashCode => _element == null ? 0 : _element.hashCode;
@@ -1272,6 +1287,9 @@ class FunctionState extends InstanceState {
12721287
if (typeArguments.length != otherTypeArguments.length) {
12731288
return false;
12741289
}
1290+
if (_viaTypeAlias != object._viaTypeAlias) {
1291+
return false;
1292+
}
12751293
for (var i = 0; i < typeArguments.length; i++) {
12761294
if (typeArguments[i] != otherTypeArguments[i]) {
12771295
return false;
@@ -1309,6 +1327,9 @@ class FunctionState extends InstanceState {
13091327
if (element?.declaration != otherElement?.declaration) {
13101328
return BoolState.FALSE_STATE;
13111329
}
1330+
if (_viaTypeAlias != rightOperand._viaTypeAlias) {
1331+
return BoolState.FALSE_STATE;
1332+
}
13121333
var typeArguments = _typeArguments;
13131334
var otherTypeArguments = rightOperand._typeArguments;
13141335
if (typeArguments == null || otherTypeArguments == null) {

pkg/analyzer/lib/src/dart/element/element.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5582,6 +5582,37 @@ class TypeAliasElementImpl extends _ExistingElementImpl
55825582
}
55835583
}
55845584

5585+
/// Returns whether this alias is a "proper rename" of [aliasedClass], as
5586+
/// defined in the constructor-tearoffs specification.
5587+
bool isProperRename() {
5588+
var aliasedType_ = aliasedType;
5589+
if (aliasedType_ is! InterfaceType) {
5590+
return false;
5591+
}
5592+
var aliasedClass = aliasedType_.element;
5593+
var typeArguments = aliasedType_.typeArguments;
5594+
var typeParameterCount = typeParameters.length;
5595+
if (typeParameterCount != aliasedClass.typeParameters.length) {
5596+
return false;
5597+
}
5598+
if (typeParameterCount != typeArguments.length) {
5599+
return false;
5600+
}
5601+
for (var i = 0; i < typeParameterCount; i++) {
5602+
var bound = typeParameters[i].bound ?? library.typeProvider.dynamicType;
5603+
var aliasedBound = aliasedClass.typeParameters[i].bound ??
5604+
library.typeProvider.dynamicType;
5605+
if (!library.typeSystem.isSubtypeOf(bound, aliasedBound) ||
5606+
!library.typeSystem.isSubtypeOf(aliasedBound, bound)) {
5607+
return false;
5608+
}
5609+
if (typeParameters[i] != typeArguments[i].element) {
5610+
return false;
5611+
}
5612+
}
5613+
return true;
5614+
}
5615+
55855616
void setLinkedData(Reference reference, ElementLinkedData linkedData) {
55865617
this.reference = reference;
55875618
reference.element = this;

pkg/analyzer/lib/src/dart/resolver/constructor_reference_resolver.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class ConstructorReferenceResolver {
123123
constructorName.staticElement = constructorElement.declaration;
124124
constructorName.name?.staticElement = constructorElement.declaration;
125125
node.staticType = inferred;
126+
// The NamedType child of `constructorName` doesn't have a static type.
126127
constructorName.type2.type = null;
127128
}
128129
} else {
@@ -132,6 +133,7 @@ class ConstructorReferenceResolver {
132133
} else {
133134
node.staticType = constructorElement.type;
134135
}
136+
// The NamedType child of `constructorName` doesn't have a static type.
135137
constructorName.type2.type = null;
136138
}
137139
}

pkg/analyzer/test/src/dart/constant/evaluation_test.dart

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,126 @@ main() {
2828
@reflectiveTest
2929
class ConstantVisitorTest extends ConstantVisitorTestSupport
3030
with ConstantVisitorTestCases {
31+
test_identical_constructorReference_aliasIsNotGeneric() async {
32+
await resolveTestCode('''
33+
class C<T> {}
34+
typedef MyC = C<int>;
35+
const a = identical(MyC.new, C<int>.new);
36+
''');
37+
expect(
38+
_evaluateConstant('a'),
39+
_boolValue(true),
40+
);
41+
}
42+
43+
test_identical_constructorReference_aliasIsNotProperRename_differentBound() async {
44+
await resolveTestCode('''
45+
class C<T> {}
46+
typedef MyC<T extends num> = C<T>;
47+
const a = identical(MyC.new, C.new);
48+
''');
49+
expect(
50+
_evaluateConstant('a'),
51+
_boolValue(false),
52+
);
53+
}
54+
55+
test_identical_constructorReference_aliasIsNotProperRename_differentCount() async {
56+
await resolveTestCode('''
57+
class C<T, U> {}
58+
typedef MyC<T> = C<T, int>;
59+
const a = identical(MyC.new, C.new);
60+
''');
61+
expect(
62+
_evaluateConstant('a'),
63+
_boolValue(false),
64+
);
65+
}
66+
67+
test_identical_constructorReference_aliasIsNotProperRename_differentOrder() async {
68+
await resolveTestCode('''
69+
class C<T, U> {}
70+
typedef MyC<T, U> = C<U, T>;
71+
const a = identical(MyC.new, C.new);
72+
''');
73+
expect(
74+
_evaluateConstant('a'),
75+
_boolValue(false),
76+
);
77+
}
78+
79+
test_identical_constructorReference_aliasIsNotProperRename_instantiated() async {
80+
await resolveTestCode('''
81+
class C<T> {}
82+
typedef MyC<T extends num> = C<T>;
83+
const a = identical(MyC<int>.new, C<int>.new);
84+
''');
85+
expect(
86+
_evaluateConstant('a'),
87+
_boolValue(true),
88+
);
89+
}
90+
91+
test_identical_constructorReference_aliasIsNotProperRename_mixedInstantiations() async {
92+
await resolveTestCode('''
93+
class C<T> {}
94+
typedef MyC<T extends num> = C<T>;
95+
const a = identical(MyC<int>.new, (MyC.new)<int>);
96+
''');
97+
expect(
98+
_evaluateConstant('a'),
99+
_boolValue(false),
100+
);
101+
}
102+
103+
test_identical_constructorReference_aliasIsProperRename_instantiated() async {
104+
await resolveTestCode('''
105+
class C<T> {}
106+
typedef MyC<T> = C<T>;
107+
const a = identical(MyC<int>.new, MyC<int>.new);
108+
''');
109+
expect(
110+
_evaluateConstant('a'),
111+
_boolValue(true),
112+
);
113+
}
114+
115+
test_identical_constructorReference_aliasIsProperRename_mixedInstantiations() async {
116+
await resolveTestCode('''
117+
class C<T> {}
118+
typedef MyC<T> = C<T>;
119+
const a = identical(MyC<int>.new, (MyC.new)<int>);
120+
''');
121+
expect(
122+
_evaluateConstant('a'),
123+
_boolValue(true),
124+
);
125+
}
126+
127+
test_identical_constructorReference_aliasIsProperRename_mutualSubtypes() async {
128+
await resolveTestCode('''
129+
class C<T> {}
130+
typedef MyC<T extends Object?> = C<T>;
131+
const a = identical(MyC<int>.new, MyC<int>.new);
132+
''');
133+
expect(
134+
_evaluateConstant('a'),
135+
_boolValue(true),
136+
);
137+
}
138+
139+
test_identical_constructorReference_aliasIsProperRename_uninstantiated() async {
140+
await resolveTestCode('''
141+
class C<T> {}
142+
typedef MyC<T> = C<T>;
143+
const a = identical(MyC.new, MyC.new);
144+
''');
145+
expect(
146+
_evaluateConstant('a'),
147+
_boolValue(true),
148+
);
149+
}
150+
31151
test_identical_constructorReference_explicitTypeArgs_differentClasses() async {
32152
await resolveTestCode('''
33153
class C<T> {}
@@ -76,6 +196,19 @@ const a = identical(C<int>.new, C<int>.new);
76196
);
77197
}
78198

199+
test_identical_constructorReference_inferredTypeArgs_sameElement() async {
200+
await resolveTestCode('''
201+
class C<T> {}
202+
const C<int> Function() c1 = C.new;
203+
const c2 = C<int>.new;
204+
const a = identical(c1, c2);
205+
''');
206+
expect(
207+
_evaluateConstant('a'),
208+
_boolValue(true),
209+
);
210+
}
211+
79212
test_identical_constructorReference_notInstantiated_differentClasses() async {
80213
await resolveTestCode('''
81214
class C<T> {}

pkg/analyzer/test/src/dart/resolution/constructor_reference_test.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,26 @@ void bar() {
8585
);
8686
}
8787

88+
test_alias_generic_uninstantiated_const() async {
89+
await assertNoErrorsInCode('''
90+
class A<T, U> {
91+
const A.foo();
92+
}
93+
typedef TA<T, U> = A<U, T>;
94+
95+
const a = TA.foo;
96+
''');
97+
98+
var classElement = findElement.class_('A');
99+
assertConstructorReference(
100+
findNode.constructorReference('TA.foo;'),
101+
classElement.getNamedConstructor('foo'),
102+
classElement,
103+
'A<U, T> Function<T, U>()',
104+
expectedTypeNameElement: findElement.typeAlias('TA'),
105+
);
106+
}
107+
88108
test_alias_generic_unnamed() async {
89109
await assertNoErrorsInCode('''
90110
class A<T> {

pkg/analyzer/test/src/dart/resolution/resolution.dart

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,12 @@ mixin ResolutionTest implements ResourceProviderMixin {
150150
}
151151
}
152152

153-
void assertConstructorElement(
154-
ConstructorElement? expected, ConstructorElement? actual) {
155-
if (expected is ConstructorMember && actual is ConstructorMember) {
156-
expect(expected.declaration, same(actual.declaration));
153+
void assertConstructorElement(ConstructorElement? actual, Object? expected) {
154+
if (actual is ConstructorMember && expected is ConstructorMember) {
155+
expect(actual.declaration, same(expected.declaration));
157156
// TODO(brianwilkerson) Compare the type arguments of the two members.
158157
} else {
159-
expect(expected, same(actual));
158+
assertElement(actual, expected);
160159
}
161160
}
162161

@@ -168,12 +167,11 @@ mixin ResolutionTest implements ResourceProviderMixin {
168167
PrefixElement? expectedPrefix,
169168
Element? expectedTypeNameElement,
170169
}) {
171-
var actualConstructorElement = getNodeElement(node) as ConstructorElement?;
172170
var actualConstructorName = node.constructorName.name;
173171
if (actualConstructorName != null) {
174172
assertConstructorElement(
175173
actualConstructorName.staticElement as ConstructorElement?,
176-
actualConstructorElement,
174+
expectedConstructorElement,
177175
);
178176
}
179177

@@ -182,8 +180,13 @@ mixin ResolutionTest implements ResourceProviderMixin {
182180

183181
var namedType = node.constructorName.type2;
184182
expectedTypeNameElement ??= expectedClassElement;
185-
assertNamedType(namedType, expectedTypeNameElement, null,
186-
expectedPrefix: expectedPrefix);
183+
assertNamedType(
184+
namedType, expectedTypeNameElement,
185+
// The [NamedType] child node of the [ConstructorName] should not have a
186+
// static type.
187+
null,
188+
expectedPrefix: expectedPrefix,
189+
);
187190
}
188191

189192
void assertConstructors(ClassElement class_, List<String> expected) {

0 commit comments

Comments
 (0)