Skip to content

Commit a42244f

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Flow analysis: begin tracking non-promotion reasons.
This CL implements the core flow analysis infrastructure for tracking reasons why an expression was not promoted. It supports the following reasons: - Expression was a property access - Expression has been written to since it was promoted I expect to add support for other non-promotion reasons in the future, for example: - `this` cannot be promoted - Expression has been write captured - Expression was a reference to a static field or top level variable These non-promotion reasons are plumbed through to the CFE and analyzer for the purpose of making errors easier for the user to understand. For example, given the following code: class C { int? i; f() { if (i == null) return; print(i.isEven); } } The front end now prints: ../../tmp/test.dart:5:13: Error: Property 'isEven' cannot be accessed on 'int?' because it is potentially null. Try accessing using ?. instead. print(i.isEven); ^^^^^^ Context: 'i' refers to a property so it could not be promoted. Much work still needs to be done to round out this feature, for example: - Currently the analyzer only shows the new "why not promoted" messages when the "--verbose" flag is specified; this means the feature is unlikely to be noticed by users. - Currently the analyzer doesn't show a "why not promoted" message when the non-promotion reason is that the expression is a property access. - We need one or more web pages explaining non-promotion reasons in more detail so that the error messages can contain pointers to them. - The analyzer and front end currently only show non-promotion reasons for expressions of the form `x.y` where `x` fails to be promoted to non-nullable. There are many other scenarios that should be handled. Change-Id: I0a12df74d0fc6274dfb3cb555abea81a75884231 Bug: #38773 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/181741 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]>
1 parent 91be638 commit a42244f

39 files changed

+1676
-177
lines changed

