@@ -19,6 +19,48 @@ import 'package:meta/meta.dart';
19
19
import 'package:nnbd_migration/src/decorated_class_hierarchy.dart' ;
20
20
import 'package:nnbd_migration/src/variables.dart' ;
21
21
22
+ /// Information about the target of an assignment expression analyzed by
23
+ /// [FixBuilder] .
24
+ class AssignmentTargetInfo {
25
+ /// The type that the assignment target has when read. This is only relevant
26
+ /// for compound assignments (since they both read and write the assignment
27
+ /// target)
28
+ final DartType readType;
29
+
30
+ /// The type that the assignment target has when written to.
31
+ final DartType writeType;
32
+
33
+ AssignmentTargetInfo (this .readType, this .writeType);
34
+ }
35
+
36
+ /// Problem reported by [FixBuilder] when encountering a compound assignment
37
+ /// for which the combination result is nullable. This occurs if the compound
38
+ /// assignment resolves to a user-defined operator that returns a nullable type,
39
+ /// but the target of the assignment expects a non-nullable type. We need to
40
+ /// add a null check but it's nontrivial to do so because we would have to
41
+ /// rewrite the assignment as an ordinary assignment (e.g. change `x += y` to
42
+ /// `x = (x + y)!` ), but that might change semantics by causing subexpressions
43
+ /// of the target to be evaluated twice.
44
+ ///
45
+ /// TODO(paulberry): consider alternatives.
46
+ /// See https://github.com/dart-lang/sdk/issues/38675.
47
+ class CompoundAssignmentCombinedNullable implements Problem {
48
+ const CompoundAssignmentCombinedNullable ();
49
+ }
50
+
51
+ /// Problem reported by [FixBuilder] when encountering a compound assignment
52
+ /// for which the value read from the target of the assignment has a nullable
53
+ /// type. We need to add a null check but it's nontrivial to do so because we
54
+ /// would have to rewrite the assignment as an ordinary assignment (e.g. change
55
+ /// `x += y` to `x = x! + y` ), but that might change semantics by causing
56
+ /// subexpressions of the target to be evaluated twice.
57
+ ///
58
+ /// TODO(paulberry): consider alternatives.
59
+ /// See https://github.com/dart-lang/sdk/issues/38676.
60
+ class CompoundAssignmentReadNullable implements Problem {
61
+ const CompoundAssignmentReadNullable ();
62
+ }
63
+
22
64
/// This class visits the AST of code being migrated, after graph propagation,
23
65
/// to figure out what changes need to be made to the code. It doesn't actually
24
66
/// make the changes; it simply reports what changes are necessary through
@@ -58,6 +100,9 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
58
100
/// inserted.
59
101
void addNullCheck (Expression subexpression);
60
102
103
+ /// Called whenever code is found that can't be automatically fixed.
104
+ void addProblem (AstNode node, Problem problem);
105
+
61
106
/// Initializes flow analysis for a function node.
62
107
void createFlowAnalysis (AstNode node) {
63
108
assert (_flowAnalysis == null );
@@ -70,6 +115,61 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
70
115
_assignedVariables = FlowAnalysisHelper .computeAssignedVariables (node);
71
116
}
72
117
118
+ @override
119
+ DartType visitAssignmentExpression (AssignmentExpression node) {
120
+ var targetInfo = visitAssignmentTarget (node.leftHandSide);
121
+ if (node.operator .type == TokenType .EQ ) {
122
+ return visitSubexpression (node.rightHandSide, targetInfo.writeType);
123
+ } else if (node.operator .type == TokenType .QUESTION_QUESTION_EQ ) {
124
+ // TODO(paulberry): if targetInfo.readType is non-nullable, then the
125
+ // assignment is dead code.
126
+ // See https://github.com/dart-lang/sdk/issues/38678
127
+ // TODO(paulberry): once flow analysis supports `??=`, integrate it here.
128
+ // See https://github.com/dart-lang/sdk/issues/38680
129
+ var rhsType =
130
+ visitSubexpression (node.rightHandSide, targetInfo.writeType);
131
+ return _typeSystem.leastUpperBound (
132
+ _typeSystem.promoteToNonNull (targetInfo.readType as TypeImpl ),
133
+ rhsType);
134
+ } else {
135
+ var combiner = node.staticElement;
136
+ DartType combinedType;
137
+ if (combiner == null ) {
138
+ visitSubexpression (node.rightHandSide, _typeProvider.dynamicType);
139
+ combinedType = _typeProvider.dynamicType;
140
+ } else {
141
+ if (_typeSystem.isNullable (targetInfo.readType)) {
142
+ addProblem (node, const CompoundAssignmentReadNullable ());
143
+ }
144
+ var combinerType = _computeMigratedType (combiner) as FunctionType ;
145
+ visitSubexpression (node.rightHandSide, combinerType.parameters[0 ].type);
146
+ combinedType =
147
+ _fixNumericTypes (combinerType.returnType, node.staticType);
148
+ }
149
+ if (_doesAssignmentNeedCheck (
150
+ from: combinedType, to: targetInfo.writeType)) {
151
+ addProblem (node, const CompoundAssignmentCombinedNullable ());
152
+ combinedType = _typeSystem.promoteToNonNull (combinedType as TypeImpl );
153
+ }
154
+ return combinedType;
155
+ }
156
+ }
157
+
158
+ /// Recursively visits an assignment target, returning information about the
159
+ /// target's read and write types.
160
+ AssignmentTargetInfo visitAssignmentTarget (Expression node) {
161
+ if (node is SimpleIdentifier ) {
162
+ var writeType = _computeMigratedType (node.staticElement);
163
+ var auxiliaryElements = node.auxiliaryElements;
164
+ var readType = auxiliaryElements == null
165
+ ? writeType
166
+ : _computeMigratedType (auxiliaryElements.staticElement);
167
+ return AssignmentTargetInfo (readType, writeType);
168
+ } else {
169
+ throw UnimplementedError ('TODO(paulberry)' );
170
+ }
171
+ }
172
+
73
173
@override
74
174
DartType visitBinaryExpression (BinaryExpression node) {
75
175
var leftOperand = node.leftOperand;
@@ -101,6 +201,7 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
101
201
// If `a ?? b` is used in a non-nullable context, we don't want to
102
202
// migrate it to `(a ?? b)!`. We want to migrate it to `a ?? b!`.
103
203
// TODO(paulberry): once flow analysis supports `??`, integrate it here.
204
+ // See https://github.com/dart-lang/sdk/issues/38680
104
205
var leftType = visitSubexpression (node.leftOperand,
105
206
_typeSystem.makeNullable (_contextType as TypeImpl ));
106
207
var rightType = visitSubexpression (node.rightOperand, _contextType);
@@ -152,6 +253,8 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
152
253
153
254
@override
154
255
DartType visitSimpleIdentifier (SimpleIdentifier node) {
256
+ assert (! node.inSetterContext (),
257
+ 'Should use visitAssignmentTarget in setter contexts' );
155
258
var element = node.staticElement;
156
259
if (element == null ) return _typeProvider.dynamicType;
157
260
if (element is PromotableElement ) {
@@ -186,7 +289,6 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
186
289
DartType _computeMigratedType (Element element, {DartType targetType}) {
187
290
Element baseElement;
188
291
if (element is Member ) {
189
- assert (targetType != null );
190
292
baseElement = element.baseElement;
191
293
} else {
192
294
baseElement = element;
@@ -203,7 +305,7 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
203
305
var functionType = _variables.decoratedElementType (baseElement);
204
306
var decoratedType = baseElement.isGetter
205
307
? functionType.returnType
206
- : throw UnimplementedError ( 'TODO(paulberry)' ) ;
308
+ : functionType.positionalParameters[ 0 ] ;
207
309
type = decoratedType.toFinalType (_typeProvider);
208
310
}
209
311
} else {
@@ -252,3 +354,6 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
252
354
}
253
355
}
254
356
}
357
+
358
+ /// Common supertype for problems reported by [FixBuilder.addProblem] .
359
+ abstract class Problem {}
0 commit comments