Skip to content

Commit 559e49a

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Migration: propagate nullability inference through override relationships: return types.
Parameter types will be handled in a follow-up CL. Change-Id: I71862fa6cf27aeede24bef60c096f631f6d8f60d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107100 Reviewed-by: Dan Rubel <danrubel@google.com>
1 parent 0400593 commit 559e49a

7 files changed

Lines changed: 194 additions & 18 deletions

pkg/nnbd_migration/lib/src/decorated_class_hierarchy.dart

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,8 @@ class DecoratedClassHierarchy {
6262
var supertype = decoratedSupertype.type as InterfaceType;
6363
// Compute a type substitution to determine how [class_] relates to
6464
// this specific [superclass].
65-
var argumentTypes = supertype.typeArguments;
66-
var parameterTypes =
67-
supertype.typeParameters.map((p) => p.type).toList();
6865
Map<TypeParameterElement, DecoratedType> substitution = {};
69-
for (int i = 0; i < argumentTypes.length; i++) {
66+
for (int i = 0; i < supertype.typeArguments.length; i++) {
7067
substitution[supertype.typeParameters[i]] =
7168
decoratedSupertype.typeArguments[i];
7269
}
@@ -76,10 +73,7 @@ class DecoratedClassHierarchy {
7673
var recursiveSupertypeDecorations =
7774
_getGenericSupertypeDecorations(superclass);
7875
for (var entry in recursiveSupertypeDecorations.entries) {
79-
var undecoratedResult =
80-
entry.value.type.substitute2(argumentTypes, parameterTypes);
81-
decorations[entry.key] ??=
82-
entry.value.substitute(substitution, undecoratedResult);
76+
decorations[entry.key] ??= entry.value.substitute(substitution);
8377
}
8478
// Also record the relation between [class_] and its direct
8579
// superclass.

pkg/nnbd_migration/lib/src/decorated_type.dart

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,41 @@ class DecoratedType {
9696
return decoratedType;
9797
}
9898

99+
/// If `this` represents an interface type, returns the substitution necessary
100+
/// to produce this type using the class's type as a starting point.
101+
/// Otherwise throws an exception.
102+
///
103+
/// For instance, if `this` represents `List<int?1>`, returns the substitution
104+
/// `{T: int?1}`, where `T` is the [TypeParameterElement] for `List`'s type
105+
/// parameter.
106+
Map<TypeParameterElement, DecoratedType> get asSubstitution {
107+
var type = this.type;
108+
if (type is InterfaceType) {
109+
return Map<TypeParameterElement, DecoratedType>.fromIterables(
110+
type.element.typeParameters, typeArguments);
111+
} else {
112+
throw StateError(
113+
'Tried to convert a non-interface type to a substitution');
114+
}
115+
}
116+
99117
/// Apply the given [substitution] to this type.
100118
///
101119
/// [undecoratedResult] is the result of the substitution, as determined by
102-
/// the normal type system.
120+
/// the normal type system. If not supplied, it is inferred.
103121
DecoratedType substitute(
104122
Map<TypeParameterElement, DecoratedType> substitution,
105-
DartType undecoratedResult) {
123+
[DartType undecoratedResult]) {
106124
if (substitution.isEmpty) return this;
125+
if (undecoratedResult == null) {
126+
List<DartType> argumentTypes = [];
127+
List<DartType> parameterTypes = [];
128+
for (var entry in substitution.entries) {
129+
argumentTypes.add(entry.value.type);
130+
parameterTypes.add(entry.key.type);
131+
}
132+
undecoratedResult = type.substitute2(argumentTypes, parameterTypes);
133+
}
107134
return _substitute(substitution, undecoratedResult);
108135
}
109136

pkg/nnbd_migration/lib/src/edge_builder.dart

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import 'package:analyzer/dart/ast/token.dart';
77
import 'package:analyzer/dart/ast/visitor.dart';
88
import 'package:analyzer/dart/element/element.dart';
99
import 'package:analyzer/dart/element/type.dart';
10+
import 'package:analyzer/src/dart/element/inheritance_manager2.dart';
1011
import 'package:analyzer/src/dart/element/member.dart';
1112
import 'package:analyzer/src/generated/resolver.dart';
1213
import 'package:analyzer/src/generated/source.dart';
1314
import 'package:meta/meta.dart';
1415
import 'package:nnbd_migration/nnbd_migration.dart';
1516
import 'package:nnbd_migration/src/conditional_discard.dart';
17+
import 'package:nnbd_migration/src/decorated_class_hierarchy.dart';
1618
import 'package:nnbd_migration/src/decorated_type.dart';
1719
import 'package:nnbd_migration/src/edge_origin.dart';
1820
import 'package:nnbd_migration/src/expression_checks.dart';
@@ -27,6 +29,8 @@ import 'package:nnbd_migration/src/nullability_node.dart';
2729
/// variables that will determine its nullability. For `visit...` methods that
2830
/// don't visit expressions, `null` will be returned.
2931
class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType> {
32+
final InheritanceManager2 _inheritanceManager;
33+
3034
/// The repository of constraint variables and decorated types (from a
3135
/// previous pass over the source code).
3236
final VariableRepository _variables;
@@ -38,6 +42,8 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType> {
3842
/// The file being analyzed.
3943
final Source _source;
4044

45+
final DecoratedClassHierarchy _decoratedClassHierarchy;
46+
4147
/// For convenience, a [DecoratedType] representing non-nullable `Object`.
4248
final DecoratedType _notNullType;
4349

@@ -77,9 +83,11 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType> {
7783

7884
NullabilityNode _lastConditionalNode;
7985

80-
EdgeBuilder(TypeProvider typeProvider, this._variables, this._graph,
81-
this._source, this.listener)
82-
: _notNullType = DecoratedType(typeProvider.objectType, _graph.never),
86+
EdgeBuilder(TypeProvider typeProvider, TypeSystem typeSystem, this._variables,
87+
this._graph, this._source, this.listener)
88+
: _decoratedClassHierarchy = DecoratedClassHierarchy(_variables, _graph),
89+
_inheritanceManager = InheritanceManager2(typeSystem),
90+
_notNullType = DecoratedType(typeProvider.objectType, _graph.never),
8391
_nonNullableBoolType =
8492
DecoratedType(typeProvider.boolType, _graph.never),
8593
_nonNullableTypeType =
@@ -490,11 +498,31 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType> {
490498
DecoratedType visitMethodDeclaration(MethodDeclaration node) {
491499
node.parameters?.accept(this);
492500
assert(_currentFunctionType == null);
493-
_currentFunctionType =
494-
_variables.decoratedElementType(node.declaredElement);
501+
var declaredElement = node.declaredElement;
502+
_currentFunctionType = _variables.decoratedElementType(declaredElement);
495503
_inConditionalControlFlow = false;
496504
try {
497505
node.body.accept(this);
506+
var classElement = declaredElement.enclosingElement as ClassElement;
507+
for (var overridden in _inheritanceManager.getOverridden(
508+
classElement.type,
509+
Name(classElement.library.source.uri, declaredElement.name)) ??
510+
const []) {
511+
var overriddenElement = overridden.element as ExecutableElement;
512+
assert(overriddenElement is! ExecutableMember);
513+
var overriddenClass =
514+
overriddenElement.enclosingElement as ClassElement;
515+
var decoratedOverriddenFunctionType =
516+
_variables.decoratedElementType(overriddenElement);
517+
var decoratedSupertype = _decoratedClassHierarchy.getDecoratedSupertype(
518+
classElement, overriddenClass);
519+
var substitution = decoratedSupertype.asSubstitution;
520+
_checkAssignment(null,
521+
source: _currentFunctionType,
522+
destination:
523+
decoratedOverriddenFunctionType.substitute(substitution),
524+
hard: true);
525+
}
498526
} finally {
499527
_currentFunctionType = null;
500528
}

pkg/nnbd_migration/lib/src/nullability_migration_impl.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ class NullabilityMigrationImpl implements NullabilityMigration {
5959

6060
void processInput(ResolvedUnitResult result) {
6161
var unit = result.unit;
62-
unit.accept(EdgeBuilder(result.typeProvider, _variables, _graph,
63-
unit.declaredElement.source, _permissive ? listener : null));
62+
unit.accept(EdgeBuilder(result.typeProvider, result.typeSystem, _variables,
63+
_graph, unit.declaredElement.source, _permissive ? listener : null));
6464
}
6565
}
6666

pkg/nnbd_migration/test/api_test.dart

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,88 @@ main() {
10431043
await _checkSingleFileChanges(content, expected);
10441044
}
10451045

1046+
test_override_return_type_non_nullable() async {
1047+
var content = '''
1048+
abstract class Base {
1049+
int/*!*/ f();
1050+
}
1051+
class Derived extends Base {
1052+
int f() => g();
1053+
}
1054+
int g() => null;
1055+
''';
1056+
var expected = '''
1057+
abstract class Base {
1058+
int/*!*/ f();
1059+
}
1060+
class Derived extends Base {
1061+
int f() => g()!;
1062+
}
1063+
int? g() => null;
1064+
''';
1065+
await _checkSingleFileChanges(content, expected);
1066+
}
1067+
1068+
test_override_return_type_nullable() async {
1069+
var content = '''
1070+
abstract class Base {
1071+
int f();
1072+
}
1073+
class Derived extends Base {
1074+
int f() => null;
1075+
}
1076+
''';
1077+
var expected = '''
1078+
abstract class Base {
1079+
int? f();
1080+
}
1081+
class Derived extends Base {
1082+
int? f() => null;
1083+
}
1084+
''';
1085+
await _checkSingleFileChanges(content, expected);
1086+
}
1087+
1088+
test_override_return_type_nullable_substitution_complex() async {
1089+
var content = '''
1090+
abstract class Base<T> {
1091+
T f();
1092+
}
1093+
class Derived extends Base<List<int>> {
1094+
List<int> f() => <int>[null];
1095+
}
1096+
''';
1097+
var expected = '''
1098+
abstract class Base<T> {
1099+
T f();
1100+
}
1101+
class Derived extends Base<List<int?>> {
1102+
List<int?> f() => <int?>[null];
1103+
}
1104+
''';
1105+
await _checkSingleFileChanges(content, expected);
1106+
}
1107+
1108+
test_override_return_type_nullable_substitution_simple() async {
1109+
var content = '''
1110+
abstract class Base<T> {
1111+
T f();
1112+
}
1113+
class Derived extends Base<int> {
1114+
int f() => null;
1115+
}
1116+
''';
1117+
var expected = '''
1118+
abstract class Base<T> {
1119+
T f();
1120+
}
1121+
class Derived extends Base<int?> {
1122+
int? f() => null;
1123+
}
1124+
''';
1125+
await _checkSingleFileChanges(content, expected);
1126+
}
1127+
10461128
test_parameter_genericFunctionType() async {
10471129
var content = '''
10481130
int f(int x, int Function(int i) g) {

pkg/nnbd_migration/test/edge_builder_test.dart

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ class EdgeBuilderTest extends MigrationVisitorTestBase {
2525
@override
2626
Future<CompilationUnit> analyze(String code) async {
2727
var unit = await super.analyze(code);
28-
unit.accept(EdgeBuilder(typeProvider, variables, graph, testSource, null));
28+
unit.accept(EdgeBuilder(
29+
typeProvider, typeSystem, variables, graph, testSource, null));
2930
return unit;
3031
}
3132

@@ -1273,6 +1274,48 @@ void test(C c) {
12731274
expect(never.isNullable, isFalse);
12741275
}
12751276

1277+
test_override_return_type_getter() async {
1278+
await analyze('''
1279+
abstract class Base {
1280+
int/*1*/ get x;
1281+
}
1282+
class Derived extends Base {
1283+
int/*2*/ get x => null;
1284+
}
1285+
''');
1286+
var int1 = decoratedTypeAnnotation('int/*1*/');
1287+
var int2 = decoratedTypeAnnotation('int/*2*/');
1288+
assertEdge(int2.node, int1.node, hard: true);
1289+
}
1290+
1291+
test_override_return_type_method() async {
1292+
await analyze('''
1293+
abstract class Base {
1294+
int/*1*/ f();
1295+
}
1296+
class Derived extends Base {
1297+
int/*2*/ f() => null;
1298+
}
1299+
''');
1300+
var int1 = decoratedTypeAnnotation('int/*1*/');
1301+
var int2 = decoratedTypeAnnotation('int/*2*/');
1302+
assertEdge(int2.node, int1.node, hard: true);
1303+
}
1304+
1305+
test_override_return_type_operator() async {
1306+
await analyze('''
1307+
abstract class Base {
1308+
Base/*1*/ operator-();
1309+
}
1310+
class Derived extends Base {
1311+
Derived/*2*/ operator-() => null;
1312+
}
1313+
''');
1314+
var base1 = decoratedTypeAnnotation('Base/*1*/');
1315+
var derived2 = decoratedTypeAnnotation('Derived/*2*/');
1316+
assertEdge(derived2.node, base1.node, hard: true);
1317+
}
1318+
12761319
test_parenthesizedExpression() async {
12771320
await analyze('''
12781321
int f() {

pkg/nnbd_migration/test/migration_visitor_test_base.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ class MigrationVisitorTestBase extends AbstractSingleUnitTest {
9797

9898
TypeProvider get typeProvider => testAnalysisResult.typeProvider;
9999

100+
TypeSystem get typeSystem => testAnalysisResult.typeSystem;
101+
100102
Future<CompilationUnit> analyze(String code) async {
101103
await resolveTestUnit(code);
102104
testUnit

0 commit comments

Comments
 (0)