.dart_tool/package_config.json

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@
5555
"rootUri": "../pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion",
5656
"packageUri": ".nonexisting/"
5757
},
58+
{
59+
"name": "_fe_analyzer_shared_why_not_promoted",
60+
"rootUri": "../pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted",
61+
"packageUri": ".nonexisting/"
62+
},
5863
{
5964
"name": "_js_interop_checks",
6065
"rootUri": "../pkg/_js_interop_checks",

pkg/_fe_analyzer_shared/analysis_options_no_lints.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ analyzer:
1515
- test/flow_analysis/nullability/data/**
1616
- test/flow_analysis/reachability/data/**
1717
- test/flow_analysis/type_promotion/data/**
18+
- test/flow_analysis/why_not_promoted/data/**
1819
- test/inheritance/data/**

pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart

+523-82
Large diffs are not rendered by default.

pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart

+51
Original file line numberDiff line numberDiff line change
@@ -4032,6 +4032,29 @@ const MessageCode messageFieldInitializerOutsideConstructor = const MessageCode(
40324032
message: r"""Field formal parameters can only be used in a constructor.""",
40334033
tip: r"""Try removing 'this.'.""");
40344034

4035+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
4036+
const Template<Message Function(String name)> templateFieldNotPromoted =
4037+
const Template<Message Function(String name)>(
4038+
messageTemplate:
4039+
r"""'#name' refers to a property so it could not be promoted.""",
4040+
withArguments: _withArgumentsFieldNotPromoted);
4041+
4042+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
4043+
const Code<Message Function(String name)> codeFieldNotPromoted =
4044+
const Code<Message Function(String name)>(
4045+
"FieldNotPromoted",
4046+
);
4047+
4048+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
4049+
Message _withArgumentsFieldNotPromoted(String name) {
4050+
if (name.isEmpty) throw 'No name provided';
4051+
name = demangleMixinApplicationName(name);
4052+
return new Message(codeFieldNotPromoted,
4053+
message:
4054+
"""'${name}' refers to a property so it could not be promoted.""",
4055+
arguments: {'name': name});
4056+
}
4057+
40354058
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
40364059
const Code<Null> codeFinalAndCovariant = messageFinalAndCovariant;
40374060

@@ -9620,6 +9643,34 @@ const MessageCode messageVarReturnType = const MessageCode("VarReturnType",
96209643
tip:
96219644
r"""Try removing the keyword 'var', or replacing it with the name of the return type.""");
96229645

9646+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
9647+
const Template<
9648+
Message Function(
9649+
String
9650+
name)> templateVariableCouldBeNullDueToWrite = const Template<
9651+
Message Function(String name)>(
9652+
messageTemplate:
9653+
r"""Variable '#name' could be null due to a write occurring here.""",
9654+
tipTemplate: r"""Try null checking the variable after the write.""",
9655+
withArguments: _withArgumentsVariableCouldBeNullDueToWrite);
9656+
9657+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
9658+
const Code<Message Function(String name)> codeVariableCouldBeNullDueToWrite =
9659+
const Code<Message Function(String name)>(
9660+
"VariableCouldBeNullDueToWrite",
9661+
);
9662+
9663+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
9664+
Message _withArgumentsVariableCouldBeNullDueToWrite(String name) {
9665+
if (name.isEmpty) throw 'No name provided';
9666+
name = demangleMixinApplicationName(name);
9667+
return new Message(codeVariableCouldBeNullDueToWrite,
9668+
message:
9669+
"""Variable '${name}' could be null due to a write occurring here.""",
9670+
tip: """Try null checking the variable after the write.""",
9671+
arguments: {'name': name});
9672+
}
9673+
96239674
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
96249675
const Code<Null> codeVerificationErrorOriginContext =
96259676
messageVerificationErrorOriginContext;

pkg/_fe_analyzer_shared/test/annotated_code_helper_test.dart

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ main() {
1616
testDir('pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data');
1717
testDir('pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data');
1818
testDir('pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data');
19+
testDir('pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data');
1920
testDir('pkg/_fe_analyzer_shared/test/inheritance/data');
2021
}
2122

pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart

+136-4
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ Statement switch_(Expression expression, List<SwitchCase> cases,
154154
{required bool isExhaustive}) =>
155155
new _Switch(expression, cases, isExhaustive);
156156

157+
Expression this_(String type) => new _This(Type(type));
158+
159+
Expression thisOrSuperPropertyGet(String name, {String type = 'Object?'}) =>
160+
new _ThisOrSuperPropertyGet(name, type);
161+
157162
Expression throw_(Expression operand) => new _Throw(operand);
158163

159164
Statement tryCatch(List<Statement> body, List<CatchClause> catches) =>
@@ -273,12 +278,24 @@ abstract class Expression extends Node implements _Visitable<Type> {
273278
/// If `this` is an expression `x`, creates the expression `x || other`.
274279
Expression or(Expression other) => new _Logical(this, other, isAnd: false);
275280

281+
/// If `this` is an expression `x`, creates the expression `x.name`.
282+
Expression propertyGet(String name, {String type = 'Object?'}) =>
283+
new _PropertyGet(this, name, type);
284+
276285
/// If `this` is an expression `x`, creates a pseudo-expression that models
277286
/// evaluation of `x` followed by execution of [stmt]. This can be used to
278287
/// test that flow analysis is in the correct state after an expression is
279288
/// visited.
280289
Expression thenStmt(Statement stmt) =>
281290
new _WrappedExpression(null, this, stmt);
291+
292+
/// Creates an [Expression] that, when analyzed, will behave the same as
293+
/// `this`, but after visiting it, will cause [callback] to be passed the
294+
/// non-promotion info associated with it. If the expression has no
295+
/// non-promotion info, an empty map will be passed to [callback].
296+
Expression whyNotPromoted(
297+
void Function(Map<Type, NonPromotionReason>) callback) =>
298+
new _WhyNotPromoted(this, callback);
282299
}
283300

284301
/// Test harness for creating flow analysis tests. This class implements all
@@ -311,8 +328,10 @@ class Harness extends TypeOperations<Var, Type> {
311328
'int? <: num?': true,
312329
'int? <: Object': false,
313330
'int? <: Object?': true,
331+
'Never <: Object?': true,
314332
'Null <: int': false,
315333
'Null <: Object': false,
334+
'Null <: Object?': true,
316335
'num <: int': false,
317336
'num <: Iterable': false,
318337
'num <: List': false,
@@ -347,13 +366,15 @@ class Harness extends TypeOperations<Var, Type> {
347366
'Object <: int': false,
348367
'Object <: int?': false,
349368
'Object <: List': false,
369+
'Object <: Null': false,
350370
'Object <: num': false,
351371
'Object <: num?': false,
352372
'Object <: Object?': true,
353373
'Object <: String': false,
354374
'Object? <: Object': false,
355375
'Object? <: int': false,
356376
'Object? <: int?': false,
377+
'Object? <: Null': false,
357378
'String <: int': false,
358379
'String <: int?': false,
359380
'String <: num?': false,
@@ -364,6 +385,8 @@ class Harness extends TypeOperations<Var, Type> {
364385
static final Map<String, Type> _coreFactors = {
365386
'Object? - int': Type('Object?'),
366387
'Object? - int?': Type('Object'),
388+
'Object? - Never': Type('Object?'),
389+
'Object? - Null': Type('Object'),
367390
'Object? - num?': Type('Object'),
368391
'Object? - Object?': Type('Never?'),
369392
'Object? - String': Type('Object?'),
@@ -410,6 +433,9 @@ class Harness extends TypeOperations<Var, Type> {
410433

411434
Harness({this.allowLocalBooleanVarsToPromote = false, this.legacy = false});
412435

436+
@override
437+
Type get topType => Type('Object?');
438+
413439
/// Updates the harness so that when a [factor] query is invoked on types
414440
/// [from] and [what], [result] will be returned.
415441
void addFactor(String from, String what, String result) {
@@ -597,15 +623,30 @@ class SwitchCase implements _Visitable<void> {
597623
/// testing. This is essentially a thin wrapper around a string representation
598624
/// of the type.
599625
class Type {
626+
static bool _allowComparisons = false;
627+
600628
final String type;
601629

602630
Type(this.type);
603631

632+
@override
633+
int get hashCode {
634+
if (!_allowComparisons) {
635+
// The flow analysis engine should not hash types using hashCode. It
636+
// should compare them using TypeOperations.
637+
fail('Unexpected use of operator== on types');
638+
}
639+
return type.hashCode;
640+
}
641+
604642
@override
605643
bool operator ==(Object other) {
606-
// The flow analysis engine should not compare types using operator==. It
607-
// should compare them using TypeOperations.
608-
fail('Unexpected use of operator== on types');
644+
if (!_allowComparisons) {
645+
// The flow analysis engine should not compare types using operator==. It
646+
// should compare them using TypeOperations.
647+
fail('Unexpected use of operator== on types');
648+
}
649+
return other is Type && this.type == other.type;
609650
}
610651

611652
@override
@@ -1424,6 +1465,29 @@ class _PlaceholderExpression extends Expression {
14241465
type;
14251466
}
14261467

1468+
class _PropertyGet extends Expression {
1469+
final Expression target;
1470+
1471+
final String propertyName;
1472+
1473+
final String type;
1474+
1475+
_PropertyGet(this.target, this.propertyName, this.type);
1476+
1477+
@override
1478+
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
1479+
target._preVisit(assignedVariables);
1480+
}
1481+
1482+
@override
1483+
Type _visit(
1484+
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
1485+
target._visit(h, flow);
1486+
flow.propertyGet(this, target, propertyName);
1487+
return Type(type);
1488+
}
1489+
}
1490+
14271491
class _Return extends Statement {
14281492
_Return() : super._();
14291493

@@ -1481,6 +1545,43 @@ class _Switch extends Statement {
14811545
}
14821546
}
14831547

1548+
class _This extends Expression {
1549+
final Type type;
1550+
1551+
_This(this.type);
1552+
1553+
@override
1554+
String toString() => 'this';
1555+
1556+
@override
1557+
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
1558+
1559+
@override
1560+
Type _visit(
1561+
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
1562+
flow.thisOrSuper(this);
1563+
return type;
1564+
}
1565+
}
1566+
1567+
class _ThisOrSuperPropertyGet extends Expression {
1568+
final String propertyName;
1569+
1570+
final String type;
1571+
1572+
_ThisOrSuperPropertyGet(this.propertyName, this.type);
1573+
1574+
@override
1575+
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
1576+
1577+
@override
1578+
Type _visit(
1579+
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
1580+
flow.thisOrSuperPropertyGet(this, propertyName);
1581+
return Type(type);
1582+
}
1583+
}
1584+
14841585
class _Throw extends Expression {
14851586
final Expression operand;
14861587

@@ -1622,6 +1723,37 @@ class _While extends Statement {
16221723
}
16231724
}
16241725

1726+
class _WhyNotPromoted extends Expression {
1727+
final Expression target;
1728+
1729+
final void Function(Map<Type, NonPromotionReason>) callback;
1730+
1731+
_WhyNotPromoted(this.target, this.callback);
1732+
1733+
@override
1734+
String toString() => '$target (whyNotPromoted)';
1735+
1736+
@override
1737+
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
1738+
target._preVisit(assignedVariables);
1739+
}
1740+
1741+
@override
1742+
Type _visit(
1743+
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
1744+
var type = target._visit(h, flow);
1745+
flow.forwardExpression(this, target);
1746+
assert(!Type._allowComparisons);
1747+
Type._allowComparisons = true;
1748+
try {
1749+
callback(flow.whyNotPromoted(this));
1750+
} finally {
1751+
Type._allowComparisons = false;
1752+
}
1753+
return type;
1754+
}
1755+
}
1756+
16251757
class _WrappedExpression extends Expression {
16261758
final Statement? before;
16271759
final Expression expr;
@@ -1681,7 +1813,7 @@ class _Write extends Expression {
16811813
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
16821814
var rhs = this.rhs;
16831815
var type = rhs == null ? variable.type : rhs._visit(h, flow);
1684-
flow.write(variable, type, rhs);
1816+
flow.write(this, variable, type, rhs);
16851817
return type;
16861818
}
16871819
}

0 commit comments

Comments
 (0)