mirror of
https://github.com/dart-lang/sdk
synced 2024-09-19 15:01:29 +00:00
Revert "Flow analysis changes to fix mixed-mode unsoundness loophole."
This reverts commit d833f2f65c
.
Reason for revert: Broke build, e.g. https://ci.chromium.org/p/dart/builders/ci/dart-sdk-mac/12688
Original change's description:
> Flow analysis changes to fix mixed-mode unsoundness loophole.
>
> This is the flow analysis portion of the fix to
> https://github.com/dart-lang/language/issues/1143. Follow-up changes
> will be needed in the CFE and/or backends to ensure that exceptions
> are thrown under appropriate circumstances.
>
> This CL also makes some improvements to flow analysis's reachability
> analysis so that it accounts for nullability of the target when
> analyzing the reachability of `??=` and `?.`. Hopefully these
> improvements should make the fix to
> https://github.com/dart-lang/language/issues/1143 clearer and more
> consistent.
>
> Change-Id: I5fa5c070f13fd57ac4c2fb87f2d67588861594b0
> Bug: https://github.com/dart-lang/language/issues/1143
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/160440
> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
> Reviewed-by: Johnni Winther <johnniwinther@google.com>
> Commit-Queue: Paul Berry <paulberry@google.com>
TBR=paulberry@google.com,scheglov@google.com,johnniwinther@google.com
Change-Id: If1215b19975e0958d612dd69767088095d853879
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: https://github.com/dart-lang/language/issues/1143
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/161580
Reviewed-by: Paul Berry <paulberry@google.com>
This commit is contained in:
parent
7363adca67
commit
ab16d79af9
|
@ -362,16 +362,7 @@ abstract class FlowAnalysis<Node, Statement extends Node, Expression, Variable,
|
|||
void doStatement_end(Expression condition);
|
||||
|
||||
/// Call this method just after visiting a binary `==` or `!=` expression.
|
||||
///
|
||||
/// Return value indicates whether flow analysis believes that a successful
|
||||
/// equality check is reachable. If `false` is returned, the client should
|
||||
/// ensure that the `==` test behaves like `x == y && throw ...`.
|
||||
///
|
||||
/// Note that if `notEqual` is `true`, then the return value describes the
|
||||
/// behavior of the underlying `==` test. So if `notEqual` is `true` and
|
||||
/// `false` is returned, the client should ensure that the `!=` test behaves
|
||||
/// like `!(x == y && throw ...)`.
|
||||
bool equalityOp_end(Expression wholeExpression, Expression rightOperand,
|
||||
void equalityOp_end(Expression wholeExpression, Expression rightOperand,
|
||||
Type rightOperandType,
|
||||
{bool notEqual = false});
|
||||
|
||||
|
@ -484,13 +475,7 @@ abstract class FlowAnalysis<Node, Statement extends Node, Expression, Variable,
|
|||
|
||||
/// Call this method after visiting the LHS of an if-null expression ("??")
|
||||
/// or if-null assignment ("??=").
|
||||
///
|
||||
/// Return value indicates whether flow analysis believes that the right hand
|
||||
/// side is reachable. If `false` is returned, the client should ensure that
|
||||
/// `x ?? y` behaves like `x ?? throw ...` (or, correspondingly, that
|
||||
/// `x ??= y` behaves like `x ??= throw ...`).
|
||||
bool ifNullExpression_rightBegin(
|
||||
Expression leftHandSide, Type leftHandSideType);
|
||||
void ifNullExpression_rightBegin(Expression leftHandSide);
|
||||
|
||||
/// Call this method after visiting the "then" part of an if statement, and
|
||||
/// before visiting the "else" part.
|
||||
|
@ -526,16 +511,7 @@ abstract class FlowAnalysis<Node, Statement extends Node, Expression, Variable,
|
|||
/// be the expression to which the "is" check was applied. [isNot] should be
|
||||
/// a boolean indicating whether this is an "is" or an "is!" expression.
|
||||
/// [type] should be the type being checked.
|
||||
///
|
||||
/// Return value indicates whether flow analysis believes that a failure of
|
||||
/// the `is` test is reachable. If `false` is returned, the client should
|
||||
/// ensure that the `is` test behaves like `x is T || throw ...`.
|
||||
///
|
||||
/// Note that if `isNot` is `true`, then the return value describes the
|
||||
/// behavior of the underlying `if` test. So if `isNot` is `true` and `false`
|
||||
/// is returned, the client should ensure that the `is!` test behaves like
|
||||
/// `!(x is T || throw ...)`.
|
||||
bool isExpression_end(
|
||||
void isExpression_end(
|
||||
Expression isExpression, Expression subExpression, bool isNot, Type type);
|
||||
|
||||
/// Return whether the [variable] is definitely unassigned in the current
|
||||
|
@ -582,22 +558,12 @@ abstract class FlowAnalysis<Node, Statement extends Node, Expression, Variable,
|
|||
/// [target] should be the expression just before the null-aware operator, or
|
||||
/// `null` if the null-aware access starts a cascade section.
|
||||
///
|
||||
/// [targetType] should be the type of the expression just before the
|
||||
/// null-aware operator, and should be non-null even if the null-aware access
|
||||
/// starts a cascade section.
|
||||
///
|
||||
/// Note that [nullAwareAccess_end] should be called after the conclusion
|
||||
/// of any null-shorting that is caused by the `?.`. So, for example, if the
|
||||
/// code being analyzed is `x?.y?.z(x)`, [nullAwareAccess_rightBegin] should
|
||||
/// be called once upon reaching each `?.`, but [nullAwareAccess_end] should
|
||||
/// not be called until after processing the method call to `z(x)`.
|
||||
///
|
||||
/// Return value indicates whether flow analysis believes that a null target
|
||||
/// is reachable. If `false` is returned, the client should ensure that
|
||||
/// `x?.y` behaves like `x!.y`. (Note that this is necessary even if `y`
|
||||
/// exists on `Object`--see
|
||||
/// https://github.com/dart-lang/language/issues/1143#issuecomment-682096575.)
|
||||
bool nullAwareAccess_rightBegin(Expression target, Type targetType);
|
||||
void nullAwareAccess_rightBegin(Expression target);
|
||||
|
||||
/// Call this method when encountering an expression that is a `null` literal.
|
||||
void nullLiteral(Expression expression);
|
||||
|
@ -845,17 +811,15 @@ class FlowAnalysisDebug<Node, Statement extends Node, Expression, Variable,
|
|||
}
|
||||
|
||||
@override
|
||||
bool equalityOp_end(Expression wholeExpression, Expression rightOperand,
|
||||
void equalityOp_end(Expression wholeExpression, Expression rightOperand,
|
||||
Type rightOperandType,
|
||||
{bool notEqual = false}) {
|
||||
return _wrap(
|
||||
_wrap(
|
||||
'equalityOp_end($wholeExpression, $rightOperand, $rightOperandType, '
|
||||
'notEqual: $notEqual)',
|
||||
() => _wrapped.equalityOp_end(
|
||||
wholeExpression, rightOperand, rightOperandType,
|
||||
notEqual: notEqual),
|
||||
isQuery: true,
|
||||
isPure: false);
|
||||
notEqual: notEqual));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -938,14 +902,9 @@ class FlowAnalysisDebug<Node, Statement extends Node, Expression, Variable,
|
|||
}
|
||||
|
||||
@override
|
||||
bool ifNullExpression_rightBegin(
|
||||
Expression leftHandSide, Type leftHandSideType) {
|
||||
return _wrap(
|
||||
'ifNullExpression_rightBegin($leftHandSide, $leftHandSideType)',
|
||||
() => _wrapped.ifNullExpression_rightBegin(
|
||||
leftHandSide, leftHandSideType),
|
||||
isQuery: true,
|
||||
isPure: false);
|
||||
void ifNullExpression_rightBegin(Expression leftHandSide) {
|
||||
return _wrap('ifNullExpression_rightBegin($leftHandSide)',
|
||||
() => _wrapped.ifNullExpression_rightBegin(leftHandSide));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -972,14 +931,12 @@ class FlowAnalysisDebug<Node, Statement extends Node, Expression, Variable,
|
|||
}
|
||||
|
||||
@override
|
||||
bool isExpression_end(Expression isExpression, Expression subExpression,
|
||||
void isExpression_end(Expression isExpression, Expression subExpression,
|
||||
bool isNot, Type type) {
|
||||
return _wrap(
|
||||
_wrap(
|
||||
'isExpression_end($isExpression, $subExpression, $isNot, $type)',
|
||||
() =>
|
||||
_wrapped.isExpression_end(isExpression, subExpression, isNot, type),
|
||||
isQuery: true,
|
||||
isPure: false);
|
||||
() => _wrapped.isExpression_end(
|
||||
isExpression, subExpression, isNot, type));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1034,10 +991,9 @@ class FlowAnalysisDebug<Node, Statement extends Node, Expression, Variable,
|
|||
}
|
||||
|
||||
@override
|
||||
bool nullAwareAccess_rightBegin(Expression target, Type targetType) {
|
||||
return _wrap('nullAwareAccess_rightBegin($target, $targetType)',
|
||||
() => _wrapped.nullAwareAccess_rightBegin(target, targetType),
|
||||
isQuery: true, isPure: false);
|
||||
void nullAwareAccess_rightBegin(Expression target) {
|
||||
_wrap('nullAwareAccess_rightBegin($target)',
|
||||
() => _wrapped.nullAwareAccess_rightBegin(target));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -2500,7 +2456,7 @@ class _FlowAnalysisImpl<Node, Statement extends Node, Expression, Variable,
|
|||
}
|
||||
|
||||
@override
|
||||
bool equalityOp_end(Expression wholeExpression, Expression rightOperand,
|
||||
void equalityOp_end(Expression wholeExpression, Expression rightOperand,
|
||||
Type rightOperandType,
|
||||
{bool notEqual = false}) {
|
||||
_EqualityOpContext<Variable, Type> context =
|
||||
|
@ -2515,16 +2471,14 @@ class _FlowAnalysisImpl<Node, Statement extends Node, Expression, Variable,
|
|||
typeOperations.classifyType(rightOperandType);
|
||||
if (leftOperandTypeClassification == TypeClassification.nullOrEquivalent &&
|
||||
rightOperandTypeClassification == TypeClassification.nullOrEquivalent) {
|
||||
booleanLiteral(wholeExpression, !notEqual);
|
||||
return true;
|
||||
return booleanLiteral(wholeExpression, !notEqual);
|
||||
} else if ((leftOperandTypeClassification ==
|
||||
TypeClassification.nullOrEquivalent &&
|
||||
rightOperandTypeClassification == TypeClassification.nonNullable) ||
|
||||
(rightOperandTypeClassification ==
|
||||
TypeClassification.nullOrEquivalent &&
|
||||
leftOperandTypeClassification == TypeClassification.nonNullable)) {
|
||||
booleanLiteral(wholeExpression, notEqual);
|
||||
return false;
|
||||
return booleanLiteral(wholeExpression, notEqual);
|
||||
} else if (lhsInfo is _NullInfo<Variable, Type> &&
|
||||
rhsInfo is _VariableReadInfo<Variable, Type>) {
|
||||
assert(
|
||||
|
@ -2536,11 +2490,10 @@ class _FlowAnalysisImpl<Node, Statement extends Node, Expression, Variable,
|
|||
equalityInfo =
|
||||
_current.tryMarkNonNullable(typeOperations, lhsInfo._variable);
|
||||
} else {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
_storeExpressionInfo(wholeExpression,
|
||||
notEqual ? equalityInfo : ExpressionInfo.invert(equalityInfo));
|
||||
return equalityInfo.ifFalse.reachable;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -2666,8 +2619,7 @@ class _FlowAnalysisImpl<Node, Statement extends Node, Expression, Variable,
|
|||
}
|
||||
|
||||
@override
|
||||
bool ifNullExpression_rightBegin(
|
||||
Expression leftHandSide, Type leftHandSideType) {
|
||||
void ifNullExpression_rightBegin(Expression leftHandSide) {
|
||||
ExpressionInfo<Variable, Type> lhsInfo = _getExpressionInfo(leftHandSide);
|
||||
FlowModel<Variable, Type> promoted;
|
||||
if (lhsInfo is _VariableReadInfo<Variable, Type>) {
|
||||
|
@ -2678,12 +2630,7 @@ class _FlowAnalysisImpl<Node, Statement extends Node, Expression, Variable,
|
|||
} else {
|
||||
promoted = _current;
|
||||
}
|
||||
if (typeOperations.classifyType(leftHandSideType) ==
|
||||
TypeClassification.nonNullable) {
|
||||
_current = _current.setReachable(false);
|
||||
}
|
||||
_stack.add(new _SimpleContext<Variable, Type>(promoted));
|
||||
return _current.reachable;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -2723,7 +2670,7 @@ class _FlowAnalysisImpl<Node, Statement extends Node, Expression, Variable,
|
|||
}
|
||||
|
||||
@override
|
||||
bool isExpression_end(Expression isExpression, Expression subExpression,
|
||||
void isExpression_end(Expression isExpression, Expression subExpression,
|
||||
bool isNot, Type type) {
|
||||
ExpressionInfo<Variable, Type> subExpressionInfo =
|
||||
_getExpressionInfo(subExpression);
|
||||
|
@ -2731,13 +2678,12 @@ class _FlowAnalysisImpl<Node, Statement extends Node, Expression, Variable,
|
|||
if (subExpressionInfo is _VariableReadInfo<Variable, Type>) {
|
||||
variable = subExpressionInfo._variable;
|
||||
} else {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
ExpressionInfo<Variable, Type> expressionInfo =
|
||||
_current.tryPromoteForTypeCheck(typeOperations, variable, type);
|
||||
_storeExpressionInfo(isExpression,
|
||||
isNot ? ExpressionInfo.invert(expressionInfo) : expressionInfo);
|
||||
return expressionInfo.ifFalse.reachable;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -2814,16 +2760,8 @@ class _FlowAnalysisImpl<Node, Statement extends Node, Expression, Variable,
|
|||
}
|
||||
|
||||
@override
|
||||
bool nullAwareAccess_rightBegin(Expression target, Type targetType) {
|
||||
assert(targetType != null);
|
||||
bool shortingIsReachable = true;
|
||||
FlowModel<Variable, Type> shortingModel = _current;
|
||||
if (typeOperations.classifyType(targetType) ==
|
||||
TypeClassification.nonNullable) {
|
||||
shortingModel = shortingModel.setReachable(false);
|
||||
shortingIsReachable = false;
|
||||
}
|
||||
_stack.add(new _SimpleContext<Variable, Type>(shortingModel));
|
||||
void nullAwareAccess_rightBegin(Expression target) {
|
||||
_stack.add(new _SimpleContext<Variable, Type>(_current));
|
||||
if (target != null) {
|
||||
ExpressionInfo<Variable, Type> targetInfo = _getExpressionInfo(target);
|
||||
if (targetInfo is _VariableReadInfo<Variable, Type>) {
|
||||
|
@ -2832,7 +2770,6 @@ class _FlowAnalysisImpl<Node, Statement extends Node, Expression, Variable,
|
|||
.ifTrue;
|
||||
}
|
||||
}
|
||||
return shortingIsReachable;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -185,76 +185,15 @@ main() {
|
|||
var nullExpr = _Expression();
|
||||
flow.nullLiteral(nullExpr);
|
||||
var expr = _Expression();
|
||||
var successIsReachable =
|
||||
flow.equalityOp_end(expr, nullExpr, _Type('Null'), notEqual: true);
|
||||
expect(successIsReachable, true);
|
||||
flow.equalityOp_end(expr, nullExpr, _Type('Null'), notEqual: true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, true);
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(x != null) when x is non-nullable', () {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', 'int');
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
var varExpr = _Expression();
|
||||
flow.variableRead(varExpr, x);
|
||||
flow.equalityOp_rightBegin(varExpr, _Type('int'));
|
||||
var nullExpr = _Expression();
|
||||
flow.nullLiteral(nullExpr);
|
||||
var expr = _Expression();
|
||||
var successIsReachable =
|
||||
flow.equalityOp_end(expr, nullExpr, _Type('Null'), notEqual: true);
|
||||
expect(successIsReachable, false);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, false);
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(<expr> == <expr>) has no special effect', () {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
flow.equalityOp_rightBegin(_Expression(), _Type('int?'));
|
||||
var expr = _Expression();
|
||||
var successIsReachable = flow.equalityOp_end(
|
||||
expr, _Expression(), _Type('int?'),
|
||||
notEqual: false);
|
||||
expect(successIsReachable, true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(<expr> != <expr>) has no special effect', () {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
flow.equalityOp_rightBegin(_Expression(), _Type('int?'));
|
||||
var expr = _Expression();
|
||||
var successIsReachable = flow
|
||||
.equalityOp_end(expr, _Expression(), _Type('int?'), notEqual: true);
|
||||
expect(successIsReachable, true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(x != <null expr>) does not promote', () {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', 'int?');
|
||||
|
@ -285,43 +224,15 @@ main() {
|
|||
var nullExpr = _Expression();
|
||||
flow.nullLiteral(nullExpr);
|
||||
var expr = _Expression();
|
||||
var successIsReachable =
|
||||
flow.equalityOp_end(expr, nullExpr, _Type('Null'), notEqual: false);
|
||||
expect(successIsReachable, true);
|
||||
flow.equalityOp_end(expr, nullExpr, _Type('Null'), notEqual: false);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, true);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(x == null) when x is non-nullable', () {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', 'int');
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
var varExpr = _Expression();
|
||||
flow.variableRead(varExpr, x);
|
||||
flow.equalityOp_rightBegin(varExpr, _Type('int'));
|
||||
var nullExpr = _Expression();
|
||||
flow.nullLiteral(nullExpr);
|
||||
var expr = _Expression();
|
||||
var successIsReachable =
|
||||
flow.equalityOp_end(expr, nullExpr, _Type('Null'), notEqual: false);
|
||||
expect(successIsReachable, false);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, false);
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, true);
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(null != x) promotes true branch', () {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', 'int?');
|
||||
|
@ -388,9 +299,7 @@ main() {
|
|||
flow.equalityOp_rightBegin(null1, _Type('Null'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
var successIsReachable =
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'));
|
||||
expect(successIsReachable, true);
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'));
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_elseBegin();
|
||||
|
@ -406,9 +315,7 @@ main() {
|
|||
flow.equalityOp_rightBegin(null1, _Type('Null'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
var successIsReachable =
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'), notEqual: true);
|
||||
expect(successIsReachable, true);
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'), notEqual: true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, false);
|
||||
flow.ifStatement_elseBegin();
|
||||
|
@ -424,8 +331,7 @@ main() {
|
|||
flow.equalityOp_rightBegin(null1, _Type('Null'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
var successIsReachable = flow.equalityOp_end(expr, null2, _Type('int'));
|
||||
expect(successIsReachable, false);
|
||||
flow.equalityOp_end(expr, null2, _Type('int'));
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, false);
|
||||
flow.ifStatement_elseBegin();
|
||||
|
@ -441,9 +347,7 @@ main() {
|
|||
flow.equalityOp_rightBegin(null1, _Type('Null'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
var successIsReachable =
|
||||
flow.equalityOp_end(expr, null2, _Type('int'), notEqual: true);
|
||||
expect(successIsReachable, false);
|
||||
flow.equalityOp_end(expr, null2, _Type('int'), notEqual: true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_elseBegin();
|
||||
|
@ -459,9 +363,7 @@ main() {
|
|||
flow.equalityOp_rightBegin(null1, _Type('int'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
var successIsReachable =
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'));
|
||||
expect(successIsReachable, false);
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'));
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, false);
|
||||
flow.ifStatement_elseBegin();
|
||||
|
@ -477,9 +379,7 @@ main() {
|
|||
flow.equalityOp_rightBegin(null1, _Type('int'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
var successIsReachable =
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'), notEqual: true);
|
||||
expect(successIsReachable, false);
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'), notEqual: true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_elseBegin();
|
||||
|
@ -985,14 +885,10 @@ main() {
|
|||
h.assignedVariables((vars) => vars.write(x));
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
var rhsIsReachable = flow.ifNullExpression_rightBegin(
|
||||
h.variableRead(x)(), _Type('int?'));
|
||||
expect(rhsIsReachable, true);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifNullExpression_rightBegin(h.variableRead(x)());
|
||||
flow.write(x, _Type('int'));
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.ifNullExpression_end();
|
||||
expect(flow.isReachable, true);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
});
|
||||
});
|
||||
|
@ -1002,14 +898,10 @@ main() {
|
|||
var x = h.addVar('x', 'int?');
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
var rhsIsReachable = flow.ifNullExpression_rightBegin(
|
||||
h.variableRead(x)(), _Type('int?'));
|
||||
expect(rhsIsReachable, true);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifNullExpression_rightBegin(h.variableRead(x)());
|
||||
h.promote(x, 'int');
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.ifNullExpression_end();
|
||||
expect(flow.isReachable, true);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
});
|
||||
});
|
||||
|
@ -1019,43 +911,14 @@ main() {
|
|||
var x = h.addVar('x', 'int?');
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
var rhsIsReachable =
|
||||
flow.ifNullExpression_rightBegin(h.expr(), _Type('int?'));
|
||||
expect(rhsIsReachable, true);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifNullExpression_rightBegin(h.expr());
|
||||
h.promote(x, 'int');
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.ifNullExpression_end();
|
||||
expect(flow.isReachable, true);
|
||||
expect(flow.promotedType(x), null);
|
||||
});
|
||||
});
|
||||
|
||||
test('ifNullExpression detects when RHS is unreachable', () {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
var rhsIsReachable =
|
||||
flow.ifNullExpression_rightBegin(h.expr(), _Type('int'));
|
||||
expect(rhsIsReachable, false);
|
||||
expect(flow.isReachable, false);
|
||||
flow.ifNullExpression_end();
|
||||
expect(flow.isReachable, true);
|
||||
});
|
||||
});
|
||||
|
||||
test('ifNullExpression determines reachability correctly for `Null` type',
|
||||
() {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
var rhsIsReachable =
|
||||
flow.ifNullExpression_rightBegin(h.expr(), _Type('Null'));
|
||||
expect(rhsIsReachable, true);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifNullExpression_end();
|
||||
expect(flow.isReachable, true);
|
||||
});
|
||||
});
|
||||
|
||||
test('ifStatement_end(false) keeps else branch if then branch exits', () {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', 'int?');
|
||||
|
@ -1069,12 +932,11 @@ main() {
|
|||
});
|
||||
|
||||
void _checkIs(
|
||||
String declaredType,
|
||||
String tryPromoteType,
|
||||
String expectedPromotedTypeThen,
|
||||
String expectedPromotedTypeElse,
|
||||
bool expectedFailureReachable,
|
||||
{bool inverted = false}) {
|
||||
String declaredType,
|
||||
String tryPromoteType,
|
||||
String expectedPromotedTypeThen,
|
||||
String expectedPromotedTypeElse,
|
||||
) {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', declaredType);
|
||||
h.run((flow) {
|
||||
|
@ -1082,18 +944,14 @@ main() {
|
|||
var read = _Expression();
|
||||
flow.variableRead(read, x);
|
||||
var expr = _Expression();
|
||||
var failureReachable =
|
||||
flow.isExpression_end(expr, read, inverted, _Type(tryPromoteType));
|
||||
expect(failureReachable, expectedFailureReachable);
|
||||
flow.isExpression_end(expr, read, false, _Type(tryPromoteType));
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, inverted ? expectedFailureReachable : true);
|
||||
if (expectedPromotedTypeThen == null) {
|
||||
expect(flow.promotedType(x), isNull);
|
||||
} else {
|
||||
expect(flow.promotedType(x).type, expectedPromotedTypeThen);
|
||||
}
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, inverted ? true : expectedFailureReachable);
|
||||
if (expectedPromotedTypeElse == null) {
|
||||
expect(flow.promotedType(x), isNull);
|
||||
} else {
|
||||
|
@ -1104,61 +962,15 @@ main() {
|
|||
}
|
||||
|
||||
test('isExpression_end promotes to a subtype', () {
|
||||
_checkIs('int?', 'int', 'int', 'Never?', true);
|
||||
});
|
||||
|
||||
test('isExpression_end promotes to a subtype, inverted', () {
|
||||
_checkIs('int?', 'int', 'Never?', 'int', true, inverted: true);
|
||||
_checkIs('int?', 'int', 'int', 'Never?');
|
||||
});
|
||||
|
||||
test('isExpression_end does not promote to a supertype', () {
|
||||
_checkIs('int', 'int?', null, 'Never', false);
|
||||
});
|
||||
|
||||
test('isExpression_end does not promote to a supertype, inverted', () {
|
||||
_checkIs('int', 'int?', 'Never', null, false, inverted: true);
|
||||
_checkIs('int', 'int?', null, 'Never');
|
||||
});
|
||||
|
||||
test('isExpression_end does not promote to an unrelated type', () {
|
||||
_checkIs('int', 'String', null, null, true);
|
||||
});
|
||||
|
||||
test('isExpression_end does not promote to an unrelated type, inverted',
|
||||
() {
|
||||
_checkIs('int', 'String', null, null, true, inverted: true);
|
||||
});
|
||||
|
||||
test('isExpression_end does nothing if applied to a non-variable', () {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
var subExpr = _Expression();
|
||||
var expr = _Expression();
|
||||
var failureReachable =
|
||||
flow.isExpression_end(expr, subExpr, false, _Type('int'));
|
||||
expect(failureReachable, true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('isExpression_end does nothing if applied to a non-variable, inverted',
|
||||
() {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
var subExpr = _Expression();
|
||||
var expr = _Expression();
|
||||
var failureReachable =
|
||||
flow.isExpression_end(expr, subExpr, true, _Type('int'));
|
||||
expect(failureReachable, true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
_checkIs('int', 'String', null, null);
|
||||
});
|
||||
|
||||
test('isExpression_end() does not promote write-captured vars', () {
|
||||
|
@ -1341,10 +1153,7 @@ main() {
|
|||
h.declare(x, initialized: true);
|
||||
var varExpr = _Expression();
|
||||
flow.variableRead(varExpr, x);
|
||||
var shortIsReachable =
|
||||
flow.nullAwareAccess_rightBegin(varExpr, _Type('int?'));
|
||||
expect(shortIsReachable, true);
|
||||
expect(flow.isReachable, true);
|
||||
flow.nullAwareAccess_rightBegin(varExpr);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.nullAwareAccess_end();
|
||||
expect(flow.promotedType(x), isNull);
|
||||
|
@ -1358,10 +1167,7 @@ main() {
|
|||
h.declare(x, initialized: true);
|
||||
var varExpr = _Expression();
|
||||
flow.variableRead(varExpr, x);
|
||||
var shortIsReachable =
|
||||
flow.nullAwareAccess_rightBegin(null, _Type('int?'));
|
||||
expect(shortIsReachable, true);
|
||||
expect(flow.isReachable, true);
|
||||
flow.nullAwareAccess_rightBegin(null);
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.nullAwareAccess_end();
|
||||
});
|
||||
|
@ -1375,10 +1181,7 @@ main() {
|
|||
h.declare(x, initialized: true);
|
||||
h.promote(x, 'int');
|
||||
var lhs = _Expression();
|
||||
var shortIsReachable =
|
||||
flow.nullAwareAccess_rightBegin(lhs, _Type('int'));
|
||||
expect(shortIsReachable, false);
|
||||
expect(flow.isReachable, true);
|
||||
flow.nullAwareAccess_rightBegin(lhs);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.write(x, _Type('int?'));
|
||||
expect(flow.promotedType(x), isNull);
|
||||
|
@ -1387,24 +1190,6 @@ main() {
|
|||
});
|
||||
});
|
||||
|
||||
test('nullAwareAccess_end ignores shorting if target is non-nullable', () {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', 'int?');
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
var shortIsReachable =
|
||||
flow.nullAwareAccess_rightBegin(_Expression(), _Type('int'));
|
||||
expect(shortIsReachable, false);
|
||||
expect(flow.isReachable, true);
|
||||
h.promote(x, 'int');
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.nullAwareAccess_end();
|
||||
// `x` should still be promoted because the target was non-nullable, so
|
||||
// the null shorting path was unreachable.
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
});
|
||||
});
|
||||
|
||||
test('parenthesizedExpression preserves promotion behaviors', () {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', 'int?');
|
||||
|
|
|
@ -1,331 +0,0 @@
|
|||
// 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.
|
||||
|
||||
void variable_if_null_reachable(int? i) {
|
||||
i ?? 0;
|
||||
}
|
||||
|
||||
void variable_if_null_assign_reachable(int? i) {
|
||||
i ??= 0;
|
||||
}
|
||||
|
||||
void variable_if_null_unreachable(int i) {
|
||||
i ?? /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void variable_if_null_assign_unreachable(int i) {
|
||||
// Note: CFE reports that the update to `i` is unreachable; analyzer does not.
|
||||
// This is ok; what matters is that the RHS is unreachable.
|
||||
/*cfe.update: unreachable*/ i ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void variable_if_null_assign_unreachable_due_to_promotion(int? i) {
|
||||
if (i == null) return;
|
||||
// Note: CFE reports that the update to `i` is unreachable; analyzer does not.
|
||||
// This is ok; what matters is that the RHS is unreachable.
|
||||
/*cfe.update: unreachable*/ i ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
/*member: topLevelNullable:doesNotComplete*/
|
||||
int? get topLevelNullable => 0;
|
||||
void set topLevelNullable(int? value) {}
|
||||
|
||||
/*member: topLevelNonNullGet:doesNotComplete*/
|
||||
int get topLevelNonNullGet => 0;
|
||||
void set topLevelNonNullGet(int? value) {}
|
||||
|
||||
void top_level_if_null_reachable() {
|
||||
topLevelNullable ?? 0;
|
||||
}
|
||||
|
||||
void top_level_if_null_assign_reachable() {
|
||||
topLevelNullable ??= 0;
|
||||
}
|
||||
|
||||
void top_level_if_null_unreachable() {
|
||||
topLevelNonNullGet ?? /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void top_level_if_null_assign_unreachable() {
|
||||
// Note: CFE reports that the update to `topLevelNonNullGet` is unreachable;
|
||||
// analyzer does not. This is ok; what matters is that the RHS is
|
||||
// unreachable.
|
||||
topLevelNonNullGet /*cfe.update: unreachable*/ ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
class HasProperty<T> {
|
||||
/*member: HasProperty.prop:doesNotComplete*/
|
||||
T get prop => throw '';
|
||||
set prop(T? value) {}
|
||||
}
|
||||
|
||||
void property_if_null_reachable(HasProperty<int?> x) {
|
||||
x.prop ?? 0;
|
||||
}
|
||||
|
||||
void property_if_null_assign_reachable(HasProperty<int?> x) {
|
||||
x.prop ??= 0;
|
||||
}
|
||||
|
||||
void property_if_null_unreachable(HasProperty<int> x) {
|
||||
x.prop ?? /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void property_if_null_assign_unreachable(HasProperty<int> x) {
|
||||
x.prop ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void null_aware_property_if_null_reachable(HasProperty<int?>? x) {
|
||||
x?.prop ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_property_if_null_assign_reachable(HasProperty<int?>? x) {
|
||||
x?.prop ??= 0;
|
||||
}
|
||||
|
||||
void null_aware_property_if_null_not_shortened(HasProperty<int>? x) {
|
||||
// If `??` participated in null-shortening, `0` would be unreachable.
|
||||
x?.prop ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_property_if_null_assign_unreachable(HasProperty<int>? x) {
|
||||
x?.prop ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
class SuperIntQuestionProperty extends HasProperty<int?> {
|
||||
void if_null_reachable() {
|
||||
super.prop ?? 0;
|
||||
}
|
||||
|
||||
void if_null_assign_reachable() {
|
||||
super.prop ??= 0;
|
||||
}
|
||||
}
|
||||
|
||||
class SuperIntProperty extends HasProperty<int> {
|
||||
void if_null_unreachable() {
|
||||
super.prop ?? /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void if_null_assign_unreachable() {
|
||||
super.prop ??= /*unreachable*/ 0;
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtensionProperty<T> on HasProperty<T> {
|
||||
/*member: ExtensionProperty|get#extendedProp:doesNotComplete*/
|
||||
T get extendedProp => prop;
|
||||
set extendedProp(T? value) {
|
||||
prop = value;
|
||||
}
|
||||
}
|
||||
|
||||
void extended_property_if_null_reachable(HasProperty<int?> x) {
|
||||
x.extendedProp ?? 0;
|
||||
}
|
||||
|
||||
void extended_property_if_null_assign_reachable(HasProperty<int?> x) {
|
||||
x.extendedProp ??= 0;
|
||||
}
|
||||
|
||||
void extended_property_if_null_unreachable(HasProperty<int> x) {
|
||||
x.extendedProp ?? /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void extended_property_if_null_assign_unreachable(HasProperty<int> x) {
|
||||
x.extendedProp ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void null_aware_extended_property_if_null_reachable(HasProperty<int?>? x) {
|
||||
x?.extendedProp ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_extended_property_if_null_assign_reachable(
|
||||
HasProperty<int?>? x) {
|
||||
x?.extendedProp ??= 0;
|
||||
}
|
||||
|
||||
void null_aware_extended_property_if_null_not_shortened(HasProperty<int>? x) {
|
||||
// If `??` participated in null-shortening, `0` would be unreachable.
|
||||
x?.extendedProp ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_extended_property_if_null_assign_unreachable(
|
||||
HasProperty<int>? x) {
|
||||
x?.extendedProp ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void explicit_extended_property_if_null_reachable(HasProperty<int?> x) {
|
||||
ExtensionProperty(x).extendedProp ?? 0;
|
||||
}
|
||||
|
||||
void explicit_extended_property_if_null_assign_reachable(HasProperty<int?> x) {
|
||||
ExtensionProperty(x).extendedProp ??= 0;
|
||||
}
|
||||
|
||||
void explicit_extended_property_if_null_unreachable(HasProperty<int> x) {
|
||||
ExtensionProperty(x).extendedProp ?? /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void explicit_extended_property_if_null_assign_unreachable(HasProperty<int> x) {
|
||||
ExtensionProperty(x).extendedProp ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void null_aware_explicit_extended_property_if_null_reachable(
|
||||
HasProperty<int?>? x) {
|
||||
ExtensionProperty(x)?.extendedProp ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_explicit_extended_property_if_null_assign_reachable(
|
||||
HasProperty<int?>? x) {
|
||||
ExtensionProperty(x)?.extendedProp ??= 0;
|
||||
}
|
||||
|
||||
void null_aware_explicit_extended_property_if_null_not_shortened(
|
||||
HasProperty<int>? x) {
|
||||
// If `??` participated in null-shortening, `0` would be unreachable.
|
||||
ExtensionProperty(x)?.extendedProp ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_explicit_extended_property_if_null_assign_unreachable(
|
||||
HasProperty<int>? x) {
|
||||
ExtensionProperty(x)?.extendedProp ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
class Indexable<T> {
|
||||
/*member: Indexable.[]:doesNotComplete*/
|
||||
T operator [](int index) => throw '';
|
||||
operator []=(int index, T? value) {}
|
||||
}
|
||||
|
||||
void index_if_null_reachable(Indexable<int?> x) {
|
||||
x[0] ?? 0;
|
||||
}
|
||||
|
||||
void index_if_null_unreachable(Indexable<int> x) {
|
||||
x[0] ?? /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void index_if_null_assign_reachable(Indexable<int?> x) {
|
||||
x[0] ??= 0;
|
||||
}
|
||||
|
||||
void index_if_null_assign_unreachable(Indexable<int> x) {
|
||||
x[0] ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void null_aware_index_if_null_reachable(Indexable<int?>? x) {
|
||||
x?[0] ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_index_if_null_unreachable(Indexable<int>? x) {
|
||||
// If `??` participated in null-shortening, `0` would be unreachable.
|
||||
x?[0] ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_index_if_null_assign_reachable(Indexable<int?>? x) {
|
||||
x?[0] ??= 0;
|
||||
}
|
||||
|
||||
void null_aware_index_if_null_assign_unreachable(Indexable<int>? x) {
|
||||
x?[0] ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
class SuperIntQuestionIndex extends Indexable<int?> {
|
||||
void if_null_reachable() {
|
||||
super[0] ?? 0;
|
||||
}
|
||||
|
||||
void if_null_assign_reachable() {
|
||||
super[0] ??= 0;
|
||||
}
|
||||
}
|
||||
|
||||
class SuperIntIndex extends Indexable<int> {
|
||||
void if_null_unreachable() {
|
||||
super[0] ?? /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void if_null_assign_unreachable() {
|
||||
super[0] ??= /*unreachable*/ 0;
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtensionIndex<T> on HasProperty<T> {
|
||||
/*member: ExtensionIndex|[]:doesNotComplete*/
|
||||
T operator [](int index) => prop;
|
||||
operator []=(int index, T? value) {
|
||||
prop = value;
|
||||
}
|
||||
}
|
||||
|
||||
void extended_index_if_null_reachable(HasProperty<int?> x) {
|
||||
x[0] ?? 0;
|
||||
}
|
||||
|
||||
void extended_index_if_null_assign_reachable(HasProperty<int?> x) {
|
||||
x[0] ??= 0;
|
||||
}
|
||||
|
||||
void extended_index_if_null_unreachable(HasProperty<int> x) {
|
||||
x[0] ?? /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void extended_index_if_null_assign_unreachable(HasProperty<int> x) {
|
||||
x[0] ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void null_aware_extended_index_if_null_reachable(HasProperty<int?>? x) {
|
||||
x?[0] ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_extended_index_if_null_assign_reachable(HasProperty<int?>? x) {
|
||||
x?[0] ??= 0;
|
||||
}
|
||||
|
||||
void null_aware_extended_index_if_null_not_shortened(HasProperty<int>? x) {
|
||||
// If `??` participated in null-shortening, `0` would be unreachable.
|
||||
x?[0] ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_extended_index_if_null_assign_unreachable(HasProperty<int>? x) {
|
||||
x?[0] ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void explicit_extended_index_if_null_reachable(HasProperty<int?> x) {
|
||||
ExtensionIndex(x)[0] ?? 0;
|
||||
}
|
||||
|
||||
void explicit_extended_index_if_null_assign_reachable(HasProperty<int?> x) {
|
||||
ExtensionIndex(x)[0] ??= 0;
|
||||
}
|
||||
|
||||
void explicit_extended_index_if_null_unreachable(HasProperty<int> x) {
|
||||
ExtensionIndex(x)[0] ?? /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void explicit_extended_index_if_null_assign_unreachable(HasProperty<int> x) {
|
||||
ExtensionIndex(x)[0] ??= /*unreachable*/ 0;
|
||||
}
|
||||
|
||||
void null_aware_explicit_extended_index_if_null_reachable(
|
||||
HasProperty<int?>? x) {
|
||||
ExtensionIndex(x)?[0] ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_explicit_extended_index_if_null_assign_reachable(
|
||||
HasProperty<int?>? x) {
|
||||
ExtensionIndex(x)?[0] ??= 0;
|
||||
}
|
||||
|
||||
void null_aware_explicit_extended_index_if_null_not_shortened(
|
||||
HasProperty<int>? x) {
|
||||
// If `??` participated in null-shortening, `0` would be unreachable.
|
||||
ExtensionIndex(x)?[0] ?? 0;
|
||||
}
|
||||
|
||||
void null_aware_explicit_extended_index_if_null_assign_unreachable(
|
||||
HasProperty<int>? x) {
|
||||
ExtensionIndex(x)?[0] ??= /*unreachable*/ 0;
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
// 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.
|
||||
|
||||
void index_reachable(List<int>? f()) {
|
||||
f()?[throw ''];
|
||||
0;
|
||||
}
|
||||
|
||||
/*member: index_unreachable:doesNotComplete*/
|
||||
void index_unreachable(List<int> f()) {
|
||||
f()?[throw ''];
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
void cascaded_index_reachable(List<int>? f()) {
|
||||
f()?..[throw ''];
|
||||
0;
|
||||
}
|
||||
|
||||
/*member: cascaded_index_unreachable:doesNotComplete*/
|
||||
void cascaded_index_unreachable(List<int> f()) {
|
||||
f()?..[throw ''];
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
void method_invocation_reachable(int? f()) {
|
||||
f()?.remainder(throw '');
|
||||
0;
|
||||
}
|
||||
|
||||
/*member: method_invocation_unreachable:doesNotComplete*/
|
||||
void method_invocation_unreachable(int f()) {
|
||||
f()?.remainder(throw '');
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
void cascaded_method_invocation_reachable(int? f()) {
|
||||
f()?..remainder(throw '');
|
||||
0;
|
||||
}
|
||||
|
||||
/*member: cascaded_method_invocation_unreachable:doesNotComplete*/
|
||||
void cascaded_method_invocation_unreachable(int f()) {
|
||||
f()?..remainder(throw '');
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
void property_get_reachable(int? f()) {
|
||||
f()?.hashCode.remainder(throw '');
|
||||
0;
|
||||
}
|
||||
|
||||
/*member: property_get_unreachable:doesNotComplete*/
|
||||
void property_get_unreachable(int f()) {
|
||||
f()?.hashCode.remainder(throw '');
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
void cascaded_property_get_reachable(int? f()) {
|
||||
f()?..hashCode.remainder(throw '');
|
||||
0;
|
||||
}
|
||||
|
||||
/*member: cascaded_property_get_unreachable:doesNotComplete*/
|
||||
void cascaded_property_get_unreachable(int f()) {
|
||||
f()?..hashCode.remainder(throw '');
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
void property_get_invocation_reachable(List<void Function(dynamic)>? f()) {
|
||||
// We need a special test case for this because it parses like a method
|
||||
// invocation but the analyzer rewrites it as a property access followed by a
|
||||
// function expression invocation.
|
||||
f()?.first(throw '');
|
||||
0;
|
||||
}
|
||||
|
||||
/*member: property_get_invocation_unreachable:doesNotComplete*/
|
||||
void property_get_invocation_unreachable(List<void Function(dynamic)> f()) {
|
||||
// We need a special test case for this because it parses like a method
|
||||
// invocation but the analyzer rewrites it as a property access followed by a
|
||||
// function expression invocation.
|
||||
f()?.first(throw '');
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
void cascaded_property_get_invocation_reachable(
|
||||
List<void Function(dynamic)>? f()) {
|
||||
// We need a special test case for this because it parses like a method
|
||||
// invocation but the analyzer rewrites it as a property access followed by a
|
||||
// function expression invocation.
|
||||
f()?..first(throw '');
|
||||
0;
|
||||
}
|
||||
|
||||
/*member: cascaded_property_get_invocation_unreachable:doesNotComplete*/
|
||||
void cascaded_property_get_invocation_unreachable(
|
||||
List<void Function(dynamic)> f()) {
|
||||
// We need a special test case for this because it parses like a method
|
||||
// invocation but the analyzer rewrites it as a property access followed by a
|
||||
// function expression invocation.
|
||||
f()?..first(throw '');
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
class C {
|
||||
int field = 0;
|
||||
}
|
||||
|
||||
void property_set_reachable(C? f()) {
|
||||
f()?.field = throw '';
|
||||
0;
|
||||
}
|
||||
|
||||
/*member: property_set_unreachable:doesNotComplete*/
|
||||
void property_set_unreachable(C f()) {
|
||||
f()?.field = throw '';
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
void cascaded_property_set_reachable(C? f()) {
|
||||
f()?..field = throw '';
|
||||
0;
|
||||
}
|
||||
|
||||
/*member: cascaded_property_set_unreachable:doesNotComplete*/
|
||||
void cascaded_property_set_unreachable(C f()) {
|
||||
f()?..field = throw '';
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
// 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 {
|
||||
static void staticMethod(dynamic d) {}
|
||||
/*member: C.staticGetter:doesNotComplete*/
|
||||
static int get staticGetter => 0;
|
||||
/*member: C.staticInvokableGetter:doesNotComplete*/
|
||||
static void Function(dynamic d) get staticInvokableGetter => (_) {};
|
||||
static void set staticSetter(int value) {}
|
||||
}
|
||||
|
||||
/*member: method_invocation_unreachable:doesNotComplete*/
|
||||
void method_invocation_unreachable() {
|
||||
C?.staticMethod(throw '');
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
/*member: property_get_unreachable:doesNotComplete*/
|
||||
void property_get_unreachable() {
|
||||
C?.staticGetter.remainder(throw '');
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
/*member: property_get_invocation_unreachable:doesNotComplete*/
|
||||
void property_get_invocation_unreachable() {
|
||||
// We need a special test case for this because it parses like a method
|
||||
// invocation but the analyzer rewrites it as a property access followed by a
|
||||
// function expression invocation.
|
||||
C?.staticInvokableGetter(throw '');
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
||||
|
||||
/*member: property_set_unreachable:doesNotComplete*/
|
||||
void property_set_unreachable() {
|
||||
C?.staticSetter = throw '';
|
||||
/*stmt: unreachable*/ 0;
|
||||
}
|
|
@ -5,7 +5,6 @@
|
|||
import 'package:analysis_server/src/services/correction/fix.dart';
|
||||
import 'package:analysis_server/src/services/linter/lint_names.dart';
|
||||
import 'package:analyzer/src/dart/analysis/experiments.dart';
|
||||
import 'package:analyzer/src/error/codes.dart';
|
||||
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
|
@ -32,10 +31,7 @@ int f(int a, int b) => a ?? b;
|
|||
''');
|
||||
await assertHasFix('''
|
||||
int f(int a, int b) => a;
|
||||
''', errorFilter: (e) {
|
||||
// See https://github.com/dart-lang/sdk/issues/43263.
|
||||
return e.errorCode != HintCode.DEAD_CODE;
|
||||
});
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_nestedChild() async {
|
||||
|
@ -44,10 +40,7 @@ int f(int a, int b) => a ?? b * 2 + 1;
|
|||
''');
|
||||
await assertHasFix('''
|
||||
int f(int a, int b) => a;
|
||||
''', errorFilter: (e) {
|
||||
// See https://github.com/dart-lang/sdk/issues/43263.
|
||||
return e.errorCode != HintCode.DEAD_CODE;
|
||||
});
|
||||
''');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -466,7 +466,7 @@ class AssignmentExpressionResolver {
|
|||
|
||||
var flow = _flowAnalysis?.flow;
|
||||
if (flow != null && operator == TokenType.QUESTION_QUESTION_EQ) {
|
||||
flow.ifNullExpression_rightBegin(left, leftType);
|
||||
flow.ifNullExpression_rightBegin(left);
|
||||
}
|
||||
|
||||
right?.accept(_resolver);
|
||||
|
|
|
@ -178,7 +178,7 @@ class BinaryExpressionResolver {
|
|||
}
|
||||
InferenceContext.setType(right, rightContextType);
|
||||
|
||||
flow?.ifNullExpression_rightBegin(left, leftType);
|
||||
flow?.ifNullExpression_rightBegin(left);
|
||||
right.accept(_resolver);
|
||||
right = node.rightOperand;
|
||||
flow?.ifNullExpression_end();
|
||||
|
|
|
@ -85,7 +85,7 @@ class FlowAnalysisHelper {
|
|||
if (flow == null) return null;
|
||||
|
||||
if (node.operator.type == TokenType.QUESTION_QUESTION_EQ) {
|
||||
flow.ifNullExpression_rightBegin(node.leftHandSide, node.readType);
|
||||
flow.ifNullExpression_rightBegin(node.leftHandSide);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -850,8 +850,7 @@ class ResolverVisitor extends ScopedVisitor {
|
|||
node.target.accept(this);
|
||||
|
||||
if (node.isNullAware && _isNonNullableByDefault) {
|
||||
_flowAnalysis.flow.nullAwareAccess_rightBegin(
|
||||
node.target, node.target.staticType ?? typeProvider.dynamicType);
|
||||
_flowAnalysis.flow.nullAwareAccess_rightBegin(node.target);
|
||||
_unfinishedNullShorts.add(node.nullShortingTermination);
|
||||
}
|
||||
|
||||
|
@ -1448,8 +1447,7 @@ class ResolverVisitor extends ScopedVisitor {
|
|||
node.target?.accept(this);
|
||||
if (_migratableAstInfoProvider.isIndexExpressionNullAware(node) &&
|
||||
_isNonNullableByDefault) {
|
||||
_flowAnalysis.flow.nullAwareAccess_rightBegin(
|
||||
node.target, node.realTarget.staticType ?? typeProvider.dynamicType);
|
||||
_flowAnalysis.flow.nullAwareAccess_rightBegin(node.target);
|
||||
_unfinishedNullShorts.add(node.nullShortingTermination);
|
||||
}
|
||||
node.accept(elementResolver);
|
||||
|
@ -1536,18 +1534,12 @@ class ResolverVisitor extends ScopedVisitor {
|
|||
|
||||
@override
|
||||
void visitMethodInvocation(MethodInvocation node) {
|
||||
var target = node.target;
|
||||
target?.accept(this);
|
||||
node.target?.accept(this);
|
||||
|
||||
if (_migratableAstInfoProvider.isMethodInvocationNullAware(node) &&
|
||||
_isNonNullableByDefault) {
|
||||
if (target is SimpleIdentifier && target.staticElement is ClassElement) {
|
||||
// `?.` to access static methods is equivalent to `.`, so do nothing.
|
||||
} else {
|
||||
_flowAnalysis.flow.nullAwareAccess_rightBegin(
|
||||
target, node.realTarget.staticType ?? typeProvider.dynamicType);
|
||||
_unfinishedNullShorts.add(node.nullShortingTermination);
|
||||
}
|
||||
_flowAnalysis.flow.nullAwareAccess_rightBegin(node.target);
|
||||
_unfinishedNullShorts.add(node.nullShortingTermination);
|
||||
}
|
||||
|
||||
node.typeArguments?.accept(this);
|
||||
|
@ -1640,17 +1632,11 @@ class ResolverVisitor extends ScopedVisitor {
|
|||
// We visit the target, but do not visit the property name because it needs
|
||||
// to be visited in the context of the property access node.
|
||||
//
|
||||
var target = node.target;
|
||||
target?.accept(this);
|
||||
node.target?.accept(this);
|
||||
if (_migratableAstInfoProvider.isPropertyAccessNullAware(node) &&
|
||||
_isNonNullableByDefault) {
|
||||
if (target is SimpleIdentifier && target.staticElement is ClassElement) {
|
||||
// `?.` to access static methods is equivalent to `.`, so do nothing.
|
||||
} else {
|
||||
_flowAnalysis.flow.nullAwareAccess_rightBegin(
|
||||
target, node.realTarget.staticType ?? typeProvider.dynamicType);
|
||||
_unfinishedNullShorts.add(node.nullShortingTermination);
|
||||
}
|
||||
_flowAnalysis.flow.nullAwareAccess_rightBegin(node.target);
|
||||
_unfinishedNullShorts.add(node.nullShortingTermination);
|
||||
}
|
||||
node.accept(elementResolver);
|
||||
node.accept(typeAnalyzer);
|
||||
|
@ -2020,14 +2006,8 @@ class ResolverVisitor extends ScopedVisitor {
|
|||
if (function is PropertyAccess &&
|
||||
_migratableAstInfoProvider.isPropertyAccessNullAware(function) &&
|
||||
_isNonNullableByDefault) {
|
||||
var target = function.target;
|
||||
if (target is SimpleIdentifier && target.staticElement is ClassElement) {
|
||||
// `?.` to access static methods is equivalent to `.`, so do nothing.
|
||||
} else {
|
||||
_flowAnalysis.flow.nullAwareAccess_rightBegin(function,
|
||||
function.realTarget.staticType ?? typeProvider.dynamicType);
|
||||
_unfinishedNullShorts.add(node.nullShortingTermination);
|
||||
}
|
||||
_flowAnalysis.flow.nullAwareAccess_rightBegin(function);
|
||||
_unfinishedNullShorts.add(node.nullShortingTermination);
|
||||
}
|
||||
|
||||
_functionExpressionInvocationResolver.resolve(node);
|
||||
|
|
|
@ -84,13 +84,6 @@ abstract class AstDataExtractor<T> extends GeneralizingAstVisitor<void>
|
|||
var memberName = element.name;
|
||||
var className = element.enclosingElement.name;
|
||||
return MemberId.internal(memberName, className: className);
|
||||
} else if (element.enclosingElement is ExtensionElement) {
|
||||
var memberName = element.name;
|
||||
var extensionName = element.enclosingElement.name;
|
||||
if (element is PropertyAccessorElement) {
|
||||
memberName = '${element.isGetter ? 'get' : 'set'}#$memberName';
|
||||
}
|
||||
return MemberId.internal('$extensionName|$memberName');
|
||||
}
|
||||
throw UnimplementedError(
|
||||
'TODO(paulberry): $element (${element.runtimeType})');
|
||||
|
|
|
@ -22,16 +22,13 @@ class DeadNullAwareExpressionTest extends PubPackageResolutionTest
|
|||
var x = 0;
|
||||
''');
|
||||
|
||||
await assertErrorsInCode('''
|
||||
await assertNoErrorsInCode('''
|
||||
import 'a.dart';
|
||||
|
||||
f() {
|
||||
x ??= 0;
|
||||
}
|
||||
''', [
|
||||
// See https://github.com/dart-lang/sdk/issues/43263.
|
||||
error(HintCode.DEAD_CODE, 32, 2),
|
||||
]);
|
||||
''');
|
||||
}
|
||||
|
||||
test_assignCompound_map() async {
|
||||
|
@ -54,8 +51,6 @@ f(int x) {
|
|||
}
|
||||
''', [
|
||||
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 19, 1),
|
||||
// See https://github.com/dart-lang/sdk/issues/43263.
|
||||
error(HintCode.DEAD_CODE, 19, 2),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -73,16 +68,13 @@ f(int? x) {
|
|||
var x = 0;
|
||||
''');
|
||||
|
||||
await assertErrorsInCode('''
|
||||
await assertNoErrorsInCode('''
|
||||
import 'a.dart';
|
||||
|
||||
f() {
|
||||
x ?? 0;
|
||||
}
|
||||
''', [
|
||||
// See https://github.com/dart-lang/sdk/issues/43263.
|
||||
error(HintCode.DEAD_CODE, 31, 2),
|
||||
]);
|
||||
''');
|
||||
}
|
||||
|
||||
test_binary_nonNullable() async {
|
||||
|
@ -92,8 +84,6 @@ f(int x) {
|
|||
}
|
||||
''', [
|
||||
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 18, 1),
|
||||
// See https://github.com/dart-lang/sdk/issues/43263.
|
||||
error(HintCode.DEAD_CODE, 18, 2),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -84,8 +84,6 @@ void f() {
|
|||
}
|
||||
''', [
|
||||
_notAssignedError(22, 1),
|
||||
// See https://github.com/dart-lang/sdk/issues/43263.
|
||||
error(HintCode.DEAD_CODE, 28, 2),
|
||||
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 28, 1),
|
||||
]);
|
||||
}
|
||||
|
@ -98,8 +96,6 @@ void f() {
|
|||
}
|
||||
''', [
|
||||
_notAssignedError(22, 1),
|
||||
// See https://github.com/dart-lang/sdk/issues/43263.
|
||||
error(HintCode.DEAD_CODE, 28, 2),
|
||||
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 28, 1),
|
||||
_notAssignedError(28, 1),
|
||||
]);
|
||||
|
@ -122,11 +118,7 @@ void f() {
|
|||
(v = 0) ?? 0;
|
||||
v;
|
||||
}
|
||||
''', [
|
||||
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 33, 1),
|
||||
// See https://github.com/dart-lang/sdk/issues/43263.
|
||||
error(HintCode.DEAD_CODE, 33, 7),
|
||||
]);
|
||||
''', [error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 33, 1)]);
|
||||
}
|
||||
|
||||
test_binaryExpression_ifNull_right() async {
|
||||
|
@ -138,8 +130,6 @@ void f(int a) {
|
|||
}
|
||||
''', [
|
||||
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 32, 7),
|
||||
// See https://github.com/dart-lang/sdk/issues/43263.
|
||||
error(HintCode.DEAD_CODE, 32, 13),
|
||||
_notAssignedError(43, 1),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -409,4 +409,10 @@ class A {
|
|||
int f() => A.x;
|
||||
''');
|
||||
}
|
||||
|
||||
@override
|
||||
@FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/42957')
|
||||
test_typeLiteral_conditionalAccess() {
|
||||
return super.test_typeLiteral_conditionalAccess();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1159,8 +1159,7 @@ class InferenceVisitor
|
|||
lhsResult.inferredType, equalsName, node.fileOffset)
|
||||
.member;
|
||||
|
||||
inferrer.flowAnalysis
|
||||
.ifNullExpression_rightBegin(node.left, lhsResult.inferredType);
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(node.left);
|
||||
// - Let J = T0 if K is `?` else K.
|
||||
// - Infer e1 in context J to get T1
|
||||
ExpressionInferenceResult rhsResult;
|
||||
|
@ -2671,7 +2670,7 @@ class InferenceVisitor
|
|||
Expression read = readResult.expression;
|
||||
DartType readType = readResult.inferredType;
|
||||
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read, readType);
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read);
|
||||
ExpressionInferenceResult writeResult = inferrer
|
||||
.inferExpression(node.write, typeContext, true, isVoidAllowed: true);
|
||||
inferrer.flowAnalysis.ifNullExpression_end();
|
||||
|
@ -2743,7 +2742,7 @@ class InferenceVisitor
|
|||
read = readResult.expression;
|
||||
readType = readResult.inferredType;
|
||||
}
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read, readType);
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read);
|
||||
ExpressionInferenceResult writeResult = inferrer
|
||||
.inferExpression(node.write, typeContext, true, isVoidAllowed: true);
|
||||
inferrer.flowAnalysis.ifNullExpression_end();
|
||||
|
@ -3099,7 +3098,7 @@ class InferenceVisitor
|
|||
readResult.inferredType, "??=", node.readOffset);
|
||||
Expression read = readResult.expression;
|
||||
DartType readType = readResult.inferredType;
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read, readType);
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read);
|
||||
|
||||
Member equalsMember = inferrer
|
||||
.findInterfaceMember(readType, equalsName, node.testOffset)
|
||||
|
@ -3270,7 +3269,7 @@ class InferenceVisitor
|
|||
..fileOffset = node.readOffset;
|
||||
}
|
||||
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read, readType);
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read);
|
||||
ExpressionInferenceResult valueResult = inferrer
|
||||
.inferExpression(node.value, valueType, true, isVoidAllowed: true);
|
||||
Expression value = inferrer.ensureAssignableResult(valueType, valueResult);
|
||||
|
@ -3424,7 +3423,7 @@ class InferenceVisitor
|
|||
readResult.inferredType, "??=", node.readOffset);
|
||||
Expression read = readResult.expression;
|
||||
DartType readType = readResult.inferredType;
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read, readType);
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read);
|
||||
|
||||
Member equalsMember = inferrer
|
||||
.findInterfaceMember(readType, equalsName, node.testOffset)
|
||||
|
@ -4904,7 +4903,7 @@ class InferenceVisitor
|
|||
isThisReceiver: node.receiver is ThisExpression);
|
||||
Expression read = readResult.expression;
|
||||
DartType readType = readResult.inferredType;
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read, readType);
|
||||
inferrer.flowAnalysis.ifNullExpression_rightBegin(read);
|
||||
|
||||
Member readEqualsMember = inferrer
|
||||
.findInterfaceMember(readType, equalsName, node.testOffset)
|
||||
|
|
|
@ -3814,15 +3814,14 @@ class NullAwareGuard {
|
|||
assert(_inferrer != null) {
|
||||
// Ensure the initializer of [_nullAwareVariable] is promoted to
|
||||
// non-nullable.
|
||||
_inferrer.flowAnalysis.nullAwareAccess_rightBegin(
|
||||
_nullAwareVariable.initializer, _nullAwareVariable.type);
|
||||
_inferrer.flowAnalysis
|
||||
.nullAwareAccess_rightBegin(_nullAwareVariable.initializer);
|
||||
// Ensure [_nullAwareVariable] is promoted to non-nullable.
|
||||
// TODO(johnniwinther): Avoid creating a [VariableGet] to promote the
|
||||
// variable.
|
||||
VariableGet read = new VariableGet(_nullAwareVariable);
|
||||
_inferrer.flowAnalysis.variableRead(read, _nullAwareVariable);
|
||||
_inferrer.flowAnalysis
|
||||
.nullAwareAccess_rightBegin(read, _nullAwareVariable.type);
|
||||
_inferrer.flowAnalysis.nullAwareAccess_rightBegin(read);
|
||||
}
|
||||
|
||||
/// Creates the null-guarded application of [nullAwareAction] with the
|
||||
|
|
|
@ -276,7 +276,6 @@ behavior
|
|||
behaviors
|
||||
behind
|
||||
being
|
||||
believes
|
||||
belong
|
||||
belongs
|
||||
below
|
||||
|
|
|
@ -456,7 +456,7 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
|
|||
} else if (operatorType == TokenType.QUESTION_QUESTION) {
|
||||
DecoratedType expressionType;
|
||||
var leftType = _dispatch(leftOperand);
|
||||
_flowAnalysis.ifNullExpression_rightBegin(node.leftOperand, leftType);
|
||||
_flowAnalysis.ifNullExpression_rightBegin(node.leftOperand);
|
||||
try {
|
||||
_guards.add(leftType.node);
|
||||
DecoratedType rightType;
|
||||
|
@ -2210,8 +2210,8 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
|
|||
|
||||
if (questionAssignNode != null) {
|
||||
_guards.add(destinationType.node);
|
||||
_flowAnalysis.ifNullExpression_rightBegin(
|
||||
questionAssignNode.leftHandSide, destinationType);
|
||||
_flowAnalysis
|
||||
.ifNullExpression_rightBegin(questionAssignNode.leftHandSide);
|
||||
}
|
||||
DecoratedType sourceType;
|
||||
try {
|
||||
|
|
Loading…
Reference in a new issue