Skip to content

Commit 5ec81d0

Browse files
stereotype441Commit Queue
authored andcommitted
[mini_types] Add support for type parameters of function types with bounds.
This required improving the implementation of `operator == ` for function types, to check that the bounds match (after performing appropriate substitutions). It also required modifiying `FunctionType.substitute` to apply the substitution to the bounds. The API for `TypeParameter` was changed slightly, so that it's possible to distinguish between an explicit bound of `Object?` an an implicit one. This allows `FunctionType.toString()` to avoid outputting `extends Object?` next to a type parameter that has an implicit bound. Change-Id: Iad31a44a1f87ca0cd830b8c802495b7ce2c1caab Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/396141 Reviewed-by: Chloe Stefantsova <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent 6dac184 commit 5ec81d0

File tree

3 files changed

+194
-23
lines changed

3 files changed

+194
-23
lines changed

pkg/_fe_analyzer_shared/test/mini_types.dart

Lines changed: 103 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,23 @@ class FunctionType extends Type
164164
..excludeNamesUsedIn(other);
165165
var thisSubstitution = <TypeParameter, Type>{};
166166
var otherSubstitution = <TypeParameter, Type>{};
167+
var thisTypeFormalBounds = <Type>[];
168+
var otherTypeFormalBounds = <Type>[];
167169
for (var i = 0; i < typeFormals.length; i++) {
168170
var freshTypeParameterType =
169171
TypeParameterType(freshTypeParameterGenerator.generate());
170172
thisSubstitution[typeFormals[i]] = freshTypeParameterType;
171173
otherSubstitution[other.typeFormals[i]] = freshTypeParameterType;
174+
thisTypeFormalBounds.add(typeFormals[i].bound);
175+
otherTypeFormalBounds.add(other.typeFormals[i].bound);
172176
}
173-
return substitute(thisSubstitution, dropTypeFormals: true) ==
174-
other.substitute(otherSubstitution, dropTypeFormals: true);
177+
return const ListEquality().equals(
178+
thisTypeFormalBounds.substitute(thisSubstitution) ??
179+
thisTypeFormalBounds,
180+
otherTypeFormalBounds.substitute(otherSubstitution) ??
181+
otherTypeFormalBounds) &&
182+
substitute(thisSubstitution, dropTypeFormals: true) ==
183+
other.substitute(otherSubstitution, dropTypeFormals: true);
175184
} else {
176185
return returnType == other.returnType &&
177186
const ListEquality()
@@ -212,6 +221,7 @@ class FunctionType extends Type
212221
}
213222
for (var typeFormal in typeFormals) {
214223
identifiers.add(typeFormal.name);
224+
typeFormal.explicitBound?.gatherUsedIdentifiers(identifiers);
215225
}
216226
for (var namedParameter in namedParameters) {
217227
// As explained in the documentation for `Type.gatherUsedIdentifiers`,
@@ -246,11 +256,43 @@ class FunctionType extends Type
246256
@override
247257
FunctionType? substitute(Map<TypeParameter, Type> substitution,
248258
{bool dropTypeFormals = false}) {
259+
List<TypeParameter>? newTypeFormals;
260+
if (typeFormals.isNotEmpty) {
261+
if (dropTypeFormals) {
262+
newTypeFormals = const <TypeParameter>[];
263+
} else {
264+
// Check if any of the type formal bounds will be changed by the
265+
// substitution.
266+
if (typeFormals.any((typeFormal) =>
267+
typeFormal.explicitBound?.substitute(substitution) != null)) {
268+
// Yes, at least one of the type formal bounds will be changed by the
269+
// substitution. So that type formal will have to be replaced by a
270+
// fresh one. Since type formal bounds can refer to other type
271+
// formals, other type formals might need to be replaced by fresh ones
272+
// too. To make things easier, go ahead and replace all the type
273+
// formals. Also, extend the substitution so that any references to
274+
// old type formals will be replaced by references to the new type
275+
// formals.
276+
substitution = {...substitution};
277+
newTypeFormals = [];
278+
for (var typeFormal in typeFormals) {
279+
var newTypeFormal = TypeParameter._(typeFormal.name);
280+
newTypeFormals.add(newTypeFormal);
281+
substitution[typeFormal] = TypeParameterType(newTypeFormal);
282+
}
283+
// Now that the substitution has been created, fix up all the bounds.
284+
for (var i = 0; i < typeFormals.length; i++) {
285+
if (typeFormals[i].explicitBound case var bound?) {
286+
newTypeFormals[i].explicitBound =
287+
bound.substitute(substitution) ?? bound;
288+
}
289+
}
290+
}
291+
}
292+
}
293+
249294
var newReturnType = returnType.substitute(substitution);
250295
var newPositionalParameters = positionalParameters.substitute(substitution);
251-
var newTypeFormals = dropTypeFormals && !typeFormals.isEmpty
252-
? const <TypeParameter>[]
253-
: null;
254296
var newNamedParameters = namedParameters.substitute(substitution);
255297
if (newReturnType == null &&
256298
newPositionalParameters == null &&
@@ -277,7 +319,18 @@ class FunctionType extends Type
277319

278320
@override
279321
String _toStringWithoutSuffix({required bool parenthesizeIfComplex}) {
280-
var formals = typeFormals.isEmpty ? '' : '<${typeFormals.join(', ')}>';
322+
var formals = '';
323+
if (typeFormals.isNotEmpty) {
324+
var formalStrings = <String>[];
325+
for (var typeFormal in typeFormals) {
326+
if (typeFormal.explicitBound case var bound?) {
327+
formalStrings.add('${typeFormal.name} extends $bound');
328+
} else {
329+
formalStrings.add(typeFormal.name);
330+
}
331+
}
332+
formals = '<${formalStrings.join(', ')}>';
333+
}
281334
var parameters = <Object>[
282335
...positionalParameters.sublist(0, requiredPositionalParameterCount)
283336
];
@@ -817,13 +870,17 @@ sealed class TypeNameInfo {
817870
/// A type name that represents a type variable.
818871
class TypeParameter extends TypeNameInfo
819872
implements SharedTypeParameterStructure<Type> {
820-
/// The type variable's bound. Defaults to `Object?`.
821-
@override
822-
Type bound;
873+
/// The type variable's bound. If `null`, the bound is `Object?`.
874+
///
875+
/// This is non-final because it needs to be possible to set it after
876+
/// construction, in order to create "F-bounded" type parameters (type
877+
/// parameters whose bound refers to the type parameter itself).
878+
Type? explicitBound;
879+
880+
TypeParameter._(super.name) : super(expectedRuntimeType: TypeParameterType);
823881

824-
TypeParameter._(super.name)
825-
: bound = Type('Object?'),
826-
super(expectedRuntimeType: TypeParameterType);
882+
@override
883+
Type get bound => explicitBound ?? Type('Object?');
827884

828885
@override
829886
String get displayName => name;
@@ -1673,7 +1730,7 @@ class VoidType extends _SpecialSimpleType
16731730
/// meaning assigned to its identifiers yet.
16741731
class _PreFunctionType extends _PreType {
16751732
final _PreType returnType;
1676-
final List<TypeParameter> typeFormals;
1733+
final List<_PreTypeFormal> typeFormals;
16771734
final List<_PreType> positionalParameterTypes;
16781735
final int requiredPositionalParameterCount;
16791736
final List<_PreNamedFunctionParameter> namedParameters;
@@ -1687,11 +1744,23 @@ class _PreFunctionType extends _PreType {
16871744

16881745
@override
16891746
Type materialize({required Map<String, TypeParameter> typeFormalScope}) {
1747+
List<TypeParameter> materializedTypeFormals;
16901748
if (typeFormals.isNotEmpty) {
1749+
materializedTypeFormals = <TypeParameter>[];
16911750
typeFormalScope = Map.of(typeFormalScope);
16921751
for (var typeFormal in typeFormals) {
1693-
typeFormalScope[typeFormal.name] = typeFormal;
1752+
var materializedTypeFormal = TypeParameter._(typeFormal.name);
1753+
materializedTypeFormals.add(materializedTypeFormal);
1754+
typeFormalScope[typeFormal.name] = materializedTypeFormal;
1755+
}
1756+
for (var i = 0; i < typeFormals.length; i++) {
1757+
if (typeFormals[i].bound case var bound?) {
1758+
materializedTypeFormals[i].explicitBound =
1759+
bound.materialize(typeFormalScope: typeFormalScope);
1760+
}
16941761
}
1762+
} else {
1763+
materializedTypeFormals = const [];
16951764
}
16961765
return FunctionType(
16971766
returnType.materialize(typeFormalScope: typeFormalScope),
@@ -1700,7 +1769,7 @@ class _PreFunctionType extends _PreType {
17001769
positionalParameterType.materialize(
17011770
typeFormalScope: typeFormalScope)
17021771
],
1703-
typeFormals: typeFormals,
1772+
typeFormals: materializedTypeFormals,
17041773
requiredPositionalParameterCount: requiredPositionalParameterCount,
17051774
namedParameters: [
17061775
for (var namedParameter in namedParameters)
@@ -1846,6 +1915,15 @@ sealed class _PreType {
18461915
Type materialize({required Map<String, TypeParameter> typeFormalScope});
18471916
}
18481917

1918+
/// Representation of a formal parameter of a function type that has been parsed
1919+
/// but hasn't had meaning assigned to its identifiers yet.
1920+
class _PreTypeFormal {
1921+
final String name;
1922+
final _PreType? bound;
1923+
1924+
_PreTypeFormal({required this.name, required this.bound});
1925+
}
1926+
18491927
/// Representation of a [Type] with a nullability suffix that has been parsed
18501928
/// but hasn't had meaning assigned to its identifiers yet.
18511929
class _PreTypeWithNullability extends _PreType {
@@ -2050,7 +2128,7 @@ class _TypeParser {
20502128
return _PrePromotedType(inner: type, promotion: promotion);
20512129
} else if (_currentToken == 'Function') {
20522130
_next();
2053-
List<TypeParameter>? typeFormals;
2131+
List<_PreTypeFormal> typeFormals;
20542132
if (_currentToken == '<') {
20552133
typeFormals = _parseTypeFormals();
20562134
} else {
@@ -2094,7 +2172,7 @@ class _TypeParser {
20942172
requiredPositionalParameterCount: requiredPositionalParameterCount ??
20952173
positionalParameterTypes.length,
20962174
namedParameters: namedFunctionParameters ?? const [],
2097-
typeFormals: typeFormals ?? const []);
2175+
typeFormals: typeFormals);
20982176
} else {
20992177
return null;
21002178
}
@@ -2139,17 +2217,22 @@ class _TypeParser {
21392217
return result;
21402218
}
21412219

2142-
List<TypeParameter>? _parseTypeFormals() {
2220+
List<_PreTypeFormal> _parseTypeFormals() {
21432221
assert(_currentToken == '<');
21442222
_next();
2145-
var typeFormals = <TypeParameter>[];
2223+
var typeFormals = <_PreTypeFormal>[];
21462224
while (true) {
21472225
var name = _currentToken;
21482226
if (_identifierRegexp.matchAsPrefix(name) == null) {
21492227
_parseFailure('Expected an identifier');
21502228
}
2151-
typeFormals.add(TypeParameter._(name));
21522229
_next();
2230+
_PreType? bound;
2231+
if (_currentToken == 'extends') {
2232+
_next();
2233+
bound = _parseType();
2234+
}
2235+
typeFormals.add(_PreTypeFormal(name: name, bound: bound));
21532236
if (_currentToken == ',') {
21542237
_next();
21552238
continue;

pkg/_fe_analyzer_shared/test/mini_types_test.dart

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,19 @@ main() {
7474
'T Function(U, {V y})');
7575
});
7676

77-
test('type formals', () {
77+
test('type formals, unbounded', () {
7878
expect(
7979
FunctionType(VoidType.instance, [], typeFormals: [t, u]).toString(),
8080
'void Function<T, U>()');
8181
});
8282

83+
test('type formals, bounded', () {
84+
t.explicitBound = TypeParameterType(u);
85+
expect(
86+
FunctionType(VoidType.instance, [], typeFormals: [t, u]).toString(),
87+
'void Function<T extends U, U>()');
88+
});
89+
8390
test('needs parentheses', () {
8491
expect(
8592
TypeParameterType(t, promotion: FunctionType(VoidType.instance, []))
@@ -443,6 +450,25 @@ main() {
443450
same(t));
444451
});
445452

453+
test('unbounded', () {
454+
var type = Type('void Function<T>()') as FunctionType;
455+
var t = type.typeFormals.single;
456+
expect(t.explicitBound, isNull);
457+
});
458+
459+
test('bounded', () {
460+
var type = Type('void Function<T extends Object>()') as FunctionType;
461+
var t = type.typeFormals.single;
462+
expect(t.explicitBound!.type, 'Object');
463+
});
464+
465+
test('F-bounded', () {
466+
var type = Type('void Function<T extends U, U>()') as FunctionType;
467+
var t = type.typeFormals[0];
468+
var u = type.typeFormals[1];
469+
expect((t.explicitBound as TypeParameterType).typeParameter, same(u));
470+
});
471+
446472
test('invalid token in type formals', () {
447473
expect(() => Type('int Function<{>()'), throwsParseError);
448474
});
@@ -605,6 +631,16 @@ main() {
605631
Type('void Function<U, T>({U p1, T p2})'));
606632
checkNotEqual(Type('void Function<T, U>({T p1, U p2})'),
607633
Type('void Function<T, U>({U p1, T p2})'));
634+
checkEqual(Type('void Function<T extends Object>()'),
635+
Type('void Function<U extends Object>()'));
636+
checkEqual(Type('void Function<T extends Object?>()'),
637+
Type('void Function<U>()'));
638+
checkNotEqual(Type('void Function<T extends Object>()'),
639+
Type('void Function<U extends int>()'));
640+
checkEqual(Type('void Function<T extends U, U>()'),
641+
Type('void Function<V extends W, W>()'));
642+
checkEqual(Type('void Function<T>(void Function<U extends T>(T, U))'),
643+
Type('void Function<V>(void Function<W extends V>(V, W))'));
608644

609645
// For these final test cases, we give one of the type parameters a name
610646
// that would be chosen by `FreshTypeParameterGenerator`, to verify that
@@ -1142,6 +1178,8 @@ main() {
11421178
test('FunctionType', () {
11431179
expect(queryUsedIdentifiers(Type('int Function<X>(String, {bool b})')),
11441180
unorderedEquals({'int', 'X', 'String', 'bool', 'b'}));
1181+
expect(queryUsedIdentifiers(Type('void Function<X extends int>()')),
1182+
unorderedEquals({'void', 'X', 'int'}));
11451183
});
11461184

11471185
test('PrimaryType', () {
@@ -1199,6 +1237,55 @@ main() {
11991237
Type('int Function(String, String)'));
12001238
expect(Type('int Function({T t1, T t2})').substitute({t: Type('String')}),
12011239
Type('int Function({String t1, String t2})'));
1240+
1241+
// Verify that bounds of type parameters are substituted
1242+
var origType = Type(
1243+
'Map<U, V> Function<U extends T, V extends U>(U, V, {U u, V v})');
1244+
var substitutedType =
1245+
origType.substitute({t: Type('String')}) as FunctionType;
1246+
expect(
1247+
substitutedType,
1248+
Type('Map<U, V> Function<U extends String, V extends U>(U, V, {U u, '
1249+
'V v})'));
1250+
// And verify that references to the type parameters now point to the
1251+
// new, updated type parameters.
1252+
expect(
1253+
((substitutedType.returnType as PrimaryType).args[0]
1254+
as TypeParameterType)
1255+
.typeParameter,
1256+
same(substitutedType.typeFormals[0]));
1257+
expect(
1258+
((substitutedType.returnType as PrimaryType).args[1]
1259+
as TypeParameterType)
1260+
.typeParameter,
1261+
same(substitutedType.typeFormals[1]));
1262+
expect(
1263+
(substitutedType.typeFormals[1].explicitBound as TypeParameterType)
1264+
.typeParameter,
1265+
same(substitutedType.typeFormals[0]));
1266+
expect(
1267+
(substitutedType.positionalParameters[0] as TypeParameterType)
1268+
.typeParameter,
1269+
same(substitutedType.typeFormals[0]));
1270+
expect(
1271+
(substitutedType.positionalParameters[1] as TypeParameterType)
1272+
.typeParameter,
1273+
same(substitutedType.typeFormals[1]));
1274+
expect(
1275+
(substitutedType.namedParameters[0].type as TypeParameterType)
1276+
.typeParameter,
1277+
same(substitutedType.typeFormals[0]));
1278+
expect(
1279+
(substitutedType.namedParameters[1].type as TypeParameterType)
1280+
.typeParameter,
1281+
same(substitutedType.typeFormals[1]));
1282+
// Finally, verify that the original type didn't change (this is important
1283+
// because `TypeParameter.explicitBound` is non-final in order to allow
1284+
// for the creation of F-bounded types).
1285+
expect(
1286+
origType,
1287+
Type('Map<U, V> Function<U extends T, V extends U>(U, V, {U u, '
1288+
'V v})'));
12021289
});
12031290

12041291
test('PrimaryType', () {

pkg/_fe_analyzer_shared/test/type_inference/type_constraint_gatherer_test.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ main() {
833833
var tcg = _TypeConstraintGatherer({'T'});
834834
check(tcg.performSubtypeConstraintGenerationInternal(
835835
TypeParameterType(TypeRegistry.addTypeParameter('X')
836-
..bound = Type('Future<String>')),
836+
..explicitBound = Type('Future<String>')),
837837
Type('Future<T>'),
838838
leftSchema: false,
839839
astNodeForTesting: Node.placeholder()))
@@ -845,7 +845,8 @@ main() {
845845
var tcg = _TypeConstraintGatherer({'T'});
846846
check(tcg.performSubtypeConstraintGenerationInternal(
847847
TypeParameterType(
848-
TypeRegistry.addTypeParameter('X')..bound = Type('Object'),
848+
TypeRegistry.addTypeParameter('X')
849+
..explicitBound = Type('Object'),
849850
promotion: Type('Future<num>')),
850851
Type('Future<T>'),
851852
leftSchema: false,

0 commit comments

Comments
 (0)