Version 2.12.0-236.0.dev
Merge commit '5e66c2b1a1efa890aef04f7c583ee74cc68f9c51' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index ade689e..6809bbc 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -94,12 +94,18 @@
AssignedVariablesNodeInfo<Variable> deferNode(
{bool isClosureOrLateVariableInitializer: false}) {
AssignedVariablesNodeInfo<Variable> info = _stack.removeLast();
+ info._read.removeAll(info._declared);
info._written.removeAll(info._declared);
+ info._readCaptured.removeAll(info._declared);
info._captured.removeAll(info._declared);
AssignedVariablesNodeInfo<Variable> last = _stack.last;
+ last._read.addAll(info._read);
last._written.addAll(info._written);
+ last._readCaptured.addAll(info._readCaptured);
last._captured.addAll(info._captured);
if (isClosureOrLateVariableInitializer) {
+ last._readCaptured.addAll(info._read);
+ _anywhere._readCaptured.addAll(info._read);
last._captured.addAll(info._written);
_anywhere._captured.addAll(info._written);
}
@@ -123,7 +129,9 @@
AssignedVariablesNodeInfo<Variable> discarded = _stack.removeLast();
AssignedVariablesNodeInfo<Variable> last = _stack.last;
last._declared.addAll(discarded._declared);
+ last._read.addAll(discarded._read);
last._written.addAll(discarded._written);
+ last._readCaptured.addAll(discarded._readCaptured);
last._captured.addAll(discarded._captured);
}
@@ -154,6 +162,9 @@
_deferredInfos.isEmpty, "Deferred infos not stored: $_deferredInfos");
assert(_stack.length == 1, "Unexpected stack: $_stack");
AssignedVariablesNodeInfo<Variable> last = _stack.last;
+ Set<Variable> undeclaredReads = last._read.difference(last._declared);
+ assert(undeclaredReads.isEmpty,
+ 'Variables read from but not declared: $undeclaredReads');
Set<Variable> undeclaredWrites = last._written.difference(last._declared);
assert(undeclaredWrites.isEmpty,
'Variables written to but not declared: $undeclaredWrites');
@@ -181,6 +192,11 @@
_stack.add(node);
}
+ void read(Variable variable) {
+ _stack.last._read.add(variable);
+ _anywhere._read.add(variable);
+ }
+
/// Call this method to register that the node [from] for which information
/// has been stored is replaced by the node [to].
// TODO(johnniwinther): Remove this when unified collections are encoded as
@@ -242,6 +258,10 @@
Set<Variable> get declaredAtTopLevel => _stack.first._declared;
+ Set<Variable> get readAnywhere => _anywhere._read;
+
+ Set<Variable> get readCapturedAnywhere => _anywhere._readCaptured;
+
Set<Variable> get writtenAnywhere => _anywhere._written;
Set<Variable> capturedInNode(Node node) => _getInfoForNode(node)._captured;
@@ -250,6 +270,11 @@
bool isTracked(Node node) => _info.containsKey(node);
+ Set<Variable> readCapturedInNode(Node node) =>
+ _getInfoForNode(node)._readCaptured;
+
+ Set<Variable> readInNode(Node node) => _getInfoForNode(node)._read;
+
String toString() {
StringBuffer sb = new StringBuffer();
sb.write('AssignedVariablesForTesting(');
@@ -263,9 +288,13 @@
/// Information tracked by [AssignedVariables] for a single node.
class AssignedVariablesNodeInfo<Variable extends Object> {
+ final Set<Variable> _read = new Set<Variable>.identity();
+
/// The set of local variables that are potentially written in the node.
final Set<Variable> _written = new Set<Variable>.identity();
+ final Set<Variable> _readCaptured = new Set<Variable>.identity();
+
/// The set of local variables for which a potential write is captured by a
/// local function or closure inside the node.
final Set<Variable> _captured = new Set<Variable>.identity();
@@ -324,6 +353,10 @@
allowLocalBooleanVarsToPromote: allowLocalBooleanVarsToPromote);
}
+ factory FlowAnalysis.legacy(TypeOperations<Variable, Type> typeOperations,
+ AssignedVariables<Node, Variable> assignedVariables) =
+ _LegacyTypePromotion;
+
/// Return `true` if the current state is reachable.
bool get isReachable;
@@ -383,7 +416,8 @@
/// Call this method upon reaching the "?" part of a conditional expression
/// ("?:"). [condition] should be the expression preceding the "?".
- void conditional_thenBegin(Expression condition);
+ /// [conditionalExpression] should be the entire conditional expression.
+ void conditional_thenBegin(Expression condition, Node conditionalExpression);
/// Register a declaration of the [variable] in the current state.
/// Should also be called for function parameters.
@@ -575,8 +609,9 @@
void ifStatement_end(bool hasElse);
/// Call this method after visiting the condition part of an if statement.
- /// [condition] should be the if statement's condition.
- void ifStatement_thenBegin(Expression condition);
+ /// [condition] should be the if statement's condition. [ifNode] should be
+ /// the entire `if` statement (or the collection literal entry).
+ void ifStatement_thenBegin(Expression condition, Node ifNode);
/// Call this method after visiting the initializer of a variable declaration.
void initialize(
@@ -627,8 +662,9 @@
/// Call this method after visiting the LHS of a logical binary operation
/// ("||" or "&&").
/// [rightOperand] should be the LHS. [isAnd] should indicate whether the
- /// logical operator is "&&" or "||".
- void logicalBinaryOp_rightBegin(Expression leftOperand,
+ /// logical operator is "&&" or "||". [wholeExpression] should be the whole
+ /// logical binary expression.
+ void logicalBinaryOp_rightBegin(Expression leftOperand, Node wholeExpression,
{required bool isAnd});
/// Call this method after visiting a logical not ("!") expression.
@@ -855,6 +891,14 @@
allowLocalBooleanVarsToPromote: allowLocalBooleanVarsToPromote));
}
+ factory FlowAnalysisDebug.legacy(
+ TypeOperations<Variable, Type> typeOperations,
+ AssignedVariables<Node, Variable> assignedVariables) {
+ print('FlowAnalysisDebug.legacy()');
+ return new FlowAnalysisDebug._(
+ new _LegacyTypePromotion(typeOperations, assignedVariables));
+ }
+
FlowAnalysisDebug._(this._wrapped);
@override
@@ -909,9 +953,9 @@
}
@override
- void conditional_thenBegin(Expression condition) {
- _wrap('conditional_thenBegin($condition)',
- () => _wrapped.conditional_thenBegin(condition));
+ void conditional_thenBegin(Expression condition, Node conditionalExpression) {
+ _wrap('conditional_thenBegin($condition, $conditionalExpression)',
+ () => _wrapped.conditional_thenBegin(condition, conditionalExpression));
}
@override
@@ -1069,9 +1113,9 @@
}
@override
- void ifStatement_thenBegin(Expression condition) {
- _wrap('ifStatement_thenBegin($condition)',
- () => _wrapped.ifStatement_thenBegin(condition));
+ void ifStatement_thenBegin(Expression condition, Node ifNode) {
+ _wrap('ifStatement_thenBegin($condition, $ifNode)',
+ () => _wrapped.ifStatement_thenBegin(condition, ifNode));
}
@override
@@ -1146,10 +1190,13 @@
}
@override
- void logicalBinaryOp_rightBegin(Expression leftOperand,
+ void logicalBinaryOp_rightBegin(Expression leftOperand, Node wholeExpression,
{required bool isAnd}) {
- _wrap('logicalBinaryOp_rightBegin($leftOperand, isAnd: $isAnd)',
- () => _wrapped.logicalBinaryOp_rightBegin(leftOperand, isAnd: isAnd));
+ _wrap(
+ 'logicalBinaryOp_rightBegin($leftOperand, $wholeExpression, '
+ 'isAnd: $isAnd)',
+ () => _wrapped.logicalBinaryOp_rightBegin(leftOperand, wholeExpression,
+ isAnd: isAnd));
}
@override
@@ -3244,7 +3291,7 @@
}
@override
- void conditional_thenBegin(Expression condition) {
+ void conditional_thenBegin(Expression condition, Node conditionalExpression) {
ExpressionInfo<Variable, Type> conditionInfo = _expressionEnd(condition);
_stack.add(new _ConditionalContext(conditionInfo));
_current = conditionInfo.ifTrue;
@@ -3512,7 +3559,7 @@
}
@override
- void ifStatement_thenBegin(Expression condition) {
+ void ifStatement_thenBegin(Expression condition, Node ifNode) {
ExpressionInfo<Variable, Type> conditionInfo = _expressionEnd(condition);
_stack.add(new _IfContext(conditionInfo));
_current = conditionInfo.ifTrue;
@@ -3625,7 +3672,7 @@
}
@override
- void logicalBinaryOp_rightBegin(Expression leftOperand,
+ void logicalBinaryOp_rightBegin(Expression leftOperand, Node wholeExpression,
{required bool isAnd}) {
ExpressionInfo<Variable, Type> conditionInfo = _expressionEnd(leftOperand);
_stack.add(new _BranchContext<Variable, Type>(conditionInfo));
@@ -3992,6 +4039,544 @@
String toString() => '_IfNullExpressionContext(previous: $_previous)';
}
+/// Contextual information tracked by legacy type promotion about a binary "and"
+/// expression (`&&`).
+class _LegacyBinaryAndContext<Variable extends Object, Type extends Object>
+ extends _LegacyContext<Variable, Type> {
+ /// Types that were shown by the LHS of the "and" expression.
+ final Map<Variable, Type> _lhsShownTypes;
+
+ /// Information about variables that might be assigned by the RHS of the "and"
+ /// expression.
+ final AssignedVariablesNodeInfo<Variable> _assignedVariablesInfoForRhs;
+
+ _LegacyBinaryAndContext(Map<Variable, Type> previousKnownTypes,
+ this._lhsShownTypes, this._assignedVariablesInfoForRhs)
+ : super(previousKnownTypes);
+}
+
+/// Contextual information tracked by legacy type promotion about a statement or
+/// expression.
+class _LegacyContext<Variable, Type> {
+ /// The set of known types in effect before the statement or expression in
+ /// question was encountered.
+ final Map<Variable, Type> _previousKnownTypes;
+
+ _LegacyContext(this._previousKnownTypes);
+}
+
+/// Data tracked by legacy type promotion about an expression.
+class _LegacyExpressionInfo<Variable, Type> {
+ /// Variables whose types are "shown" by the expression in question.
+ ///
+ /// For example, the spec says that the expression `x is T` "shows" `x` to
+ /// have type `T`, so accordingly, the [_LegacyExpressionInfo] for `x is T`
+ /// will have an entry in this map that maps `x` to `T`.
+ final Map<Variable, Type> _shownTypes;
+
+ _LegacyExpressionInfo(this._shownTypes);
+
+ @override
+ String toString() => 'LegacyExpressionInfo($_shownTypes)';
+}
+
+/// Implementation of [FlowAnalysis] that performs legacy (pre-null-safety) type
+/// promotion.
+class _LegacyTypePromotion<Node extends Object, Statement extends Node,
+ Expression extends Object, Variable extends Object, Type extends Object>
+ implements FlowAnalysis<Node, Statement, Expression, Variable, Type> {
+ /// The [TypeOperations], used to access types, and check subtyping.
+ final TypeOperations<Variable, Type> _typeOperations;
+
+ /// Information about variable assignments computed during the previous
+ /// compilation pass.
+ final AssignedVariables<Node, Variable> _assignedVariables;
+
+ /// The most recently visited expression for which a [_LegacyExpressionInfo]
+ /// object exists, or `null` if no expression has been visited that has a
+ /// corresponding [_LegacyExpressionInfo] object.
+ Expression? _expressionWithInfo;
+
+ /// If [_expressionWithInfo] is not `null`, the [_LegacyExpressionInfo] object
+ /// corresponding to it. Otherwise `null`.
+ _LegacyExpressionInfo<Variable, Type>? _expressionInfo;
+
+ /// The set of type promotions currently in effect.
+ Map<Variable, Type> _knownTypes = {};
+
+ /// Stack of [_LegacyContext] objects representing the statements and
+ /// expressions that are currently being visited.
+ final List<_LegacyContext<Variable, Type>> _contextStack = [];
+
+ /// Stack for tracking writes occurring on the LHS of a binary "and" (`&&`)
+ /// operation. Whenever we visit a write, we update the top entry in this
+ /// stack; whenever we begin to visit the LHS of a binary "and", we push
+ /// a fresh empty entry onto this stack; accordingly, upon reaching the RHS of
+ /// the binary "and", the top entry of the stack contains the set of variables
+ /// written to during the LHS of the "and".
+ final List<Set<Variable>> _writeStackForAnd = [{}];
+
+ _LegacyTypePromotion(this._typeOperations, this._assignedVariables);
+
+ @override
+ bool get isReachable => true;
+
+ @override
+ void asExpression_end(Expression subExpression, Type type) {}
+
+ @override
+ void assert_afterCondition(Expression condition) {}
+
+ @override
+ void assert_begin() {}
+
+ @override
+ void assert_end() {}
+
+ @override
+ void booleanLiteral(Expression expression, bool value) {}
+
+ @override
+ void conditional_conditionBegin() {}
+
+ @override
+ void conditional_elseBegin(Expression thenExpression) {
+ _knownTypes = _contextStack.removeLast()._previousKnownTypes;
+ }
+
+ @override
+ void conditional_end(
+ Expression conditionalExpression, Expression elseExpression) {}
+
+ @override
+ void conditional_thenBegin(Expression condition, Node conditionalExpression) {
+ _conditionalOrIf_thenBegin(condition, conditionalExpression);
+ }
+
+ @override
+ void declare(Variable variable, bool initialized) {}
+
+ @override
+ void doStatement_bodyBegin(Statement doStatement) {}
+
+ @override
+ void doStatement_conditionBegin() {}
+
+ @override
+ void doStatement_end(Expression condition) {}
+
+ @override
+ void equalityOp_end(Expression wholeExpression, Expression rightOperand,
+ Type rightOperandType,
+ {bool notEqual = false}) {}
+
+ @override
+ void equalityOp_rightBegin(Expression leftOperand, Type leftOperandType) {}
+
+ @override
+ ExpressionInfo<Variable, Type>? expressionInfoForTesting(Expression target) {
+ throw new StateError(
+ 'expressionInfoForTesting requires null-aware flow analysis');
+ }
+
+ @override
+ void finish() {
+ assert(_contextStack.isEmpty, 'Unexpected stack: $_contextStack');
+ }
+
+ @override
+ void for_bodyBegin(Statement? node, Expression? condition) {}
+
+ @override
+ void for_conditionBegin(Node node) {}
+
+ @override
+ void for_end() {}
+
+ @override
+ void for_updaterBegin() {}
+
+ @override
+ void forEach_bodyBegin(
+ Node node, Variable? loopVariable, Type? writtenType) {}
+
+ @override
+ void forEach_end() {}
+
+ @override
+ void forwardExpression(Expression newExpression, Expression oldExpression) {
+ if (identical(_expressionWithInfo, oldExpression)) {
+ _expressionWithInfo = newExpression;
+ }
+ }
+
+ @override
+ void functionExpression_begin(Node node) {}
+
+ @override
+ void functionExpression_end() {}
+
+ @override
+ void handleBreak(Statement target) {}
+
+ @override
+ void handleContinue(Statement target) {}
+
+ @override
+ void handleExit() {}
+
+ @override
+ void ifNullExpression_end() {}
+
+ @override
+ void ifNullExpression_rightBegin(
+ Expression leftHandSide, Type leftHandSideType) {}
+
+ @override
+ void ifStatement_conditionBegin() {}
+
+ @override
+ void ifStatement_elseBegin() {
+ _knownTypes = _contextStack.removeLast()._previousKnownTypes;
+ }
+
+ @override
+ void ifStatement_end(bool hasElse) {
+ if (!hasElse) {
+ _knownTypes = _contextStack.removeLast()._previousKnownTypes;
+ }
+ }
+
+ @override
+ void ifStatement_thenBegin(Expression condition, Node ifNode) {
+ _conditionalOrIf_thenBegin(condition, ifNode);
+ }
+
+ @override
+ void initialize(
+ Variable variable, Type initializerType, Expression initializerExpression,
+ {required bool isFinal, required bool isLate}) {}
+
+ @override
+ bool isAssigned(Variable variable) {
+ return true;
+ }
+
+ @override
+ void isExpression_end(Expression isExpression, Expression subExpression,
+ bool isNot, Type type) {
+ _LegacyExpressionInfo<Variable, Type>? expressionInfo =
+ _getExpressionInfo(subExpression);
+ if (!isNot && expressionInfo is _LegacyVariableReadInfo<Variable, Type>) {
+ Variable variable = expressionInfo._variable;
+ Type currentType =
+ _knownTypes[variable] ?? _typeOperations.variableType(variable);
+ Type? promotedType = _typeOperations.tryPromoteToType(type, currentType);
+ if (promotedType != null &&
+ !_typeOperations.isSameType(currentType, promotedType)) {
+ _storeExpressionInfo(
+ isExpression,
+ new _LegacyExpressionInfo<Variable, Type>(
+ {variable: promotedType}));
+ }
+ }
+ }
+
+ @override
+ bool isUnassigned(Variable variable) {
+ return false;
+ }
+
+ @override
+ void labeledStatement_begin(Node node) {}
+
+ @override
+ void labeledStatement_end() {}
+
+ @override
+ void lateInitializer_begin(Node node) {}
+
+ @override
+ void lateInitializer_end() {}
+
+ @override
+ void logicalBinaryOp_begin() {
+ _writeStackForAnd.add({});
+ }
+
+ @override
+ void logicalBinaryOp_end(Expression wholeExpression, Expression rightOperand,
+ {required bool isAnd}) {
+ if (!isAnd) return;
+ _LegacyBinaryAndContext<Variable, Type> context =
+ _contextStack.removeLast() as _LegacyBinaryAndContext<Variable, Type>;
+ _knownTypes = context._previousKnownTypes;
+ AssignedVariablesNodeInfo<Variable> assignedVariablesInfoForRhs =
+ context._assignedVariablesInfoForRhs;
+ Map<Variable, Type> lhsShownTypes = context._lhsShownTypes;
+ Map<Variable, Type> rhsShownTypes =
+ _getExpressionInfo(rightOperand)?._shownTypes ?? {};
+ // A logical boolean expression b of the form `e1 && e2` shows that a local
+ // variable v has type T if both of the following conditions hold:
+ // - Either e1 shows that v has type T or e2 shows that v has type T.
+ // - v is not mutated in e2 or within a function other than the one where v
+ // is declared.
+ // We don't have to worry about whether v is mutated within a function other
+ // than the one where v is declared, because that is checked every time we
+ // evaluate whether v is known to have type T. So we just have to combine
+ // together the things shown by e1 and e2, and discard anything mutated in
+ // e2.
+ //
+ // Note, however, that there is an ambiguity that isn't addressed by the
+ // spec: what happens if e1 shows that v has type T1 and e2 shows that v has
+ // type T2? The de facto behavior we have had for a long time is to combine
+ // the two types in the same way we would combine it if c were first
+ // promoted to T1 and then had a successful `is T2` check.
+ Map<Variable, Type> newShownTypes = {};
+ for (MapEntry<Variable, Type> entry in lhsShownTypes.entries) {
+ if (assignedVariablesInfoForRhs._written.contains(entry.key)) continue;
+ newShownTypes[entry.key] = entry.value;
+ }
+ for (MapEntry<Variable, Type> entry in rhsShownTypes.entries) {
+ if (assignedVariablesInfoForRhs._written.contains(entry.key)) continue;
+ Type? previouslyShownType = newShownTypes[entry.key];
+ if (previouslyShownType == null) {
+ newShownTypes[entry.key] = entry.value;
+ } else {
+ Type? newShownType =
+ _typeOperations.tryPromoteToType(entry.value, previouslyShownType);
+ if (newShownType != null &&
+ !_typeOperations.isSameType(previouslyShownType, newShownType)) {
+ newShownTypes[entry.key] = newShownType;
+ }
+ }
+ }
+ _storeExpressionInfo(wholeExpression,
+ new _LegacyExpressionInfo<Variable, Type>(newShownTypes));
+ }
+
+ @override
+ void logicalBinaryOp_rightBegin(Expression leftOperand, Node wholeExpression,
+ {required bool isAnd}) {
+ Set<Variable> variablesWrittenOnLhs = _writeStackForAnd.removeLast();
+ _writeStackForAnd.last.addAll(variablesWrittenOnLhs);
+ if (!isAnd) return;
+ AssignedVariablesNodeInfo<Variable> info =
+ _assignedVariables._getInfoForNode(wholeExpression);
+ Map<Variable, Type> lhsShownTypes =
+ _getExpressionInfo(leftOperand)?._shownTypes ?? {};
+ _contextStack.add(new _LegacyBinaryAndContext<Variable, Type>(
+ _knownTypes, lhsShownTypes, info));
+ Map<Variable, Type>? newKnownTypes;
+ for (MapEntry<Variable, Type> entry in lhsShownTypes.entries) {
+ // Given a statement of the form `e1 && e2`, if e1 shows that a
+ // local variable v has type T, then the type of v is known to be T in
+ // e2, unless any of the following are true:
+ // - v is potentially mutated in e1,
+ if (variablesWrittenOnLhs.contains(entry.key)) continue;
+ // - v is potentially mutated in e2,
+ if (info._written.contains(entry.key)) continue;
+ // - v is potentially mutated within a function other than the one where
+ // v is declared, or
+ if (_assignedVariables._anywhere._captured.contains(entry.key)) {
+ continue;
+ }
+ // - v is accessed by a function defined in e2 and v is potentially
+ // mutated anywhere in the scope of v.
+ if (info._readCaptured.contains(entry.key) &&
+ _assignedVariables._anywhere._written.contains(entry.key)) {
+ continue;
+ }
+ (newKnownTypes ??= new Map<Variable, Type>.from(_knownTypes))[entry.key] =
+ entry.value;
+ }
+ if (newKnownTypes != null) _knownTypes = newKnownTypes;
+ }
+
+ @override
+ void logicalNot_end(Expression notExpression, Expression operand) {}
+
+ @override
+ void nonNullAssert_end(Expression operand) {}
+
+ @override
+ void nullAwareAccess_end() {}
+
+ @override
+ void nullAwareAccess_rightBegin(Expression? target, Type targetType) {}
+
+ @override
+ void nullLiteral(Expression expression) {}
+
+ @override
+ void parenthesizedExpression(
+ Expression outerExpression, Expression innerExpression) {
+ forwardExpression(outerExpression, innerExpression);
+ }
+
+ @override
+ void promote(Variable variable, Type type) {
+ throw new UnimplementedError('TODO(paulberry)');
+ }
+
+ @override
+ Type? promotedType(Variable variable) {
+ return _knownTypes[variable];
+ }
+
+ @override
+ SsaNode<Variable, Type>? ssaNodeForTesting(Variable variable) {
+ throw new StateError('ssaNodeForTesting requires null-aware flow analysis');
+ }
+
+ @override
+ void switchStatement_beginCase(bool hasLabel, Node node) {}
+
+ @override
+ void switchStatement_end(bool isExhaustive) {}
+
+ @override
+ void switchStatement_expressionEnd(Statement switchStatement) {}
+
+ @override
+ void tryCatchStatement_bodyBegin() {}
+
+ @override
+ void tryCatchStatement_bodyEnd(Node body) {}
+
+ @override
+ void tryCatchStatement_catchBegin(
+ Variable? exceptionVariable, Variable? stackTraceVariable) {}
+
+ @override
+ void tryCatchStatement_catchEnd() {}
+
+ @override
+ void tryCatchStatement_end() {}
+
+ @override
+ void tryFinallyStatement_bodyBegin() {}
+
+ @override
+ void tryFinallyStatement_end(Node finallyBlock) {}
+
+ @override
+ void tryFinallyStatement_finallyBegin(Node body) {}
+
+ @override
+ Type? variableRead(Expression expression, Variable variable) {
+ _storeExpressionInfo(
+ expression, new _LegacyVariableReadInfo<Variable, Type>(variable));
+ return _knownTypes[variable];
+ }
+
+ @override
+ void whileStatement_bodyBegin(
+ Statement whileStatement, Expression condition) {}
+
+ @override
+ void whileStatement_conditionBegin(Node node) {}
+
+ @override
+ void whileStatement_end() {}
+
+ @override
+ void write(
+ Variable variable, Type writtenType, Expression? writtenExpression) {
+ assert(
+ _assignedVariables._anywhere._written.contains(variable),
+ "Variable is written to, but was not included in "
+ "_variablesWrittenAnywhere: $variable");
+ _writeStackForAnd.last.add(variable);
+ }
+
+ void _conditionalOrIf_thenBegin(Expression condition, Node node) {
+ _contextStack.add(new _LegacyContext<Variable, Type>(_knownTypes));
+ AssignedVariablesNodeInfo<Variable> info =
+ _assignedVariables._getInfoForNode(node);
+ Map<Variable, Type>? newKnownTypes;
+ _LegacyExpressionInfo<Variable, Type>? expressionInfo =
+ _getExpressionInfo(condition);
+ if (expressionInfo != null) {
+ for (MapEntry<Variable, Type> entry
+ in expressionInfo._shownTypes.entries) {
+ // Given an expression of the form n1?n2:n3 or a statement of the form
+ // `if (n1) n2 else n3`, if n1 shows that a local variable v has type T,
+ // then the type of v is known to be T in n2, unless any of the
+ // following are true:
+ // - v is potentially mutated in n2,
+ if (info._written.contains(entry.key)) continue;
+ // - v is potentially mutated within a function other than the one where
+ // v is declared, or
+ if (_assignedVariables._anywhere._captured.contains(entry.key)) {
+ continue;
+ }
+ // - v is accessed by a function defined in n2 and v is potentially
+ // mutated anywhere in the scope of v.
+ if (info._readCaptured.contains(entry.key) &&
+ _assignedVariables._anywhere._written.contains(entry.key)) {
+ continue;
+ }
+ (newKnownTypes ??=
+ new Map<Variable, Type>.from(_knownTypes))[entry.key] = entry.value;
+ }
+ if (newKnownTypes != null) _knownTypes = newKnownTypes;
+ }
+ }
+
+ @override
+ void _dumpState() {
+ print(' knownTypes: $_knownTypes');
+ print(' expressionWithInfo: $_expressionWithInfo');
+ print(' expressionInfo: $_expressionInfo');
+ print(' contextStack:');
+ for (_LegacyContext<Variable, Type> stackEntry in _contextStack.reversed) {
+ print(' $stackEntry');
+ }
+ print(' writeStackForAnd:');
+ for (Set<Variable> stackEntry in _writeStackForAnd.reversed) {
+ print(' $stackEntry');
+ }
+ }
+
+ /// Gets the [_LegacyExpressionInfo] associated with [expression], if any;
+ /// otherwise returns `null`.
+ _LegacyExpressionInfo<Variable, Type>? _getExpressionInfo(
+ Expression expression) {
+ if (identical(expression, _expressionWithInfo)) {
+ _LegacyExpressionInfo<Variable, Type>? expressionInfo = _expressionInfo;
+ _expressionInfo = null;
+ return expressionInfo;
+ } else {
+ return null;
+ }
+ }
+
+ /// Associates [expressionInfo] with [expression] for use by a future call to
+ /// [_getExpressionInfo].
+ void _storeExpressionInfo(Expression expression,
+ _LegacyExpressionInfo<Variable, Type> expressionInfo) {
+ _expressionWithInfo = expression;
+ _expressionInfo = expressionInfo;
+ }
+}
+
+/// Data tracked by legacy type promotion about an expression that reads the
+/// value of a local variable.
+class _LegacyVariableReadInfo<Variable, Type>
+ implements _LegacyExpressionInfo<Variable, Type> {
+ /// The variable being referred to.
+ final Variable _variable;
+
+ _LegacyVariableReadInfo(this._variable);
+
+ @override
+ Map<Variable, Type> get _shownTypes => {};
+
+ @override
+ String toString() => 'LegacyVariableReadInfo($_variable, $_shownTypes)';
+}
+
/// [_FlowContext] representing a null aware access (`?.`).
class _NullAwareAccessContext<Variable extends Object, Type extends Object>
extends _SimpleContext<Variable, Type> {
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/assigned_variables_test.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/assigned_variables_test.dart
index cae1c1c..8676f23 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/assigned_variables_test.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/assigned_variables_test.dart
@@ -6,6 +6,39 @@
import 'package:test/test.dart';
main() {
+ test('readCapturedAnywhere records reads in closures', () {
+ var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
+ var v1 = _Variable('v1');
+ var v2 = _Variable('v2');
+ var v3 = _Variable('v3');
+ assignedVariables.declare(v1);
+ assignedVariables.declare(v2);
+ assignedVariables.declare(v3);
+ assignedVariables.read(v1);
+ assignedVariables.beginNode();
+ assignedVariables.read(v2);
+ assignedVariables.endNode(_Node(),
+ isClosureOrLateVariableInitializer: true);
+ assignedVariables.read(v3);
+ assignedVariables.finish();
+ expect(assignedVariables.readCapturedAnywhere, {v2});
+ });
+
+ test('readCapturedAnywhere does not record variables local to a closure', () {
+ var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
+ var v1 = _Variable('v1');
+ var v2 = _Variable('v2');
+ assignedVariables.declare(v1);
+ assignedVariables.beginNode();
+ assignedVariables.declare(v2);
+ assignedVariables.read(v1);
+ assignedVariables.read(v2);
+ assignedVariables.endNode(_Node(),
+ isClosureOrLateVariableInitializer: true);
+ assignedVariables.finish();
+ expect(assignedVariables.readCapturedAnywhere, {v1});
+ });
+
test('capturedAnywhere records assignments in closures', () {
var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
var v1 = _Variable('v1');
@@ -39,6 +72,24 @@
expect(assignedVariables.capturedAnywhere, {v1});
});
+ test('readAnywhere records all reads', () {
+ var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
+ var v1 = _Variable('v1');
+ var v2 = _Variable('v2');
+ var v3 = _Variable('v3');
+ assignedVariables.declare(v1);
+ assignedVariables.declare(v2);
+ assignedVariables.declare(v3);
+ assignedVariables.read(v1);
+ assignedVariables.beginNode();
+ assignedVariables.read(v2);
+ assignedVariables.endNode(_Node(),
+ isClosureOrLateVariableInitializer: true);
+ assignedVariables.read(v3);
+ assignedVariables.finish();
+ expect(assignedVariables.readAnywhere, {v1, v2, v3});
+ });
+
test('writtenAnywhere records all assignments', () {
var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
var v1 = _Variable('v1');
@@ -57,6 +108,59 @@
expect(assignedVariables.writtenAnywhere, {v1, v2, v3});
});
+ test('readInNode ignores reads outside the node', () {
+ var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
+ var v1 = _Variable('v1');
+ var v2 = _Variable('v2');
+ assignedVariables.declare(v1);
+ assignedVariables.declare(v2);
+ assignedVariables.read(v1);
+ assignedVariables.beginNode();
+ var node = _Node();
+ assignedVariables.endNode(node);
+ assignedVariables.read(v2);
+ assignedVariables.finish();
+ expect(assignedVariables.readInNode(node), isEmpty);
+ });
+
+ test('readInNode records reads inside the node', () {
+ var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
+ var v1 = _Variable('v1');
+ assignedVariables.declare(v1);
+ assignedVariables.beginNode();
+ assignedVariables.read(v1);
+ var node = _Node();
+ assignedVariables.endNode(node);
+ assignedVariables.finish();
+ expect(assignedVariables.readInNode(node), {v1});
+ });
+
+ test('readInNode records reads in a nested node', () {
+ var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
+ var v1 = _Variable('v1');
+ assignedVariables.declare(v1);
+ assignedVariables.beginNode();
+ assignedVariables.beginNode();
+ assignedVariables.read(v1);
+ assignedVariables.endNode(_Node());
+ var node = _Node();
+ assignedVariables.endNode(node);
+ assignedVariables.finish();
+ expect(assignedVariables.readInNode(node), {v1});
+ });
+
+ test('readInNode records reads in a closure', () {
+ var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
+ var v1 = _Variable('v1');
+ assignedVariables.declare(v1);
+ assignedVariables.beginNode();
+ assignedVariables.read(v1);
+ var node = _Node();
+ assignedVariables.endNode(node, isClosureOrLateVariableInitializer: true);
+ assignedVariables.finish();
+ expect(assignedVariables.readInNode(node), {v1});
+ });
+
test('writtenInNode ignores assignments outside the node', () {
var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
var v1 = _Variable('v1');
@@ -110,6 +214,46 @@
expect(assignedVariables.writtenInNode(node), {v1});
});
+ test('readCapturedInNode ignores reads in non-nested closures', () {
+ var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
+ var v1 = _Variable('v1');
+ var v2 = _Variable('v2');
+ assignedVariables.declare(v1);
+ assignedVariables.declare(v2);
+ assignedVariables.beginNode();
+ assignedVariables.read(v1);
+ assignedVariables.endNode(_Node(),
+ isClosureOrLateVariableInitializer: true);
+ assignedVariables.beginNode();
+ var node = _Node();
+ assignedVariables.endNode(node);
+ assignedVariables.beginNode();
+ assignedVariables.read(v2);
+ assignedVariables.endNode(_Node(),
+ isClosureOrLateVariableInitializer: true);
+ assignedVariables.finish();
+ expect(assignedVariables.readCapturedInNode(node), isEmpty);
+ });
+
+ test('readCapturedInNode records assignments in nested closures', () {
+ var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
+ var v1 = _Variable('v1');
+ assignedVariables.declare(v1);
+ assignedVariables.beginNode();
+ assignedVariables.beginNode();
+ assignedVariables.beginNode();
+ assignedVariables.read(v1);
+ assignedVariables.endNode(_Node(),
+ isClosureOrLateVariableInitializer: true);
+ var innerNode = _Node();
+ assignedVariables.endNode(innerNode);
+ var outerNode = _Node();
+ assignedVariables.endNode(outerNode);
+ assignedVariables.finish();
+ expect(assignedVariables.readCapturedInNode(innerNode), {v1});
+ expect(assignedVariables.readCapturedInNode(outerNode), {v1});
+ });
+
test('capturedInNode ignores assignments in non-nested closures', () {
var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
var v1 = _Variable('v1');
@@ -160,8 +304,10 @@
assignedVariables.beginNode();
assignedVariables.declare(v1);
assignedVariables.declare(v2);
+ assignedVariables.read(v1);
assignedVariables.write(v1);
assignedVariables.beginNode();
+ assignedVariables.read(v2);
assignedVariables.write(v2);
assignedVariables.endNode(_Node(),
isClosureOrLateVariableInitializer: true);
@@ -171,9 +317,13 @@
var outerNode = _Node();
assignedVariables.endNode(outerNode);
assignedVariables.finish();
+ expect(assignedVariables.readInNode(innerNode), isEmpty);
expect(assignedVariables.writtenInNode(innerNode), isEmpty);
+ expect(assignedVariables.readCapturedInNode(innerNode), isEmpty);
expect(assignedVariables.capturedInNode(innerNode), isEmpty);
+ expect(assignedVariables.readInNode(outerNode), isEmpty);
expect(assignedVariables.writtenInNode(outerNode), isEmpty);
+ expect(assignedVariables.readCapturedInNode(outerNode), isEmpty);
expect(assignedVariables.capturedInNode(outerNode), isEmpty);
});
@@ -185,8 +335,10 @@
assignedVariables.beginNode();
assignedVariables.declare(v1);
assignedVariables.declare(v2);
+ assignedVariables.read(v1);
assignedVariables.write(v1);
assignedVariables.beginNode();
+ assignedVariables.read(v2);
assignedVariables.write(v2);
assignedVariables.endNode(_Node(),
isClosureOrLateVariableInitializer: true);
@@ -196,9 +348,13 @@
var outerNode = _Node();
assignedVariables.endNode(outerNode);
assignedVariables.finish();
+ expect(assignedVariables.readInNode(innerNode), isEmpty);
expect(assignedVariables.writtenInNode(innerNode), isEmpty);
+ expect(assignedVariables.readCapturedInNode(innerNode), isEmpty);
expect(assignedVariables.capturedInNode(innerNode), isEmpty);
+ expect(assignedVariables.readInNode(outerNode), isEmpty);
expect(assignedVariables.writtenInNode(outerNode), isEmpty);
+ expect(assignedVariables.readCapturedInNode(outerNode), isEmpty);
expect(assignedVariables.capturedInNode(outerNode), isEmpty);
});
});
@@ -218,6 +374,23 @@
expect(assignedVariables.declaredInNode(node), unorderedEquals([v1, v2]));
});
+ test('discardNode percolates reads to enclosing node', () {
+ var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
+ var v1 = _Variable('v1');
+ var v2 = _Variable('v2');
+ var node = _Node();
+ assignedVariables.declare(v1);
+ assignedVariables.declare(v2);
+ assignedVariables.beginNode();
+ assignedVariables.beginNode();
+ assignedVariables.read(v1);
+ assignedVariables.read(v2);
+ assignedVariables.discardNode();
+ assignedVariables.endNode(node);
+ assignedVariables.finish();
+ expect(assignedVariables.readInNode(node), unorderedEquals([v1, v2]));
+ });
+
test('discardNode percolates writes to enclosing node', () {
var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
var v1 = _Variable('v1');
@@ -235,7 +408,28 @@
expect(assignedVariables.writtenInNode(node), unorderedEquals([v1, v2]));
});
- test('discardNode percolates captures to enclosing node', () {
+ test('discardNode percolates read captures to enclosing node', () {
+ var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
+ var v1 = _Variable('v1');
+ var v2 = _Variable('v2');
+ var node = _Node();
+ assignedVariables.declare(v1);
+ assignedVariables.declare(v2);
+ assignedVariables.beginNode();
+ assignedVariables.beginNode();
+ assignedVariables.beginNode();
+ assignedVariables.read(v1);
+ assignedVariables.read(v2);
+ assignedVariables.endNode(_Node(),
+ isClosureOrLateVariableInitializer: true);
+ assignedVariables.discardNode();
+ assignedVariables.endNode(node);
+ assignedVariables.finish();
+ expect(
+ assignedVariables.readCapturedInNode(node), unorderedEquals([v1, v2]));
+ });
+
+ test('discardNode percolates write captures to enclosing node', () {
var assignedVariables = AssignedVariablesForTesting<_Node, _Variable>();
var v1 = _Variable('v1');
var v2 = _Variable('v2');
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
index 84f68e4..9838684 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
@@ -209,8 +209,8 @@
/// Representation of an expression in the pseudo-Dart language used for flow
/// analysis testing. Methods in this class may be used to create more complex
/// expressions based on this one.
-abstract class Expression implements _Visitable<Type> {
- const Expression();
+abstract class Expression extends Node implements _Visitable<Type> {
+ const Expression() : super._();
/// If `this` is an expression `x`, creates the expression `x!`.
Expression get nonNullAssert => new _NonNullAssert(this);
@@ -398,13 +398,17 @@
final bool allowLocalBooleanVarsToPromote;
+ final bool legacy;
+
final Map<String, bool> _subtypes = Map.of(_coreSubtypes);
final Map<String, Type> _factorResults = Map.of(_coreFactors);
Node? _currentSwitch;
- Harness({this.allowLocalBooleanVarsToPromote = false});
+ Map<String, Map<String, String>> _promotionExceptions = {};
+
+ Harness({this.allowLocalBooleanVarsToPromote = false, this.legacy = false});
/// Updates the harness so that when a [factor] query is invoked on types
/// [from] and [what], [result] will be returned.
@@ -413,6 +417,10 @@
_factorResults[query] = Type(result);
}
+ void addPromotionException(String from, String to, String result) {
+ (_promotionExceptions[from] ??= {})[to] = result;
+ }
+
/// Updates the harness so that when an [isSubtypeOf] query is invoked on
/// types [leftType] and [rightType], [isSubtype] will be returned.
void addSubtype(String leftType, String rightType, bool isSubtype) {
@@ -470,15 +478,22 @@
void run(List<Statement> statements) {
var assignedVariables = AssignedVariables<Node, Var>();
statements._preVisit(assignedVariables);
- var flow = FlowAnalysis<Node, Statement, Expression, Var, Type>(
- this, assignedVariables,
- allowLocalBooleanVarsToPromote: allowLocalBooleanVarsToPromote);
+ var flow = legacy
+ ? FlowAnalysis<Node, Statement, Expression, Var, Type>.legacy(
+ this, assignedVariables)
+ : FlowAnalysis<Node, Statement, Expression, Var, Type>(
+ this, assignedVariables,
+ allowLocalBooleanVarsToPromote: allowLocalBooleanVarsToPromote);
statements._visit(this, flow);
flow.finish();
}
@override
Type? tryPromoteToType(Type to, Type from) {
+ var exception = (_promotionExceptions[from.type] ?? {})[to.type];
+ if (exception != null) {
+ return Type(exception);
+ }
if (isSubtypeOf(to, from)) {
return to;
} else {
@@ -529,7 +544,7 @@
/// Representation of an expression or statement in the pseudo-Dart language
/// used for flow analysis testing.
class Node {
- Node._();
+ const Node._();
}
/// Helper class allowing tests to examine the values of variables' SSA nodes.
@@ -606,7 +621,12 @@
Var(this.name, String typeStr) : type = Type(typeStr);
/// Creates an expression representing a read of this variable.
- Expression get read => new _VariableRead(this);
+ Expression get read => new _VariableRead(this, null);
+
+ /// Creates an expression representing a read of this variable, which as a
+ /// side effect will call the given callback with the returned promoted type.
+ Expression readAndCheckPromotedType(void Function(Type?) callback) =>
+ new _VariableRead(this, callback);
@override
String toString() => '$type $name';
@@ -828,7 +848,9 @@
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
condition._preVisit(assignedVariables);
+ assignedVariables.beginNode();
ifTrue._preVisit(assignedVariables);
+ assignedVariables.endNode(this);
ifFalse._preVisit(assignedVariables);
}
@@ -836,7 +858,7 @@
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.conditional_conditionBegin();
- flow.conditional_thenBegin(condition.._visit(h, flow));
+ flow.conditional_thenBegin(condition.._visit(h, flow), this);
var ifTrueType = ifTrue._visit(h, flow);
flow.conditional_elseBegin(ifTrue);
var ifFalseType = ifFalse._visit(h, flow);
@@ -1131,7 +1153,9 @@
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
condition._preVisit(assignedVariables);
+ assignedVariables.beginNode();
ifTrue._preVisit(assignedVariables);
+ assignedVariables.endNode(this);
ifFalse?._preVisit(assignedVariables);
}
@@ -1139,7 +1163,7 @@
void _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.ifStatement_conditionBegin();
- flow.ifStatement_thenBegin(condition.._visit(h, flow));
+ flow.ifStatement_thenBegin(condition.._visit(h, flow), this);
ifTrue._visit(h, flow);
if (ifFalse == null) {
flow.ifStatement_end(false);
@@ -1259,14 +1283,16 @@
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
lhs._preVisit(assignedVariables);
+ assignedVariables.beginNode();
rhs._preVisit(assignedVariables);
+ assignedVariables.endNode(this);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.logicalBinaryOp_begin();
- flow.logicalBinaryOp_rightBegin(lhs.._visit(h, flow), isAnd: isAnd);
+ flow.logicalBinaryOp_rightBegin(lhs.._visit(h, flow), this, isAnd: isAnd);
flow.logicalBinaryOp_end(this, rhs.._visit(h, flow), isAnd: isAnd);
return Type('bool');
}
@@ -1540,18 +1566,24 @@
class _VariableRead extends Expression {
final Var variable;
- _VariableRead(this.variable);
+ final void Function(Type?)? callback;
+
+ _VariableRead(this.variable, this.callback);
@override
String toString() => variable.name;
@override
- void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
+ void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
+ assignedVariables.read(variable);
+ }
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
- return flow.variableRead(this, variable) ?? variable.type;
+ var readResult = flow.variableRead(this, variable);
+ callback?.call(readResult);
+ return readResult ?? variable.type;
}
}
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
index 5d522be..9621ff7 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
@@ -502,10 +502,11 @@
test('finish checks proper nesting', () {
var h = Harness();
var e = expr('Null');
+ var s = if_(e, []);
var flow = FlowAnalysis<Node, Statement, Expression, Var, Type>(
h, AssignedVariables<Node, Var>());
flow.ifStatement_conditionBegin();
- flow.ifStatement_thenBegin(e);
+ flow.ifStatement_thenBegin(e, s);
expect(() => flow.finish(), _asserts);
});
@@ -4602,6 +4603,766 @@
expect(m1.inheritTested(h, m2), same(m1));
});
});
+
+ group('Legacy promotion', () {
+ group('if statement', () {
+ group('promotes a variable whose type is shown by its condition', () {
+ test('within then-block', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int'), [
+ checkPromoted(x, 'int'),
+ ]),
+ ]);
+ });
+
+ test('but not within else-block', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int'), [], [
+ checkNotPromoted(x),
+ ]),
+ ]);
+ });
+
+ test('unless the then-block mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int'), [
+ checkNotPromoted(x),
+ x.write(expr('int')).stmt,
+ ]),
+ ]);
+ });
+
+ test('even if the condition mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(
+ x
+ .write(expr('int'))
+ .parenthesized
+ .eq(expr('int'))
+ .and(x.read.is_('int')),
+ [
+ checkPromoted(x, 'int'),
+ ]),
+ ]);
+ });
+
+ test('even if the else-block mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int'), [
+ checkPromoted(x, 'int'),
+ ], [
+ x.write(expr('int')).stmt,
+ ]),
+ ]);
+ });
+
+ test('unless a closure mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int'), [
+ checkNotPromoted(x),
+ ]),
+ localFunction([
+ x.write(expr('int')).stmt,
+ ]),
+ ]);
+ });
+
+ test(
+ 'unless a closure in the then-block accesses it and it is mutated '
+ 'anywhere', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int'), [
+ checkNotPromoted(x),
+ localFunction([
+ x.read.stmt,
+ ]),
+ ]),
+ x.write(expr('int')).stmt,
+ ]);
+ });
+
+ test(
+ 'unless a closure in the then-block accesses it and it is mutated '
+ 'anywhere, even if the access is deeply nested', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int'), [
+ checkNotPromoted(x),
+ localFunction([
+ localFunction([
+ x.read.stmt,
+ ]),
+ ]),
+ ]),
+ x.write(expr('int')).stmt,
+ ]);
+ });
+
+ test(
+ 'even if a closure in the condition accesses it and it is mutated '
+ 'somewhere', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(
+ localFunction([
+ x.read.stmt,
+ ]).thenExpr(expr('bool')).and(x.read.is_('int')),
+ [
+ checkPromoted(x, 'int'),
+ ]),
+ x.write(expr('int')).stmt,
+ ]);
+ });
+
+ test(
+ 'even if a closure in the else-block accesses it and it is mutated '
+ 'somewhere', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int'), [
+ checkPromoted(x, 'int'),
+ ], [
+ localFunction([
+ x.read.stmt,
+ ]),
+ ]),
+ x.write(expr('int')).stmt,
+ ]);
+ });
+
+ test(
+ 'even if a closure in the then-block accesses it, provided it is '
+ 'not mutated anywhere', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int'), [
+ checkPromoted(x, 'int'),
+ localFunction([
+ x.read.stmt,
+ ]),
+ ]),
+ ]);
+ });
+ });
+
+ test('handles arbitrary conditions', () {
+ var h = Harness(legacy: true);
+ h.run([
+ if_(expr('bool'), []),
+ ]);
+ });
+
+ test('handles a condition that is a variable', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'bool');
+ h.run([
+ if_(x.read, []),
+ ]);
+ });
+
+ test('handles multiple promotions', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ var y = Var('y', 'Object');
+ h.run([
+ if_(x.read.is_('int').and(y.read.is_('String')), [
+ checkPromoted(x, 'int'),
+ checkPromoted(y, 'String'),
+ ]),
+ ]);
+ });
+ });
+
+ group('conditional expression', () {
+ group('promotes a variable whose type is shown by its condition', () {
+ test('within then-expression', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .conditional(checkPromoted(x, 'int').thenExpr(expr('Object')),
+ expr('Object'))
+ .stmt,
+ ]);
+ });
+
+ test('but not within else-expression', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .conditional(expr('Object'),
+ checkNotPromoted(x).thenExpr(expr('Object')))
+ .stmt,
+ ]);
+ });
+
+ test('unless the then-expression mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .conditional(
+ block([
+ checkNotPromoted(x),
+ x.write(expr('int')).stmt,
+ ]).thenExpr(expr('Object')),
+ expr('Object'))
+ .stmt,
+ ]);
+ });
+
+ test('even if the condition mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x
+ .write(expr('int'))
+ .parenthesized
+ .eq(expr('int'))
+ .and(x.read.is_('int'))
+ .conditional(checkPromoted(x, 'int').thenExpr(expr('Object')),
+ expr('Object'))
+ .stmt,
+ ]);
+ });
+
+ test('even if the else-expression mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .conditional(checkPromoted(x, 'int').thenExpr(expr('int')),
+ x.write(expr('int')))
+ .stmt,
+ ]);
+ });
+
+ test('unless a closure mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .conditional(checkNotPromoted(x).thenExpr(expr('Object')),
+ expr('Object'))
+ .stmt,
+ localFunction([
+ x.write(expr('int')).stmt,
+ ]),
+ ]);
+ });
+
+ test(
+ 'unless a closure in the then-expression accesses it and it is '
+ 'mutated anywhere', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .conditional(
+ block([
+ checkNotPromoted(x),
+ localFunction([
+ x.read.stmt,
+ ]),
+ ]).thenExpr(expr('Object')),
+ expr('Object'))
+ .stmt,
+ x.write(expr('int')).stmt,
+ ]);
+ });
+
+ test(
+ 'even if a closure in the condition accesses it and it is mutated '
+ 'somewhere', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ localFunction([
+ x.read.stmt,
+ ])
+ .thenExpr(expr('Object'))
+ .and(x.read.is_('int'))
+ .conditional(checkPromoted(x, 'int').thenExpr(expr('Object')),
+ expr('Object'))
+ .stmt,
+ x.write(expr('int')).stmt,
+ ]);
+ });
+
+ test(
+ 'even if a closure in the else-expression accesses it and it is '
+ 'mutated somewhere', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .conditional(
+ checkPromoted(x, 'int').thenExpr(expr('Object')),
+ localFunction([
+ x.read.stmt,
+ ]).thenExpr(expr('Object')))
+ .stmt,
+ x.write(expr('int')).stmt,
+ ]);
+ });
+
+ test(
+ 'even if a closure in the then-expression accesses it, provided it '
+ 'is not mutated anywhere', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .conditional(
+ block([
+ checkPromoted(x, 'int'),
+ localFunction([
+ x.read.stmt,
+ ]),
+ ]).thenExpr(expr('Object')),
+ expr('Object'))
+ .stmt,
+ ]);
+ });
+ });
+
+ test('handles arbitrary conditions', () {
+ var h = Harness(legacy: true);
+ h.run([
+ expr('bool').conditional(expr('Object'), expr('Object')).stmt,
+ ]);
+ });
+
+ test('handles a condition that is a variable', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'bool');
+ h.run([
+ x.read.conditional(expr('Object'), expr('Object')).stmt,
+ ]);
+ });
+
+ test('handles multiple promotions', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ var y = Var('y', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .and(y.read.is_('String'))
+ .conditional(
+ block([
+ checkPromoted(x, 'int'),
+ checkPromoted(y, 'String'),
+ ]).thenExpr(expr('Object')),
+ expr('Object'))
+ .stmt
+ ]);
+ });
+ });
+
+ group('logical', () {
+ group('and', () {
+ group("shows a variable's type", () {
+ test('if the lhs shows the type', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int').and(expr('bool')), [
+ checkPromoted(x, 'int'),
+ ]),
+ ]);
+ });
+
+ test('if the rhs shows the type', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(expr('bool').and(x.read.is_('int')), [
+ checkPromoted(x, 'int'),
+ ]),
+ ]);
+ });
+
+ test('unless the rhs mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int').and(x.write(expr('bool'))), [
+ checkNotPromoted(x),
+ ]),
+ ]);
+ });
+
+ test('unless the rhs mutates it, even if the rhs also shows the type',
+ () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(
+ expr('bool').and(x
+ .write(expr('Object'))
+ .and(x.read.is_('int'))
+ .parenthesized),
+ [
+ checkNotPromoted(x),
+ ]),
+ ]);
+ });
+
+ test('unless a closure mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int').and(expr('bool')), [
+ checkNotPromoted(x),
+ ]),
+ localFunction([
+ x.write(expr('int')).stmt,
+ ]),
+ ]);
+ });
+ });
+
+ group('promotes a variable whose type is shown by its lhs', () {
+ test('within its rhs', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .and(checkPromoted(x, 'int').thenExpr(expr('bool')))
+ .stmt,
+ ]);
+ });
+
+ test('unless the lhs mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x
+ .write(expr('int'))
+ .parenthesized
+ .eq(expr('int'))
+ .and(x.read.is_('int'))
+ .parenthesized
+ .and(checkNotPromoted(x).thenExpr(expr('bool')))
+ .stmt,
+ ]);
+ });
+
+ test('unless the rhs mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .and(checkNotPromoted(x).thenExpr(x.write(expr('bool'))))
+ .stmt,
+ ]);
+ });
+
+ test('unless a closure mutates it', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .and(checkNotPromoted(x).thenExpr(expr('bool')))
+ .stmt,
+ localFunction([
+ x.write(expr('int')).stmt,
+ ]),
+ ]);
+ });
+
+ test(
+ 'unless a closure in the rhs accesses it and it is mutated '
+ 'anywhere', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .and(block([
+ checkNotPromoted(x),
+ localFunction([
+ x.read.stmt,
+ ]),
+ ]).thenExpr(expr('bool')))
+ .stmt,
+ x.write(expr('int')).stmt,
+ ]);
+ });
+
+ test(
+ 'even if a closure in the lhs accesses it and it is mutated '
+ 'somewhere', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ localFunction([
+ x.read.stmt,
+ ])
+ .thenExpr(expr('Object'))
+ .and(x.read.is_('int'))
+ .parenthesized
+ .and(checkPromoted(x, 'int').thenExpr(expr('bool')))
+ .stmt,
+ x.write(expr('int')).stmt,
+ ]);
+ });
+
+ test(
+ 'even if a closure in the rhs accesses it, provided it is not '
+ 'mutated anywhere', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .and(block([
+ checkPromoted(x, 'int'),
+ localFunction([
+ x.read.stmt,
+ ]),
+ ]).thenExpr(expr('bool')))
+ .stmt,
+ ]);
+ });
+ });
+
+ test('uses lhs promotion if rhs is not to a subtype', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ // Note: for this to be an effective test, we need to mutate `x` on
+ // the LHS of the outer `&&` so that `x` is not promoted on the RHS
+ // (and thus the lesser promotion on the RHS can take effect).
+ h.run([
+ if_(
+ x
+ .write(expr('Object'))
+ .parenthesized
+ .and(x.read.is_('int'))
+ .parenthesized
+ .and(x.read.is_('num')),
+ [
+ checkPromoted(x, 'int'),
+ ]),
+ ]);
+ });
+
+ test('uses rhs promotion if rhs is to a subtype', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('num').and(x.read.is_('int')), [
+ checkPromoted(x, 'int'),
+ ]),
+ ]);
+ });
+
+ test('can handle multiple promotions on lhs', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ var y = Var('y', 'Object');
+ h.run([
+ x.read
+ .is_('int')
+ .and(y.read.is_('String'))
+ .parenthesized
+ .and(block([
+ checkPromoted(x, 'int'),
+ checkPromoted(y, 'String'),
+ ]).thenExpr(expr('bool')))
+ .stmt,
+ ]);
+ });
+
+ test('handles variables', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'bool');
+ var y = Var('y', 'bool');
+ h.run([
+ if_(x.read.and(y.read), []),
+ ]);
+ });
+
+ test('handles arbitrary expressions', () {
+ var h = Harness(legacy: true);
+ h.run([
+ if_(expr('bool').and(expr('bool')), []),
+ ]);
+ });
+ });
+
+ test('or is ignored', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int').or(x.read.is_('int')), [
+ checkNotPromoted(x),
+ ], [
+ checkNotPromoted(x),
+ ])
+ ]);
+ });
+ });
+
+ group('is test', () {
+ group("shows a variable's type", () {
+ test('normally', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int'), [
+ checkPromoted(x, 'int'),
+ ], [
+ checkNotPromoted(x),
+ ])
+ ]);
+ });
+
+ test('unless the test is inverted', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int', isInverted: true), [
+ checkNotPromoted(x),
+ ], [
+ checkNotPromoted(x),
+ ])
+ ]);
+ });
+
+ test('unless the tested type is not a subtype of the declared type',
+ () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'String');
+ h.run([
+ if_(x.read.is_('int'), [
+ checkNotPromoted(x),
+ ], [
+ checkNotPromoted(x),
+ ])
+ ]);
+ });
+
+ test("even when the variable's type has been previously promoted", () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('num'), [
+ if_(x.read.is_('int'), [
+ checkPromoted(x, 'int'),
+ ], [
+ checkPromoted(x, 'num'),
+ ])
+ ]),
+ ]);
+ });
+
+ test(
+ 'unless the tested type is not a subtype of the previously '
+ 'promoted type', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('String'), [
+ if_(x.read.is_('int'), [
+ checkPromoted(x, 'String'),
+ ], [
+ checkPromoted(x, 'String'),
+ ])
+ ]),
+ ]);
+ });
+
+ test('even when the declared type is a type variable', () {
+ var h = Harness(legacy: true);
+ h.addPromotionException('T', 'int', 'T&int');
+ var x = Var('x', 'T');
+ h.run([
+ if_(x.read.is_('int'), [
+ checkPromoted(x, 'T&int'),
+ ]),
+ ]);
+ });
+ });
+
+ test('handles arbitrary expressions', () {
+ var h = Harness(legacy: true);
+ h.run([
+ if_(expr('Object').is_('int'), []),
+ ]);
+ });
+ });
+
+ test('forwardExpression does not re-activate a deeply nested expression',
+ () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int').eq(expr('Object')).thenStmt(block([])), [
+ checkNotPromoted(x),
+ ]),
+ ]);
+ });
+
+ test(
+ 'parenthesizedExpression does not re-activate a deeply nested '
+ 'expression', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(x.read.is_('int').eq(expr('Object')).parenthesized, [
+ checkNotPromoted(x),
+ ]),
+ ]);
+ });
+
+ test('variableRead returns the promoted type if promoted', () {
+ var h = Harness(legacy: true);
+ var x = Var('x', 'Object');
+ h.run([
+ if_(
+ x
+ .readAndCheckPromotedType((type) => expect(type, isNull))
+ .is_('int'),
+ [
+ x
+ .readAndCheckPromotedType((type) => expect(type!.type, 'int'))
+ .stmt,
+ ]),
+ ]);
+ });
+ });
}
/// Returns the appropriate matcher for expecting an assertion error to be
diff --git a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
index 6d94b51..ae7dbd0 100644
--- a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
@@ -83,7 +83,15 @@
import 'package:analyzer_plugin/utilities/change_builder/conflicting_edit_exception.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
-/// A fix producer that produces changes to fix multiple diagnostics.
+/// A fix producer that produces changes that will fix multiple diagnostics in
+/// one or more files.
+///
+/// Each diagnostic should have a single fix (correction producer) associated
+/// with it except in cases where at most one of the given producers will ever
+/// produce a fix.
+///
+/// The correction producers that are associated with the diagnostics should not
+/// produce changes that alter the semantics of the code.
class BulkFixProcessor {
/// A map from the name of a lint rule to a list of generators used to create
/// the correction producer used to build a fix for that diagnostic. The
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index cb191c0..6a9b1ed 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -86,7 +86,7 @@
/// TODO(scheglov) Clean up the list of implicitly analyzed files.
class AnalysisDriver implements AnalysisDriverGeneric {
/// The version of data format, should be incremented on every format change.
- static const int DATA_VERSION = 119;
+ static const int DATA_VERSION = 120;
/// The length of the list returned by [_computeDeclaredVariablesSignature].
static const int _declaredVariablesSignatureLength = 4;
diff --git a/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
index 7572eeb..0414591 100644
--- a/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/binary_expression_resolver.dart
@@ -181,7 +181,7 @@
left = node.leftOperand;
if (_flowAnalysis != null) {
- flow?.logicalBinaryOp_rightBegin(left, isAnd: true);
+ flow?.logicalBinaryOp_rightBegin(left, node, isAnd: true);
_resolver.checkUnreachableNode(right);
right.accept(_resolver);
@@ -218,7 +218,7 @@
left.accept(_resolver);
left = node.leftOperand;
- flow?.logicalBinaryOp_rightBegin(left, isAnd: false);
+ flow?.logicalBinaryOp_rightBegin(left, node, isAnd: false);
_resolver.checkUnreachableNode(right);
right.accept(_resolver);
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 2112e2d..2579d39 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -1110,7 +1110,7 @@
if (_flowAnalysis != null) {
if (flow != null) {
- flow.conditional_thenBegin(condition);
+ flow.conditional_thenBegin(condition, node);
checkUnreachableNode(thenExpression);
}
thenExpression.accept(this);
@@ -1517,7 +1517,7 @@
CollectionElement thenElement = node.thenElement;
if (_flowAnalysis != null) {
- _flowAnalysis.flow?.ifStatement_thenBegin(condition);
+ _flowAnalysis.flow?.ifStatement_thenBegin(condition, node);
thenElement.accept(this);
} else {
_promoteManager.visitIfElement_thenElement(
@@ -1557,7 +1557,7 @@
Statement thenStatement = node.thenStatement;
if (_flowAnalysis != null) {
- _flowAnalysis.flow?.ifStatement_thenBegin(condition);
+ _flowAnalysis.flow?.ifStatement_thenBegin(condition, node);
visitStatementInScope(thenStatement);
nullSafetyDeadCodeVerifier?.flowEnd(thenStatement);
} else {
diff --git a/pkg/analyzer/lib/src/summary2/apply_resolution.dart b/pkg/analyzer/lib/src/summary2/apply_resolution.dart
index e254ae4..f1094c6 100644
--- a/pkg/analyzer/lib/src/summary2/apply_resolution.dart
+++ b/pkg/analyzer/lib/src/summary2/apply_resolution.dart
@@ -528,6 +528,15 @@
}
@override
+ void visitForPartsWithExpression(ForPartsWithExpression node) {
+ _expectMarker(MarkerTag.ForPartsWithExpression_initialization);
+ node.initialization?.accept(this);
+ _expectMarker(MarkerTag.ForPartsWithExpression_forParts);
+ _forParts(node);
+ _expectMarker(MarkerTag.ForPartsWithExpression_end);
+ }
+
+ @override
void visitFunctionDeclaration(FunctionDeclaration node) {
_assertNoLocalElements();
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
index 3bc726f..6313f8e 100644
--- a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
@@ -94,6 +94,8 @@
return _readForElement();
case Tag.ForPartsWithDeclarations:
return _readForPartsWithDeclarations();
+ case Tag.ForPartsWithExpression:
+ return _readForPartsWithExpression();
case Tag.FieldFormalParameter:
return _readFieldFormalParameter();
case Tag.FormalParameterList:
@@ -857,6 +859,19 @@
);
}
+ ForPartsWithExpression _readForPartsWithExpression() {
+ var initialization = _readOptionalNode() as Expression;
+ var condition = _readOptionalNode() as Expression;
+ var updaters = _readNodeList<Expression>();
+ return astFactory.forPartsWithExpression(
+ condition: condition,
+ initialization: initialization,
+ leftSeparator: Tokens.SEMICOLON,
+ rightSeparator: Tokens.SEMICOLON,
+ updaters: updaters,
+ );
+ }
+
FunctionDeclaration _readFunctionDeclaration() {
var flags = _readByte();
var codeOffset = _readInformativeUint30();
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart b/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart
index cf220f2..bcda462 100644
--- a/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart
@@ -131,6 +131,9 @@
ForPartsWithDeclarations_variables,
ForPartsWithDeclarations_forParts,
ForPartsWithDeclarations_end,
+ ForPartsWithExpression_initialization,
+ ForPartsWithExpression_forParts,
+ ForPartsWithExpression_end,
FunctionDeclaration_functionExpression,
FunctionDeclaration_returnType,
FunctionDeclaration_namedCompilationUnitMember,
@@ -365,6 +368,7 @@
static const int ForEachPartsWithDeclaration = 89;
static const int ForElement = 88;
static const int ForPartsWithDeclarations = 91;
+ static const int ForPartsWithExpression = 99;
static const int FormalParameterList = 17;
static const int FunctionDeclaration = 18;
static const int FunctionDeclaration_getter = 57;
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
index 6f7f8a9..c805bbd 100644
--- a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
@@ -752,6 +752,16 @@
}
@override
+ void visitForPartsWithExpression(ForPartsWithExpression node) {
+ _writeByte(Tag.ForPartsWithExpression);
+ _writeMarker(MarkerTag.ForPartsWithExpression_initialization);
+ _writeOptionalNode(node.initialization);
+ _writeMarker(MarkerTag.ForPartsWithExpression_forParts);
+ _storeForParts(node);
+ _writeMarker(MarkerTag.ForPartsWithExpression_end);
+ }
+
+ @override
void visitFunctionDeclaration(FunctionDeclaration node) {
var indexTag = Tag.FunctionDeclaration;
if (node.isGetter) {
diff --git a/pkg/analyzer/test/generated/invalid_code_test.dart b/pkg/analyzer/test/generated/invalid_code_test.dart
index f459e36..4b043ca 100644
--- a/pkg/analyzer/test/generated/invalid_code_test.dart
+++ b/pkg/analyzer/test/generated/invalid_code_test.dart
@@ -20,6 +20,13 @@
/// and analysis finishes without exceptions.
@reflectiveTest
class InvalidCodeTest extends PubPackageResolutionTest {
+ test_const_ForPartsWithExpression() async {
+ await _assertCanBeAnalyzed(r'''
+@A([for (;;) 0])
+void f() {}
+''');
+ }
+
/// This code results in a method with the empty name, and the default
/// constructor, which also has the empty name. The `Map` in `f` initializer
/// references the empty name.
diff --git a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
index 36a1be3..7762bfb 100644
--- a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
@@ -374,7 +374,7 @@
Expression condition =
inferrer.ensureAssignableResult(expectedType, conditionResult);
node.condition = condition..parent = node;
- inferrer.flowAnalysis.conditional_thenBegin(node.condition);
+ inferrer.flowAnalysis.conditional_thenBegin(node.condition, node);
bool isThenReachable = inferrer.flowAnalysis.isReachable;
ExpressionInferenceResult thenResult = inferrer
.inferExpression(node.then, typeContext, true, isVoidAllowed: true);
@@ -1205,7 +1205,7 @@
Expression condition =
inferrer.ensureAssignableResult(expectedType, conditionResult);
node.condition = condition..parent = node;
- inferrer.flowAnalysis.ifStatement_thenBegin(condition);
+ inferrer.flowAnalysis.ifStatement_thenBegin(condition, node);
StatementInferenceResult thenResult = inferrer.inferStatement(node.then);
if (thenResult.hasChanged) {
node.then = thenResult.statement..parent = node;
@@ -1471,7 +1471,7 @@
Expression condition =
inferrer.ensureAssignableResult(boolType, conditionResult);
element.condition = condition..parent = element;
- inferrer.flowAnalysis.ifStatement_thenBegin(condition);
+ inferrer.flowAnalysis.ifStatement_thenBegin(condition, element);
ExpressionInferenceResult thenResult = inferElement(
element.then,
inferredTypeArgument,
@@ -1774,7 +1774,7 @@
isVoidAllowed: false);
Expression left = inferrer.ensureAssignableResult(boolType, leftResult);
node.left = left..parent = node;
- inferrer.flowAnalysis.logicalBinaryOp_rightBegin(node.left,
+ inferrer.flowAnalysis.logicalBinaryOp_rightBegin(node.left, node,
isAnd: node.operatorEnum == LogicalExpressionOperator.AND);
ExpressionInferenceResult rightResult = inferrer.inferExpression(
node.right, boolType, !inferrer.isTopLevel,
@@ -2049,7 +2049,7 @@
Expression condition =
inferrer.ensureAssignableResult(boolType, conditionResult);
entry.condition = condition..parent = entry;
- inferrer.flowAnalysis.ifStatement_thenBegin(condition);
+ inferrer.flowAnalysis.ifStatement_thenBegin(condition, entry);
// Note that this recursive invocation of inferMapEntry will add two types
// to actualTypes; they are the actual types of the current invocation if
// the 'else' branch is empty.
diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt
index 4d427f4..9ff711a 100644
--- a/pkg/front_end/test/spell_checking_list_common.txt
+++ b/pkg/front_end/test/spell_checking_list_common.txt
@@ -119,6 +119,7 @@
although
altogether
always
+ambiguity
ambiguous
among
amongst
@@ -1140,6 +1141,7 @@
facilitate
fact
fact's
+facto
factories
factory
facts
@@ -3338,6 +3340,7 @@
worklist
works
world
+worry
worth
would
wouldn't
diff --git a/pkg/nnbd_migration/lib/migration_cli.dart b/pkg/nnbd_migration/lib/migration_cli.dart
index 7013c22..785b5a6 100644
--- a/pkg/nnbd_migration/lib/migration_cli.dart
+++ b/pkg/nnbd_migration/lib/migration_cli.dart
@@ -604,7 +604,7 @@
String summaryPath,
@required String sdkPath}) {
return NonNullableFix(listener, resourceProvider, getLineInfo, bindAddress,
- logger, (String path) => shouldBeMigrated(null, path),
+ logger, (String path) => shouldBeMigrated(path),
included: included,
preferredPort: preferredPort,
summaryPath: summaryPath,
@@ -813,12 +813,7 @@
/// return additional paths that aren't inside the user's project, but doesn't
/// override this method, then those additional paths will be analyzed but not
/// migrated.
- ///
- /// Note: in a future version of the code, the [context] argument will be
- /// removed; to ease the transition, clients should stop overriding this
- /// method and should override [shouldBeMigrated2] instead.
- bool shouldBeMigrated(DriverBasedAnalysisContext context, String path) =>
- shouldBeMigrated2(path);
+ bool shouldBeMigrated(String path) => shouldBeMigrated2(path);
/// Determines whether a migrated version of the file at [path] should be
/// output by the migration too. May be overridden by a derived class.
@@ -832,6 +827,10 @@
/// return additional paths that aren't inside the user's project, but doesn't
/// override this method, then those additional paths will be analyzed but not
/// migrated.
+ ///
+ /// Note: in a future version of the code, this method will be removed;
+ /// clients that are overriding this method should switch to overriding
+ /// [shouldBeMigrated] instead.
bool shouldBeMigrated2(String path) {
return analysisContext.contextRoot.isAnalyzed(path);
}
@@ -1171,7 +1170,7 @@
});
await processResources((ResolvedUnitResult result) async {
_progressBar.tick();
- if (_migrationCli.shouldBeMigrated(context, result.path)) {
+ if (_migrationCli.shouldBeMigrated(result.path)) {
await _task.finalizeUnit(result);
}
});
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index 58f484b..bca2e93 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -470,7 +470,8 @@
bool isAnd = operatorType == TokenType.AMPERSAND_AMPERSAND;
_flowAnalysis.logicalBinaryOp_begin();
_checkExpressionNotNull(leftOperand);
- _flowAnalysis.logicalBinaryOp_rightBegin(node.leftOperand, isAnd: isAnd);
+ _flowAnalysis.logicalBinaryOp_rightBegin(node.leftOperand, node,
+ isAnd: isAnd);
_postDominatedLocals.doScoped(
action: () => _checkExpressionNotNull(rightOperand));
_flowAnalysis.logicalBinaryOp_end(node, rightOperand, isAnd: isAnd);
@@ -659,7 +660,7 @@
// Note: we don't have to create a scope for each branch because they can't
// define variables.
_postDominatedLocals.doScoped(action: () {
- _flowAnalysis.conditional_thenBegin(node.condition);
+ _flowAnalysis.conditional_thenBegin(node.condition, node);
if (trueGuard != null) {
_guards.add(trueGuard);
}
@@ -992,7 +993,7 @@
_guards.add(trueGuard);
}
try {
- _flowAnalysis.ifStatement_thenBegin(node.condition);
+ _flowAnalysis.ifStatement_thenBegin(node.condition, node);
// We branched, so create a new scope for post-dominators.
_postDominatedLocals.doScoped(
action: () => _dispatch(node.thenStatement));
diff --git a/tools/VERSION b/tools/VERSION
index 623857b..235cc3e 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 12
PATCH 0
-PRERELEASE 235
+PRERELEASE 236
PRERELEASE_PATCH 0
\ No newline at end of file