Skip to content

Commit 588fadc

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Flow analysis: expand analyzer support for "why not promoted" messages.
This CL adds support for the following scenarios to the analyzer: - Attempt to use a non-promoted nullable expression as the iterable of a for-in loop - Attempt to use a non-promoted nullable expression as the argument of a `yield *` statement - Attempt to implicitly invoke `.call` on a non-promoted nullable expression - Attempt to use a non-promoted nullable expression as the argument of a spread operator (`...`) that is not null-aware Some of these cases are already handled by the CFE. Others will be addressed in a follow-up CL. Change-Id: I3cf31b1496e1bd92fdd3f8192f04c98dff15077c Bug: #44898 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/186320 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent 2c982b0 commit 588fadc

10 files changed

+245
-146
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// This test contains a test case for each condition that can lead to the front
6+
// end's `ForInLoopTypeNotIterableNullability` or
7+
// `ForInLoopTypeNotIterablePartNullability` errors, for which we wish to report
8+
// "why not promoted" context information.
9+
10+
// TODO(paulberry): get this to work with the CFE and add additional test cases
11+
// if needed.
12+
13+
class C1 {
14+
List<int>? bad;
15+
}
16+
17+
test(C1 c) {
18+
if (c.bad == null) return;
19+
for (var x
20+
in /*analyzer.notPromoted(propertyNotPromoted(member:C1.bad))*/ c.bad) {}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// This test contains a test case for each condition that can lead to the front
6+
// end's `InvalidAssignmentErrorNullability` or
7+
// `InvalidAssignmentErrorPartNullability` errors, for which we wish to report
8+
// "why not promoted" context information.
9+
10+
// TODO(paulberry): get this to work with the CFE and add additional test cases
11+
// if needed.
12+
13+
class C1 {
14+
List<int>? bad;
15+
}
16+
17+
test(C1 c) sync* {
18+
if (c.bad == null) return;
19+
yield* /*analyzer.notPromoted(propertyNotPromoted(member:C1.bad))*/ c.bad;
20+
}

pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_expression_call_error.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class C2 {
1616

1717
instance_method_invocation(C1 c) {
1818
if (c.bad == null) return;
19-
c.bad
19+
/*analyzer.notPromoted(propertyNotPromoted(member:C1.bad))*/ c.bad
2020
/*cfe.invoke: notPromoted(propertyNotPromoted(member:C1.bad))*/
2121
();
2222
}
@@ -42,7 +42,7 @@ extension_invocation_method(C3 c) {
4242
if (c.ok == null) return;
4343
c.ok();
4444
if (c.bad == null) return;
45-
c.bad
45+
/*analyzer.notPromoted(propertyNotPromoted(member:C3.bad))*/ c.bad
4646
/*cfe.invoke: notPromoted(propertyNotPromoted(member:C3.bad))*/
4747
();
4848
}
@@ -57,7 +57,7 @@ class C7 {
5757

5858
instance_getter_invocation(C6 c) {
5959
if (c.bad == null) return;
60-
c.bad
60+
/*analyzer.notPromoted(propertyNotPromoted(member:C6.bad))*/ c.bad
6161
/*cfe.invoke: notPromoted(propertyNotPromoted(member:C6.bad))*/
6262
();
6363
}
@@ -83,7 +83,7 @@ extension_invocation_getter(C8 c) {
8383
if (c.ok == null) return;
8484
c.ok();
8585
if (c.bad == null) return;
86-
c.bad
86+
/*analyzer.notPromoted(propertyNotPromoted(member:C8.bad))*/ c.bad
8787
/*cfe.invoke: notPromoted(propertyNotPromoted(member:C8.bad))*/
8888
();
8989
}
@@ -94,7 +94,7 @@ class C11 {
9494

9595
function_invocation(C11 c) {
9696
if (c.bad == null) return;
97-
c.bad
97+
/*analyzer.notPromoted(propertyNotPromoted(member:C11.bad))*/ c.bad
9898
/*cfe.invoke: notPromoted(propertyNotPromoted(member:C11.bad))*/
9999
();
100100
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// This test contains a test case for each condition that can lead to the front
6+
// end's `NullableSpreadError` error, for which we wish to report "why not
7+
// promoted" context information.
8+
9+
// TODO(paulberry): get this to work with the CFE and add additional test cases
10+
// if needed.
11+
12+
class C1 {
13+
List<int>? bad;
14+
}
15+
16+
test(C1 c) {
17+
if (c.bad == null) return;
18+
return [
19+
... /*analyzer.notPromoted(propertyNotPromoted(member:C1.bad))*/ c.bad
20+
];
21+
}

pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,19 @@ class FlowAnalysisHelper {
240240
flow!.finish();
241241
}
242242

243+
/// Transfers any test data that was recorded for [oldNode] so that it is now
244+
/// associated with [newNode]. We need to do this when doing AST rewriting,
245+
/// so that test data can be found using the rewritten tree.
246+
void transferTestData(AstNode oldNode, AstNode newNode) {
247+
var dataForTesting = this.dataForTesting;
248+
if (dataForTesting != null) {
249+
var oldNonPromotionReasons = dataForTesting.nonPromotionReasons[oldNode];
250+
if (oldNonPromotionReasons != null) {
251+
dataForTesting.nonPromotionReasons[newNode] = oldNonPromotionReasons;
252+
}
253+
}
254+
}
255+
243256
void variableDeclarationList(VariableDeclarationList node) {
244257
if (flow != null) {
245258
var variables = node.variables;

pkg/analyzer/lib/src/dart/resolver/function_expression_invocation_resolver.dart

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,24 @@ class FunctionExpressionInvocationResolver {
4242
return;
4343
}
4444

45+
var receiverType = function.staticType;
46+
if (receiverType is InterfaceType) {
47+
// Note: in this circumstance it's not necessary to call
48+
// `_nullableDereferenceVerifier.expression` because
49+
// `_resolveReceiverInterfaceType` calls `TypePropertyResolver.resolve`,
50+
// which does the necessary null checking.
51+
_resolveReceiverInterfaceType(node, function, receiverType);
52+
return;
53+
}
54+
4555
_nullableDereferenceVerifier.expression(function,
4656
errorCode: CompileTimeErrorCode.UNCHECKED_INVOCATION_OF_NULLABLE_VALUE);
4757

48-
var receiverType = function.staticType;
4958
if (receiverType is FunctionType) {
5059
_resolve(node, receiverType);
5160
return;
5261
}
5362

54-
if (receiverType is InterfaceType) {
55-
_resolveReceiverInterfaceType(node, function, receiverType);
56-
return;
57-
}
58-
5963
if (identical(receiverType, NeverTypeImpl.instance)) {
6064
_unresolved(node, NeverTypeImpl.instance);
6165
return;

pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,8 @@ class MethodInvocationResolver {
752752
node.methodName,
753753
);
754754
}
755+
_resolver.flowAnalysis?.flow
756+
?.propertyGet(functionExpression, target, node.methodName.name);
755757
functionExpression.staticType = targetType;
756758
}
757759

@@ -763,6 +765,7 @@ class MethodInvocationResolver {
763765
NodeReplacer.replace(node, invocation);
764766
node.setProperty(_rewriteResultKey, invocation);
765767
InferenceContext.setTypeFromNode(invocation, node);
768+
_resolver.flowAnalysis?.transferTestData(node, invocation);
766769
}
767770

768771
void _setDynamicResolution(MethodInvocation node,

pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart

Lines changed: 2 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
65
import 'package:analyzer/dart/ast/ast.dart';
76
import 'package:analyzer/dart/ast/syntactic_entity.dart';
87
import 'package:analyzer/dart/element/element.dart';
@@ -13,13 +12,9 @@ import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
1312
import 'package:analyzer/src/dart/element/type_provider.dart';
1413
import 'package:analyzer/src/dart/element/type_system.dart';
1514
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
16-
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
1715
import 'package:analyzer/src/dart/resolver/resolution_result.dart';
18-
import 'package:analyzer/src/diagnostic/diagnostic.dart';
1916
import 'package:analyzer/src/error/codes.dart';
2017
import 'package:analyzer/src/generated/resolver.dart';
21-
import 'package:analyzer/src/generated/source.dart';
22-
import 'package:analyzer/src/util/ast_data_extractor.dart';
2318

2419
/// Helper for resolving properties (getters, setters, or methods).
2520
class TypePropertyResolver {
@@ -120,35 +115,8 @@ class TypePropertyResolver {
120115
}
121116
}
122117

123-
List<DiagnosticMessage> messages = [];
124-
if (receiver != null) {
125-
var whyNotPromoted =
126-
_resolver.flowAnalysis?.flow?.whyNotPromoted(receiver);
127-
if (whyNotPromoted != null) {
128-
for (var entry in whyNotPromoted.entries) {
129-
var whyNotPromotedVisitor = _WhyNotPromotedVisitor(_resolver.source,
130-
receiver, _resolver.flowAnalysis!.dataForTesting);
131-
if (_typeSystem.isPotentiallyNullable(entry.key)) continue;
132-
var message = entry.value.accept(whyNotPromotedVisitor);
133-
if (message != null) {
134-
if (_resolver.flowAnalysis!.dataForTesting != null) {
135-
var nonPromotionReasonText = entry.value.shortName;
136-
if (whyNotPromotedVisitor.propertyReference != null) {
137-
var id =
138-
computeMemberId(whyNotPromotedVisitor.propertyReference!);
139-
nonPromotionReasonText += '($id)';
140-
}
141-
_resolver.flowAnalysis!.dataForTesting!
142-
.nonPromotionReasons[nameErrorEntity] =
143-
nonPromotionReasonText;
144-
}
145-
messages = [message];
146-
}
147-
break;
148-
}
149-
}
150-
}
151-
118+
List<DiagnosticMessage> messages =
119+
_resolver.computeWhyNotPromotedMessages(receiver, nameErrorEntity);
152120
_resolver.nullableDereferenceVerifier.report(
153121
receiverErrorNode, receiverType,
154122
errorCode: errorCode, arguments: [name], messages: messages);
@@ -299,102 +267,3 @@ class TypePropertyResolver {
299267
);
300268
}
301269
}
302-
303-
class _WhyNotPromotedVisitor
304-
implements
305-
NonPromotionReasonVisitor<DiagnosticMessage?, AstNode, Expression,
306-
PromotableElement> {
307-
final Source source;
308-
309-
final Expression _receiver;
310-
311-
final FlowAnalysisDataForTesting? _dataForTesting;
312-
313-
PropertyAccessorElement? propertyReference;
314-
315-
_WhyNotPromotedVisitor(this.source, this._receiver, this._dataForTesting);
316-
317-
@override
318-
DiagnosticMessage? visitDemoteViaExplicitWrite(
319-
DemoteViaExplicitWrite<PromotableElement, Expression> reason) {
320-
var writeExpression = reason.writeExpression;
321-
if (_dataForTesting != null) {
322-
_dataForTesting!.nonPromotionReasonTargets[writeExpression] =
323-
reason.shortName;
324-
}
325-
var variableName = reason.variable.name;
326-
if (variableName == null) return null;
327-
return _contextMessageForWrite(variableName, writeExpression);
328-
}
329-
330-
@override
331-
DiagnosticMessage? visitDemoteViaForEachVariableWrite(
332-
DemoteViaForEachVariableWrite<PromotableElement, AstNode> reason) {
333-
var node = reason.node;
334-
var variableName = reason.variable.name;
335-
if (variableName == null) return null;
336-
ForLoopParts parts;
337-
if (node is ForStatement) {
338-
parts = node.forLoopParts;
339-
} else if (node is ForElement) {
340-
parts = node.forLoopParts;
341-
} else {
342-
assert(false, 'Unexpected node type');
343-
return null;
344-
}
345-
if (parts is ForEachPartsWithIdentifier) {
346-
var identifier = parts.identifier;
347-
if (_dataForTesting != null) {
348-
_dataForTesting!.nonPromotionReasonTargets[identifier] =
349-
reason.shortName;
350-
}
351-
return _contextMessageForWrite(variableName, identifier);
352-
} else {
353-
assert(false, 'Unexpected parts type');
354-
return null;
355-
}
356-
}
357-
358-
@override
359-
DiagnosticMessage? visitPropertyNotPromoted(PropertyNotPromoted reason) {
360-
var receiver = _receiver;
361-
Element? receiverElement;
362-
if (receiver is SimpleIdentifier) {
363-
receiverElement = receiver.staticElement;
364-
} else if (receiver is PropertyAccess) {
365-
receiverElement = receiver.propertyName.staticElement;
366-
} else if (receiver is PrefixedIdentifier) {
367-
receiverElement = receiver.identifier.staticElement;
368-
} else {
369-
assert(false, 'Unrecognized receiver: ${receiver.runtimeType}');
370-
}
371-
if (receiverElement is PropertyAccessorElement) {
372-
propertyReference = receiverElement;
373-
return _contextMessageForProperty(receiverElement, reason.propertyName);
374-
} else {
375-
assert(receiverElement == null,
376-
'Unrecognized receiver element: ${receiverElement.runtimeType}');
377-
return null;
378-
}
379-
}
380-
381-
DiagnosticMessageImpl _contextMessageForProperty(
382-
PropertyAccessorElement property, String propertyName) {
383-
return DiagnosticMessageImpl(
384-
filePath: property.source.fullName,
385-
message:
386-
"'$propertyName' refers to a property so it could not be promoted.",
387-
offset: property.nameOffset,
388-
length: property.nameLength);
389-
}
390-
391-
DiagnosticMessageImpl _contextMessageForWrite(
392-
String variableName, Expression writeExpression) {
393-
return DiagnosticMessageImpl(
394-
filePath: source.fullName,
395-
message: "Variable '$variableName' could be null due to an intervening "
396-
"write.",
397-
offset: writeExpression.offset,
398-
length: writeExpression.length);
399-
}
400-
}

pkg/analyzer/lib/src/error/nullable_dereference_verifier.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,23 @@ import 'package:analyzer/src/dart/ast/extensions.dart';
1111
import 'package:analyzer/src/dart/element/type.dart';
1212
import 'package:analyzer/src/dart/element/type_system.dart';
1313
import 'package:analyzer/src/error/codes.dart';
14+
import 'package:analyzer/src/generated/resolver.dart';
1415

1516
/// Helper for checking potentially nullable dereferences.
1617
class NullableDereferenceVerifier {
1718
final TypeSystemImpl _typeSystem;
1819
final ErrorReporter _errorReporter;
1920

21+
/// The resolver driving this participant.
22+
final ResolverVisitor _resolver;
23+
2024
NullableDereferenceVerifier({
2125
required TypeSystemImpl typeSystem,
2226
required ErrorReporter errorReporter,
27+
required ResolverVisitor resolver,
2328
}) : _typeSystem = typeSystem,
24-
_errorReporter = errorReporter;
29+
_errorReporter = errorReporter,
30+
_resolver = resolver;
2531

2632
bool expression(Expression expression,
2733
{DartType? type, ErrorCode? errorCode}) {
@@ -59,7 +65,11 @@ class NullableDereferenceVerifier {
5965
return false;
6066
}
6167

62-
report(errorNode, receiverType, errorCode: errorCode);
68+
List<DiagnosticMessage>? messages;
69+
if (errorNode is Expression) {
70+
messages = _resolver.computeWhyNotPromotedMessages(errorNode, errorNode);
71+
}
72+
report(errorNode, receiverType, errorCode: errorCode, messages: messages);
6373
return true;
6474
}
6575
}

0 commit comments

Comments
 (0)