Migration: clean up handling of type substitutions.

Previously, when the migration tool needed to change one type variable
to another during a substitution, it created a DecortedType to
represent the substitution using
`DecoratedType._forTypeParameterSubstitution`.  This was problematic
because the resulting DecoratedType had a null nullability `node`.
This in turn forced nearly every reference to `DecoratedType.node` to
be followed by a `!` operator.

With this change, we now have a new base class, `SubstitutedType`,
which can either be a `DecoratedType` (representing a fully general
substitution) or a `_TypeVariableReplacement` (representing a simple
change from one type variable to another).  This paves the way for a
follow-up CL that will make `DecoratedType.node` non-nullable.

Change-Id: I7fdaef57994c7678ecd517ab4fe84d00f0d81424
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/261941
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry 2022-09-30 13:14:31 +00:00 committed by Commit Queue
parent fef905cbe2
commit 175385825a
4 changed files with 73 additions and 49 deletions

View file

@ -84,10 +84,10 @@ class DecoratedClassHierarchy {
var supertype = decoratedSupertype.type as InterfaceType;
// Compute a type substitution to determine how [class_] relates to
// this specific [superclass].
Map<TypeParameterElement, DecoratedType?> substitution = {};
Map<TypeParameterElement, DecoratedType> substitution = {};
for (int i = 0; i < supertype.typeArguments.length; i++) {
substitution[supertype.element2.typeParameters[i]] =
decoratedSupertype.typeArguments[i];
decoratedSupertype.typeArguments[i]!;
}
// Apply that substitution to the relation between [superclass] and
// each of its transitive superclasses, to determine the relation

View file

@ -16,7 +16,7 @@ import 'package:nnbd_migration/src/nullability_node_target.dart';
/// Representation of a type in the code to be migrated. In addition to
/// tracking the (unmigrated) [DartType], we track the [ConstraintVariable]s
/// indicating whether the type, and the types that compose it, are nullable.
class DecoratedType implements DecoratedTypeInfo {
class DecoratedType implements DecoratedTypeInfo, SubstitutedType {
@override
final DartType? type;
@ -169,25 +169,6 @@ class DecoratedType implements DecoratedTypeInfo {
}
}
/// Creates a [DecoratedType] for a synthetic type parameter, to be used
/// during comparison of generic function types.
DecoratedType._forTypeParameterSubstitution(TypeParameterElement parameter)
: type = TypeParameterTypeImpl(
element2: parameter,
nullabilitySuffix: NullabilitySuffix.star,
),
node = null,
returnType = null,
positionalParameters = const [],
namedParameters = const {},
typeArguments = const [] {
// We'll be storing the type parameter bounds in
// [_decoratedTypeParameterBounds] so the type parameter needs to have an
// enclosing element of `null`.
assert(parameter.enclosingElement == null,
'$parameter should not have parent ${parameter.enclosingElement}');
}
/// If `this` represents an interface type, returns the substitution necessary
/// to produce this type using the class's type as a starting point.
/// Otherwise throws an exception.
@ -195,11 +176,13 @@ class DecoratedType implements DecoratedTypeInfo {
/// For instance, if `this` represents `List<int?1>`, returns the substitution
/// `{T: int?1}`, where `T` is the [TypeParameterElement] for `List`'s type
/// parameter.
Map<TypeParameterElement, DecoratedType?> get asSubstitution {
Map<TypeParameterElement, DecoratedType> get asSubstitution {
var type = this.type;
if (type is InterfaceType) {
return Map<TypeParameterElement, DecoratedType?>.fromIterables(
type.element2.typeParameters, typeArguments);
return {
for (int i = 0; i < typeArguments.length; i++)
type.element2.typeParameters[i]: typeArguments[i]!
};
} else {
throw StateError(
'Tried to convert a non-interface type to a substitution');
@ -311,14 +294,14 @@ class DecoratedType implements DecoratedTypeInfo {
/// [undecoratedResult] is the result of the substitution, as determined by
/// the normal type system. If not supplied, it is inferred.
DecoratedType substitute(
Map<TypeParameterElement, DecoratedType?> substitution,
Map<TypeParameterElement, SubstitutedType> substitution,
[DartType? undecoratedResult]) {
if (substitution.isEmpty) return this;
if (undecoratedResult == null) {
var type = this.type!;
undecoratedResult = Substitution.fromPairs(
substitution.keys.toList(),
substitution.values.map((d) => d!.type!).toList(),
substitution.values.map((d) => d.type!).toList(),
).substituteType(type);
if (undecoratedResult is FunctionType && type is FunctionType) {
for (int i = 0; i < undecoratedResult.typeFormals.length; i++) {
@ -398,9 +381,15 @@ class DecoratedType implements DecoratedTypeInfo {
namedParameters: namedParameters,
typeArguments: typeArguments);
@override
DecoratedType _performSubstitution(
DecoratedType other, DartType undecoratedResult) =>
withNodeAndType(
NullabilityNode.forSubstitution(node, other.node), undecoratedResult);
/// Internal implementation of [_substitute], used as a recursion target.
DecoratedType _substitute(
Map<TypeParameterElement, DecoratedType?> substitution,
Map<TypeParameterElement, SubstitutedType> substitution,
DartType? undecoratedResult) {
var type = this.type;
if (type is FunctionType && undecoratedResult is FunctionType) {
@ -411,13 +400,12 @@ class DecoratedType implements DecoratedTypeInfo {
// substitutions, so we need to reflect that in our decorations by
// substituting to use the type variables the analyzer used.
substitution =
Map<TypeParameterElement, DecoratedType>.from(substitution);
Map<TypeParameterElement, SubstitutedType>.from(substitution);
for (int i = 0; i < typeFormals.length; i++) {
// Check if it's a fresh type variable.
if (undecoratedResult.typeFormals[i].enclosingElement == null) {
substitution[typeFormals[i]] =
DecoratedType._forTypeParameterSubstitution(
undecoratedResult.typeFormals[i]);
_TypeVariableReplacement(undecoratedResult.typeFormals[i]);
}
}
for (int i = 0; i < typeFormals.length; i++) {
@ -461,9 +449,7 @@ class DecoratedType implements DecoratedTypeInfo {
if (inner == null) {
return this;
} else {
return inner.withNodeAndType(
NullabilityNode.forSubstitution(inner.node, node),
undecoratedResult);
return inner._performSubstitution(this, undecoratedResult!);
}
} else if (type!.isVoid || type.isDynamic) {
return this;
@ -476,7 +462,7 @@ class DecoratedType implements DecoratedTypeInfo {
/// is [undecoratedResult], and whose return type, positional parameters, and
/// named parameters are formed by performing the given [substitution].
DecoratedType _substituteFunctionAfterFormals(FunctionType undecoratedResult,
Map<TypeParameterElement, DecoratedType?> substitution) {
Map<TypeParameterElement, SubstitutedType> substitution) {
var newPositionalParameters = <DecoratedType>[];
var numRequiredParameters = undecoratedResult.normalParameterTypes.length;
for (int i = 0; i < positionalParameters!.length; i++) {
@ -604,17 +590,16 @@ class RenamedDecoratedFunctionTypes {
}
// Create a fresh set of type variables and substitute so we can
// compare safely.
var substitution1 = <TypeParameterElement, DecoratedType>{};
var substitution2 = <TypeParameterElement, DecoratedType>{};
var substitution1 = <TypeParameterElement, SubstitutedType>{};
var substitution2 = <TypeParameterElement, SubstitutedType>{};
var newParameters = <TypeParameterElement>[];
for (int i = 0; i < type1.typeFormals!.length; i++) {
var newParameter =
TypeParameterElementImpl.synthetic(type1.typeFormals![i].name);
newParameters.add(newParameter);
var newParameterType =
DecoratedType._forTypeParameterSubstitution(newParameter);
substitution1[type1.typeFormals![i]] = newParameterType;
substitution2[type2.typeFormals![i]] = newParameterType;
var newType = _TypeVariableReplacement(newParameter);
substitution1[type1.typeFormals![i]] = newType;
substitution2[type2.typeFormals![i]] = newType;
}
for (int i = 0; i < type1.typeFormals!.length; i++) {
var bound1 = DecoratedTypeParameterBounds.current!
@ -656,13 +641,13 @@ class RenamedDecoratedFunctionTypes {
}
static List<DecoratedType> _substituteList(List<DecoratedType?> list,
Map<TypeParameterElement, DecoratedType> substitution) {
Map<TypeParameterElement, SubstitutedType> substitution) {
return list.map((t) => t!.substitute(substitution)).toList();
}
static Map<String, DecoratedType> _substituteMap(
Map<String, DecoratedType?> map,
Map<TypeParameterElement, DecoratedType> substitution) {
Map<TypeParameterElement, SubstitutedType> substitution) {
var result = <String, DecoratedType>{};
for (var entry in map.entries) {
result[entry.key] = entry.value!.substitute(substitution);
@ -670,3 +655,42 @@ class RenamedDecoratedFunctionTypes {
return result;
}
}
/// Abstract base class for anything that can appear in the "value" position of
/// a decorated type substitution map (in other words, anything that a type
/// variable can be replaced with when performing a substitution). A
/// substituted type is either a [DecoratedType] (in which case the substitution
/// will be fully general, and the resulting decorated type will have a
/// nullability that's based on both the original type and the substituted
/// type), or it can be a [_TypeVariableReplacement], which is used when one
/// type variable is being replaced with another, but there is no change in
/// nullability.
abstract class SubstitutedType {
/// The undecorated type to be substituted
DartType? get type;
/// Called by substitute methods to perform the actual substitution. [other]
/// is the decorated type to be substituted, and [undecoratedResult] is the
/// expected undecorated result of the substitution.
DecoratedType _performSubstitution(
DecoratedType other, DartType undecoratedResult);
}
/// Data structure used as a value in a substitution map if the only
/// substitution that needs to be performed is to replace one type variable with
/// another.
class _TypeVariableReplacement implements SubstitutedType {
@override
final DartType type;
_TypeVariableReplacement(TypeParameterElement newTypeVariable)
: type = TypeParameterTypeImpl(
element2: newTypeVariable,
nullabilitySuffix: NullabilitySuffix.star,
);
@override
DecoratedType _performSubstitution(
DecoratedType other, DartType undecoratedResult) =>
other.withNodeAndType(other.node!, undecoratedResult);
}

View file

@ -282,7 +282,7 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
/// (receiver) for a method, getter, or setter invocation.
DecoratedType getOrComputeElementType(AstNode node, Element element,
{DecoratedType? targetType, Expression? targetExpression}) {
Map<TypeParameterElement, DecoratedType?>? substitution;
Map<TypeParameterElement, DecoratedType>? substitution;
Element? baseElement = element.declaration;
if (targetType != null) {
var enclosingElement = baseElement!.enclosingElement;
@ -2214,7 +2214,7 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
astNode,
type,
left.substitute(
{typeParam: _variables.decoratedTypeParameterBound(typeParam)}),
{typeParam: _variables.decoratedTypeParameterBound(typeParam)!}),
right,
isLUB,
node: node);
@ -2227,7 +2227,7 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
type,
left,
right.substitute(
{typeParam: _variables.decoratedTypeParameterBound(typeParam)}),
{typeParam: _variables.decoratedTypeParameterBound(typeParam)!}),
isLUB,
node: node);
}
@ -2979,8 +2979,8 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
setType =
_variables.decoratedElementType(setter).positionalParameters!.single;
}
Map<TypeParameterElement, DecoratedType?> getterSubstitution = const {};
Map<TypeParameterElement, DecoratedType?> setterSubstitution = const {};
Map<TypeParameterElement, DecoratedType> getterSubstitution = const {};
Map<TypeParameterElement, DecoratedType> setterSubstitution = const {};
if (class_ != null) {
var getterClass = getter.enclosingElement as ClassElement;
if (!identical(class_, getterClass)) {

View file

@ -8268,7 +8268,7 @@ class _DecoratedClassHierarchyForTesting implements DecoratedClassHierarchy {
}
if (class_.name == 'MyListOfList' && superclass.name == 'List') {
return assignmentCheckerTest._myListOfListSupertype
.substitute({class_.typeParameters[0]: type.typeArguments[0]});
.substitute({class_.typeParameters[0]: type.typeArguments[0]!});
}
if (class_.name == 'List' && superclass.name == 'Iterable') {
return DecoratedType(