Skip to content

Commit 8ba6f7e

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Migration: add support for assignment expressions to FixBuilder.
Change-Id: I8e9a76cd3a0952341bd7b354ca4deddee6496b7d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/119525 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
1 parent 6364c59 commit 8ba6f7e

File tree

2 files changed

+434
-3
lines changed

2 files changed

+434
-3
lines changed

pkg/nnbd_migration/lib/src/fix_builder.dart

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,48 @@ import 'package:meta/meta.dart';
1919
import 'package:nnbd_migration/src/decorated_class_hierarchy.dart';
2020
import 'package:nnbd_migration/src/variables.dart';
2121

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+
2264
/// This class visits the AST of code being migrated, after graph propagation,
2365
/// to figure out what changes need to be made to the code. It doesn't actually
2466
/// make the changes; it simply reports what changes are necessary through
@@ -58,6 +100,9 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
58100
/// inserted.
59101
void addNullCheck(Expression subexpression);
60102

103+
/// Called whenever code is found that can't be automatically fixed.
104+
void addProblem(AstNode node, Problem problem);
105+
61106
/// Initializes flow analysis for a function node.
62107
void createFlowAnalysis(AstNode node) {
63108
assert(_flowAnalysis == null);
@@ -70,6 +115,61 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
70115
_assignedVariables = FlowAnalysisHelper.computeAssignedVariables(node);
71116
}
72117

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+
73173
@override
74174
DartType visitBinaryExpression(BinaryExpression node) {
75175
var leftOperand = node.leftOperand;
@@ -101,6 +201,7 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
101201
// If `a ?? b` is used in a non-nullable context, we don't want to
102202
// migrate it to `(a ?? b)!`. We want to migrate it to `a ?? b!`.
103203
// TODO(paulberry): once flow analysis supports `??`, integrate it here.
204+
// See https://github.com/dart-lang/sdk/issues/38680
104205
var leftType = visitSubexpression(node.leftOperand,
105206
_typeSystem.makeNullable(_contextType as TypeImpl));
106207
var rightType = visitSubexpression(node.rightOperand, _contextType);
@@ -152,6 +253,8 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
152253

153254
@override
154255
DartType visitSimpleIdentifier(SimpleIdentifier node) {
256+
assert(!node.inSetterContext(),
257+
'Should use visitAssignmentTarget in setter contexts');
155258
var element = node.staticElement;
156259
if (element == null) return _typeProvider.dynamicType;
157260
if (element is PromotableElement) {
@@ -186,7 +289,6 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
186289
DartType _computeMigratedType(Element element, {DartType targetType}) {
187290
Element baseElement;
188291
if (element is Member) {
189-
assert(targetType != null);
190292
baseElement = element.baseElement;
191293
} else {
192294
baseElement = element;
@@ -203,7 +305,7 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
203305
var functionType = _variables.decoratedElementType(baseElement);
204306
var decoratedType = baseElement.isGetter
205307
? functionType.returnType
206-
: throw UnimplementedError('TODO(paulberry)');
308+
: functionType.positionalParameters[0];
207309
type = decoratedType.toFinalType(_typeProvider);
208310
}
209311
} else {
@@ -252,3 +354,6 @@ abstract class FixBuilder extends GeneralizingAstVisitor<DartType> {
252354
}
253355
}
254356
}
357+
358+
/// Common supertype for problems reported by [FixBuilder.addProblem].
359+
abstract class Problem {}

0 commit comments

Comments
 (0)