Issue 43100. Verify local variable reads for null safety.

Bug: https://github.com/dart-lang/sdk/issues/43100
Change-Id: Ia8ece4de32f65d7912e534ce61936a45f2c768c0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/159382
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Konstantin Shcheglov 2020-08-20 15:50:50 +00:00
parent d52a577f3b
commit 25edea3d8d
22 changed files with 700 additions and 152 deletions

View file

@ -354,6 +354,7 @@ const List<ErrorCode> errorCodeValues = [
CompileTimeErrorCode.PRIVATE_COLLISION_IN_MIXIN_APPLICATION,
CompileTimeErrorCode.PRIVATE_OPTIONAL_PARAMETER,
CompileTimeErrorCode.PRIVATE_SETTER,
CompileTimeErrorCode.READ_POTENTIALLY_UNASSIGNED_FINAL,
CompileTimeErrorCode.RECURSIVE_COMPILE_TIME_CONSTANT,
CompileTimeErrorCode.RECURSIVE_CONSTRUCTOR_REDIRECT,
CompileTimeErrorCode.RECURSIVE_FACTORY_REDIRECT,

View file

@ -83,6 +83,9 @@ class ContextBuilder {
/// interface.
PerformanceLog performanceLog;
/// If `true`, additional analysis data useful for testing is stored.
bool retainDataForTesting = false;
/// The byte store used by any analysis drivers created through this interface.
ByteStore byteStore;
@ -114,17 +117,19 @@ class ContextBuilder {
final sf = createSourceFactory(path, options, summaryData: summaryData);
AnalysisDriver driver = AnalysisDriver(
analysisDriverScheduler,
performanceLog,
resourceProvider,
byteStore,
fileContentOverlay,
contextRoot,
sf,
options,
packages: createPackageMap(path),
enableIndex: enableIndex,
externalSummaries: summaryData);
analysisDriverScheduler,
performanceLog,
resourceProvider,
byteStore,
fileContentOverlay,
contextRoot,
sf,
options,
packages: createPackageMap(path),
enableIndex: enableIndex,
externalSummaries: summaryData,
retainDataForTesting: retainDataForTesting,
);
// Set API AnalysisContext for the driver.
var apiContextRoots = api.ContextLocator(

View file

@ -30,6 +30,7 @@ class AnalysisContextCollectionImpl implements AnalysisContextCollection {
@required List<String> includedPaths,
List<String> excludedPaths,
ResourceProvider resourceProvider,
bool retainDataForTesting = false,
String sdkPath,
}) : resourceProvider =
resourceProvider ?? PhysicalResourceProvider.INSTANCE {
@ -56,6 +57,7 @@ class AnalysisContextCollectionImpl implements AnalysisContextCollection {
contextRoot: root,
declaredVariables: DeclaredVariables.fromMap(declaredVariables ?? {}),
enableIndex: enableIndex,
retainDataForTesting: retainDataForTesting,
sdkPath: sdkPath,
);
contexts.add(context);

View file

@ -45,6 +45,7 @@ class ContextBuilderImpl implements ContextBuilder {
bool enableIndex = false,
List<String> librarySummaryPaths,
@deprecated PerformanceLog performanceLog,
bool retainDataForTesting = false,
@deprecated AnalysisDriverScheduler scheduler,
String sdkPath,
String sdkSummaryPath}) {
@ -85,6 +86,7 @@ class ContextBuilderImpl implements ContextBuilder {
builder.fileContentOverlay = fileContentOverlay;
builder.enableIndex = enableIndex;
builder.performanceLog = performanceLog;
builder.retainDataForTesting = retainDataForTesting;
old.ContextRoot oldContextRoot = old.ContextRoot(
contextRoot.root.path, contextRoot.excludedPaths.toList(),

View file

@ -228,7 +228,7 @@ class AssignmentExpressionResolver {
return;
}
_assignmentShared.checkLateFinalAlreadyAssigned(leftHandSide);
_assignmentShared.checkFinalAlreadyAssigned(leftHandSide);
// For any compound assignments to a void or nullable variable, report it.
// Example: `y += voidFn()`, not allowed.
@ -404,18 +404,31 @@ class AssignmentExpressionShared {
ErrorReporter get _errorReporter => _resolver.errorReporter;
void checkLateFinalAlreadyAssigned(Expression left) {
void checkFinalAlreadyAssigned(Expression left) {
var flow = _flowAnalysis?.flow;
if (flow != null && left is SimpleIdentifier) {
var element = left.staticElement;
if (element is LocalVariableElement &&
element.isLate &&
element.isFinal) {
if (flow.isAssigned(element)) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.LATE_FINAL_LOCAL_ALREADY_ASSIGNED,
left,
);
if (element is VariableElement) {
var assigned = _flowAnalysis.isDefinitelyAssigned(left, element);
var unassigned = _flowAnalysis.isDefinitelyUnassigned(left, element);
if (element.isFinal) {
if (element.isLate) {
if (assigned) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.LATE_FINAL_LOCAL_ALREADY_ASSIGNED,
left,
);
}
} else {
if (!unassigned) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.ASSIGNMENT_TO_FINAL_LOCAL,
left,
[element.name],
);
}
}
}
}
}

View file

@ -24,12 +24,18 @@ class FlowAnalysisDataForTesting {
/// there is a `return` statement at the end of the function body block.
final List<FunctionBody> functionBodiesThatDontComplete = [];
/// The list of [Expression]s representing variable accesses that occur before
/// the corresponding variable has been definitely assigned.
final List<AstNode> potentiallyUnassignedNodes = [];
/// The list of [SimpleIdentifier]s that were checked if they are definitely
/// assigned, but were not.
final List<AstNode> notDefinitelyAssignedNodes = [];
/// The list of [SimpleIdentifier]s representing variable accesses that occur
/// when the corresponding variable has been definitely unassigned.
/// The list of [SimpleIdentifier]s representing variable references (reads,
/// writes, or both) that occur when the corresponding variable has been
/// definitely assigned.
final List<AstNode> definitelyAssignedNodes = [];
/// The list of [SimpleIdentifier]s representing variable references (reads,
/// writes, or both) that occur when the corresponding variable has been
/// definitely unassigned.
final List<AstNode> definitelyUnassignedNodes = [];
/// For each top level or class level declaration, the assigned variables
@ -144,6 +150,36 @@ class FlowAnalysisHelper {
flow.for_conditionBegin(node);
}
bool isDefinitelyAssigned(
SimpleIdentifier node,
PromotableElement element,
) {
var isAssigned = flow.isAssigned(element);
if (dataForTesting != null) {
if (isAssigned) {
dataForTesting.definitelyAssignedNodes.add(node);
} else {
dataForTesting.notDefinitelyAssignedNodes.add(node);
}
}
return isAssigned;
}
bool isDefinitelyUnassigned(
SimpleIdentifier node,
PromotableElement element,
) {
var isUnassigned = flow.isUnassigned(element);
if (dataForTesting != null && isUnassigned) {
dataForTesting.definitelyUnassignedNodes.add(node);
}
return isUnassigned;
}
void isExpression(IsExpression node) {
if (flow == null) return;
@ -158,52 +194,6 @@ class FlowAnalysisHelper {
);
}
bool isPotentiallyNonNullableLocalReadBeforeWrite(SimpleIdentifier node) {
if (flow == null) return false;
if (node.inDeclarationContext()) return false;
if (!node.inGetterContext()) return false;
var element = node.staticElement;
if (element is LocalVariableElement) {
var typeSystem = _typeOperations.typeSystem;
var isUnassigned = !flow.isAssigned(element);
if (isUnassigned) {
dataForTesting?.potentiallyUnassignedNodes?.add(node);
}
if (typeSystem.isPotentiallyNonNullable(element.type)) {
// Note: in principle we could make this slightly more performant by
// checking element.isLate earlier, but we would lose the ability to
// test the flow analysis mechanism using late variables. And it seems
// unlikely that the `late` modifier will be used often enough for it to
// make a significant difference.
if (element.isLate) return false;
return isUnassigned;
}
}
return false;
}
bool isReadOfDefinitelyUnassignedLateLocal(SimpleIdentifier node) {
if (flow == null) return false;
if (node.inDeclarationContext()) return false;
if (!node.inGetterContext()) return false;
var element = node.staticElement;
if (element is LocalVariableElement) {
if (flow.isUnassigned(element)) {
dataForTesting?.definitelyUnassignedNodes?.add(node);
if (element.isLate) {
return true;
}
}
}
return false;
}
void labeledStatement_enter(LabeledStatement node) {
flow.labeledStatement_begin(node);
}

View file

@ -9,6 +9,7 @@ import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_schema.dart';
import 'package:analyzer/src/dart/resolver/assignment_expression_resolver.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:meta/meta.dart';
@ -83,7 +84,12 @@ class ForResolver {
loopVariable = forEachParts.loopVariable;
} else if (forEachParts is ForEachPartsWithIdentifier) {
identifier = forEachParts.identifier;
// TODO(scheglov) replace with lexical lookup
identifier?.accept(_resolver);
AssignmentExpressionShared(
resolver: _resolver,
flowAnalysis: _flowAnalysis,
).checkFinalAlreadyAssigned(identifier);
}
DartType valueType;

View file

@ -483,6 +483,8 @@ class MethodInvocationResolver {
void _resolveReceiverNull(
MethodInvocation node, SimpleIdentifier nameNode, String name) {
_resolver.checkReadOfNotAssignedLocalVariable(nameNode);
var element = nameScope.lookup2(name).getter;
if (element != null) {
element = _resolver.toLegacyElement(element);

View file

@ -58,7 +58,7 @@ class PostfixExpressionResolver {
node.operand,
);
_assignmentShared.checkLateFinalAlreadyAssigned(node.operand);
_assignmentShared.checkFinalAlreadyAssigned(node.operand);
_resolve1(node, receiverType);
_resolve2(node, receiverType);

View file

@ -58,7 +58,9 @@ class PrefixExpressionResolver {
node.operand.accept(_resolver);
_assignmentShared.checkLateFinalAlreadyAssigned(node.operand);
if (operator.isIncrementOperator) {
_assignmentShared.checkFinalAlreadyAssigned(node.operand);
}
_resolve1(node);
_resolve2(node);

View file

@ -7590,6 +7590,14 @@ class CompileTimeErrorCode extends AnalyzerErrorCode {
"library that declares it.",
correction: "Try making it public.");
static const CompileTimeErrorCode READ_POTENTIALLY_UNASSIGNED_FINAL =
CompileTimeErrorCode(
'READ_POTENTIALLY_UNASSIGNED_FINAL',
"The final variable '{0}' can't be read because it is potentially "
"unassigned at this point.",
correction: "Ensure that it is assigned on necessary execution paths.",
);
/**
* 12.1 Constants: It is a compile-time error if the value of a compile-time
* constant expression depends on itself.

View file

@ -1669,10 +1669,14 @@ class ErrorVerifier extends RecursiveAstVisitor<void> {
}
return;
}
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.ASSIGNMENT_TO_FINAL_LOCAL,
highlightedNode,
[element.name]);
if (_isNonNullableByDefault && element is PromotableElement) {
// Handled during resolution, with flow analysis.
} else {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.ASSIGNMENT_TO_FINAL_LOCAL,
highlightedNode,
[element.name]);
}
}
} else if (element is FunctionElement) {
_errorReporter.reportErrorForNode(
@ -2607,6 +2611,14 @@ class ErrorVerifier extends RecursiveAstVisitor<void> {
if (_isInNativeClass || list.isSynthetic) {
return;
}
// Handled during resolution, with flow analysis.
if (_isNonNullableByDefault &&
list.isFinal &&
list.parent is VariableDeclarationStatement) {
return;
}
bool isConst = list.isConst;
if (!(isConst || list.isFinal)) {
return;

View file

@ -427,21 +427,49 @@ class ResolverVisitor extends ScopedVisitor {
}
void checkReadOfNotAssignedLocalVariable(SimpleIdentifier node) {
if (_flowAnalysis != null) {
if (_flowAnalysis.isPotentiallyNonNullableLocalReadBeforeWrite(node)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode
.NOT_ASSIGNED_POTENTIALLY_NON_NULLABLE_LOCAL_VARIABLE,
node,
[node.name],
);
if (_flowAnalysis?.flow == null) {
return;
}
if (!node.inGetterContext()) {
return;
}
var element = node.staticElement;
if (element is VariableElement) {
var assigned = _flowAnalysis.isDefinitelyAssigned(node, element);
var unassigned = _flowAnalysis.isDefinitelyUnassigned(node, element);
if (element.isLate) {
if (unassigned) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.DEFINITELY_UNASSIGNED_LATE_LOCAL_VARIABLE,
node,
[node.name],
);
}
return;
}
if (_flowAnalysis.isReadOfDefinitelyUnassignedLateLocal(node)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.DEFINITELY_UNASSIGNED_LATE_LOCAL_VARIABLE,
node,
[node.name],
);
if (!assigned) {
if (element.isFinal) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.READ_POTENTIALLY_UNASSIGNED_FINAL,
node,
[node.name],
);
return;
}
if (typeSystem.isPotentiallyNonNullable(element.type)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode
.NOT_ASSIGNED_POTENTIALLY_NON_NULLABLE_LOCAL_VARIABLE,
node,
[node.name],
);
return;
}
}
}
}

View file

@ -73,7 +73,7 @@ class _DefiniteAssignmentDataExtractor extends AstDataExtractor<String> {
if (node is SimpleIdentifier && node.inGetterContext()) {
var element = node.staticElement;
if (element is LocalVariableElement || element is ParameterElement) {
if (_flowResult.potentiallyUnassignedNodes.contains(node)) {
if (_flowResult.notDefinitelyAssignedNodes.contains(node)) {
return 'unassigned';
}
}

View file

@ -146,6 +146,8 @@ abstract class ContextResolutionTest
_declaredVariables = map;
}
bool get retainDataForTesting => false;
void assertBasicWorkspaceFor(String path) {
var workspace = contextFor(path).workspace;
expect(workspace, TypeMatcher<BasicWorkspace>());
@ -232,6 +234,7 @@ abstract class ContextResolutionTest
enableIndex: true,
includedPaths: collectionIncludedPaths.map(convertPath).toList(),
resourceProvider: resourceProvider,
retainDataForTesting: retainDataForTesting,
sdkPath: convertPath('/sdk'),
);

View file

@ -114,44 +114,6 @@ main(C c) {
assertType(cRef, 'C');
}
test_error_invocationOfNonFunction_localVariable() async {
await assertErrorsInCode(r'''
main() {
Object foo;
foo();
}
''', [
error(CompileTimeErrorCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION, 25, 3),
]);
var invocation = findNode.functionExpressionInvocation('foo();');
assertElementNull(invocation);
assertInvokeTypeDynamic(invocation);
assertTypeDynamic(invocation);
var foo = invocation.function as SimpleIdentifier;
assertElement(foo, findElement.localVar('foo'));
assertType(foo, 'Object');
}
test_error_invocationOfNonFunction_OK_dynamic_localVariable() async {
await assertNoErrorsInCode(r'''
main() {
var foo;
foo();
}
''');
var invocation = findNode.functionExpressionInvocation('foo();');
assertElementNull(invocation);
assertInvokeTypeDynamic(invocation);
assertTypeDynamic(invocation);
var foo = invocation.function as SimpleIdentifier;
assertElement(foo, findElement.localVar('foo'));
assertTypeDynamic(foo);
}
test_error_invocationOfNonFunction_OK_dynamicGetter_instance() async {
await assertNoErrorsInCode(r'''
class C {
@ -258,6 +220,42 @@ class C<T extends MyFunction> {
assertType(foo, 'double Function(int)');
}
test_error_invocationOfNonFunction_parameter() async {
await assertErrorsInCode(r'''
main(Object foo) {
foo();
}
''', [
error(CompileTimeErrorCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION, 21, 3),
]);
var invocation = findNode.functionExpressionInvocation('foo();');
assertElementNull(invocation);
assertInvokeTypeDynamic(invocation);
assertTypeDynamic(invocation);
var foo = invocation.function as SimpleIdentifier;
assertElement(foo, findElement.parameter('foo'));
assertType(foo, 'Object');
}
test_error_invocationOfNonFunction_parameter_dynamic() async {
await assertNoErrorsInCode(r'''
main(var foo) {
foo();
}
''');
var invocation = findNode.functionExpressionInvocation('foo();');
assertElementNull(invocation);
assertInvokeTypeDynamic(invocation);
assertTypeDynamic(invocation);
var foo = invocation.function as SimpleIdentifier;
assertElement(foo, findElement.parameter('foo'));
assertTypeDynamic(foo);
}
test_error_invocationOfNonFunction_static_hasTarget() async {
await assertErrorsInCode(r'''
class C {
@ -1402,25 +1400,6 @@ main() {
);
}
test_noReceiver_localVariable() async {
await assertNoErrorsInCode(r'''
main() {
void Function(int) foo;
foo(0);
}
''');
var invocation = findNode.functionExpressionInvocation('foo(0);');
assertElementNull(invocation);
assertInvokeType(invocation, 'void Function(int)');
assertType(invocation, 'void');
var foo = invocation.function as SimpleIdentifier;
assertElement(foo, findElement.localVar('foo'));
assertType(foo, 'void Function(int)');
}
test_noReceiver_localVariable_call() async {
await assertNoErrorsInCode(r'''
class C {
@ -1502,6 +1481,23 @@ class C {
);
}
test_noReceiver_parameter() async {
await assertNoErrorsInCode(r'''
main(void Function(int) foo) {
foo(0);
}
''');
var invocation = findNode.functionExpressionInvocation('foo(0);');
assertElementNull(invocation);
assertInvokeType(invocation, 'void Function(int)');
assertType(invocation, 'void');
var foo = invocation.function as SimpleIdentifier;
assertElement(foo, findElement.parameter('foo'));
assertType(foo, 'void Function(int)');
}
test_noReceiver_parameter_call_nullAware() async {
var question = typeToStringWithNullability ? '?' : '';
await assertNoErrorsInCode('''

View file

@ -135,6 +135,16 @@ class C {
''');
}
test_field_ofClass() async {
await assertErrorsInCode('''
abstract class A {
final int x;
}
''', [
error(CompileTimeErrorCode.FINAL_NOT_INITIALIZED, 31, 1),
]);
}
test_field_unnamedConstructor_constructorInitializer() async {
await assertNoErrorsInCode('''
class C {

View file

@ -0,0 +1,452 @@
// Copyright (c) 2020, 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/src/error/codes.dart';
import 'package:meta/meta.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../dart/resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ReadWriteWithNullSafetyTest);
});
}
@reflectiveTest
class ReadWriteWithNullSafetyTest extends PubPackageResolutionTest
with WithNullSafetyMixin {
@override
bool get retainDataForTesting => true;
test_final_definitelyAssigned_read() async {
await assertNoErrorsInCode(r'''
void f() {
final x;
x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_final_definitelyAssigned_read_prefixNegate() async {
await assertNoErrorsInCode(r'''
void f() {
// ignore:unused_local_variable
final x;
x = 0;
-x; // 0
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_final_definitelyAssigned_write_assignment_simple() async {
await assertErrorsInCode(r'''
void f() {
// ignore:unused_local_variable
final x;
x = 0;
x = 1;
}
''', [
error(CompileTimeErrorCode.ASSIGNMENT_TO_FINAL_LOCAL, 67, 1),
]);
_assertAssigned('x = 0', assigned: false, unassigned: true);
_assertAssigned('x = 1', assigned: true, unassigned: false);
}
test_final_definitelyAssigned_write_forEachLoop_identifier() async {
await assertErrorsInCode(r'''
void f() {
final x = 0;
for (x in [0, 1, 2]) {
x;
}
}
''', [
error(CompileTimeErrorCode.ASSIGNMENT_TO_FINAL_LOCAL, 33, 1),
]);
_assertAssigned('x in', assigned: true, unassigned: false);
}
test_final_definitelyUnassigned_read() async {
await assertErrorsInCode(r'''
void f() {
final x;
x; // 0
x();
}
''', [
error(CompileTimeErrorCode.READ_POTENTIALLY_UNASSIGNED_FINAL, 24, 1),
error(CompileTimeErrorCode.READ_POTENTIALLY_UNASSIGNED_FINAL, 34, 1),
]);
_assertAssigned('x; // 0', assigned: false, unassigned: true);
_assertAssigned('x()', assigned: false, unassigned: true);
}
test_final_neither_read() async {
await assertErrorsInCode(r'''
void f(bool b) {
final x;
if (b) x = 0;
x; // 0
}
''', [
error(CompileTimeErrorCode.READ_POTENTIALLY_UNASSIGNED_FINAL, 46, 1),
]);
_assertAssigned('x; // 0', assigned: false, unassigned: false);
}
test_lateFinal_definitelyAssigned_read() async {
await assertNoErrorsInCode(r'''
void f() {
late final x;
x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_lateFinal_definitelyUnassigned_read() async {
await assertErrorsInCode(r'''
void f() {
late final x;
x; // 0
}
''', [
error(CompileTimeErrorCode.DEFINITELY_UNASSIGNED_LATE_LOCAL_VARIABLE, 29,
1),
]);
_assertAssigned('x; // 0', assigned: false, unassigned: true);
}
test_lateFinal_neither_read() async {
await assertNoErrorsInCode(r'''
void f(bool b) {
late var x;
if (b) x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: false, unassigned: false);
}
test_lateFinalNullable_definitelyAssigned_read() async {
await assertNoErrorsInCode(r'''
void f() {
late final int? x;
x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_lateFinalNullable_definitelyUnassigned_read() async {
await assertErrorsInCode(r'''
void f() {
late final int? x;
x; // 0
}
''', [
error(CompileTimeErrorCode.DEFINITELY_UNASSIGNED_LATE_LOCAL_VARIABLE, 34,
1),
]);
_assertAssigned('x; // 0', assigned: false, unassigned: true);
}
test_lateFinalNullable_neither_read() async {
await assertNoErrorsInCode(r'''
void f(bool b) {
late final int? x;
if (b) x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: false, unassigned: false);
}
test_lateFinalPotentiallyNonNullable_definitelyAssigned_read() async {
await assertNoErrorsInCode(r'''
void f<T>(T t) {
late final T x;
x = t;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_lateFinalPotentiallyNonNullable_definitelyUnassigned_read() async {
await assertErrorsInCode(r'''
void f<T>() {
late final T x;
x; // 0
}
''', [
error(CompileTimeErrorCode.DEFINITELY_UNASSIGNED_LATE_LOCAL_VARIABLE, 34,
1),
]);
_assertAssigned('x; // 0', assigned: false, unassigned: true);
}
test_lateFinalPotentiallyNonNullable_neither_read() async {
await assertNoErrorsInCode(r'''
void f<T>(bool b, T t) {
late final T x;
if (b) x = t;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: false, unassigned: false);
}
test_lateNullable_definitelyAssigned_read() async {
await assertNoErrorsInCode(r'''
void f() {
late int? x;
x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_lateNullable_definitelyUnassigned_read() async {
await assertErrorsInCode(r'''
void f() {
late int? x;
x; // 0
}
''', [
error(CompileTimeErrorCode.DEFINITELY_UNASSIGNED_LATE_LOCAL_VARIABLE, 28,
1),
]);
_assertAssigned('x; // 0', assigned: false, unassigned: true);
}
test_lateNullable_neither_read() async {
await assertNoErrorsInCode(r'''
void f(bool b) {
late int? x;
if (b) x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: false, unassigned: false);
}
test_latePotentiallyNonNullable_definitelyAssigned_read() async {
await assertNoErrorsInCode(r'''
void f<T>(T t) {
late T x;
x = t;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_latePotentiallyNonNullable_definitelyUnassigned_read() async {
await assertErrorsInCode(r'''
void f<T>() {
late T x;
x; // 0
}
''', [
error(CompileTimeErrorCode.DEFINITELY_UNASSIGNED_LATE_LOCAL_VARIABLE, 28,
1),
]);
_assertAssigned('x; // 0', assigned: false, unassigned: true);
}
test_latePotentiallyNonNullable_neither_read() async {
await assertNoErrorsInCode(r'''
void f<T>(bool b, T t) {
late T x;
if (b) x = t;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: false, unassigned: false);
}
test_lateVar_definitelyAssigned_read() async {
await assertNoErrorsInCode(r'''
void f() {
late var x;
x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_lateVar_definitelyUnassigned_read() async {
await assertErrorsInCode(r'''
void f() {
late var x;
x; // 0
}
''', [
error(CompileTimeErrorCode.DEFINITELY_UNASSIGNED_LATE_LOCAL_VARIABLE, 27,
1),
]);
_assertAssigned('x; // 0', assigned: false, unassigned: true);
}
test_lateVar_neither_read() async {
await assertNoErrorsInCode(r'''
void f(bool b) {
late var x;
if (b) x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: false, unassigned: false);
}
test_notNullable_write_forEachLoop_identifier() async {
await assertNoErrorsInCode(r'''
void f() {
int x;
for (x in [0, 1, 2]) {
x; // 0
}
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_nullable_definitelyAssigned_read() async {
await assertNoErrorsInCode(r'''
void f(int? x) {
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_nullable_definitelyUnassigned_read() async {
await assertNoErrorsInCode(r'''
void f() {
int? x;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: false, unassigned: true);
}
test_nullable_neither_read() async {
await assertNoErrorsInCode(r'''
void f(bool b) {
int? x;
if (b) x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: false, unassigned: false);
}
test_potentiallyNonNullable_definitelyAssigned_read() async {
await assertNoErrorsInCode(r'''
void f<T>(T x) {
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_potentiallyNonNullable_definitelyUnassigned_read() async {
await assertErrorsInCode(r'''
void f<T>() {
T x;
x; // 0
}
''', [
error(
CompileTimeErrorCode
.NOT_ASSIGNED_POTENTIALLY_NON_NULLABLE_LOCAL_VARIABLE,
23,
1),
]);
_assertAssigned('x; // 0', assigned: false, unassigned: true);
}
test_potentiallyNonNullable_neither_read() async {
await assertErrorsInCode(r'''
void f<T>(bool b, T t) {
T x;
if (b) x = t;
x; // 0
}
''', [
error(
CompileTimeErrorCode
.NOT_ASSIGNED_POTENTIALLY_NON_NULLABLE_LOCAL_VARIABLE,
50,
1),
]);
_assertAssigned('x; // 0', assigned: false, unassigned: false);
}
test_var_definitelyAssigned_read() async {
await assertNoErrorsInCode(r'''
void f() {
var x;
x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: true, unassigned: false);
}
test_var_definitelyUnassigned_read() async {
await assertNoErrorsInCode(r'''
void f() {
var x;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: false, unassigned: true);
}
test_var_neither_read() async {
await assertNoErrorsInCode(r'''
void f(bool b) {
var x;
if (b) x = 0;
x; // 0
}
''');
_assertAssigned('x; // 0', assigned: false, unassigned: false);
}
void _assertAssigned(
String search, {
@required bool assigned,
@required bool unassigned,
}) {
var node = findNode.simple(search);
var testingData = driverFor(testFilePath).testingData;
var unitData = testingData.uriToFlowAnalysisData[result.uri];
if (assigned) {
expect(unitData.definitelyAssignedNodes, contains(node));
} else {
expect(unitData.definitelyAssignedNodes, isNot(contains(node)));
}
if (unassigned) {
expect(unitData.definitelyUnassignedNodes, contains(node));
} else {
expect(unitData.definitelyUnassignedNodes, isNot(contains(node)));
}
}
}

View file

@ -450,6 +450,7 @@ import 'null_aware_before_operator_test.dart' as null_aware_before_operator;
import 'null_aware_in_condition_test.dart' as null_aware_in_condition;
import 'null_aware_in_logical_operator_test.dart'
as null_aware_in_logical_operator;
import 'null_safety_read_write_test.dart' as null_safety_read_write;
import 'nullable_type_in_catch_clause_test.dart'
as nullable_type_in_catch_clause;
import 'nullable_type_in_extends_clause_test.dart'
@ -936,6 +937,7 @@ main() {
null_aware_before_operator.main();
null_aware_in_condition.main();
null_aware_in_logical_operator.main();
null_safety_read_write.main();
nullable_type_in_catch_clause.main();
nullable_type_in_extends_clause.main();
nullable_type_in_implements_clause.main();

View file

@ -6,9 +6,9 @@ import "package:expect/expect.dart";
main() {
final f0 = 42;
final f1; //# 01: compile-time error
final f1; //# 01: ok
final int f2 = 87;
final int f3; //# 02: compile-time error
final int f3; //# 02: ok
Expect.equals(42, f0);
Expect.equals(87, f2);

View file

@ -1022,12 +1022,16 @@ void testDefinitelyAssignedReadForms() {
final dynamic x;
x = 3;
x++;
// ^
// [analyzer] COMPILE_TIME_ERROR.ASSIGNMENT_TO_FINAL_LOCAL
}
{
final dynamic x;
x = 3;
++x;
// ^
// [analyzer] COMPILE_TIME_ERROR.ASSIGNMENT_TO_FINAL_LOCAL
}
{
@ -1040,12 +1044,16 @@ void testDefinitelyAssignedReadForms() {
final dynamic x;
x = 3;
x += 3;
// ^
// [analyzer] COMPILE_TIME_ERROR.ASSIGNMENT_TO_FINAL_LOCAL
}
{
final dynamic x;
x = 3;
x ??= 3;
// ^
// [analyzer] COMPILE_TIME_ERROR.ASSIGNMENT_TO_FINAL_LOCAL
}
{
@ -1058,6 +1066,8 @@ void testDefinitelyAssignedReadForms() {
final dynamic x;
x = 3;
3 ?? x;
// ^
// [analyzer] STATIC_WARNING.DEAD_NULL_AWARE_EXPRESSION
}
}

View file

@ -45,6 +45,8 @@ class C {
use(x); // Refers to top-level x.
// ^
// [analyzer] COMPILE_TIME_ERROR.REFERENCED_BEFORE_DECLARATION
// ^
// [analyzer] COMPILE_TIME_ERROR.READ_POTENTIALLY_UNASSIGNED_FINAL
use(y); // Refers to top-level y.
// ^
// [analyzer] COMPILE_TIME_ERROR.REFERENCED_BEFORE_DECLARATION
@ -100,6 +102,8 @@ void testLibPrefix() {
var pie = math.pi;
// ^^^^
// [analyzer] COMPILE_TIME_ERROR.REFERENCED_BEFORE_DECLARATION
// ^^^^
// [analyzer] COMPILE_TIME_ERROR.READ_POTENTIALLY_UNASSIGNED_FINAL
final math = 0;
// ^
// [cfe] Can't declare 'math' because it was already used in this scope.