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:
committed by
commit-bot@chromium.org
parent
91be638a56
commit
a42244f73b
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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/'),
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user