Skip to content

Commit 58f9beb

Browse files
stereotype441Commit Queue
authored andcommitted
[mini_types] Add a Type.substitute method.
This method will be used by a follow-up CL that adds support for generic functions, since the implementation of `==` and `hashCode` for generic functions needs to perform substitutions in order to recognize that alpha-equivalent types (such as `T Function<T>()` and `U Function<U>`) are equal. Change-Id: If641c4d0ae48708abf3701853014dc52dfc559c5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/395684 Reviewed-by: Chloe Stefantsova <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent e1c76b2 commit 58f9beb

File tree

2 files changed

+172
-8
lines changed

2 files changed

+172
-8
lines changed

pkg/_fe_analyzer_shared/test/mini_types.dart

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,24 @@ class FunctionType extends Type
130130
nullabilitySuffix: nullabilitySuffix);
131131
}
132132

133+
@override
134+
FunctionType? substitute(Map<TypeParameter, Type> substitution) {
135+
var newReturnType = returnType.substitute(substitution);
136+
var newPositionalParameters = positionalParameters.substitute(substitution);
137+
var newNamedParameters = namedParameters.substitute(substitution);
138+
if (newReturnType == null &&
139+
newPositionalParameters == null &&
140+
newNamedParameters == null) {
141+
return null;
142+
} else {
143+
return FunctionType(newReturnType ?? returnType,
144+
newPositionalParameters ?? positionalParameters,
145+
requiredPositionalParameterCount: requiredPositionalParameterCount,
146+
namedParameters: newNamedParameters ?? namedParameters,
147+
nullabilitySuffix: nullabilitySuffix);
148+
}
149+
}
150+
133151
@override
134152
Type withNullability(NullabilitySuffix suffix) =>
135153
FunctionType(returnType, positionalParameters,
@@ -179,6 +197,13 @@ class FutureOrType extends PrimaryType {
179197
return FutureOrType(newArg, nullabilitySuffix: nullabilitySuffix);
180198
}
181199

200+
@override
201+
Type? substitute(Map<TypeParameter, Type> substitution) {
202+
var newArg = typeArgument.substitute(substitution);
203+
if (newArg == null) return null;
204+
return FutureOrType(newArg, nullabilitySuffix: nullabilitySuffix);
205+
}
206+
182207
@override
183208
Type withNullability(NullabilitySuffix suffix) =>
184209
FutureOrType(typeArgument, nullabilitySuffix: suffix);
@@ -204,7 +229,9 @@ class InvalidType extends _SpecialSimpleType
204229

205230
/// A named parameter of a function type.
206231
class NamedFunctionParameter
207-
implements SharedNamedFunctionParameterStructure<Type> {
232+
implements
233+
SharedNamedFunctionParameterStructure<Type>,
234+
_Substitutable<NamedFunctionParameter> {
208235
@override
209236
final String name;
210237

@@ -227,11 +254,20 @@ class NamedFunctionParameter
227254
type == other.type &&
228255
isRequired == other.isRequired;
229256

257+
@override
258+
NamedFunctionParameter? substitute(Map<TypeParameter, Type> substitution) {
259+
var newType = type.substitute(substitution);
260+
if (newType == null) return null;
261+
return NamedFunctionParameter(
262+
isRequired: isRequired, name: name, type: newType);
263+
}
264+
230265
@override
231266
String toString() => [if (isRequired) 'required', type, name].join(' ');
232267
}
233268

234-
class NamedType implements SharedNamedTypeStructure<Type> {
269+
class NamedType
270+
implements SharedNamedTypeStructure<Type>, _Substitutable<NamedType> {
235271
@override
236272
final String name;
237273

@@ -246,6 +282,13 @@ class NamedType implements SharedNamedTypeStructure<Type> {
246282
@override
247283
bool operator ==(Object other) =>
248284
other is NamedType && name == other.name && type == other.type;
285+
286+
@override
287+
NamedType? substitute(Map<TypeParameter, Type> substitution) {
288+
var newType = type.substitute(substitution);
289+
if (newType == null) return null;
290+
return NamedType(name: name, type: newType);
291+
}
249292
}
250293

251294
/// Representation of the type `Never` suitable for unit testing of code in the
@@ -348,6 +391,14 @@ class PrimaryType extends Type {
348391
args: newArgs, nullabilitySuffix: nullabilitySuffix);
349392
}
350393

394+
@override
395+
Type? substitute(Map<TypeParameter, Type> substitution) {
396+
var newArgs = args.substitute(substitution);
397+
if (newArgs == null) return null;
398+
return PrimaryType._(nameInfo,
399+
args: newArgs, nullabilitySuffix: nullabilitySuffix);
400+
}
401+
351402
@override
352403
Type withNullability(NullabilitySuffix suffix) =>
353404
PrimaryType._(nameInfo, args: args, nullabilitySuffix: suffix);
@@ -439,6 +490,17 @@ class RecordType extends Type implements SharedRecordTypeStructure<Type> {
439490
);
440491
}
441492

493+
@override
494+
Type? substitute(Map<TypeParameter, Type> substitution) {
495+
var newPositionalTypes = positionalTypes.substitute(substitution);
496+
var newNamedTypes = namedTypes.substitute(substitution);
497+
if (newPositionalTypes == null && newNamedTypes == null) return null;
498+
return RecordType(
499+
positionalTypes: newPositionalTypes ?? positionalTypes,
500+
namedTypes: newNamedTypes ?? namedTypes,
501+
nullabilitySuffix: nullabilitySuffix);
502+
}
503+
442504
@override
443505
Type withNullability(NullabilitySuffix suffix) => RecordType(
444506
positionalTypes: positionalTypes,
@@ -502,7 +564,7 @@ class SpecialTypeName extends TypeNameInfo {
502564

503565
/// Representation of a type suitable for unit testing of code in the
504566
/// `_fe_analyzer_shared` package.
505-
abstract class Type implements SharedTypeStructure<Type> {
567+
abstract class Type implements SharedTypeStructure<Type>, _Substitutable<Type> {
506568
@override
507569
final NullabilitySuffix nullabilitySuffix;
508570

@@ -582,11 +644,11 @@ sealed class TypeNameInfo {
582644
/// An assertion in the [PrimaryType] constructor verifies this.
583645
///
584646
/// This ensures that the methods [Type.closureWithRespectToUnknown],
585-
/// [Type.recursivelyDemote], and [Type.withNullability] (which create new
586-
/// instances of [Type] based on old ones) create the appropriate subtype of
587-
/// [Type]. It also ensures that when [Type] objects are directly constructed
588-
/// (as they are in this file and in `mini_ast.dart`), the appropriate subtype
589-
/// of [Type] is used.
647+
/// [Type.recursivelyDemote], [Type.substitute], and [Type.withNullability]
648+
/// (which create new instances of [Type] based on old ones) create the
649+
/// appropriate subtype of [Type]. It also ensures that when [Type] objects
650+
/// are directly constructed (as they are in this file and in
651+
/// `mini_ast.dart`), the appropriate subtype of [Type] is used.
590652
final core.Type _expectedRuntimeType;
591653

592654
TypeNameInfo(this.name, {required core.Type expectedRuntimeType})
@@ -674,6 +736,10 @@ class TypeParameterType extends Type {
674736
}
675737
}
676738

739+
@override
740+
Type? substitute(Map<TypeParameter, Type> substitution) =>
741+
substitution[typeParameter];
742+
677743
@override
678744
Type withNullability(NullabilitySuffix suffix) =>
679745
TypeParameterType(typeParameter,
@@ -1411,6 +1477,9 @@ class UnknownType extends Type implements SharedUnknownTypeStructure<Type> {
14111477
@override
14121478
Type? recursivelyDemote({required bool covariant}) => null;
14131479

1480+
@override
1481+
Type? substitute(Map<TypeParameter, Type> substitution) => null;
1482+
14141483
@override
14151484
Type withNullability(NullabilitySuffix suffix) =>
14161485
UnknownType(nullabilitySuffix: suffix);
@@ -1448,6 +1517,22 @@ abstract class _SpecialSimpleType extends PrimaryType {
14481517

14491518
@override
14501519
Type? recursivelyDemote({required bool covariant}) => null;
1520+
1521+
@override
1522+
Type? substitute(Map<TypeParameter, Type> substitution) => null;
1523+
}
1524+
1525+
/// Interface for [Type] and the data structures that comprise it, allowing
1526+
/// type substitutions to be performed.
1527+
abstract class _Substitutable<T extends _Substitutable<T>> {
1528+
/// If `this` contains any references to a [TypeParameter] matching one of the
1529+
/// keys in [substitution], returns a clone of `this` with those references
1530+
/// replaced by the corresponding value. Otherwise returns `null`.
1531+
///
1532+
/// For example, if `t` is a reference to the [TypeParameter] object
1533+
/// representing `T`, then `Type('Map<T, U>`).substitute({t: Type('int')})`
1534+
/// returns a [Type] object representing `Map<int, U>`.
1535+
T? substitute(Map<TypeParameter, Type> substitution);
14511536
}
14521537

14531538
class _TypeParser {
@@ -1878,3 +1963,25 @@ extension on List<Type> {
18781963
return newList;
18791964
}
18801965
}
1966+
1967+
extension<T extends _Substitutable<T>> on List<T> {
1968+
/// Helper method for performing substitutions on the constituent parts of a
1969+
/// [Type] that are stored in lists.
1970+
///
1971+
/// Calls [_Substitutable.substitute] on each element of the list; if all
1972+
/// those calls returned `null` (meaning no substitutions were done), returns
1973+
/// `null`. Otherwise returns a new [List] in which each element requiring
1974+
/// substitutions is replaced with the substitution result.
1975+
List<T>? substitute(Map<TypeParameter, Type> substitution) {
1976+
List<T>? result;
1977+
for (int i = 0; i < length; i++) {
1978+
var oldListElement = this[i];
1979+
var newType = oldListElement.substitute(substitution);
1980+
if (newType != null && result == null) {
1981+
result = sublist(0, i);
1982+
}
1983+
result?.add(newType ?? oldListElement);
1984+
}
1985+
return result;
1986+
}
1987+
}

pkg/_fe_analyzer_shared/test/mini_types_test.dart

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,4 +1020,61 @@ main() {
10201020
});
10211021
});
10221022
});
1023+
1024+
group('substitute:', () {
1025+
test('FunctionType', () {
1026+
expect(Type('int Function(int, {int i})').substitute({t: Type('String')}),
1027+
isNull);
1028+
expect(Type('T Function(int, {int i})').substitute({t: Type('String')}),
1029+
Type('String Function(int, {int i})'));
1030+
expect(Type('int Function(T, {int i})?').substitute({t: Type('String')}),
1031+
Type('int Function(String, {int i})?'));
1032+
expect(Type('int Function(int, {T i})').substitute({t: Type('String')}),
1033+
Type('int Function(int, {String i})'));
1034+
expect(Type('int Function(T, T)').substitute({t: Type('String')}),
1035+
Type('int Function(String, String)'));
1036+
expect(Type('int Function({T t1, T t2})').substitute({t: Type('String')}),
1037+
Type('int Function({String t1, String t2})'));
1038+
});
1039+
1040+
test('PrimaryType', () {
1041+
expect(Type('Map<int, int>').substitute({t: Type('String')}), isNull);
1042+
expect(Type('Map<T, int>').substitute({t: Type('String')}),
1043+
Type('Map<String, int>'));
1044+
expect(Type('Map<int, T>').substitute({t: Type('String')}),
1045+
Type('Map<int, String>'));
1046+
expect(Type('Map<T, T>').substitute({t: Type('String')}),
1047+
Type('Map<String, String>'));
1048+
expect(Type('dynamic').substitute({t: Type('String')}), isNull);
1049+
expect(Type('error').substitute({t: Type('String')}), isNull);
1050+
expect(Type('Never').substitute({t: Type('String')}), isNull);
1051+
expect(Type('Null').substitute({t: Type('String')}), isNull);
1052+
expect(Type('void').substitute({t: Type('String')}), isNull);
1053+
expect(Type('FutureOr<int>').substitute({t: Type('String')}), isNull);
1054+
expect(Type('FutureOr<T>').substitute({t: Type('String')}),
1055+
Type('FutureOr<String>'));
1056+
});
1057+
1058+
test('RecordType', () {
1059+
expect(Type('(int, {int i})').substitute({t: Type('String')}), isNull);
1060+
expect(Type('(T, {int i})?').substitute({t: Type('String')}),
1061+
Type('(String, {int i})?'));
1062+
expect(Type('(int, {T i})').substitute({t: Type('String')}),
1063+
Type('(int, {String i})'));
1064+
expect(Type('(T, T)').substitute({t: Type('String')}),
1065+
Type('(String, String)'));
1066+
expect(Type('({T t1, T t2})').substitute({t: Type('String')}),
1067+
Type('({String t1, String t2})'));
1068+
});
1069+
1070+
test('TypeParameterType', () {
1071+
expect(Type('T').substitute({u: Type('String')}), isNull);
1072+
expect(Type('T').substitute({t: Type('String')}), Type('String'));
1073+
expect(Type('T&Object').substitute({t: Type('String')}), Type('String'));
1074+
});
1075+
1076+
test('UnknownType', () {
1077+
expect(Type('_').substitute({t: Type('String')}), isNull);
1078+
});
1079+
});
10231080
}

0 commit comments

Comments
 (0)