Use flow analysis to report potentially non-nullable local variables that are used before definitely assigned.
Not done yet:
1. Switching all unit tests to `performFlowAnalysis()`.
2. Updating the error code and renaming tests.
3. Writing more language_2/ tests.
R=brianwilkerson@google.com, paulberry@google.com
Change-Id: I0936f2daa4ab1cd91edeaca3eef9544eced5b443
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106982
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
index ba3be49..bd04b6a 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
@@ -21,6 +21,7 @@
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/handle.dart';
import 'package:analyzer/src/dart/element/inheritance_manager2.dart';
+import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/dart/resolver/legacy_type_asserter.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/error/inheritance_override.dart';
@@ -407,11 +408,17 @@
_context.typeSystem, _inheritance, errorReporter);
inheritanceOverrideVerifier.verifyUnit(unit);
+ FlowAnalysisResult flowAnalysisResult;
+ if (unit.featureSet.isEnabled(Feature.non_nullable)) {
+ flowAnalysisResult = performFlowAnalysis(_context.typeSystem, unit);
+ }
+
//
// Use the ErrorVerifier to compute errors.
//
ErrorVerifier errorVerifier = new ErrorVerifier(
- errorReporter, _libraryElement, _typeProvider, _inheritance, false);
+ errorReporter, _libraryElement, _typeProvider, _inheritance, false,
+ flowAnalysisResult: flowAnalysisResult);
unit.accept(errorVerifier);
}
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
index b711bcb..838ae88 100644
--- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
@@ -2,15 +2,17 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-/// Sets of variables that are potentially assigned in a statement.
+/// Sets of local variables that are potentially assigned in a statement.
+///
+/// These statements are loops, `switch`, and `try` statements.
class AssignedVariables<Statement, Element> {
final emptySet = Set<Element>();
- /// Mapping from a [Statement] representing a loop to the set of variables
- /// that are potentially assigned in that loop.
+ /// Mapping from a [Statement] to the set of local variables that are
+ /// potentially assigned in that statement.
final Map<Statement, Set<Element>> _map = {};
- /// The stack of nested nodes.
+ /// The stack of nested statements.
final List<Set<Element>> _stack = [];
AssignedVariables();
@@ -21,12 +23,12 @@
return _map[statement] ?? emptySet;
}
- void beginLoop() {
+ void beginStatement() {
var set = Set<Element>.identity();
_stack.add(set);
}
- void endLoop(Statement node) {
+ void endStatement(Statement node) {
_map[node] = _stack.removeLast();
}
@@ -126,6 +128,17 @@
_current = _current.add(variable, assigned: assigned);
}
+ void booleanLiteral(Expression expression, bool value) {
+ _condition = expression;
+ if (value) {
+ _conditionTrue = _current;
+ _conditionFalse = _identity;
+ } else {
+ _conditionTrue = _identity;
+ _conditionFalse = _current;
+ }
+ }
+
void conditional_elseBegin(Expression conditionalExpression,
Expression thenExpression, bool isBool) {
var afterThen = _current;
@@ -223,12 +236,6 @@
_current = _join(falseCondition, breakState);
}
- void falseLiteral(Expression expression) {
- _condition = expression;
- _conditionTrue = _identity;
- _conditionFalse = _current;
- }
-
void forEachStatement_bodyBegin(Set<Element> loopAssigned) {
_stack.add(_current);
_current = _current.removePromotedAll(loopAssigned);
@@ -494,12 +501,6 @@
_stack.add(_current); // afterExpression
}
- void trueLiteral(Expression expression) {
- _condition = expression;
- _conditionTrue = _current;
- _conditionFalse = _identity;
- }
-
void tryCatchStatement_bodyBegin() {
_stack.add(_current);
// Tail of the stack: beforeBody
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
new file mode 100644
index 0000000..a95f526
--- /dev/null
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
@@ -0,0 +1,750 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/standard_ast_factory.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/dart/element/type_system.dart';
+import 'package:analyzer/src/dart/resolver/flow_analysis.dart';
+
+/// Perform flow analysis for the given [unit].
+FlowAnalysisResult performFlowAnalysis(
+ TypeSystem typeSystem,
+ CompilationUnit unit,
+) {
+ var assignedVariables = AssignedVariables<Statement, VariableElement>();
+ unit.accept(AssignedVariablesVisitor(assignedVariables));
+
+ var readBeforeWritten = <LocalVariableElement>[];
+
+ unit.accept(FlowAnalysisVisitor(
+ typeSystem,
+ assignedVariables,
+ {},
+ readBeforeWritten,
+ [],
+ [],
+ [],
+ [],
+ ));
+
+ return FlowAnalysisResult(
+ readBeforeWritten,
+ );
+}
+
+/// The visitor that gathers local variables that are potentially assigned
+/// in corresponding statements, such as loops, `switch` and `try`.
+///
+/// TODO(scheglov) Change other tests to use [performFlowAnalysis],
+/// and make this class private.
+class AssignedVariablesVisitor extends RecursiveAstVisitor<void> {
+ final AssignedVariables assignedVariables;
+
+ AssignedVariablesVisitor(this.assignedVariables);
+
+ @override
+ void visitAssignmentExpression(AssignmentExpression node) {
+ var left = node.leftHandSide;
+
+ super.visitAssignmentExpression(node);
+
+ if (left is SimpleIdentifier) {
+ var element = left.staticElement;
+ if (element is VariableElement) {
+ assignedVariables.write(element);
+ }
+ }
+ }
+
+ @override
+ void visitDoStatement(DoStatement node) {
+ assignedVariables.beginStatement();
+ super.visitDoStatement(node);
+ assignedVariables.endStatement(node);
+ }
+
+ @override
+ void visitForStatement(ForStatement node) {
+ var forLoopParts = node.forLoopParts;
+ if (forLoopParts is ForParts) {
+ if (forLoopParts is ForPartsWithExpression) {
+ forLoopParts.initialization?.accept(this);
+ } else if (forLoopParts is ForPartsWithDeclarations) {
+ forLoopParts.variables?.accept(this);
+ } else {
+ throw new StateError('Unrecognized for loop parts');
+ }
+
+ assignedVariables.beginStatement();
+ forLoopParts.condition?.accept(this);
+ node.body.accept(this);
+ forLoopParts.updaters?.accept(this);
+ assignedVariables.endStatement(node);
+ } else if (forLoopParts is ForEachParts) {
+ var iterable = forLoopParts.iterable;
+ var body = node.body;
+
+ iterable.accept(this);
+
+ assignedVariables.beginStatement();
+ body.accept(this);
+ assignedVariables.endStatement(node);
+ } else {
+ throw new StateError('Unrecognized for loop parts');
+ }
+ }
+
+ @override
+ void visitSwitchStatement(SwitchStatement node) {
+ var expression = node.expression;
+ var members = node.members;
+
+ expression.accept(this);
+
+ assignedVariables.beginStatement();
+ members.accept(this);
+ assignedVariables.endStatement(node);
+ }
+
+ @override
+ void visitTryStatement(TryStatement node) {
+ assignedVariables.beginStatement();
+ node.body.accept(this);
+ assignedVariables.endStatement(node.body);
+
+ node.catchClauses.accept(this);
+
+ var finallyBlock = node.finallyBlock;
+ if (finallyBlock != null) {
+ assignedVariables.beginStatement();
+ finallyBlock.accept(this);
+ assignedVariables.endStatement(finallyBlock);
+ }
+ }
+
+ @override
+ void visitWhileStatement(WhileStatement node) {
+ assignedVariables.beginStatement();
+ super.visitWhileStatement(node);
+ assignedVariables.endStatement(node);
+ }
+}
+
+/// The result of performing flow analysis on a unit.
+class FlowAnalysisResult {
+ final List<LocalVariableElement> readBeforeWritten;
+
+ FlowAnalysisResult(this.readBeforeWritten);
+}
+
+/// [AstVisitor] that drives the [FlowAnalysis].
+///
+/// TODO(scheglov) Change other tests to use [performFlowAnalysis],
+/// and make this class private.
+class FlowAnalysisVisitor extends GeneralizingAstVisitor<void> {
+ static final trueLiteral = astFactory.booleanLiteral(null, true);
+
+ final NodeOperations<Expression> nodeOperations;
+ final TypeOperations<VariableElement, DartType> typeOperations;
+ final AssignedVariables assignedVariables;
+
+ final Map<AstNode, DartType> promotedTypes;
+ final List<LocalVariableElement> readBeforeWritten;
+ final List<AstNode> nullableNodes;
+ final List<AstNode> nonNullableNodes;
+ final List<AstNode> unreachableNodes;
+ final List<FunctionBody> functionBodiesThatDontComplete;
+
+ FlowAnalysis<Statement, Expression, VariableElement, DartType> flow;
+
+ FlowAnalysisVisitor(
+ TypeSystem typeSystem,
+ this.assignedVariables,
+ this.promotedTypes,
+ this.readBeforeWritten,
+ this.nullableNodes,
+ this.nonNullableNodes,
+ this.unreachableNodes,
+ this.functionBodiesThatDontComplete)
+ : nodeOperations = _NodeOperations(),
+ typeOperations = _TypeSystemTypeOperations(typeSystem);
+
+ @override
+ void visitAssignmentExpression(AssignmentExpression node) {
+ if (flow == null) {
+ return super.visitAssignmentExpression(node);
+ }
+
+ var left = node.leftHandSide;
+ var right = node.rightHandSide;
+
+ VariableElement localElement;
+ if (left is SimpleIdentifier) {
+ var element = left.staticElement;
+ if (element is VariableElement) {
+ localElement = element;
+ }
+ }
+
+ if (localElement != null) {
+ var isCompound = node.operator.type != TokenType.EQ;
+ if (isCompound) {
+ flow.read(localElement);
+ }
+ right.accept(this);
+ flow.write(
+ localElement,
+ isNull: _isNull(right),
+ isNonNull: _isNonNull(right),
+ );
+ } else {
+ left.accept(this);
+ right.accept(this);
+ }
+ }
+
+ @override
+ void visitBinaryExpression(BinaryExpression node) {
+ if (flow == null) {
+ return super.visitBinaryExpression(node);
+ }
+
+ var left = node.leftOperand;
+ var right = node.rightOperand;
+
+ var operator = node.operator.type;
+
+ if (operator == TokenType.AMPERSAND_AMPERSAND) {
+ left.accept(this);
+
+ flow.logicalAnd_rightBegin(node, node.leftOperand);
+ _checkUnreachableNode(node.rightOperand);
+ right.accept(this);
+
+ flow.logicalAnd_end(node, node.rightOperand);
+ } else if (operator == TokenType.BAR_BAR) {
+ left.accept(this);
+
+ flow.logicalOr_rightBegin(node, node.leftOperand);
+ _checkUnreachableNode(node.rightOperand);
+ right.accept(this);
+
+ flow.logicalOr_end(node, node.rightOperand);
+ } else if (operator == TokenType.BANG_EQ) {
+ left.accept(this);
+ right.accept(this);
+ // TODO(scheglov) Support `null != name`
+ if (right is NullLiteral) {
+ if (left is SimpleIdentifier) {
+ var element = left.staticElement;
+ if (element is VariableElement) {
+ flow.conditionNotEqNull(node, element);
+ }
+ }
+ }
+ } else if (operator == TokenType.EQ_EQ) {
+ // TODO(scheglov) Support `null == name`
+ left.accept(this);
+ right.accept(this);
+ if (right is NullLiteral) {
+ if (left is SimpleIdentifier) {
+ var element = left.staticElement;
+ if (element is VariableElement) {
+ flow.conditionEqNull(node, element);
+ }
+ }
+ }
+ } else if (operator == TokenType.QUESTION_QUESTION) {
+ left.accept(this);
+
+ flow.ifNullExpression_rightBegin();
+ right.accept(this);
+
+ flow.ifNullExpression_end();
+ } else {
+ left.accept(this);
+ right.accept(this);
+ }
+ }
+
+ @override
+ void visitBlockFunctionBody(BlockFunctionBody node) {
+ if (flow != null) {
+ super.visitBlockFunctionBody(node);
+ } else {
+ flow = FlowAnalysis<Statement, Expression, VariableElement, DartType>(
+ nodeOperations,
+ typeOperations,
+ _FunctionBodyAccess(node),
+ );
+
+ var function = node.parent;
+ if (function is FunctionExpression) {
+ var parameters = function.parameters;
+ if (parameters != null) {
+ for (var parameter in parameters?.parameters) {
+ flow.add(parameter.declaredElement, assigned: true);
+ }
+ }
+ }
+ // TODO(scheglov) Methods and constructors.
+
+ super.visitBlockFunctionBody(node);
+
+ for (var variable in flow.readBeforeWritten) {
+ assert(variable is LocalVariableElement);
+ readBeforeWritten.add(variable);
+ }
+
+ if (!flow.isReachable) {
+ functionBodiesThatDontComplete.add(node);
+ }
+
+ flow.verifyStackEmpty();
+ flow = null;
+ }
+ }
+
+ @override
+ void visitBooleanLiteral(BooleanLiteral node) {
+ if (flow == null) {
+ return super.visitBooleanLiteral(node);
+ }
+
+ flow.booleanLiteral(node, node.value);
+ }
+
+ @override
+ void visitBreakStatement(BreakStatement node) {
+ super.visitBreakStatement(node);
+ var target = _getLabelTarget(node, node.label?.staticElement);
+ flow.handleBreak(target);
+ }
+
+ @override
+ void visitConditionalExpression(ConditionalExpression node) {
+ if (flow == null) {
+ return super.visitConditionalExpression(node);
+ }
+
+ var condition = node.condition;
+ var thenExpression = node.thenExpression;
+ var elseExpression = node.elseExpression;
+
+ condition.accept(this);
+
+ flow.conditional_thenBegin(node, node.condition);
+ _checkUnreachableNode(node.thenExpression);
+ thenExpression.accept(this);
+ var isBool = thenExpression.staticType.isDartCoreBool;
+
+ flow.conditional_elseBegin(node, node.thenExpression, isBool);
+ _checkUnreachableNode(node.elseExpression);
+ elseExpression.accept(this);
+
+ flow.conditional_end(node, node.elseExpression, isBool);
+ }
+
+ @override
+ void visitContinueStatement(ContinueStatement node) {
+ super.visitContinueStatement(node);
+ var target = _getLabelTarget(node, node.label?.staticElement);
+ flow.handleContinue(target);
+ }
+
+ @override
+ void visitDoStatement(DoStatement node) {
+ _checkUnreachableNode(node);
+
+ var body = node.body;
+ var condition = node.condition;
+
+ flow.doStatement_bodyBegin(node, assignedVariables[node]);
+ body.accept(this);
+
+ flow.doStatement_conditionBegin();
+ condition.accept(this);
+
+ flow.doStatement_end(node, node.condition);
+ }
+
+ @override
+ void visitForStatement(ForStatement node) {
+ _checkUnreachableNode(node);
+
+ ForLoopParts parts = node.forLoopParts;
+ if (parts is ForEachParts) {
+ parts.iterable?.accept(this);
+
+ flow.forEachStatement_bodyBegin(assignedVariables[node]);
+
+ node.body.accept(this);
+
+ flow.forEachStatement_end();
+ return;
+ }
+ VariableDeclarationList variables;
+ Expression initialization;
+ Expression condition;
+ NodeList<Expression> updaters;
+ if (parts is ForPartsWithDeclarations) {
+ variables = parts.variables;
+ condition = parts.condition;
+ updaters = parts.updaters;
+ } else if (parts is ForPartsWithExpression) {
+ initialization = parts.initialization;
+ condition = parts.condition;
+ updaters = parts.updaters;
+ }
+ initialization?.accept(this);
+ variables?.accept(this);
+
+ flow.forStatement_conditionBegin(assignedVariables[node]);
+ if (condition != null) {
+ condition.accept(this);
+ } else {
+ flow.booleanLiteral(trueLiteral, true);
+ }
+
+ flow.forStatement_bodyBegin(node, condition ?? trueLiteral);
+ node.body.accept(this);
+
+ flow.forStatement_updaterBegin();
+ updaters?.accept(this);
+
+ flow.forStatement_end();
+ }
+
+ @override
+ void visitFunctionExpression(FunctionExpression node) {
+ if (flow == null) {
+ return super.visitFunctionExpression(node);
+ }
+
+ flow.functionExpression_begin();
+ super.visitFunctionExpression(node);
+ flow.functionExpression_end();
+ }
+
+ @override
+ void visitIfStatement(IfStatement node) {
+ _checkUnreachableNode(node);
+
+ var condition = node.condition;
+ var thenStatement = node.thenStatement;
+ var elseStatement = node.elseStatement;
+
+ condition.accept(this);
+
+ flow.ifStatement_thenBegin(node, node.condition);
+ thenStatement.accept(this);
+
+ if (elseStatement != null) {
+ flow.ifStatement_elseBegin();
+ elseStatement.accept(this);
+ }
+
+ flow.ifStatement_end(elseStatement != null);
+ }
+
+ @override
+ void visitIsExpression(IsExpression node) {
+ if (flow == null) {
+ return super.visitIsExpression(node);
+ }
+
+ super.visitIsExpression(node);
+ var expression = node.expression;
+ var typeAnnotation = node.type;
+
+ if (expression is SimpleIdentifier) {
+ var element = expression.staticElement;
+ if (element is VariableElement) {
+ flow.isExpression_end(
+ node,
+ element,
+ node.notOperator != null,
+ typeAnnotation.type,
+ );
+ }
+ }
+ }
+
+ @override
+ void visitPrefixExpression(PrefixExpression node) {
+ if (flow == null) {
+ return super.visitPrefixExpression(node);
+ }
+
+ var operand = node.operand;
+
+ var operator = node.operator.type;
+ if (operator == TokenType.BANG) {
+ operand.accept(this);
+ flow.logicalNot_end(node, node.operand);
+ } else {
+ operand.accept(this);
+ }
+ }
+
+ @override
+ void visitRethrowExpression(RethrowExpression node) {
+ if (flow == null) {
+ return super.visitRethrowExpression(node);
+ }
+
+ super.visitRethrowExpression(node);
+ flow.handleExit();
+ }
+
+ @override
+ void visitReturnStatement(ReturnStatement node) {
+ super.visitReturnStatement(node);
+ flow.handleExit();
+ }
+
+ @override
+ void visitSimpleIdentifier(SimpleIdentifier node) {
+ if (flow == null) {
+ return super.visitSimpleIdentifier(node);
+ }
+
+ var element = node.staticElement;
+ var isLocalVariable = element is LocalVariableElement;
+ if (isLocalVariable || element is ParameterElement) {
+ if (node.inGetterContext() && !node.inDeclarationContext()) {
+ if (isLocalVariable) {
+ flow.read(element);
+ }
+
+ if (flow.isNullable(element)) {
+ nullableNodes?.add(node);
+ }
+
+ if (flow.isNonNullable(element)) {
+ nonNullableNodes?.add(node);
+ }
+
+ var promotedType = flow.promotedType(element);
+ if (promotedType != null) {
+ promotedTypes[node] = promotedType;
+ }
+ }
+ }
+ }
+
+ @override
+ void visitStatement(Statement node) {
+ _checkUnreachableNode(node);
+ super.visitStatement(node);
+ }
+
+ @override
+ void visitSwitchStatement(SwitchStatement node) {
+ _checkUnreachableNode(node);
+
+ node.expression.accept(this);
+ flow.switchStatement_expressionEnd(node);
+
+ var assignedInCases = assignedVariables[node];
+
+ var members = node.members;
+ var membersLength = members.length;
+ var hasDefault = false;
+ for (var i = 0; i < membersLength; i++) {
+ var member = members[i];
+
+ flow.switchStatement_beginCase(
+ member.labels.isNotEmpty ? assignedInCases : assignedVariables.emptySet,
+ );
+ member.accept(this);
+
+ // Implicit `break` at the end of `default`.
+ if (member is SwitchDefault) {
+ hasDefault = true;
+ flow.handleBreak(node);
+ }
+ }
+
+ flow.switchStatement_end(node, hasDefault);
+ }
+
+ @override
+ void visitThrowExpression(ThrowExpression node) {
+ if (flow == null) {
+ return super.visitThrowExpression(node);
+ }
+
+ super.visitThrowExpression(node);
+ flow.handleExit();
+ }
+
+ @override
+ void visitTryStatement(TryStatement node) {
+ _checkUnreachableNode(node);
+
+ var body = node.body;
+ var catchClauses = node.catchClauses;
+ var finallyBlock = node.finallyBlock;
+
+ if (finallyBlock != null) {
+ flow.tryFinallyStatement_bodyBegin();
+ }
+
+ flow.tryCatchStatement_bodyBegin();
+ body.accept(this);
+ flow.tryCatchStatement_bodyEnd(assignedVariables[body]);
+
+ var catchLength = catchClauses.length;
+ for (var i = 0; i < catchLength; ++i) {
+ var catchClause = catchClauses[i];
+ flow.tryCatchStatement_catchBegin();
+ catchClause.accept(this);
+ flow.tryCatchStatement_catchEnd();
+ }
+
+ flow.tryCatchStatement_end();
+
+ if (finallyBlock != null) {
+ flow.tryFinallyStatement_finallyBegin(assignedVariables[body]);
+ finallyBlock.accept(this);
+ flow.tryFinallyStatement_end(assignedVariables[finallyBlock]);
+ }
+ }
+
+ @override
+ void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
+ var variables = node.variables.variables;
+ for (var i = 0; i < variables.length; ++i) {
+ var variable = variables[i];
+ flow.add(variable.declaredElement,
+ assigned: variable.initializer != null);
+ }
+
+ super.visitVariableDeclarationStatement(node);
+ }
+
+ @override
+ void visitWhileStatement(WhileStatement node) {
+ _checkUnreachableNode(node);
+
+ var condition = node.condition;
+ var body = node.body;
+
+ flow.whileStatement_conditionBegin(assignedVariables[node]);
+ condition.accept(this);
+
+ flow.whileStatement_bodyBegin(node, node.condition);
+ body.accept(this);
+
+ flow.whileStatement_end();
+ }
+
+ /// Mark the [node] as unreachable if it is not covered by another node that
+ /// is already known to be unreachable.
+ void _checkUnreachableNode(AstNode node) {
+ if (flow.isReachable) return;
+
+ // Ignore the [node] if it is fully covered by the last unreachable.
+ if (unreachableNodes.isNotEmpty) {
+ var last = unreachableNodes.last;
+ if (node.offset >= last.offset && node.end <= last.end) return;
+ }
+
+ unreachableNodes.add(node);
+ }
+
+ /// Return the target of the `break` or `continue` statement with the
+ /// [element] label. The [element] might be `null` (when the statement does
+ /// not specify a label), so the default enclosing target is returned.
+ AstNode _getLabelTarget(AstNode node, LabelElement element) {
+ for (; node != null; node = node.parent) {
+ if (node is DoStatement ||
+ node is ForStatement ||
+ node is SwitchStatement ||
+ node is WhileStatement) {
+ if (element == null) {
+ return node;
+ }
+ var parent = node.parent;
+ if (parent is LabeledStatement) {
+ for (var nodeLabel in parent.labels) {
+ if (identical(nodeLabel.label.staticElement, element)) {
+ return node;
+ }
+ }
+ }
+ }
+ if (element != null && node is SwitchStatement) {
+ for (var member in node.members) {
+ for (var nodeLabel in member.labels) {
+ if (identical(nodeLabel.label.staticElement, element)) {
+ return node;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ static bool _isNonNull(Expression node) {
+ if (node is NullLiteral) return false;
+
+ return node is Literal;
+ }
+
+ static bool _isNull(Expression node) {
+ return node is NullLiteral;
+ }
+}
+
+class _FunctionBodyAccess implements FunctionBodyAccess<VariableElement> {
+ final FunctionBody node;
+
+ _FunctionBodyAccess(this.node);
+
+ @override
+ bool isPotentiallyMutatedInClosure(VariableElement variable) {
+ return node.isPotentiallyMutatedInClosure(variable);
+ }
+
+ @override
+ bool isPotentiallyMutatedInScope(VariableElement variable) {
+ return node.isPotentiallyMutatedInScope(variable);
+ }
+}
+
+class _NodeOperations implements NodeOperations<Expression> {
+ @override
+ Expression unwrapParenthesized(Expression node) {
+ return node.unParenthesized;
+ }
+}
+
+class _TypeSystemTypeOperations
+ implements TypeOperations<VariableElement, DartType> {
+ final TypeSystem typeSystem;
+
+ _TypeSystemTypeOperations(this.typeSystem);
+
+ @override
+ DartType elementType(VariableElement element) {
+ return element.type;
+ }
+
+ @override
+ bool isLocalVariable(VariableElement element) {
+ return element is LocalVariableElement;
+ }
+
+ @override
+ bool isSubtypeOf(DartType leftType, DartType rightType) {
+ return typeSystem.isSubtypeOf(leftType, rightType);
+ }
+}
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 2d8ea10..4bfcbba 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -22,6 +22,7 @@
import 'package:analyzer/src/dart/element/inheritance_manager2.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
+import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/dart/resolver/variance.dart';
import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
import 'package:analyzer/src/error/codes.dart';
@@ -43,7 +44,7 @@
class ErrorVerifier extends RecursiveAstVisitor<void> {
/**
* Properties on the object class which are safe to call on nullable types.
- *
+ *
* Note that this must include tear-offs.
*
* TODO(mfairhurst): Calculate these fields rather than hard-code them.
@@ -293,6 +294,10 @@
/// fixed.
final bool disableConflictingGenericsCheck;
+ /// If running with [_isNonNullable], the result of the flow analysis of the
+ /// unit being verified by this visitor.
+ final FlowAnalysisResult flowAnalysisResult;
+
/// The features enabled in the unit currently being checked for errors.
FeatureSet _featureSet;
@@ -301,7 +306,7 @@
*/
ErrorVerifier(ErrorReporter errorReporter, this._currentLibrary,
this._typeProvider, this._inheritanceManager, bool enableSuperMixins,
- {this.disableConflictingGenericsCheck: false})
+ {this.disableConflictingGenericsCheck: false, this.flowAnalysisResult})
: _errorReporter = errorReporter,
_uninstantiatedBoundChecker =
new _UninstantiatedBoundChecker(errorReporter) {
@@ -3395,7 +3400,9 @@
}
for (var variable in node.variables) {
- if (variable.initializer == null) {
+ if (variable.initializer == null &&
+ flowAnalysisResult.readBeforeWritten
+ .contains(variable.declaredElement)) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode
.NOT_INITIALIZED_POTENTIALLY_NON_NULLABLE_LOCAL_VARIABLE,
diff --git a/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart b/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
index 6a39734..19dc3b0 100644
--- a/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
@@ -3,13 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
-import 'package:analyzer/dart/ast/standard_ast_factory.dart';
-import 'package:analyzer/dart/ast/token.dart';
-import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
-import 'package:analyzer/dart/element/type_system.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis.dart';
+import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -1307,21 +1304,10 @@
await resolveTestFile();
var unit = result.unit;
+ var typeSystem = result.typeSystem;
- var assignedVariables = AssignedVariables<Statement, VariableElement>();
- unit.accept(_AssignedVariablesVisitor(assignedVariables));
-
- var typeSystem = unit.declaredElement.context.typeSystem;
- unit.accept(_AstVisitor(
- typeSystem,
- assignedVariables,
- {},
- readBeforeWritten,
- [],
- [],
- [],
- [],
- ));
+ var flowAnalysisResult = performFlowAnalysis(typeSystem, unit);
+ readBeforeWritten.addAll(flowAnalysisResult.readBeforeWritten);
}
}
@@ -1624,10 +1610,10 @@
var unit = result.unit;
var assignedVariables = AssignedVariables<Statement, VariableElement>();
- unit.accept(_AssignedVariablesVisitor(assignedVariables));
+ unit.accept(AssignedVariablesVisitor(assignedVariables));
var typeSystem = unit.declaredElement.context.typeSystem;
- unit.accept(_AstVisitor(
+ unit.accept(FlowAnalysisVisitor(
typeSystem,
assignedVariables,
{},
@@ -2071,10 +2057,10 @@
var unit = result.unit;
var assignedVariables = AssignedVariables<Statement, VariableElement>();
- unit.accept(_AssignedVariablesVisitor(assignedVariables));
+ unit.accept(AssignedVariablesVisitor(assignedVariables));
var typeSystem = unit.declaredElement.context.typeSystem;
- unit.accept(_AstVisitor(
+ unit.accept(FlowAnalysisVisitor(
typeSystem,
assignedVariables,
{},
@@ -2904,10 +2890,10 @@
var unit = result.unit;
var assignedVariables = AssignedVariables<Statement, VariableElement>();
- unit.accept(_AssignedVariablesVisitor(assignedVariables));
+ unit.accept(AssignedVariablesVisitor(assignedVariables));
var typeSystem = unit.declaredElement.context.typeSystem;
- unit.accept(_AstVisitor(
+ unit.accept(FlowAnalysisVisitor(
typeSystem,
assignedVariables,
promotedTypes,
@@ -2919,677 +2905,3 @@
));
}
}
-
-class _AssignedVariablesVisitor extends RecursiveAstVisitor<void> {
- final AssignedVariables assignedVariables;
-
- _AssignedVariablesVisitor(this.assignedVariables);
-
- @override
- void visitAssignmentExpression(AssignmentExpression node) {
- var left = node.leftHandSide;
-
- super.visitAssignmentExpression(node);
-
- if (left is SimpleIdentifier) {
- var element = left.staticElement;
- if (element is VariableElement) {
- assignedVariables.write(element);
- }
- }
- }
-
- @override
- void visitDoStatement(DoStatement node) {
- assignedVariables.beginLoop();
- super.visitDoStatement(node);
- assignedVariables.endLoop(node);
- }
-
- @override
- void visitForStatement(ForStatement node) {
- var forLoopParts = node.forLoopParts;
- if (forLoopParts is ForParts) {
- if (forLoopParts is ForPartsWithExpression) {
- forLoopParts.initialization?.accept(this);
- } else if (forLoopParts is ForPartsWithDeclarations) {
- forLoopParts.variables?.accept(this);
- } else {
- throw new StateError('Unrecognized for loop parts');
- }
-
- assignedVariables.beginLoop();
- forLoopParts.condition?.accept(this);
- node.body.accept(this);
- forLoopParts.updaters?.accept(this);
- assignedVariables.endLoop(node);
- } else if (forLoopParts is ForEachParts) {
- var iterable = forLoopParts.iterable;
- var body = node.body;
-
- iterable.accept(this);
-
- assignedVariables.beginLoop();
- body.accept(this);
- assignedVariables.endLoop(node);
- } else {
- throw new StateError('Unrecognized for loop parts');
- }
- }
-
- @override
- void visitSwitchStatement(SwitchStatement node) {
- var expression = node.expression;
- var members = node.members;
-
- expression.accept(this);
-
- assignedVariables.beginLoop();
- members.accept(this);
- assignedVariables.endLoop(node);
- }
-
- @override
- void visitTryStatement(TryStatement node) {
- assignedVariables.beginLoop();
- node.body.accept(this);
- assignedVariables.endLoop(node.body);
-
- node.catchClauses.accept(this);
-
- var finallyBlock = node.finallyBlock;
- if (finallyBlock != null) {
- assignedVariables.beginLoop();
- finallyBlock.accept(this);
- assignedVariables.endLoop(finallyBlock);
- }
- }
-
- @override
- void visitWhileStatement(WhileStatement node) {
- assignedVariables.beginLoop();
- super.visitWhileStatement(node);
- assignedVariables.endLoop(node);
- }
-}
-
-/// [AstVisitor] that drives the [flow] in the way we expect the resolver
-/// will do in production.
-class _AstVisitor extends GeneralizingAstVisitor<void> {
- static final trueLiteral = astFactory.booleanLiteral(null, true);
-
- final NodeOperations<Expression> nodeOperations;
- final TypeOperations<VariableElement, DartType> typeOperations;
- final AssignedVariables assignedVariables;
- final Map<AstNode, DartType> promotedTypes;
- final List<LocalVariableElement> readBeforeWritten;
- final List<AstNode> nullableNodes;
- final List<AstNode> nonNullableNodes;
- final List<AstNode> unreachableNodes;
- final List<FunctionBody> functionBodiesThatDontComplete;
-
- FlowAnalysis<Statement, Expression, VariableElement, DartType> flow;
-
- _AstVisitor(
- TypeSystem typeSystem,
- this.assignedVariables,
- this.promotedTypes,
- this.readBeforeWritten,
- this.nullableNodes,
- this.nonNullableNodes,
- this.unreachableNodes,
- this.functionBodiesThatDontComplete)
- : nodeOperations = _NodeOperations(),
- typeOperations = _TypeSystemTypeOperations(typeSystem);
-
- @override
- void visitAssignmentExpression(AssignmentExpression node) {
- var left = node.leftHandSide;
- var right = node.rightHandSide;
-
- VariableElement localElement;
- if (left is SimpleIdentifier) {
- var element = left.staticElement;
- if (element is VariableElement) {
- localElement = element;
- }
- }
-
- if (localElement != null) {
- var isPure = node.operator.type == TokenType.EQ;
- if (!isPure) {
- flow.read(localElement);
- }
- right.accept(this);
- flow.write(
- localElement,
- isNull: _isNull(right),
- isNonNull: _isNonNull(right),
- );
- } else {
- left.accept(this);
- right.accept(this);
- }
- }
-
- @override
- void visitBinaryExpression(BinaryExpression node) {
- var left = node.leftOperand;
- var right = node.rightOperand;
-
- var operator = node.operator.type;
-
- if (operator == TokenType.AMPERSAND_AMPERSAND) {
- left.accept(this);
-
- flow.logicalAnd_rightBegin(node, node.leftOperand);
- _checkUnreachableNode(node.rightOperand);
- right.accept(this);
-
- flow.logicalAnd_end(node, node.rightOperand);
- } else if (operator == TokenType.BAR_BAR) {
- left.accept(this);
-
- flow.logicalOr_rightBegin(node, node.leftOperand);
- _checkUnreachableNode(node.rightOperand);
- right.accept(this);
-
- flow.logicalOr_end(node, node.rightOperand);
- } else if (operator == TokenType.BANG_EQ) {
- left.accept(this);
- right.accept(this);
- if (right is NullLiteral) {
- if (left is SimpleIdentifier) {
- var element = left.staticElement;
- if (element is VariableElement) {
- flow.conditionNotEqNull(node, element);
- }
- }
- }
- } else if (operator == TokenType.EQ_EQ) {
- left.accept(this);
- right.accept(this);
- if (right is NullLiteral) {
- if (left is SimpleIdentifier) {
- var element = left.staticElement;
- if (element is VariableElement) {
- flow.conditionEqNull(node, element);
- }
- }
- }
- } else if (operator == TokenType.QUESTION_QUESTION) {
- left.accept(this);
-
- flow.ifNullExpression_rightBegin();
- right.accept(this);
-
- flow.ifNullExpression_end();
- } else {
- left.accept(this);
- right.accept(this);
- }
- }
-
- @override
- void visitBlockFunctionBody(BlockFunctionBody node) {
- var isFlowOwner = flow == null;
-
- if (isFlowOwner) {
- flow = FlowAnalysis<Statement, Expression, VariableElement, DartType>(
- nodeOperations,
- typeOperations,
- _FunctionBodyAccess(node),
- );
-
- var function = node.parent;
- if (function is FunctionExpression) {
- var parameters = function.parameters;
- if (parameters != null) {
- for (var parameter in parameters?.parameters) {
- flow.add(parameter.declaredElement, assigned: true);
- }
- }
- }
- }
-
- super.visitBlockFunctionBody(node);
-
- if (isFlowOwner) {
- for (var variable in flow.readBeforeWritten) {
- assert(variable is LocalVariableElement);
- readBeforeWritten.add(variable);
- }
-
- if (!flow.isReachable) {
- functionBodiesThatDontComplete.add(node);
- }
-
- flow.verifyStackEmpty();
- flow = null;
- }
- }
-
- @override
- void visitBooleanLiteral(BooleanLiteral node) {
- super.visitBooleanLiteral(node);
- if (_isFalseLiteral(node)) {
- flow.falseLiteral(node);
- }
- if (_isTrueLiteral(node)) {
- flow.trueLiteral(node);
- }
- }
-
- @override
- void visitBreakStatement(BreakStatement node) {
- super.visitBreakStatement(node);
- var target = _getLabelTarget(node, node.label?.staticElement);
- flow.handleBreak(target);
- }
-
- @override
- void visitConditionalExpression(ConditionalExpression node) {
- var condition = node.condition;
- var thenExpression = node.thenExpression;
- var elseExpression = node.elseExpression;
-
- condition.accept(this);
-
- flow.conditional_thenBegin(node, node.condition);
- _checkUnreachableNode(node.thenExpression);
- thenExpression.accept(this);
- var isBool = thenExpression.staticType.isDartCoreBool;
-
- flow.conditional_elseBegin(node, node.thenExpression, isBool);
- _checkUnreachableNode(node.elseExpression);
- elseExpression.accept(this);
-
- flow.conditional_end(node, node.elseExpression, isBool);
- }
-
- @override
- void visitContinueStatement(ContinueStatement node) {
- super.visitContinueStatement(node);
- var target = _getLabelTarget(node, node.label?.staticElement);
- flow.handleContinue(target);
- }
-
- @override
- void visitDoStatement(DoStatement node) {
- _checkUnreachableNode(node);
-
- var body = node.body;
- var condition = node.condition;
-
- flow.doStatement_bodyBegin(node, assignedVariables[node]);
- body.accept(this);
-
- flow.doStatement_conditionBegin();
- condition.accept(this);
-
- flow.doStatement_end(node, node.condition);
- }
-
- @override
- void visitForStatement(ForStatement node) {
- _checkUnreachableNode(node);
-
- ForLoopParts parts = node.forLoopParts;
- if (parts is ForEachParts) {
- parts.iterable?.accept(this);
-
- flow.forEachStatement_bodyBegin(assignedVariables[node]);
-
- node.body.accept(this);
-
- flow.forEachStatement_end();
- return;
- }
- VariableDeclarationList variables;
- Expression initialization;
- Expression condition;
- NodeList<Expression> updaters;
- if (parts is ForPartsWithDeclarations) {
- variables = parts.variables;
- condition = parts.condition;
- updaters = parts.updaters;
- } else if (parts is ForPartsWithExpression) {
- initialization = parts.initialization;
- condition = parts.condition;
- updaters = parts.updaters;
- }
- initialization?.accept(this);
- variables?.accept(this);
-
- flow.forStatement_conditionBegin(assignedVariables[node]);
- if (condition != null) {
- condition.accept(this);
- } else {
- flow.trueLiteral(trueLiteral);
- }
-
- flow.forStatement_bodyBegin(node, condition ?? trueLiteral);
- node.body.accept(this);
-
- flow.forStatement_updaterBegin();
- updaters?.accept(this);
-
- flow.forStatement_end();
- }
-
- @override
- void visitFunctionExpression(FunctionExpression node) {
- flow?.functionExpression_begin();
- super.visitFunctionExpression(node);
- flow?.functionExpression_end();
- }
-
- @override
- void visitIfStatement(IfStatement node) {
- _checkUnreachableNode(node);
-
- var condition = node.condition;
- var thenStatement = node.thenStatement;
- var elseStatement = node.elseStatement;
-
- condition.accept(this);
-
- flow.ifStatement_thenBegin(node, node.condition);
- thenStatement.accept(this);
-
- if (elseStatement != null) {
- flow.ifStatement_elseBegin();
- elseStatement.accept(this);
- }
-
- flow.ifStatement_end(elseStatement != null);
- }
-
- @override
- void visitIsExpression(IsExpression node) {
- super.visitIsExpression(node);
- var expression = node.expression;
- var typeAnnotation = node.type;
-
- if (expression is SimpleIdentifier) {
- var element = expression.staticElement;
- if (element is VariableElement) {
- flow.isExpression_end(
- node,
- element,
- node.notOperator != null,
- typeAnnotation.type,
- );
- }
- }
- }
-
- @override
- void visitPrefixExpression(PrefixExpression node) {
- var operand = node.operand;
-
- var operator = node.operator.type;
- if (operator == TokenType.BANG) {
- operand.accept(this);
- flow.logicalNot_end(node, node.operand);
- } else {
- operand.accept(this);
- }
- }
-
- @override
- void visitRethrowExpression(RethrowExpression node) {
- super.visitRethrowExpression(node);
- flow.handleExit();
- }
-
- @override
- void visitReturnStatement(ReturnStatement node) {
- super.visitReturnStatement(node);
- flow.handleExit();
- }
-
- @override
- void visitSimpleIdentifier(SimpleIdentifier node) {
- var element = node.staticElement;
- var isLocalVariable = element is LocalVariableElement;
- if (isLocalVariable || element is ParameterElement) {
- if (node.inGetterContext() && !node.inDeclarationContext()) {
- if (isLocalVariable) {
- flow.read(element);
- }
-
- if (flow.isNullable(element)) {
- nullableNodes?.add(node);
- }
-
- if (flow.isNonNullable(element)) {
- nonNullableNodes?.add(node);
- }
-
- var promotedType = flow?.promotedType(element);
- if (promotedType != null) {
- promotedTypes[node] = promotedType;
- }
- }
- }
-
- super.visitSimpleIdentifier(node);
- }
-
- @override
- void visitStatement(Statement node) {
- _checkUnreachableNode(node);
- super.visitStatement(node);
- }
-
- @override
- void visitSwitchStatement(SwitchStatement node) {
- _checkUnreachableNode(node);
-
- node.expression.accept(this);
- flow.switchStatement_expressionEnd(node);
-
- var assignedInCases = assignedVariables[node];
-
- var members = node.members;
- var membersLength = members.length;
- var hasDefault = false;
- for (var i = 0; i < membersLength; i++) {
- var member = members[i];
-
- flow.switchStatement_beginCase(
- member.labels.isNotEmpty ? assignedInCases : assignedVariables.emptySet,
- );
- member.accept(this);
-
- // Implicit `break` at the end of `default`.
- if (member is SwitchDefault) {
- hasDefault = true;
- flow.handleBreak(node);
- }
- }
-
- flow.switchStatement_end(node, hasDefault);
- }
-
- @override
- void visitThrowExpression(ThrowExpression node) {
- super.visitThrowExpression(node);
- flow.handleExit();
- }
-
- @override
- void visitTryStatement(TryStatement node) {
- _checkUnreachableNode(node);
-
- var body = node.body;
- var catchClauses = node.catchClauses;
- var finallyBlock = node.finallyBlock;
-
- if (finallyBlock != null) {
- flow.tryFinallyStatement_bodyBegin();
- }
-
- flow.tryCatchStatement_bodyBegin();
- body.accept(this);
- flow.tryCatchStatement_bodyEnd(assignedVariables[body]);
-
- var catchLength = catchClauses.length;
- for (var i = 0; i < catchLength; ++i) {
- var catchClause = catchClauses[i];
- flow.tryCatchStatement_catchBegin();
- catchClause.accept(this);
- flow.tryCatchStatement_catchEnd();
- }
-
- flow.tryCatchStatement_end();
-
- if (finallyBlock != null) {
- flow.tryFinallyStatement_finallyBegin(assignedVariables[body]);
- finallyBlock.accept(this);
- flow.tryFinallyStatement_end(assignedVariables[finallyBlock]);
- }
- }
-
- @override
- void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
- var variables = node.variables.variables;
- for (var i = 0; i < variables.length; ++i) {
- var variable = variables[i];
- flow.add(variable.declaredElement,
- assigned: variable.initializer != null);
- }
-
- super.visitVariableDeclarationStatement(node);
- }
-
- @override
- void visitWhileStatement(WhileStatement node) {
- _checkUnreachableNode(node);
-
- var condition = node.condition;
- var body = node.body;
-
- flow.whileStatement_conditionBegin(assignedVariables[node]);
- condition.accept(this);
-
- flow.whileStatement_bodyBegin(node, node.condition);
- body.accept(this);
-
- flow.whileStatement_end();
- }
-
- /// Mark the [node] as unreachable if it is not covered by another node that
- /// is already known to be unreachable.
- void _checkUnreachableNode(AstNode node) {
- if (flow.isReachable) return;
-
- // Ignore the [node] if it is fully covered by the last unreachable.
- if (unreachableNodes.isNotEmpty) {
- var last = unreachableNodes.last;
- if (node.offset >= last.offset && node.end <= last.end) return;
- }
-
- unreachableNodes.add(node);
- }
-
- /// This code has OK performance for tests, but think if there is something
- /// better when using in production.
- AstNode _getLabelTarget(AstNode node, LabelElement element) {
- for (; node != null; node = node.parent) {
- if (node is DoStatement ||
- node is ForStatement ||
- node is SwitchStatement ||
- node is WhileStatement) {
- if (element == null) {
- return node;
- }
- var parent = node.parent;
- if (parent is LabeledStatement) {
- for (var nodeLabel in parent.labels) {
- if (identical(nodeLabel.label.staticElement, element)) {
- return node;
- }
- }
- }
- }
- if (element != null && node is SwitchStatement) {
- for (var member in node.members) {
- for (var nodeLabel in member.labels) {
- if (identical(nodeLabel.label.staticElement, element)) {
- return node;
- }
- }
- }
- }
- }
- return null;
- }
-
- static bool _isFalseLiteral(AstNode node) {
- return node is BooleanLiteral && !node.value;
- }
-
- static bool _isNonNull(Expression node) {
- if (node is NullLiteral) return false;
-
- return node is Literal;
- }
-
- static bool _isNull(Expression node) {
- return node is NullLiteral;
- }
-
- static bool _isTrueLiteral(AstNode node) {
- return node is BooleanLiteral && node.value;
- }
-}
-
-class _FunctionBodyAccess implements FunctionBodyAccess<VariableElement> {
- final FunctionBody node;
-
- _FunctionBodyAccess(this.node);
-
- @override
- bool isPotentiallyMutatedInClosure(VariableElement variable) {
- return node.isPotentiallyMutatedInClosure(variable);
- }
-
- @override
- bool isPotentiallyMutatedInScope(VariableElement variable) {
- return node.isPotentiallyMutatedInScope(variable);
- }
-}
-
-class _NodeOperations implements NodeOperations<Expression> {
- @override
- Expression unwrapParenthesized(Expression node) {
- while (node is ParenthesizedExpression) {
- node = (node as ParenthesizedExpression).expression;
- }
- return node;
- }
-}
-
-class _TypeSystemTypeOperations
- implements TypeOperations<VariableElement, DartType> {
- final TypeSystem typeSystem;
-
- _TypeSystemTypeOperations(this.typeSystem);
-
- @override
- DartType elementType(VariableElement element) {
- return element.type;
- }
-
- @override
- bool isSubtypeOf(DartType leftType, DartType rightType) {
- return typeSystem.isSubtypeOf(leftType, rightType);
- }
-
- @override
- bool isLocalVariable(VariableElement element) {
- return element is LocalVariableElement;
- }
-}
diff --git a/pkg/analyzer/test/src/diagnostics/not_initialized_potentially_non_nullable_local_variable_test.dart b/pkg/analyzer/test/src/diagnostics/not_initialized_potentially_non_nullable_local_variable_test.dart
index 373e9b5..fb5d0d0 100644
--- a/pkg/analyzer/test/src/diagnostics/not_initialized_potentially_non_nullable_local_variable_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/not_initialized_potentially_non_nullable_local_variable_test.dart
@@ -24,6 +24,56 @@
AnalysisOptionsImpl get analysisOptions =>
AnalysisOptionsImpl()..enabledExperiments = [EnableString.non_nullable];
+ test_definitelyAssigned_basic() async {
+ assertNoErrorsInCode('''
+void f() {
+ int v;
+ v = 0;
+ v;
+}
+''');
+ }
+
+ test_definitelyAssigned_if_then() async {
+ assertErrorsInCode('''
+void f(bool b) {
+ int v;
+ if (b) {
+ v = 1;
+ }
+ v;
+}
+''', [
+ error(
+ CompileTimeErrorCode
+ .NOT_INITIALIZED_POTENTIALLY_NON_NULLABLE_LOCAL_VARIABLE,
+ 23,
+ 1),
+ ]);
+ }
+
+ test_definitelyAssigned_if_thenElse_all() async {
+ assertNoErrorsInCode('''
+void f(bool b) {
+ int v;
+ if (b) {
+ v = 1;
+ } else {
+ v = 2;
+ }
+ v;
+}
+''');
+ }
+
+ test_definitelyAssigned_notUsed() async {
+ assertNoErrorsInCode('''
+void f() {
+ int v;
+}
+''');
+ }
+
test_hasInitializer() async {
assertNoErrorsInCode('''
f() {
@@ -44,6 +94,7 @@
assertErrorsInCode('''
f() {
int v;
+ v;
}
''', [
error(
@@ -51,7 +102,6 @@
.NOT_INITIALIZED_POTENTIALLY_NON_NULLABLE_LOCAL_VARIABLE,
12,
1),
- error(HintCode.UNUSED_LOCAL_VARIABLE, 12, 1),
]);
}
@@ -59,6 +109,7 @@
assertErrorsInCode('''
f<T>() {
T v;
+ v;
}
''', [
error(
@@ -66,7 +117,6 @@
.NOT_INITIALIZED_POTENTIALLY_NON_NULLABLE_LOCAL_VARIABLE,
13,
1),
- error(HintCode.UNUSED_LOCAL_VARIABLE, 13, 1),
]);
}
diff --git a/tests/language_2/nnbd/static_errors/not_initialized_potentially_non_nullable_local_test.dart b/tests/language_2/nnbd/static_errors/not_initialized_potentially_non_nullable_local_test.dart
index d4b2207..45fc654 100644
--- a/tests/language_2/nnbd/static_errors/not_initialized_potentially_non_nullable_local_test.dart
+++ b/tests/language_2/nnbd/static_errors/not_initialized_potentially_non_nullable_local_test.dart
@@ -10,20 +10,21 @@
// TODO(scheglov) Update once we implement definite assignment analysis.
void main() {
- int v; //# 01: compile-time error
- int v = 0; //# 02: ok
- late int v; //# 03: ok
- late int v = 0; //# 04: ok
- int? v; //# 05: ok
- int? v = 0; //# 06: ok
+ int v; v; //# 01: compile-time error
+ int v; //# 02: ok
+ int v = 0; //# 03: ok
+ late int v; //# 04: ok
+ late int v = 0; //# 05: ok
+ int? v; //# 06: ok
+ int? v = 0; //# 07: ok
}
f<T>(T a) {
- T v; //# 07: compile-time error
- T v = a; //# 08: ok
- late T v; //# 09: ok
- late T v = a; //# 10: ok
- T? v; //# 11: ok
- T? v = a; //# 12: ok
+ T v; v; //# 08: compile-time error
+ T v = a; //# 09: ok
+ late T v; //# 10: ok
+ late T v = a; //# 11: ok
+ T? v; //# 12: ok
+ T? v = a; //# 13: ok
}