mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:09:49 +00:00
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:
parent
d52a577f3b
commit
25edea3d8d
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -58,7 +58,7 @@ class PostfixExpressionResolver {
|
|||
node.operand,
|
||||
);
|
||||
|
||||
_assignmentShared.checkLateFinalAlreadyAssigned(node.operand);
|
||||
_assignmentShared.checkFinalAlreadyAssigned(node.operand);
|
||||
|
||||
_resolve1(node, receiverType);
|
||||
_resolve2(node, receiverType);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
);
|
||||
|
||||
|
|
|
@ -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('''
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue