Skip to content

Commit

Permalink
Migration: implement substitution logic for generic function types.
Browse files Browse the repository at this point in the history
This should address ~222 exceptions whose stack trace contains the line:

DecoratedType._substitute (package:nnbd_migration/src/decorated_type.dart:386:14)

Change-Id: I53e4a1f41a44fe87aa15344a621a5b665dec1fb4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/115766
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
  • Loading branch information
stereotype441 authored and commit-bot@chromium.org committed Sep 6, 2019
1 parent f48a5a9 commit 28d16e1
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 6 deletions.
32 changes: 26 additions & 6 deletions pkg/nnbd_migration/lib/src/decorated_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,7 @@ class DecoratedType {

/// Creates a [DecoratedType] for a synthetic type parameter, to be used
/// during comparison of generic function types.
DecoratedType._forTypeParameterSubstitution(
TypeParameterElementImpl parameter)
DecoratedType._forTypeParameterSubstitution(TypeParameterElement parameter)
: type = TypeParameterTypeImpl(parameter),
node = null,
returnType = null,
Expand Down Expand Up @@ -324,7 +323,7 @@ class DecoratedType {

@override
String toString() {
var trailing = node.debugSuffix;
var trailing = node == null ? '' : node.debugSuffix;
var type = this.type;
if (type is TypeParameterType || type is VoidType) {
return '$type$trailing';
Expand Down Expand Up @@ -383,8 +382,27 @@ class DecoratedType {
DartType undecoratedResult) {
var type = this.type;
if (type is FunctionType && undecoratedResult is FunctionType) {
assert(type.typeFormals.isEmpty); // TODO(paulberry)
return _substituteFunctionAfterFormals(undecoratedResult, substitution);
var typeFormals = type.typeFormals;
assert(typeFormals.length == undecoratedResult.typeFormals.length);
var newTypeFormalBounds = <DecoratedType>[];
if (typeFormals.isNotEmpty) {
// The analyzer sometimes allocates fresh type variables when performing
// 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);
for (int i = 0; i < typeFormals.length; i++) {
substitution[typeFormals[i]] =
DecoratedType._forTypeParameterSubstitution(
undecoratedResult.typeFormals[i]);
}
for (int i = 0; i < typeFormalBounds.length; i++) {
newTypeFormalBounds.add(typeFormalBounds[i]._substitute(
substitution, typeFormals[i].bound ?? typeFormalBounds[i].type));
}
}
return _substituteFunctionAfterFormals(undecoratedResult, substitution,
newTypeFormalBounds: newTypeFormalBounds);
} else if (type is InterfaceType && undecoratedResult is InterfaceType) {
List<DecoratedType> newTypeArguments = [];
for (int i = 0; i < typeArguments.length; i++) {
Expand Down Expand Up @@ -412,7 +430,8 @@ class DecoratedType {
/// 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, DecoratedType> substitution,
{List<DecoratedType> newTypeFormalBounds = const []}) {
var newPositionalParameters = <DecoratedType>[];
var numRequiredParameters = undecoratedResult.normalParameterTypes.length;
for (int i = 0; i < positionalParameters.length; i++) {
Expand All @@ -431,6 +450,7 @@ class DecoratedType {
(entry.value._substitute(substitution, undecoratedParameterType));
}
return DecoratedType(undecoratedResult, node,
typeFormalBounds: newTypeFormalBounds,
returnType:
returnType._substitute(substitution, undecoratedResult.returnType),
positionalParameters: newPositionalParameters,
Expand Down
39 changes: 39 additions & 0 deletions pkg/nnbd_migration/test/api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,45 @@ void main() {
await _checkSingleFileChanges(content, expected);
}

@failingTest
test_map_nullable_input() async {
// TODO(paulberry): we're currently migrating this example incorrectly.
// See discussion at https://dart-review.googlesource.com/c/sdk/+/115766
var content = '''
Iterable<int> f(List<int> x) => x.map((y) => g(y));
int g(int x) => x + 1;
main() {
f([null]);
}
''';
var expected = '''
Iterable<int> f(List<int?> x) => x.map((y) => g(y!));
int g(int x) => x + 1;
main() {
f([null]);
}
''';
await _checkSingleFileChanges(content, expected);
}

test_map_nullable_output() async {
var content = '''
Iterable<int> f(List<int> x) => x.map((y) => g(y));
int g(int x) => null;
main() {
f([1]);
}
''';
var expected = '''
Iterable<int?> f(List<int> x) => x.map((y) => g(y));
int? g(int x) => null;
main() {
f([1]);
}
''';
await _checkSingleFileChanges(content, expected);
}

test_methodInvocation_typeArguments_explicit() async {
var content = '''
T f<T>(T t) => t;
Expand Down
45 changes: 45 additions & 0 deletions pkg/nnbd_migration/test/edge_builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,51 @@ int bar(Derived d, int i) => d.foo(i);
hard: false);
}

test_genericMethodInvocation_withBoundSubstitution() async {
await analyze('''
class Base<T> {
U foo<U extends T>(U x) => x;
}
class Derived<V> extends Base<Iterable<V>> {}
bar(Derived<int> d, List<int> x) => d.foo(x);
''');
// Don't bother checking any edges; the assertions in the DecoratedType
// constructor verify that we've substituted the bound correctly.
}

test_genericMethodInvocation_withSubstitution() async {
await analyze('''
class Base<T> {
U foo<U>(U x, T y) => x;
}
class Derived<V> extends Base<List<V>> {}
int bar(Derived<String> d, int i, List<String> j) => d.foo(i, j);
''');
assertEdge(
decoratedTypeAnnotation('String> j').node,
substitutionNode(decoratedTypeAnnotation('String> d').node,
decoratedTypeAnnotation('V>>').node),
hard: false);
assertEdge(
decoratedTypeAnnotation('List<String> j').node,
substitutionNode(decoratedTypeAnnotation('List<V>>').node,
decoratedTypeAnnotation('T y').node),
hard: true);
var implicitTypeArgumentMatcher = anyNode;
assertEdge(
decoratedTypeAnnotation('int i').node,
substitutionNode(
implicitTypeArgumentMatcher, decoratedTypeAnnotation('U x').node),
hard: true);
var implicitTypeArgumentNullability =
implicitTypeArgumentMatcher.matchingNode;
assertEdge(
substitutionNode(implicitTypeArgumentNullability,
decoratedTypeAnnotation('U foo').node),
decoratedTypeAnnotation('int bar').node,
hard: false);
}

test_if_condition() async {
await analyze('''
void f(bool b) {
Expand Down

0 comments on commit 28d16e1

Please sign in to comment.