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: https://github.com/dart-lang/sdk/issues/38773
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/181741
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Paul Berry
2021-02-02 16:15:34 +00:00
committed by commit-bot@chromium.org
parent 91be638a56
commit a42244f73b
39 changed files with 1676 additions and 177 deletions
+5
View File
@@ -55,6 +55,11 @@
"rootUri": "../pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion",
"packageUri": ".nonexisting/"
},
{
"name": "_fe_analyzer_shared_why_not_promoted",
"rootUri": "../pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted",
"packageUri": ".nonexisting/"
},
{
"name": "_js_interop_checks",
"rootUri": "../pkg/_js_interop_checks",
@@ -15,4 +15,5 @@ analyzer:
- test/flow_analysis/nullability/data/**
- test/flow_analysis/reachability/data/**
- test/flow_analysis/type_promotion/data/**
- test/flow_analysis/why_not_promoted/data/**
- test/inheritance/data/**
File diff suppressed because it is too large Load Diff
@@ -4032,6 +4032,29 @@ const MessageCode messageFieldInitializerOutsideConstructor = const MessageCode(
message: r"""Field formal parameters can only be used in a constructor.""",
tip: r"""Try removing 'this.'.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(String name)> templateFieldNotPromoted =
const Template<Message Function(String name)>(
messageTemplate:
r"""'#name' refers to a property so it could not be promoted.""",
withArguments: _withArgumentsFieldNotPromoted);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Message Function(String name)> codeFieldNotPromoted =
const Code<Message Function(String name)>(
"FieldNotPromoted",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
Message _withArgumentsFieldNotPromoted(String name) {
if (name.isEmpty) throw 'No name provided';
name = demangleMixinApplicationName(name);
return new Message(codeFieldNotPromoted,
message:
"""'${name}' refers to a property so it could not be promoted.""",
arguments: {'name': name});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeFinalAndCovariant = messageFinalAndCovariant;
@@ -9620,6 +9643,34 @@ const MessageCode messageVarReturnType = const MessageCode("VarReturnType",
tip:
r"""Try removing the keyword 'var', or replacing it with the name of the return type.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(
String
name)> templateVariableCouldBeNullDueToWrite = const Template<
Message Function(String name)>(
messageTemplate:
r"""Variable '#name' could be null due to a write occurring here.""",
tipTemplate: r"""Try null checking the variable after the write.""",
withArguments: _withArgumentsVariableCouldBeNullDueToWrite);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Message Function(String name)> codeVariableCouldBeNullDueToWrite =
const Code<Message Function(String name)>(
"VariableCouldBeNullDueToWrite",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
Message _withArgumentsVariableCouldBeNullDueToWrite(String name) {
if (name.isEmpty) throw 'No name provided';
name = demangleMixinApplicationName(name);
return new Message(codeVariableCouldBeNullDueToWrite,
message:
"""Variable '${name}' could be null due to a write occurring here.""",
tip: """Try null checking the variable after the write.""",
arguments: {'name': name});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeVerificationErrorOriginContext =
messageVerificationErrorOriginContext;
@@ -16,6 +16,7 @@ main() {
testDir('pkg/_fe_analyzer_shared/test/flow_analysis/nullability/data');
testDir('pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data');
testDir('pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data');
testDir('pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data');
testDir('pkg/_fe_analyzer_shared/test/inheritance/data');
}
@@ -154,6 +154,11 @@ Statement switch_(Expression expression, List<SwitchCase> cases,
{required bool isExhaustive}) =>
new _Switch(expression, cases, isExhaustive);
Expression this_(String type) => new _This(Type(type));
Expression thisOrSuperPropertyGet(String name, {String type = 'Object?'}) =>
new _ThisOrSuperPropertyGet(name, type);
Expression throw_(Expression operand) => new _Throw(operand);
Statement tryCatch(List<Statement> body, List<CatchClause> catches) =>
@@ -273,12 +278,24 @@ abstract class Expression extends Node implements _Visitable<Type> {
/// If `this` is an expression `x`, creates the expression `x || other`.
Expression or(Expression other) => new _Logical(this, other, isAnd: false);
/// If `this` is an expression `x`, creates the expression `x.name`.
Expression propertyGet(String name, {String type = 'Object?'}) =>
new _PropertyGet(this, name, type);
/// If `this` is an expression `x`, creates a pseudo-expression that models
/// evaluation of `x` followed by execution of [stmt]. This can be used to
/// test that flow analysis is in the correct state after an expression is
/// visited.
Expression thenStmt(Statement stmt) =>
new _WrappedExpression(null, this, stmt);
/// Creates an [Expression] that, when analyzed, will behave the same as
/// `this`, but after visiting it, will cause [callback] to be passed the
/// non-promotion info associated with it. If the expression has no
/// non-promotion info, an empty map will be passed to [callback].
Expression whyNotPromoted(
void Function(Map<Type, NonPromotionReason>) callback) =>
new _WhyNotPromoted(this, callback);
}
/// Test harness for creating flow analysis tests. This class implements all
@@ -311,8 +328,10 @@ class Harness extends TypeOperations<Var, Type> {
'int? <: num?': true,
'int? <: Object': false,
'int? <: Object?': true,
'Never <: Object?': true,
'Null <: int': false,
'Null <: Object': false,
'Null <: Object?': true,
'num <: int': false,
'num <: Iterable': false,
'num <: List': false,
@@ -347,6 +366,7 @@ class Harness extends TypeOperations<Var, Type> {
'Object <: int': false,
'Object <: int?': false,
'Object <: List': false,
'Object <: Null': false,
'Object <: num': false,
'Object <: num?': false,
'Object <: Object?': true,
@@ -354,6 +374,7 @@ class Harness extends TypeOperations<Var, Type> {
'Object? <: Object': false,
'Object? <: int': false,
'Object? <: int?': false,
'Object? <: Null': false,
'String <: int': false,
'String <: int?': false,
'String <: num?': false,
@@ -364,6 +385,8 @@ class Harness extends TypeOperations<Var, Type> {
static final Map<String, Type> _coreFactors = {
'Object? - int': Type('Object?'),
'Object? - int?': Type('Object'),
'Object? - Never': Type('Object?'),
'Object? - Null': Type('Object'),
'Object? - num?': Type('Object'),
'Object? - Object?': Type('Never?'),
'Object? - String': Type('Object?'),
@@ -410,6 +433,9 @@ class Harness extends TypeOperations<Var, Type> {
Harness({this.allowLocalBooleanVarsToPromote = false, this.legacy = false});
@override
Type get topType => Type('Object?');
/// Updates the harness so that when a [factor] query is invoked on types
/// [from] and [what], [result] will be returned.
void addFactor(String from, String what, String result) {
@@ -597,15 +623,30 @@ class SwitchCase implements _Visitable<void> {
/// testing. This is essentially a thin wrapper around a string representation
/// of the type.
class Type {
static bool _allowComparisons = false;
final String type;
Type(this.type);
@override
int get hashCode {
if (!_allowComparisons) {
// The flow analysis engine should not hash types using hashCode. It
// should compare them using TypeOperations.
fail('Unexpected use of operator== on types');
}
return type.hashCode;
}
@override
bool operator ==(Object other) {
// The flow analysis engine should not compare types using operator==. It
// should compare them using TypeOperations.
fail('Unexpected use of operator== on types');
if (!_allowComparisons) {
// The flow analysis engine should not compare types using operator==. It
// should compare them using TypeOperations.
fail('Unexpected use of operator== on types');
}
return other is Type && this.type == other.type;
}
@override
@@ -1424,6 +1465,29 @@ class _PlaceholderExpression extends Expression {
type;
}
class _PropertyGet extends Expression {
final Expression target;
final String propertyName;
final String type;
_PropertyGet(this.target, this.propertyName, this.type);
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
target._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
target._visit(h, flow);
flow.propertyGet(this, target, propertyName);
return Type(type);
}
}
class _Return extends Statement {
_Return() : super._();
@@ -1481,6 +1545,43 @@ class _Switch extends Statement {
}
}
class _This extends Expression {
final Type type;
_This(this.type);
@override
String toString() => 'this';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.thisOrSuper(this);
return type;
}
}
class _ThisOrSuperPropertyGet extends Expression {
final String propertyName;
final String type;
_ThisOrSuperPropertyGet(this.propertyName, this.type);
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
flow.thisOrSuperPropertyGet(this, propertyName);
return Type(type);
}
}
class _Throw extends Expression {
final Expression operand;
@@ -1622,6 +1723,37 @@ class _While extends Statement {
}
}
class _WhyNotPromoted extends Expression {
final Expression target;
final void Function(Map<Type, NonPromotionReason>) callback;
_WhyNotPromoted(this.target, this.callback);
@override
String toString() => '$target (whyNotPromoted)';
@override
void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
target._preVisit(assignedVariables);
}
@override
Type _visit(
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var type = target._visit(h, flow);
flow.forwardExpression(this, target);
assert(!Type._allowComparisons);
Type._allowComparisons = true;
try {
callback(flow.whyNotPromoted(this));
} finally {
Type._allowComparisons = false;
}
return type;
}
}
class _WrappedExpression extends Expression {
final Statement? before;
final Expression expr;
@@ -1681,7 +1813,7 @@ class _Write extends Expression {
Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
var rhs = this.rhs;
var type = rhs == null ? variable.type : rhs._visit(h, flow);
flow.write(variable, type, rhs);
flow.write(this, variable, type, rhs);
return type;
}
}
@@ -499,6 +499,94 @@ main() {
]);
});
test('equalityOp_end on property get preserves target variable', () {
// This is a regression test for a mistake made during the implementation
// of "why not promoted" functionality: when storing information about an
// attempt to promote a field (e.g. `x.y != null`) we need to make sure we
// don't wipe out information about the target variable (`x`).
var h = Harness();
var x = Var('x', 'C');
h.run([
declare(x, initialized: true),
checkAssigned(x, true),
if_(x.read.propertyGet('y').notEq(nullLiteral), [
checkAssigned(x, true),
], [
checkAssigned(x, true),
]),
]);
});
test('equalityOp_end does not set reachability for `this`', () {
var h = Harness();
h.addSubtype('C', 'Object', true);
h.run([
if_(this_('C').is_('Null'), [
if_(this_('C').eq(nullLiteral), [
checkReachable(true),
], [
checkReachable(true),
]),
]),
]);
});
group('equalityOp_end does not set reachability for property gets', () {
test('on a variable', () {
var h = Harness();
var x = Var('x', 'C');
h.run([
declare(x, initialized: true),
if_(x.read.propertyGet('f').is_('Null'), [
if_(x.read.propertyGet('f').eq(nullLiteral), [
checkReachable(true),
], [
checkReachable(true),
]),
]),
]);
});
test('on an arbitrary expression', () {
var h = Harness();
h.run([
if_(expr('C').propertyGet('f').is_('Null'), [
if_(expr('C').propertyGet('f').eq(nullLiteral), [
checkReachable(true),
], [
checkReachable(true),
]),
]),
]);
});
test('on explicit this', () {
var h = Harness();
h.run([
if_(this_('C').propertyGet('f').is_('Null'), [
if_(this_('C').propertyGet('f').eq(nullLiteral), [
checkReachable(true),
], [
checkReachable(true),
]),
]),
]);
});
test('on implicit this/super', () {
var h = Harness();
h.run([
if_(thisOrSuperPropertyGet('f').is_('Null'), [
if_(thisOrSuperPropertyGet('f').eq(nullLiteral), [
checkReachable(true),
], [
checkReachable(true),
]),
]),
]);
});
});
test('finish checks proper nesting', () {
var h = Harness();
var e = expr('Null');
@@ -1372,6 +1460,65 @@ main() {
]);
});
test('isExpression_end() does not set reachability for `this`', () {
var h = Harness();
h.run([
if_(this_('C').is_('Never'), [
checkReachable(true),
], [
checkReachable(true),
]),
]);
});
group('isExpression_end() does not set reachability for property gets', () {
test('on a variable', () {
var h = Harness();
var x = Var('x', 'C');
h.run([
declare(x, initialized: true),
if_(x.read.propertyGet('f').is_('Never'), [
checkReachable(true),
], [
checkReachable(true),
]),
]);
});
test('on an arbitrary expression', () {
var h = Harness();
h.run([
if_(expr('C').propertyGet('f').is_('Never'), [
checkReachable(true),
], [
checkReachable(true),
]),
]);
});
test('on explicit this', () {
var h = Harness();
h.run([
if_(this_('C').propertyGet('f').is_('Never'), [
checkReachable(true),
], [
checkReachable(true),
]),
]);
});
test('on implicit this/super', () {
var h = Harness();
h.run([
if_(thisOrSuperPropertyGet('f').is_('Never'), [
checkReachable(true),
], [
checkReachable(true),
]),
]);
});
});
test('labeledBlock without break', () {
var h = Harness();
var x = Var('x', 'int?');
@@ -3262,7 +3409,7 @@ main() {
// This should not happen in valid code, but test that we don't crash.
var h = Harness();
var s = FlowModel<Var, Type>(Reachability.initial).write(
objectQVar, Type('Object?'), new SsaNode<Var, Type>(null), h);
null, objectQVar, Type('Object?'), new SsaNode<Var, Type>(null), h);
expect(s.variableInfo[objectQVar], isNull);
});
@@ -3271,7 +3418,7 @@ main() {
var s1 = FlowModel<Var, Type>(Reachability.initial)
.declare(objectQVar, true);
var s2 = s1.write(
objectQVar, Type('Object?'), new SsaNode<Var, Type>(null), h);
null, objectQVar, Type('Object?'), new SsaNode<Var, Type>(null), h);
expect(s2, isNot(same(s1)));
expect(s2.reachable, same(s1.reachable));
expect(
@@ -3287,8 +3434,8 @@ main() {
var h = Harness();
var s1 = FlowModel<Var, Type>(Reachability.initial)
.declare(objectQVar, false);
var s2 =
s1.write(objectQVar, Type('int?'), new SsaNode<Var, Type>(null), h);
var s2 = s1.write(
null, objectQVar, Type('int?'), new SsaNode<Var, Type>(null), h);
expect(s2.reachable.overallReachable, true);
expect(
s2.infoFor(objectQVar),
@@ -3306,8 +3453,8 @@ main() {
.tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
.ifTrue;
expect(s1.variableInfo, contains(objectQVar));
var s2 =
s1.write(objectQVar, Type('int?'), new SsaNode<Var, Type>(null), h);
var s2 = s1.write(_MockNonPromotionReason(), objectQVar, Type('int?'),
new SsaNode<Var, Type>(null), h);
expect(s2.reachable.overallReachable, true);
expect(s2.variableInfo, {
objectQVar: _matchVariableModel(
@@ -3333,8 +3480,8 @@ main() {
assigned: true,
unassigned: false)
});
var s2 =
s1.write(objectQVar, Type('num'), new SsaNode<Var, Type>(null), h);
var s2 = s1.write(_MockNonPromotionReason(), objectQVar, Type('num'),
new SsaNode<Var, Type>(null), h);
expect(s2.reachable.overallReachable, true);
expect(s2.variableInfo, {
objectQVar: _matchVariableModel(
@@ -3362,8 +3509,8 @@ main() {
assigned: true,
unassigned: false)
});
var s2 =
s1.write(objectQVar, Type('num'), new SsaNode<Var, Type>(null), h);
var s2 = s1.write(_MockNonPromotionReason(), objectQVar, Type('num'),
new SsaNode<Var, Type>(null), h);
expect(s2.reachable.overallReachable, true);
expect(s2.variableInfo, {
objectQVar: _matchVariableModel(
@@ -3389,8 +3536,8 @@ main() {
assigned: true,
unassigned: false)
});
var s2 =
s1.write(objectQVar, Type('num'), new SsaNode<Var, Type>(null), h);
var s2 = s1.write(
null, objectQVar, Type('num'), new SsaNode<Var, Type>(null), h);
expect(s2.reachable.overallReachable, true);
expect(s2.variableInfo, isNot(same(s1.variableInfo)));
expect(s2.variableInfo, {
@@ -3417,8 +3564,8 @@ main() {
assigned: true,
unassigned: false)
});
var s2 =
s1.write(objectQVar, Type('int'), new SsaNode<Var, Type>(null), h);
var s2 = s1.write(
null, objectQVar, Type('int'), new SsaNode<Var, Type>(null), h);
expect(s2.reachable.overallReachable, true);
expect(s2.variableInfo, isNot(same(s1.variableInfo)));
expect(s2.variableInfo, {
@@ -3440,7 +3587,8 @@ main() {
x: _matchVariableModel(chain: null),
});
var s2 = s1.write(x, Type('int'), new SsaNode<Var, Type>(null), h);
var s2 =
s1.write(null, x, Type('int'), new SsaNode<Var, Type>(null), h);
expect(s2.variableInfo, {
x: _matchVariableModel(chain: ['int']),
});
@@ -3461,7 +3609,8 @@ main() {
});
// 'x' is write-captured, so not promoted
var s3 = s2.write(x, Type('int'), new SsaNode<Var, Type>(null), h);
var s3 =
s2.write(null, x, Type('int'), new SsaNode<Var, Type>(null), h);
expect(s3.variableInfo, {
x: _matchVariableModel(chain: null, writeCaptured: true),
});
@@ -3480,7 +3629,7 @@ main() {
),
});
var s2 = s1.write(
objectQVar, Type('int'), new SsaNode<Var, Type>(null), h);
null, objectQVar, Type('int'), new SsaNode<Var, Type>(null), h);
expect(s2.variableInfo, {
objectQVar: _matchVariableModel(
chain: ['int?', 'int'],
@@ -3502,7 +3651,7 @@ main() {
),
});
var s2 = s1.write(
objectQVar, Type('int'), new SsaNode<Var, Type>(null), h);
null, objectQVar, Type('int'), new SsaNode<Var, Type>(null), h);
expect(s2.variableInfo, {
objectQVar: _matchVariableModel(
chain: ['Object', 'int'],
@@ -3524,8 +3673,8 @@ main() {
ofInterest: ['num?'],
),
});
var s2 =
s1.write(objectQVar, Type('num?'), new SsaNode<Var, Type>(null), h);
var s2 = s1.write(_MockNonPromotionReason(), objectQVar, Type('num?'),
new SsaNode<Var, Type>(null), h);
expect(s2.variableInfo, {
objectQVar: _matchVariableModel(
chain: ['num?'],
@@ -3548,8 +3697,8 @@ main() {
ofInterest: ['num?', 'int?'],
),
});
var s2 =
s1.write(objectQVar, Type('int?'), new SsaNode<Var, Type>(null), h);
var s2 = s1.write(_MockNonPromotionReason(), objectQVar, Type('int?'),
new SsaNode<Var, Type>(null), h);
expect(s2.variableInfo, {
objectQVar: _matchVariableModel(
chain: ['num?', 'int?'],
@@ -3618,7 +3767,8 @@ main() {
),
});
var s2 = s1.write(x, Type('C'), new SsaNode<Var, Type>(null), h);
var s2 =
s1.write(null, x, Type('C'), new SsaNode<Var, Type>(null), h);
expect(s2.variableInfo, {
x: _matchVariableModel(
chain: ['Object', 'B'],
@@ -3643,7 +3793,8 @@ main() {
),
});
var s2 = s1.write(x, Type('C'), new SsaNode<Var, Type>(null), h);
var s2 =
s1.write(null, x, Type('C'), new SsaNode<Var, Type>(null), h);
expect(s2.variableInfo, {
x: _matchVariableModel(
chain: ['Object', 'B'],
@@ -3668,7 +3819,8 @@ main() {
),
});
var s2 = s1.write(x, Type('B'), new SsaNode<Var, Type>(null), h);
var s2 =
s1.write(null, x, Type('B'), new SsaNode<Var, Type>(null), h);
expect(s2.variableInfo, {
x: _matchVariableModel(
chain: ['Object', 'A'],
@@ -3694,7 +3846,7 @@ main() {
),
});
var s2 = s1.write(
objectQVar, Type('int'), new SsaNode<Var, Type>(null), h);
null, objectQVar, Type('int'), new SsaNode<Var, Type>(null), h);
// It's ambiguous whether to promote to num? or num*, so we don't
// promote.
expect(s2, isNot(same(s1)));
@@ -3721,8 +3873,8 @@ main() {
ofInterest: ['num?', 'num*'],
),
});
var s2 = s1.write(
objectQVar, Type('num?'), new SsaNode<Var, Type>(null), h);
var s2 = s1.write(_MockNonPromotionReason(), objectQVar, Type('num?'),
new SsaNode<Var, Type>(null), h);
// It's ambiguous whether to promote to num? or num*, but since the
// written type is exactly num?, we use that.
expect(s2.variableInfo, {
@@ -3754,7 +3906,8 @@ main() {
),
});
var s2 = s1.write(x, Type('double'), new SsaNode<Var, Type>(null), h);
var s2 = s1.write(_MockNonPromotionReason(), x, Type('double'),
new SsaNode<Var, Type>(null), h);
expect(s2.variableInfo, {
x: _matchVariableModel(
chain: ['num?', 'num'],
@@ -3908,11 +4061,11 @@ main() {
.declare(c, false)
.declare(d, false);
var s1 = s0
.write(a, Type('int'), new SsaNode<Var, Type>(null), h)
.write(b, Type('int'), new SsaNode<Var, Type>(null), h);
.write(null, a, Type('int'), new SsaNode<Var, Type>(null), h)
.write(null, b, Type('int'), new SsaNode<Var, Type>(null), h);
var s2 = s0
.write(a, Type('int'), new SsaNode<Var, Type>(null), h)
.write(c, Type('int'), new SsaNode<Var, Type>(null), h);
.write(null, a, Type('int'), new SsaNode<Var, Type>(null), h)
.write(null, c, Type('int'), new SsaNode<Var, Type>(null), h);
var result = s1.rebaseForward(h, s2);
expect(result.infoFor(a).assigned, true);
expect(result.infoFor(b).assigned, true);
@@ -3978,7 +4131,8 @@ main() {
var s0 = FlowModel<Var, Type>(Reachability.initial).declare(x, true);
var s1 = s0;
if (unsafe) {
s1 = s1.write(x, Type('Object?'), new SsaNode<Var, Type>(null), h);
s1 = s1.write(
null, x, Type('Object?'), new SsaNode<Var, Type>(null), h);
}
if (thisType != null) {
s1 =
@@ -5363,6 +5517,173 @@ main() {
]);
});
});
group('why not promoted', () {
test('due to assignment', () {
var h = Harness();
var x = Var('x', 'int?');
late Expression writeExpression;
h.run([
declare(x, initialized: true),
if_(x.read.eq(nullLiteral), [
return_(),
]),
checkPromoted(x, 'int'),
(writeExpression = x.write(expr('int?'))).stmt,
checkNotPromoted(x),
x.read.whyNotPromoted((reasons) {
expect(reasons.keys, unorderedEquals([Type('int')]));
var nonPromotionReason =
reasons.values.single as DemoteViaExplicitWrite<Var, Expression>;
expect(nonPromotionReason.writeExpression, same(writeExpression));
}).stmt,
]);
});
test('due to assignment, multiple demotions', () {
var h = Harness();
var x = Var('x', 'Object?');
late Expression writeExpression;
h.run([
declare(x, initialized: true),
if_(x.read.isNot('int?'), [
return_(),
]),
if_(x.read.eq(nullLiteral), [
return_(),
]),
checkPromoted(x, 'int'),
(writeExpression = x.write(expr('Object?'))).stmt,
checkNotPromoted(x),
x.read.whyNotPromoted((reasons) {
expect(reasons.keys, unorderedEquals([Type('int'), Type('int?')]));
expect(
(reasons[Type('int')] as DemoteViaExplicitWrite<Var, Expression>)
.writeExpression,
same(writeExpression));
expect(
(reasons[Type('int?')] as DemoteViaExplicitWrite<Var, Expression>)
.writeExpression,
same(writeExpression));
}).stmt,
]);
});
test('preserved in join when one branch unreachable', () {
var h = Harness();
var x = Var('x', 'int?');
late Expression writeExpression;
h.run([
declare(x, initialized: true),
if_(x.read.eq(nullLiteral), [
return_(),
]),
checkPromoted(x, 'int'),
(writeExpression = x.write(expr('int?'))).stmt,
checkNotPromoted(x),
if_(expr('bool'), [
return_(),
]),
x.read.whyNotPromoted((reasons) {
expect(reasons.keys, unorderedEquals([Type('int')]));
var nonPromotionReason =
reasons.values.single as DemoteViaExplicitWrite<Var, Expression>;
expect(nonPromotionReason.writeExpression, same(writeExpression));
}).stmt,
]);
});
test('preserved in later promotions', () {
var h = Harness();
var x = Var('x', 'Object');
late Expression writeExpression;
h.run([
declare(x, initialized: true),
if_(x.read.is_('int', isInverted: true), [
return_(),
]),
checkPromoted(x, 'int'),
(writeExpression = x.write(expr('Object'))).stmt,
checkNotPromoted(x),
if_(x.read.is_('num', isInverted: true), [
return_(),
]),
checkPromoted(x, 'num'),
x.read.whyNotPromoted((reasons) {
var nonPromotionReason =
reasons[Type('int')] as DemoteViaExplicitWrite;
expect(nonPromotionReason.writeExpression, same(writeExpression));
}).stmt,
]);
});
test('re-promotion', () {
var h = Harness();
var x = Var('x', 'int?');
h.run([
declare(x, initialized: true),
if_(x.read.eq(nullLiteral), [
return_(),
]),
checkPromoted(x, 'int'),
x.write(expr('int?')).stmt,
checkNotPromoted(x),
if_(x.read.eq(nullLiteral), [
return_(),
]),
checkPromoted(x, 'int'),
x.read.whyNotPromoted((reasons) {
expect(reasons, isEmpty);
}).stmt,
]);
});
group('because field', () {
test('via explicit this', () {
var h = Harness();
h.run([
if_(this_('C').propertyGet('field').eq(nullLiteral), [
return_(),
]),
this_('C').propertyGet('field').whyNotPromoted((reasons) {
expect(reasons.keys, unorderedEquals([Type('Object')]));
var nonPromotionReason = reasons.values.single;
expect(nonPromotionReason, TypeMatcher<FieldNotPromoted>());
}).stmt,
]);
});
test('via implicit this/super', () {
var h = Harness();
h.run([
if_(thisOrSuperPropertyGet('field').eq(nullLiteral), [
return_(),
]),
thisOrSuperPropertyGet('field').whyNotPromoted((reasons) {
expect(reasons.keys, unorderedEquals([Type('Object')]));
var nonPromotionReason = reasons.values.single;
expect(nonPromotionReason, TypeMatcher<FieldNotPromoted>());
}).stmt,
]);
});
test('via variable', () {
var h = Harness();
var x = Var('x', 'C');
h.run([
declare(x, initialized: true),
if_(x.read.propertyGet('field').eq(nullLiteral), [
return_(),
]),
x.read.propertyGet('field').whyNotPromoted((reasons) {
expect(reasons.keys, unorderedEquals([Type('Object')]));
var nonPromotionReason = reasons.values.single;
expect(nonPromotionReason, TypeMatcher<FieldNotPromoted>());
}).stmt,
]);
});
});
});
}
/// Returns the appropriate matcher for expecting an assertion error to be
@@ -5434,3 +5755,12 @@ Matcher _matchVariableModel(
Reference<Var, Type> _varRef(Var variable) =>
new VariableReference<Var, Type>(variable);
class _MockNonPromotionReason extends NonPromotionReason {
String get shortName => fail('Unexpected call to shortName');
R accept<R, Node extends Object, Expression extends Object,
Variable extends Object>(
NonPromotionReasonVisitor<R, Node, Expression, Variable> visitor) =>
fail('Unexpected call to accept');
}
@@ -0,0 +1,71 @@
// 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.
abstract class C {
C? operator +(int i);
int get cProperty => 0;
}
direct_assignment(int? i, int? j) {
if (i == null) return;
/*cfe.update: explicitWrite*/ /*analyzer.explicitWrite*/ i = j;
i. /*notPromoted(explicitWrite)*/ isEven;
}
compound_assignment(C? c, int i) {
if (c == null) return;
/*cfe.update: explicitWrite*/ /*analyzer.explicitWrite*/ c += i;
c. /*notPromoted(explicitWrite)*/ cProperty;
}
via_postfix_op(C? c) {
if (c == null) return;
/*cfe.update: explicitWrite*/ /*analyzer.explicitWrite*/ c++;
c. /*notPromoted(explicitWrite)*/ cProperty;
}
via_prefix_op(C? c) {
if (c == null) return;
/*analyzer.explicitWrite*/ ++ /*cfe.update: explicitWrite*/ c;
c. /*notPromoted(explicitWrite)*/ cProperty;
}
via_for_each_statement(int? i, List<int?> list) {
if (i == null) return;
for (/*cfe.update: explicitWrite*/ /*analyzer.explicitWrite*/ i in list) {
i. /*notPromoted(explicitWrite)*/ isEven;
}
}
via_for_each_list_element(int? i, List<int?> list) {
if (i == null) return;
[
for (/*cfe.update: explicitWrite*/ /*analyzer.explicitWrite*/ i in list)
i. /*notPromoted(explicitWrite)*/ isEven
];
}
via_for_each_set_element(int? i, List<int?> list) {
if (i == null) return;
({
for (/*cfe.update: explicitWrite*/ /*analyzer.explicitWrite*/ i in list)
i. /*notPromoted(explicitWrite)*/ isEven
});
}
via_for_each_map_key(int? i, List<int?> list) {
if (i == null) return;
({
for (/*cfe.update: explicitWrite*/ /*analyzer.explicitWrite*/ i in list)
i. /*notPromoted(explicitWrite)*/ isEven: null
});
}
via_for_each_map_value(int? i, List<int?> list) {
if (i == null) return;
({
for (/*cfe.update: explicitWrite*/ /*analyzer.explicitWrite*/ i in list)
null: i. /*notPromoted(explicitWrite)*/ isEven
});
}
@@ -0,0 +1,50 @@
// 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.
class C {
int? i;
int? j;
get_field_via_explicit_this() {
if (this.i == null) return;
this.i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
}
get_field_via_explicit_this_parenthesized() {
if ((this).i == null) return;
(this).i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
}
get_field_by_implicit_this() {
if (i == null) return;
i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
}
}
class D extends C {
get_field_via_explicit_super() {
if (super.i == null) return;
super.i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
}
get_field_by_implicit_super() {
if (i == null) return;
i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
}
}
get_field_via_prefixed_identifier(C c) {
if (c.i == null) return;
c.i. /*notPromoted(fieldNotPromoted(i))*/ isEven;
}
get_field_via_prefixed_identifier_mismatched_target(C c1, C c2) {
if (c1.i == null) return;
c2.i.isEven;
}
get_field_via_prefixed_identifier_mismatched_property(C c) {
if (c.i == null) return;
c.j.isEven;
}
@@ -0,0 +1,2 @@
cfe=pkg/front_end/test/id_tests/why_not_promoted_test.dart
analyzer=pkg/analyzer/test/id_tests/why_not_promoted_test.dart
+6 -4
View File
@@ -94,16 +94,18 @@ class ErrorReporter {
/// Report an error with the given [errorCode] and [arguments].
/// The [node] is used to compute the location of the error.
void reportErrorForNode(ErrorCode errorCode, AstNode node,
[List<Object?>? arguments]) {
reportErrorForOffset(errorCode, node.offset, node.length, arguments);
[List<Object?>? arguments, List<DiagnosticMessage>? messages]) {
reportErrorForOffset(
errorCode, node.offset, node.length, arguments, messages);
}
/// Report an error with the given [errorCode] and [arguments]. The location
/// of the error is specified by the given [offset] and [length].
void reportErrorForOffset(ErrorCode errorCode, int offset, int length,
[List<Object?>? arguments]) {
[List<Object?>? arguments, List<DiagnosticMessage>? messages]) {
_convertElements(arguments);
var messages = _convertTypeNames(arguments);
messages ??= [];
messages.addAll(_convertTypeNames(arguments));
_errorListener.onError(
AnalysisError(_source, offset, length, errorCode, arguments, messages));
}
@@ -26,7 +26,7 @@ class AssignmentExpressionResolver {
AssignmentExpressionResolver({
required ResolverVisitor resolver,
}) : _resolver = resolver,
}) : _resolver = resolver,
_typePropertyResolver = resolver.typePropertyResolver,
_inferenceHelper = resolver.inferenceHelper,
_assignmentShared = AssignmentExpressionShared(
@@ -88,7 +88,8 @@ class AssignmentExpressionResolver {
if (flow != null) {
if (writeElement is PromotableElement) {
flow.write(writeElement, node.staticType!, hasRead ? null : right);
flow.write(
node, writeElement, node.staticType!, hasRead ? null : right);
}
if (isIfNull) {
flow.ifNullExpression_end();
@@ -28,7 +28,7 @@ class BinaryExpressionResolver {
BinaryExpressionResolver({
required ResolverVisitor resolver,
required TypePromotionManager promoteManager,
}) : _resolver = resolver,
}) : _resolver = resolver,
_promoteManager = promoteManager,
_typePropertyResolver = resolver.typePropertyResolver,
_inferenceHelper = resolver.inferenceHelper;
@@ -4,6 +4,7 @@
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
@@ -40,6 +41,14 @@ class FlowAnalysisDataForTesting {
final Map<Declaration,
AssignedVariablesForTesting<AstNode, PromotableElement>>
assignedVariables = {};
/// For each expression that led to an error because it was not promoted, a
/// string describing the reason it was not promoted.
Map<SyntacticEntity, String> nonPromotionReasons = {};
/// For each auxiliary AST node pointed to by a non-promotion reason, a string
/// describing the non-promotion reason pointing to it.
Map<AstNode, String> nonPromotionReasonTargets = {};
}
/// The helper for performing flow analysis during resolution.
@@ -332,6 +341,9 @@ class TypeSystemTypeOperations
TypeSystemTypeOperations(this.typeSystem);
@override
DartType get topType => typeSystem.objectQuestion;
@override
TypeClassification classifyType(DartType type) {
if (isSubtypeOf(type, typeSystem.typeProvider.objectType)) {
@@ -25,7 +25,7 @@ class FunctionExpressionResolver {
required ResolverVisitor resolver,
required MigrationResolutionHooks? migrationResolutionHooks,
required TypePromotionManager promoteManager,
}) : _resolver = resolver,
}) : _resolver = resolver,
_migrationResolutionHooks = migrationResolutionHooks,
_inferenceHelper = resolver.inferenceHelper,
_promoteManager = promoteManager;
@@ -26,7 +26,7 @@ class PostfixExpressionResolver {
PostfixExpressionResolver({
required ResolverVisitor resolver,
}) : _resolver = resolver,
}) : _resolver = resolver,
_typePropertyResolver = resolver.typePropertyResolver,
_inferenceHelper = resolver.inferenceHelper,
_assignmentShared = AssignmentExpressionShared(
@@ -173,7 +173,7 @@ class PostfixExpressionResolver {
var element = operand.staticElement;
if (element is PromotableElement) {
_resolver.flowAnalysis?.flow
?.write(element, operatorReturnType, null);
?.write(node, element, operatorReturnType, null);
}
}
}
@@ -26,7 +26,7 @@ class PrefixExpressionResolver {
PrefixExpressionResolver({
required ResolverVisitor resolver,
}) : _resolver = resolver,
}) : _resolver = resolver,
_typePropertyResolver = resolver.typePropertyResolver,
_inferenceHelper = resolver.inferenceHelper,
_assignmentShared = AssignmentExpressionShared(
@@ -209,7 +209,8 @@ class PrefixExpressionResolver {
if (operand is SimpleIdentifier) {
var element = operand.staticElement;
if (element is PromotableElement) {
_resolver.flowAnalysis?.flow?.write(element, staticType, null);
_resolver.flowAnalysis?.flow
?.write(node, element, staticType, null);
}
}
}
@@ -143,6 +143,7 @@ class PropertyElementResolver {
}
return _resolve(
node: node,
target: prefix,
isCascaded: false,
isNullAware: false,
@@ -171,6 +172,7 @@ class PropertyElementResolver {
if (target is SuperExpression) {
return _resolveTargetSuperExpression(
node: node,
target: target,
propertyName: propertyName,
hasRead: hasRead,
@@ -179,6 +181,7 @@ class PropertyElementResolver {
}
return _resolve(
node: node,
target: target,
isCascaded: node.target == null,
isNullAware: node.isNullAware,
@@ -198,6 +201,9 @@ class PropertyElementResolver {
if (hasRead) {
var readLookup = _resolver.lexicalLookup(node: node, setter: false);
readElementRequested = readLookup.requested;
if (readElementRequested is PropertyAccessorElement) {
_resolver.flowAnalysis?.flow?.thisOrSuperPropertyGet(node, node.name);
}
_resolver.checkReadOfNotAssignedLocalVariable(node, readElementRequested);
}
@@ -282,6 +288,7 @@ class PropertyElementResolver {
}
PropertyElementResolverResult _resolve({
required Expression node,
required Expression target,
required bool isCascaded,
required bool isNullAware,
@@ -364,6 +371,8 @@ class PropertyElementResolver {
nameErrorEntity: propertyName,
);
_resolver.flowAnalysis?.flow?.propertyGet(node, target, propertyName.name);
if (hasRead && result.needsGetterError) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_GETTER,
@@ -595,6 +604,7 @@ class PropertyElementResolver {
}
PropertyElementResolverResult _resolveTargetSuperExpression({
required Expression node,
required SuperExpression target,
required SimpleIdentifier propertyName,
required bool hasRead,
@@ -610,6 +620,8 @@ class PropertyElementResolver {
if (targetType is InterfaceTypeImpl) {
if (hasRead) {
_resolver.flowAnalysis?.flow
?.propertyGet(node, target, propertyName.name);
var name = Name(_definingLibrary.source.uri, propertyName.name);
readElement = _resolver.inheritance
.getMember2(targetType.element, name, forSuper: true);
@@ -2,18 +2,23 @@
// 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:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/dart/resolver/resolution_result.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/source.dart';
/// Helper for resolving properties (getters, setters, or methods).
class TypePropertyResolver {
@@ -114,9 +119,30 @@ class TypePropertyResolver {
}
}
var whyNotPromoted = receiver == null
? null
: _resolver.flowAnalysis?.flow?.whyNotPromoted(receiver);
List<DiagnosticMessage> messages = [];
if (whyNotPromoted != null) {
for (var entry in whyNotPromoted.entries) {
var whyNotPromotedVisitor = _WhyNotPromotedVisitor(
_resolver.source, _resolver.flowAnalysis!.dataForTesting);
if (_typeSystem.isPotentiallyNullable(entry.key)) continue;
if (_resolver.flowAnalysis!.dataForTesting != null) {
_resolver.flowAnalysis!.dataForTesting!
.nonPromotionReasons[nameErrorEntity] = entry.value.shortName;
}
var message = entry.value.accept(whyNotPromotedVisitor);
if (message != null) {
messages = [message];
}
break;
}
}
_resolver.nullableDereferenceVerifier.report(
receiverErrorNode, receiverType,
errorCode: errorCode, arguments: [name]);
errorCode: errorCode, arguments: [name], messages: messages);
_reportedGetterError = true;
_reportedSetterError = true;
@@ -264,3 +290,71 @@ class TypePropertyResolver {
);
}
}
class _WhyNotPromotedVisitor
implements
NonPromotionReasonVisitor<DiagnosticMessage?, AstNode, Expression,
PromotableElement> {
final Source source;
final FlowAnalysisDataForTesting? _dataForTesting;
_WhyNotPromotedVisitor(this.source, this._dataForTesting);
@override
DiagnosticMessage? visitDemoteViaExplicitWrite(
DemoteViaExplicitWrite<PromotableElement, Expression> reason) {
var writeExpression = reason.writeExpression;
if (_dataForTesting != null) {
_dataForTesting!.nonPromotionReasonTargets[writeExpression] =
reason.shortName;
}
var variableName = reason.variable.name;
if (variableName == null) return null;
return _contextMessageForWrite(variableName, writeExpression);
}
@override
DiagnosticMessage? visitDemoteViaForEachVariableWrite(
DemoteViaForEachVariableWrite<PromotableElement, AstNode> reason) {
var node = reason.node;
var variableName = reason.variable.name;
if (variableName == null) return null;
ForLoopParts parts;
if (node is ForStatement) {
parts = node.forLoopParts;
} else if (node is ForElement) {
parts = node.forLoopParts;
} else {
assert(false, 'Unexpected node type');
return null;
}
if (parts is ForEachPartsWithIdentifier) {
var identifier = parts.identifier;
if (_dataForTesting != null) {
_dataForTesting!.nonPromotionReasonTargets[identifier] =
reason.shortName;
}
return _contextMessageForWrite(variableName, identifier);
} else {
assert(false, 'Unexpected parts type');
return null;
}
}
@override
DiagnosticMessage? visitFieldNotPromoted(FieldNotPromoted reason) {
// TODO(paulberry): how to report this?
return null;
}
DiagnosticMessageImpl _contextMessageForWrite(
String variableName, Expression writeExpression) {
return DiagnosticMessageImpl(
filePath: source.fullName,
message:
"Variable '$variableName' could be null due to a write occurring here.",
offset: writeExpression.offset,
length: writeExpression.length);
}
}
@@ -20,7 +20,7 @@ class VariableDeclarationResolver {
VariableDeclarationResolver({
required ResolverVisitor resolver,
required bool strictInference,
}) : _resolver = resolver,
}) : _resolver = resolver,
_strictInference = strictInference;
void resolve(VariableDeclarationImpl node) {
@@ -4,6 +4,7 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/element/type.dart';
@@ -32,13 +33,16 @@ class NullableDereferenceVerifier {
}
void report(AstNode errorNode, DartType receiverType,
{ErrorCode? errorCode, List<String> arguments = const <String>[]}) {
{ErrorCode? errorCode,
List<String> arguments = const <String>[],
List<DiagnosticMessage>? messages}) {
if (receiverType == _typeSystem.typeProvider.nullType) {
errorCode = CompileTimeErrorCode.INVALID_USE_OF_NULL_VALUE;
} else {
errorCode ??= CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE;
}
_errorReporter.reportErrorForNode(errorCode, errorNode, arguments);
_errorReporter.reportErrorForNode(
errorCode, errorNode, arguments, messages);
}
/// If the [receiverType] is potentially nullable, report it.
+2 -4
View File
@@ -1158,8 +1158,7 @@ class ResolverVisitor extends ScopedVisitor {
_enclosingFunction = node.declaredElement!;
if (flowAnalysis != null) {
flowAnalysis!
.topLevelDeclaration_enter(node, node.parameters, node.body);
flowAnalysis!.topLevelDeclaration_enter(node, node.parameters, node.body);
flowAnalysis!.executableDeclaration_enter(node, node.parameters, false);
} else {
_promoteManager.enterFunctionBody(node.body!);
@@ -1655,8 +1654,7 @@ class ResolverVisitor extends ScopedVisitor {
_enclosingFunction = node.declaredElement!;
if (flowAnalysis != null) {
flowAnalysis!
.topLevelDeclaration_enter(node, node.parameters, node.body);
flowAnalysis!.topLevelDeclaration_enter(node, node.parameters, node.body);
flowAnalysis!.executableDeclaration_enter(node, node.parameters, false);
} else {
_promoteManager.enterFunctionBody(node.body);
@@ -284,6 +284,7 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
@override
void visitSuperExpression(SuperExpression node) {
_resolver.flowAnalysis?.flow?.thisOrSuper(node);
var thisType = _resolver.thisType;
if (thisType == null ||
node.thisOrAncestorOfType<ExtensionDeclaration>() != null) {
@@ -304,6 +305,7 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
/// interface of the immediately enclosing class.</blockquote>
@override
void visitThisExpression(ThisExpression node) {
_resolver.flowAnalysis?.flow?.thisOrSuper(node);
var thisType = _resolver.thisType;
if (thisType == null) {
// TODO(brianwilkerson) Report this error if it hasn't already been
@@ -0,0 +1,87 @@
// 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 'dart:io';
import 'package:_fe_analyzer_shared/src/testing/id.dart' show ActualData, Id;
import 'package:_fe_analyzer_shared/src/testing/id_testing.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/null_safety_understanding_flag.dart';
import 'package:analyzer/src/dart/analysis/testing_data.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/util/ast_data_extractor.dart';
import '../util/id_testing_helper.dart';
main(List<String> args) async {
Directory dataDir = Directory.fromUri(
Platform.script.resolve('../../../_fe_analyzer_shared/test/flow_analysis/'
'why_not_promoted/data'));
await NullSafetyUnderstandingFlag.enableNullSafetyTypes(() {
return runTests<String?>(dataDir,
args: args,
createUriForFileName: createUriForFileName,
onFailure: onFailure,
runTest: runTestFor(
const _WhyNotPromotedDataComputer(), [analyzerNnbdConfig]));
});
}
class _WhyNotPromotedDataComputer extends DataComputer<String?> {
const _WhyNotPromotedDataComputer();
@override
DataInterpreter<String?> get dataValidator =>
const _WhyNotPromotedDataInterpreter();
@override
bool get supportsErrors => true;
@override
void computeUnitData(TestingData testingData, CompilationUnit unit,
Map<Id, ActualData<String?>> actualMap) {
var flowResult =
testingData.uriToFlowAnalysisData[unit.declaredElement!.source.uri]!;
_WhyNotPromotedDataExtractor(
unit.declaredElement!.source.uri, actualMap, flowResult)
.run(unit);
}
}
class _WhyNotPromotedDataExtractor extends AstDataExtractor<String?> {
final FlowAnalysisDataForTesting _flowResult;
_WhyNotPromotedDataExtractor(
Uri uri, Map<Id, ActualData<String?>> actualMap, this._flowResult)
: super(uri, actualMap);
@override
String? computeNodeValue(Id id, AstNode node) {
String? nonPromotionReason = _flowResult.nonPromotionReasons[node];
if (nonPromotionReason != null) {
return 'notPromoted($nonPromotionReason)';
}
return _flowResult.nonPromotionReasonTargets[node];
}
}
class _WhyNotPromotedDataInterpreter implements DataInterpreter<String?> {
const _WhyNotPromotedDataInterpreter();
@override
String getText(String? actualData, [String? indentation]) =>
actualData.toString();
@override
String? isAsExpected(String? actualData, String? expectedData) {
if (actualData == expectedData) {
return null;
} else {
return 'Expected $expectedData, got $actualData';
}
}
@override
bool isEmpty(String? actualData) => actualData == null;
}
@@ -5,6 +5,7 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
@@ -80,7 +81,7 @@ class CollectingReporter extends ErrorReporter {
@override
void reportErrorForNode(ErrorCode errorCode, AstNode node,
[List<Object?>? arguments]) {
[List<Object?>? arguments, List<DiagnosticMessage>? messages]) {
code = errorCode;
}
+14 -14
View File
@@ -163,8 +163,8 @@ class GnWorkspaceTest with ResourceProviderMixin {
newFile('/workspace/.fx-build-dir', content: '$buildDir\n');
newFile(
'/workspace/out/debug-x87_128/dartlang/gen/some/code/foo_package_config.json');
var workspace =
GnWorkspace.find(resourceProvider, convertPath('/workspace/some/code'))!;
var workspace = GnWorkspace.find(
resourceProvider, convertPath('/workspace/some/code'))!;
expect(workspace.root, convertPath('/workspace'));
}
@@ -189,8 +189,8 @@ class GnWorkspaceTest with ResourceProviderMixin {
}
]
}''');
var workspace =
GnWorkspace.find(resourceProvider, convertPath('/workspace/some/code'))!;
var workspace = GnWorkspace.find(
resourceProvider, convertPath('/workspace/some/code'))!;
expect(workspace.root, convertPath('/workspace'));
expect(workspace.packageMap.length, 1);
expect(workspace.packageMap['flutter']![0].path,
@@ -218,8 +218,8 @@ class GnWorkspaceTest with ResourceProviderMixin {
}
]
}''');
var workspace =
GnWorkspace.find(resourceProvider, convertPath('/workspace/some/code'))!;
var workspace = GnWorkspace.find(
resourceProvider, convertPath('/workspace/some/code'))!;
expect(workspace.root, convertPath('/workspace'));
expect(workspace.packageMap.length, 1);
expect(workspace.packageMap['flutter']![0].path,
@@ -245,8 +245,8 @@ class GnWorkspaceTest with ResourceProviderMixin {
}
]
}''');
var workspace =
GnWorkspace.find(resourceProvider, convertPath('/workspace/some/code'))!;
var workspace = GnWorkspace.find(
resourceProvider, convertPath('/workspace/some/code'))!;
expect(workspace.root, convertPath('/workspace'));
expect(workspace.packageMap.length, 1);
expect(workspace.packageMap['flutter']![0].path,
@@ -273,8 +273,8 @@ class GnWorkspaceTest with ResourceProviderMixin {
}
]
}''');
var workspace =
GnWorkspace.find(resourceProvider, convertPath('/workspace/some/code'))!;
var workspace = GnWorkspace.find(
resourceProvider, convertPath('/workspace/some/code'))!;
expect(workspace.root, convertPath('/workspace'));
expect(workspace.packageMap.length, 1);
expect(workspace.packageMap['flutter']![0].path,
@@ -318,8 +318,8 @@ class GnWorkspaceTest with ResourceProviderMixin {
}
]
}''');
var workspace =
GnWorkspace.find(resourceProvider, convertPath('/workspace/some/code'))!;
var workspace = GnWorkspace.find(
resourceProvider, convertPath('/workspace/some/code'))!;
expect(workspace.root, convertPath('/workspace'));
expect(workspace.packageMap.length, 1);
expect(workspace.packageMap['rettulf']![0].path,
@@ -363,8 +363,8 @@ class GnWorkspaceTest with ResourceProviderMixin {
}
]
}''');
var workspace =
GnWorkspace.find(resourceProvider, convertPath('/workspace/some/code'))!;
var workspace = GnWorkspace.find(
resourceProvider, convertPath('/workspace/some/code'))!;
expect(workspace.root, convertPath('/workspace'));
expect(workspace.packageMap.length, 2);
expect(workspace.packageMap['flutter']![0].path,
+1
View File
@@ -17,4 +17,5 @@ const List<String> idTests = <String>[
'pkg/analyzer/test/id_tests/nullability_test.dart',
'pkg/analyzer/test/id_tests/reachability_test.dart',
'pkg/analyzer/test/id_tests/type_promotion_test.dart',
'pkg/analyzer/test/id_tests/why_not_promoted_test.dart',
];
@@ -5,10 +5,13 @@
// @dart = 2.9
import 'dart:core' hide MapEntry;
import 'dart:core' as core;
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:_fe_analyzer_shared/src/util/link.dart';
import 'package:front_end/src/api_prototype/lowering_predicates.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/ast.dart'
hide Reference; // Work around https://github.com/dart-lang/sdk/issues/44667
import 'package:kernel/src/legacy_erasure.dart';
import 'package:kernel/type_algebra.dart' show Substitution;
import 'package:kernel/type_environment.dart';
@@ -4738,12 +4741,31 @@ class InferenceVisitor
readResult ??= new ExpressionInferenceResult(readType, read);
if (!inferrer.isTopLevel && readTarget.isNullable) {
Map<DartType, NonPromotionReason> whyNotPromoted =
inferrer.flowAnalysis?.whyNotPromoted(receiver);
List<LocatedMessage> context;
if (whyNotPromoted != null && whyNotPromoted.isNotEmpty) {
_WhyNotPromotedVisitor whyNotPromotedVisitor =
new _WhyNotPromotedVisitor(inferrer);
for (core.MapEntry<DartType, NonPromotionReason> entry
in whyNotPromoted.entries) {
if (entry.key.isPotentiallyNullable) continue;
if (inferrer.dataForTesting != null) {
inferrer.dataForTesting.flowAnalysisResult
.nonPromotionReasons[read] = entry.value.shortName;
}
LocatedMessage message = entry.value.accept(whyNotPromotedVisitor);
context = [message];
break;
}
}
readResult = inferrer.wrapExpressionInferenceResultInProblem(
readResult,
templateNullablePropertyAccessError.withArguments(
propertyName.text, receiverType, inferrer.isNonNullableByDefault),
read.fileOffset,
propertyName.text.length);
propertyName.text.length,
context: context);
}
return readResult;
}
@@ -5709,8 +5731,13 @@ class InferenceVisitor
ExpressionInferenceResult readResult = _computePropertyGet(
node.fileOffset, receiver, receiverType, node.name, typeContext,
isThisReceiver: node.receiver is ThisExpression);
return inferrer.createNullAwareExpressionInferenceResult(
readResult.inferredType, readResult.expression, nullAwareGuards);
inferrer.flowAnalysis.propertyGet(node, node.receiver, node.name.name);
ExpressionInferenceResult expressionInferenceResult =
inferrer.createNullAwareExpressionInferenceResult(
readResult.inferredType, readResult.expression, nullAwareGuards);
inferrer.flowAnalysis
.forwardExpression(expressionInferenceResult.nullAwareAction, node);
return expressionInferenceResult;
}
@override
@@ -5973,6 +6000,7 @@ class InferenceVisitor
@override
ExpressionInferenceResult visitSuperPropertyGet(
SuperPropertyGet node, DartType typeContext) {
inferrer.flowAnalysis.thisOrSuperPropertyGet(node, node.name.name);
if (node.interfaceTarget != null) {
inferrer.instrumentation?.record(
inferrer.uriForInstrumentation,
@@ -6160,6 +6188,7 @@ class InferenceVisitor
ExpressionInferenceResult visitThisExpression(
ThisExpression node, DartType typeContext) {
inferrer.flowAnalysis.thisOrSuper(node);
return new ExpressionInferenceResult(inferrer.thisType, node);
}
@@ -6280,7 +6309,7 @@ class InferenceVisitor
fileOffset: node.fileOffset,
isVoidAllowed: declaredOrInferredType is VoidType);
inferrer.flowAnalysis
.write(variable, rhsResult.inferredType, rhsResult.expression);
.write(node, variable, rhsResult.inferredType, rhsResult.expression);
DartType resultType = rhsResult.inferredType;
Expression resultExpression;
if (variable.lateSetter != null) {
@@ -6956,6 +6985,44 @@ class InferenceVisitor
}
}
class _WhyNotPromotedVisitor
implements
NonPromotionReasonVisitor<LocatedMessage, Node, Expression,
VariableDeclaration> {
final TypeInferrerImpl inferrer;
_WhyNotPromotedVisitor(this.inferrer);
@override
LocatedMessage visitDemoteViaExplicitWrite(
DemoteViaExplicitWrite<VariableDeclaration, Expression> reason) {
if (inferrer.dataForTesting != null) {
inferrer.dataForTesting.flowAnalysisResult
.nonPromotionReasonTargets[reason.writeExpression] = reason.shortName;
}
int offset = reason.writeExpression.fileOffset;
return templateVariableCouldBeNullDueToWrite
.withArguments(reason.variable.name)
.withLocation(inferrer.helper.uri, offset, noLength);
}
@override
LocatedMessage visitDemoteViaForEachVariableWrite(
DemoteViaForEachVariableWrite<VariableDeclaration, Node> reason) {
int offset = (reason.node as TreeNode).fileOffset;
return templateVariableCouldBeNullDueToWrite
.withArguments(reason.variable.name)
.withLocation(inferrer.helper.uri, offset, noLength);
}
@override
LocatedMessage visitFieldNotPromoted(FieldNotPromoted reason) {
return templateFieldNotPromoted
.withArguments(reason.propertyName)
.withoutLocation();
}
}
class ForInResult {
final VariableDeclaration variable;
final Expression iterable;
@@ -7005,7 +7072,8 @@ class LocalForInVariable implements ForInVariable {
isVoidAllowed: true);
variableSet.value = rhs..parent = variableSet;
inferrer.flowAnalysis.write(variableSet.variable, rhsType, null);
inferrer.flowAnalysis
.write(variableSet, variableSet.variable, rhsType, null);
return variableSet;
}
}
@@ -257,6 +257,14 @@ class FlowAnalysisResult {
/// The assigned variables information that computed for the member.
AssignedVariablesForTesting<TreeNode, VariableDeclaration> assignedVariables;
/// For each expression that led to an error because it was not promoted, a
/// string describing the reason it was not promoted.
final Map<TreeNode, String> nonPromotionReasons = {};
/// For each auxiliary AST node pointed to by a non-promotion reason, a string
/// describing the non-promotion reason pointing to it.
final Map<TreeNode, String> nonPromotionReasonTargets = {};
}
/// CFE-specific implementation of [TypeOperations].
@@ -265,6 +273,9 @@ class TypeOperationsCfe extends TypeOperations<VariableDeclaration, DartType> {
TypeOperationsCfe(this.typeEnvironment);
@override
DartType get topType => typeEnvironment.objectNullableRawType;
@override
TypeClassification classifyType(DartType type) {
if (type == null) {
@@ -1784,7 +1784,8 @@ class TypeInferrerImpl implements TypeInferrer {
return createNullAwareExpressionInferenceResult(
result.inferredType,
helper.wrapInProblem(
result.nullAwareAction, message, fileOffset, length),
result.nullAwareAction, message, fileOffset, length,
context: context),
result.nullAwareGuards);
}
+4
View File
@@ -344,6 +344,8 @@ FieldNonNullableWithoutInitializerError/analyzerCode: Fail
FieldNonNullableWithoutInitializerError/example: Fail
FieldNonNullableWithoutInitializerWarning/analyzerCode: Fail
FieldNonNullableWithoutInitializerWarning/example: Fail
FieldNotPromoted/analyzerCode: Fail
FieldNotPromoted/example: Fail
FinalAndCovariant/part_wrapped_script2: Fail
FinalAndCovariant/script2: Fail
FinalFieldWithoutInitializer/example: Fail
@@ -793,6 +795,8 @@ ValueForRequiredParameterNotProvidedWarning/analyzerCode: Fail
ValueForRequiredParameterNotProvidedWarning/example: Fail
VarAsTypeName/part_wrapped_script1: Fail
VarAsTypeName/script1: Fail # Too many problems
VariableCouldBeNullDueToWrite/analyzerCode: Fail
VariableCouldBeNullDueToWrite/example: Fail
WeakWithStrongDillLibrary/analyzerCode: Fail
WeakWithStrongDillLibrary/example: Fail
WebLiteralCannotBeRepresentedExactly/analyzerCode: Fail
+7
View File
@@ -4584,6 +4584,13 @@ MultipleVarianceModifiers:
tip: "Use at most one of the 'in', 'out', or 'inout' modifiers."
analyzerCode: ParserErrorCode.MULTIPLE_VARIANCE_MODIFIERS
VariableCouldBeNullDueToWrite:
template: "Variable '#name' could be null due to a write occurring here."
tip: "Try null checking the variable after the write."
FieldNotPromoted:
template: "'#name' refers to a property so it could not be promoted."
NullablePropertyAccessError:
template: "Property '#name' cannot be accessed on '#type' because it is potentially null."
tip: "Try accessing using ?. instead."
@@ -0,0 +1,93 @@
// 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.
// @dart = 2.9
import 'dart:io' show Directory, Platform;
import 'package:_fe_analyzer_shared/src/testing/id.dart' show ActualData, Id;
import 'package:_fe_analyzer_shared/src/testing/id_testing.dart'
show DataInterpreter, runTests;
import 'package:_fe_analyzer_shared/src/testing/id_testing.dart';
import 'package:front_end/src/fasta/builder/member_builder.dart';
import 'package:front_end/src/fasta/type_inference/type_inference_engine.dart';
import 'package:front_end/src/testing/id_testing_helper.dart';
import 'package:front_end/src/testing/id_testing_utils.dart';
import 'package:kernel/ast.dart' hide Variance, MapEntry;
main(List<String> args) async {
Directory dataDir = new Directory.fromUri(
Platform.script.resolve('../../../_fe_analyzer_shared/test/flow_analysis/'
'why_not_promoted/data'));
await runTests<String>(dataDir,
args: args,
createUriForFileName: createUriForFileName,
onFailure: onFailure,
runTest: runTestFor(
const WhyNotPromotedDataComputer(), [cfeNonNullableOnlyConfig]));
}
class WhyNotPromotedDataComputer extends DataComputer<String> {
const WhyNotPromotedDataComputer();
@override
DataInterpreter<String> get dataValidator =>
const _WhyNotPromotedDataInterpreter();
/// Errors are supported for testing erroneous code. The reported errors are
/// not tested.
@override
bool get supportsErrors => true;
/// Function that computes a data mapping for [member].
///
/// Fills [actualMap] with the data.
void computeMemberData(
TestConfig config,
InternalCompilerResult compilerResult,
Member member,
Map<Id, ActualData<String>> actualMap,
{bool verbose}) {
MemberBuilderImpl memberBuilder =
lookupMemberBuilder(compilerResult, member);
member.accept(new WhyNotPromotedDataExtractor(compilerResult, actualMap,
memberBuilder.dataForTesting.inferenceData.flowAnalysisResult));
}
}
class WhyNotPromotedDataExtractor extends CfeDataExtractor<String> {
final FlowAnalysisResult _flowResult;
WhyNotPromotedDataExtractor(InternalCompilerResult compilerResult,
Map<Id, ActualData<String>> actualMap, this._flowResult)
: super(compilerResult, actualMap);
@override
String computeNodeValue(Id id, TreeNode node) {
String nonPromotionReason = _flowResult.nonPromotionReasons[node];
if (nonPromotionReason != null) {
return 'notPromoted($nonPromotionReason)';
}
return _flowResult.nonPromotionReasonTargets[node];
}
}
class _WhyNotPromotedDataInterpreter implements DataInterpreter<String> {
const _WhyNotPromotedDataInterpreter();
@override
String getText(String actualData, [String indentation]) => actualData;
@override
String isAsExpected(String actualData, String expectedData) {
if (actualData == expectedData) {
return null;
} else {
return 'Expected $expectedData, got $actualData';
}
}
@override
bool isEmpty(String actualData) => actualData == null;
}
+1
View File
@@ -20,6 +20,7 @@ front_end/lib/src/fasta/incremental_compiler/ImportsTwice: Fail
front_end/lib/src/fasta/kernel/body_builder/ImportsTwice: Fail
front_end/lib/src/fasta/kernel/constant_evaluator/ExplicitType: Pass
front_end/lib/src/fasta/kernel/expression_generator_helper/ImportsTwice: Fail
front_end/lib/src/fasta/kernel/inference_visitor/ImportsTwice: Fail
front_end/lib/src/fasta/kernel/kernel_api/Exports: Fail
front_end/lib/src/fasta/kernel/kernel_ast_api/Exports: Fail
front_end/lib/src/fasta/kernel/kernel_builder/Exports: Fail
@@ -498,6 +498,7 @@ h
hacky
hadn't
hang
happy
hardcode
harness
hashes
@@ -847,6 +848,7 @@ player
plugin
pm
pn
pointed
pointwise
polluted
pool
+1
View File
@@ -19,6 +19,7 @@ const List<String> idTests = <String>[
'pkg/front_end/test/id_tests/nullability_test.dart',
'pkg/front_end/test/id_tests/reachability_test.dart',
'pkg/front_end/test/id_tests/type_promotion_test.dart',
'pkg/front_end/test/id_tests/why_not_promoted_test.dart',
'pkg/front_end/test/language_versioning/language_versioning_test.dart',
'pkg/front_end/test/patching/patching_test.dart',
'pkg/front_end/test/static_types/static_type_test.dart',
@@ -20,6 +20,14 @@ class DecoratedTypeOperations
DecoratedTypeOperations(
this._typeSystem, this._variableRepository, this._graph);
@override
DecoratedType get topType {
// This is only needed for explaining to the user why fields aren't
// promoted, functionality of flow analysis that we don't take advantage of
// during migration. So this method should never be called.
throw StateError('Unexpected call to topType');
}
@override
TypeClassification classifyType(DecoratedType type) {
if (type.type.isDartCoreNull) {
+17 -15
View File
@@ -407,7 +407,7 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
}
var expressionType = _handleAssignment(node.rightHandSide,
destinationExpression: node.leftHandSide,
assignmentExpression: node,
compoundOperatorInfo: isCompound ? node : null,
questionAssignNode: isQuestionAssign ? node : null,
sourceIsSetupCall: sourceIsSetupCall);
@@ -1387,7 +1387,7 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
if (operand is SimpleIdentifier) {
var element = getWriteOrReadElement(operand);
if (element is PromotableElement) {
_flowAnalysis.write(element, writeType, null);
_flowAnalysis.write(node, element, writeType, null);
}
}
return targetType;
@@ -1438,7 +1438,7 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
if (operand is SimpleIdentifier) {
var element = getWriteOrReadElement(operand);
if (element is PromotableElement) {
_flowAnalysis.write(element, staticType, null);
_flowAnalysis.write(node, element, staticType, null);
}
}
}
@@ -2285,26 +2285,28 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
/// Creates the necessary constraint(s) for an assignment of the given
/// [expression] to a destination whose type is [destinationType].
///
/// Optionally, the caller may supply a [destinationExpression] instead of
/// Optionally, the caller may supply an [assignmentExpression] instead of
/// [destinationType]. In this case, then the type comes from visiting the
/// destination expression. If the destination expression refers to a local
/// variable, we mark it as assigned in flow analysis at the proper time.
/// LHS of the assignment expression. If the LHS of the assignment expression
/// refers to a local variable, we mark it as assigned in flow analysis at the
/// proper time.
///
/// Set [wrapFuture] to true to handle assigning Future<flatten(T)> to R.
DecoratedType _handleAssignment(Expression expression,
{DecoratedType destinationType,
Expression destinationExpression,
AssignmentExpression assignmentExpression,
AssignmentExpression compoundOperatorInfo,
AssignmentExpression questionAssignNode,
bool fromDefaultValue = false,
bool wrapFuture = false,
bool sourceIsSetupCall = false}) {
assert(
(destinationExpression == null) != (destinationType == null),
'Either destinationExpression or destinationType should be supplied, '
(assignmentExpression == null) != (destinationType == null),
'Either assignmentExpression or destinationType should be supplied, '
'but not both');
PromotableElement destinationLocalVariable;
if (destinationType == null) {
var destinationExpression = assignmentExpression.leftHandSide;
if (destinationExpression is SimpleIdentifier) {
var element = getWriteOrReadElement(destinationExpression);
if (element is PromotableElement) {
@@ -2345,7 +2347,7 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
source: destinationType,
destination: _createNonNullableType(compoundOperatorInfo),
hard: _postDominatedLocals
.isReferenceInScope(destinationExpression));
.isReferenceInScope(assignmentExpression.leftHandSide));
DecoratedType compoundOperatorType = getOrComputeElementType(
compoundOperatorMethod,
targetType: destinationType);
@@ -2403,8 +2405,8 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
}
}
if (destinationLocalVariable != null) {
_flowAnalysis.write(destinationLocalVariable, sourceType,
compoundOperatorInfo == null ? expression : null);
_flowAnalysis.write(assignmentExpression, destinationLocalVariable,
sourceType, compoundOperatorInfo == null ? expression : null);
}
if (questionAssignNode != null) {
_flowAnalysis.ifNullExpression_end();
@@ -2419,9 +2421,9 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
_guards.removeLast();
}
}
if (destinationExpression != null) {
var element =
_postDominatedLocals.referencedElement(destinationExpression);
if (assignmentExpression != null) {
var element = _postDominatedLocals
.referencedElement(assignmentExpression.leftHandSide);
if (element != null) {
_postDominatedLocals.removeFromAllScopes(element);
_elementsWrittenToInLocalFunction?.add(element);
+2
View File
@@ -48,6 +48,8 @@ void main(List<String> args) {
'pkg/_fe_analyzer_shared/test/flow_analysis/reachability/'),
packageDirectory(
'pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/'),
packageDirectory(
'pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted//'),
packageDirectory('pkg/_fe_analyzer_shared/test/inheritance/'),
];