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:
Paul Berry 2020-09-02 15:02:46 +00:00
parent 7363adca67
commit ab16d79af9
18 changed files with 88 additions and 918 deletions

View file

@ -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

View file

@ -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?');

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
});
''');
}
}

View file

@ -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);

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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})');

View file

@ -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),
]);
}

View file

@ -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),
]);
}

View file

@ -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();
}
}

View file

@ -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)

View file

@ -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

View file

@ -276,7 +276,6 @@ behavior
behaviors
behind
being
believes
belong
belongs
below

View file

@ -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 {