mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:39:48 +00:00
Flow analysis: rework handling of equality tests.
The new logic classifies the LHS and RHS of the equality check as either null, non-nullable, or potentially nullable, and then either promotes, treats the expression as equivalent to a boolean, or does nothing, as appropriate. This means, for example, that a comparison between a non-nullable value and `null` is now known to evaluate to `true` for reachability analysis. Note: as part of this change, it was tempting to trigger promotion whenever a varialbe is equality compared to an expression of type `Null`, but this would be unsound (consider `(int? x) => x == (x = null) ? true : x.isEven`). So we still only promote when the variable is compared to a literal `null`. Fixes #41985. There's a corresponding spec change out for review: https://github.com/dart-lang/language/pull/1134 Change-Id: Id7f1d4eaa3b0fa57124445bb8352eef32c304feb Bug: https://github.com/dart-lang/sdk/issues/41985 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/155926 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
parent
2adaa2f93a
commit
e403e6ae77
|
@ -363,11 +363,12 @@ abstract class FlowAnalysis<Node, Statement extends Node, Expression, Variable,
|
|||
|
||||
/// Call this method just after visiting a binary `==` or `!=` expression.
|
||||
void equalityOp_end(Expression wholeExpression, Expression rightOperand,
|
||||
Type rightOperandType,
|
||||
{bool notEqual = false});
|
||||
|
||||
/// Call this method just after visiting the left hand side of a binary `==`
|
||||
/// or `!=` expression.
|
||||
void equalityOp_rightBegin(Expression leftOperand);
|
||||
void equalityOp_rightBegin(Expression leftOperand, Type leftOperandType);
|
||||
|
||||
/// This method should be called at the conclusion of flow analysis for a top
|
||||
/// level function or method. Performs assertion checks.
|
||||
|
@ -811,17 +812,20 @@ class FlowAnalysisDebug<Node, Statement extends Node, Expression, Variable,
|
|||
|
||||
@override
|
||||
void equalityOp_end(Expression wholeExpression, Expression rightOperand,
|
||||
Type rightOperandType,
|
||||
{bool notEqual = false}) {
|
||||
_wrap(
|
||||
'equalityOp_end($wholeExpression, $rightOperand, notEqual: $notEqual)',
|
||||
() => _wrapped.equalityOp_end(wholeExpression, rightOperand,
|
||||
'equalityOp_end($wholeExpression, $rightOperand, $rightOperandType, '
|
||||
'notEqual: $notEqual)',
|
||||
() => _wrapped.equalityOp_end(
|
||||
wholeExpression, rightOperand, rightOperandType,
|
||||
notEqual: notEqual));
|
||||
}
|
||||
|
||||
@override
|
||||
void equalityOp_rightBegin(Expression leftOperand) {
|
||||
_wrap('equalityOp_rightBegin($leftOperand)',
|
||||
() => _wrapped.equalityOp_rightBegin(leftOperand));
|
||||
void equalityOp_rightBegin(Expression leftOperand, Type leftOperandType) {
|
||||
_wrap('equalityOp_rightBegin($leftOperand, $leftOperandType)',
|
||||
() => _wrapped.equalityOp_rightBegin(leftOperand, leftOperandType));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1651,8 +1655,26 @@ class FlowModel<Variable, Type> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Enum representing the different classifications of types that can be
|
||||
/// returned by [TypeOperations.classifyType].
|
||||
enum TypeClassification {
|
||||
/// The type is `Null` or an equivalent type (e.g. `Never?`)
|
||||
nullOrEquivalent,
|
||||
|
||||
/// The type is a potentially nullable type, but not equivalent to `Null`
|
||||
/// (e.g. `int?`, or a type variable whose bound is potentially nullable)
|
||||
potentiallyNullable,
|
||||
|
||||
/// The type is a non-nullable type.
|
||||
nonNullable,
|
||||
}
|
||||
|
||||
/// Operations on types, abstracted from concrete type interfaces.
|
||||
abstract class TypeOperations<Variable, Type> {
|
||||
/// Classifies the given type into one of the three categories defined by
|
||||
/// the [TypeClassification] enum.
|
||||
TypeClassification classifyType(Type type);
|
||||
|
||||
/// Returns the "remainder" of [from] when [what] has been removed from
|
||||
/// consideration by an instance check.
|
||||
Type factor(Type from, Type what);
|
||||
|
@ -2277,6 +2299,21 @@ class _ConditionalContext<Variable, Type>
|
|||
'thenInfo: $_thenInfo)';
|
||||
}
|
||||
|
||||
/// [_FlowContext] representing an equality comparison using `==` or `!=`.
|
||||
class _EqualityOpContext<Variable, Type> extends _BranchContext {
|
||||
/// The type of the expression on the LHS of `==` or `!=`.
|
||||
final Type _leftOperandType;
|
||||
|
||||
_EqualityOpContext(
|
||||
ExpressionInfo<Variable, Type> conditionInfo, this._leftOperandType)
|
||||
: super(conditionInfo);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'_EqualityOpContext(conditionInfo: $_conditionInfo, lhsType: '
|
||||
'$_leftOperandType)';
|
||||
}
|
||||
|
||||
class _FlowAnalysisImpl<Node, Statement extends Node, Expression, Variable,
|
||||
Type> implements FlowAnalysis<Node, Statement, Expression, Variable, Type> {
|
||||
/// The [TypeOperations], used to access types, and check subtyping.
|
||||
|
@ -2420,31 +2457,49 @@ class _FlowAnalysisImpl<Node, Statement extends Node, Expression, Variable,
|
|||
|
||||
@override
|
||||
void equalityOp_end(Expression wholeExpression, Expression rightOperand,
|
||||
Type rightOperandType,
|
||||
{bool notEqual = false}) {
|
||||
_BranchContext<Variable, Type> context =
|
||||
_stack.removeLast() as _BranchContext<Variable, Type>;
|
||||
_EqualityOpContext<Variable, Type> context =
|
||||
_stack.removeLast() as _EqualityOpContext<Variable, Type>;
|
||||
ExpressionInfo<Variable, Type> lhsInfo = context._conditionInfo;
|
||||
Type leftOperandType = context._leftOperandType;
|
||||
ExpressionInfo<Variable, Type> rhsInfo = _getExpressionInfo(rightOperand);
|
||||
Variable variable;
|
||||
if (lhsInfo is _NullInfo<Variable, Type> &&
|
||||
ExpressionInfo<Variable, Type> equalityInfo;
|
||||
TypeClassification leftOperandTypeClassification =
|
||||
typeOperations.classifyType(leftOperandType);
|
||||
TypeClassification rightOperandTypeClassification =
|
||||
typeOperations.classifyType(rightOperandType);
|
||||
if (leftOperandTypeClassification == TypeClassification.nullOrEquivalent &&
|
||||
rightOperandTypeClassification == TypeClassification.nullOrEquivalent) {
|
||||
return booleanLiteral(wholeExpression, !notEqual);
|
||||
} else if ((leftOperandTypeClassification ==
|
||||
TypeClassification.nullOrEquivalent &&
|
||||
rightOperandTypeClassification == TypeClassification.nonNullable) ||
|
||||
(rightOperandTypeClassification ==
|
||||
TypeClassification.nullOrEquivalent &&
|
||||
leftOperandTypeClassification == TypeClassification.nonNullable)) {
|
||||
return booleanLiteral(wholeExpression, notEqual);
|
||||
} else if (lhsInfo is _NullInfo<Variable, Type> &&
|
||||
rhsInfo is _VariableReadInfo<Variable, Type>) {
|
||||
variable = rhsInfo._variable;
|
||||
assert(
|
||||
leftOperandTypeClassification == TypeClassification.nullOrEquivalent);
|
||||
equalityInfo =
|
||||
_current.tryMarkNonNullable(typeOperations, rhsInfo._variable);
|
||||
} else if (rhsInfo is _NullInfo<Variable, Type> &&
|
||||
lhsInfo is _VariableReadInfo<Variable, Type>) {
|
||||
variable = lhsInfo._variable;
|
||||
equalityInfo =
|
||||
_current.tryMarkNonNullable(typeOperations, lhsInfo._variable);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
ExpressionInfo<Variable, Type> expressionInfo =
|
||||
_current.tryMarkNonNullable(typeOperations, variable);
|
||||
_storeExpressionInfo(wholeExpression,
|
||||
notEqual ? expressionInfo : ExpressionInfo.invert(expressionInfo));
|
||||
notEqual ? equalityInfo : ExpressionInfo.invert(equalityInfo));
|
||||
}
|
||||
|
||||
@override
|
||||
void equalityOp_rightBegin(Expression leftOperand) {
|
||||
_stack.add(
|
||||
new _BranchContext<Variable, Type>(_getExpressionInfo(leftOperand)));
|
||||
void equalityOp_rightBegin(Expression leftOperand, Type leftOperandType) {
|
||||
_stack.add(new _EqualityOpContext<Variable, Type>(
|
||||
_getExpressionInfo(leftOperand), leftOperandType));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -8,7 +8,7 @@ ifNull_left() {
|
|||
v;
|
||||
}
|
||||
|
||||
ifNull_right(int a) {
|
||||
ifNull_right(int? a) {
|
||||
late int v;
|
||||
a ?? (v = 0);
|
||||
v;
|
||||
|
|
|
@ -33,7 +33,7 @@ main() {
|
|||
var x = h.addVar('x', 'int?');
|
||||
h.run((flow) {
|
||||
flow.assert_begin();
|
||||
var expr = h.eqNull(x)();
|
||||
var expr = h.eqNull(x, _Type('int?'))();
|
||||
flow.assert_afterCondition(expr);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.assert_end();
|
||||
|
@ -55,7 +55,8 @@ main() {
|
|||
flow.assert_begin();
|
||||
flow.write(x, _Type('int?'));
|
||||
flow.write(z, _Type('int?'));
|
||||
var expr = h.and(h.notNull(x), h.notNull(y))();
|
||||
var expr =
|
||||
h.and(h.notNull(x, _Type('int?')), h.notNull(y, _Type('int?')))();
|
||||
flow.assert_afterCondition(expr);
|
||||
flow.assert_end();
|
||||
// x should be promoted because it was promoted before the assert, and
|
||||
|
@ -75,7 +76,7 @@ main() {
|
|||
var x = h.addVar('x', 'int?');
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
flow.conditional_thenBegin(h.notNull(x)());
|
||||
flow.conditional_thenBegin(h.notNull(x, _Type('int?'))());
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.conditional_elseBegin(_Expression());
|
||||
expect(flow.promotedType(x), isNull);
|
||||
|
@ -89,7 +90,7 @@ main() {
|
|||
var x = h.addVar('x', 'int?');
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
flow.conditional_thenBegin(h.eqNull(x)());
|
||||
flow.conditional_thenBegin(h.eqNull(x, _Type('int?'))());
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.conditional_elseBegin(_Expression());
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
|
@ -134,8 +135,12 @@ main() {
|
|||
h.declare(y, initialized: true);
|
||||
h.declare(z, initialized: true);
|
||||
h.if_(
|
||||
h.conditional(h.expr, h.and(h.notNull(x), h.notNull(y)),
|
||||
h.and(h.notNull(x), h.notNull(z))), () {
|
||||
h.conditional(
|
||||
h.expr,
|
||||
h.and(h.notNull(x, _Type('int?')), h.notNull(y, _Type('int?'))),
|
||||
h.and(
|
||||
h.notNull(x, _Type('int?')), h.notNull(z, _Type('int?')))),
|
||||
() {
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
expect(flow.promotedType(y), isNull);
|
||||
expect(flow.promotedType(z), isNull);
|
||||
|
@ -157,8 +162,10 @@ main() {
|
|||
h.declare(y, initialized: true);
|
||||
h.declare(z, initialized: true);
|
||||
h.ifElse(
|
||||
h.conditional(h.expr, h.or(h.eqNull(x), h.eqNull(y)),
|
||||
h.or(h.eqNull(x), h.eqNull(z))),
|
||||
h.conditional(
|
||||
h.expr,
|
||||
h.or(h.eqNull(x, _Type('int?')), h.eqNull(y, _Type('int?'))),
|
||||
h.or(h.eqNull(x, _Type('int?')), h.eqNull(z, _Type('int?')))),
|
||||
() {}, () {
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
expect(flow.promotedType(y), isNull);
|
||||
|
@ -174,11 +181,11 @@ main() {
|
|||
h.declare(x, initialized: true);
|
||||
var varExpr = _Expression();
|
||||
flow.variableRead(varExpr, x);
|
||||
flow.equalityOp_rightBegin(varExpr);
|
||||
flow.equalityOp_rightBegin(varExpr, _Type('int?'));
|
||||
var nullExpr = _Expression();
|
||||
flow.nullLiteral(nullExpr);
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, nullExpr, notEqual: true);
|
||||
flow.equalityOp_end(expr, nullExpr, _Type('Null'), notEqual: true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.ifStatement_elseBegin();
|
||||
|
@ -187,6 +194,25 @@ main() {
|
|||
});
|
||||
});
|
||||
|
||||
test('equalityOp(x != <null expr>) does not promote', () {
|
||||
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();
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, nullExpr, _Type('Null'), notEqual: true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(x == null) promotes false branch', () {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', 'int?');
|
||||
|
@ -194,11 +220,11 @@ main() {
|
|||
h.declare(x, initialized: true);
|
||||
var varExpr = _Expression();
|
||||
flow.variableRead(varExpr, x);
|
||||
flow.equalityOp_rightBegin(varExpr);
|
||||
flow.equalityOp_rightBegin(varExpr, _Type('int?'));
|
||||
var nullExpr = _Expression();
|
||||
flow.nullLiteral(nullExpr);
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, nullExpr, notEqual: false);
|
||||
flow.equalityOp_end(expr, nullExpr, _Type('Null'), notEqual: false);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_elseBegin();
|
||||
|
@ -214,11 +240,11 @@ main() {
|
|||
h.declare(x, initialized: true);
|
||||
var nullExpr = _Expression();
|
||||
flow.nullLiteral(nullExpr);
|
||||
flow.equalityOp_rightBegin(nullExpr);
|
||||
flow.equalityOp_rightBegin(nullExpr, _Type('Null'));
|
||||
var varExpr = _Expression();
|
||||
flow.variableRead(varExpr, x);
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, varExpr, notEqual: true);
|
||||
flow.equalityOp_end(expr, varExpr, _Type('int?'), notEqual: true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.ifStatement_elseBegin();
|
||||
|
@ -227,6 +253,25 @@ main() {
|
|||
});
|
||||
});
|
||||
|
||||
test('equalityOp(<null expr> != x) does not promote', () {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', 'int?');
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
var nullExpr = _Expression();
|
||||
flow.equalityOp_rightBegin(nullExpr, _Type('Null'));
|
||||
var varExpr = _Expression();
|
||||
flow.variableRead(varExpr, x);
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, varExpr, _Type('int?'), notEqual: true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(null == x) promotes false branch', () {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', 'int?');
|
||||
|
@ -234,11 +279,11 @@ main() {
|
|||
h.declare(x, initialized: true);
|
||||
var nullExpr = _Expression();
|
||||
flow.nullLiteral(nullExpr);
|
||||
flow.equalityOp_rightBegin(nullExpr);
|
||||
flow.equalityOp_rightBegin(nullExpr, _Type('Null'));
|
||||
var varExpr = _Expression();
|
||||
flow.variableRead(varExpr, x);
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, varExpr, notEqual: false);
|
||||
flow.equalityOp_end(expr, varExpr, _Type('int?'), notEqual: false);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.ifStatement_elseBegin();
|
||||
|
@ -247,6 +292,102 @@ main() {
|
|||
});
|
||||
});
|
||||
|
||||
test('equalityOp(null == null) equivalent to true', () {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
var null1 = _Expression();
|
||||
flow.equalityOp_rightBegin(null1, _Type('Null'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'));
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, false);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(null != null) equivalent to false', () {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
var null1 = _Expression();
|
||||
flow.equalityOp_rightBegin(null1, _Type('Null'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'), notEqual: true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, false);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(null == non-null) equivalent to false', () {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
var null1 = _Expression();
|
||||
flow.equalityOp_rightBegin(null1, _Type('Null'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, null2, _Type('int'));
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, false);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(null != non-null) equivalent to true', () {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
var null1 = _Expression();
|
||||
flow.equalityOp_rightBegin(null1, _Type('Null'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, null2, _Type('int'), notEqual: true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, false);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(non-null == null) equivalent to false', () {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
var null1 = _Expression();
|
||||
flow.equalityOp_rightBegin(null1, _Type('int'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'));
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, false);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('equalityOp(non-null != null) equivalent to true', () {
|
||||
var h = _Harness();
|
||||
h.run((flow) {
|
||||
var null1 = _Expression();
|
||||
flow.equalityOp_rightBegin(null1, _Type('int'));
|
||||
var null2 = _Expression();
|
||||
var expr = _Expression();
|
||||
flow.equalityOp_end(expr, null2, _Type('Null'), notEqual: true);
|
||||
flow.ifStatement_thenBegin(expr);
|
||||
expect(flow.isReachable, true);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.isReachable, false);
|
||||
flow.ifStatement_end(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('conditionEqNull() does not promote write-captured vars', () {
|
||||
var h = _Harness();
|
||||
var x = h.addVar('x', 'int?');
|
||||
|
@ -255,13 +396,13 @@ main() {
|
|||
(vars) => vars.function(functionNode, () => vars.write(x)));
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
h.if_(h.notNull(x), () {
|
||||
h.if_(h.notNull(x, _Type('int?')), () {
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
});
|
||||
h.function(functionNode, () {
|
||||
flow.write(x, _Type('int?'));
|
||||
});
|
||||
h.if_(h.notNull(x), () {
|
||||
h.if_(h.notNull(x, _Type('int?')), () {
|
||||
expect(flow.promotedType(x), isNull);
|
||||
});
|
||||
});
|
||||
|
@ -314,7 +455,7 @@ main() {
|
|||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
flow.doStatement_bodyBegin(stmt);
|
||||
h.if_(h.notNull(x), () {
|
||||
h.if_(h.notNull(x, _Type('int?')), () {
|
||||
flow.handleContinue(stmt);
|
||||
});
|
||||
flow.handleExit();
|
||||
|
@ -337,7 +478,7 @@ main() {
|
|||
flow.doStatement_bodyBegin(stmt);
|
||||
flow.doStatement_conditionBegin();
|
||||
expect(flow.promotedType(x), isNull);
|
||||
flow.doStatement_end(h.eqNull(x)());
|
||||
flow.doStatement_end(h.eqNull(x, _Type('int?'))());
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
});
|
||||
});
|
||||
|
@ -432,7 +573,7 @@ main() {
|
|||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
flow.for_conditionBegin(stmt);
|
||||
flow.for_bodyBegin(stmt, h.notNull(x)());
|
||||
flow.for_bodyBegin(stmt, h.notNull(x, _Type('int?'))());
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.for_updaterBegin();
|
||||
flow.for_end();
|
||||
|
@ -448,7 +589,7 @@ main() {
|
|||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
flow.for_conditionBegin(node);
|
||||
flow.for_bodyBegin(null, h.notNull(x)());
|
||||
flow.for_bodyBegin(null, h.notNull(x, _Type('int?'))());
|
||||
flow.for_updaterBegin();
|
||||
flow.for_end();
|
||||
});
|
||||
|
@ -500,7 +641,8 @@ main() {
|
|||
h.declare(y, initialized: true);
|
||||
h.declare(z, initialized: true);
|
||||
flow.for_conditionBegin(stmt);
|
||||
flow.for_bodyBegin(stmt, h.or(h.eqNull(x), h.eqNull(z))());
|
||||
flow.for_bodyBegin(stmt,
|
||||
h.or(h.eqNull(x, _Type('int?')), h.eqNull(z, _Type('int?')))());
|
||||
h.if_(h.expr, () {
|
||||
h.promote(x, 'int');
|
||||
h.promote(y, 'int');
|
||||
|
@ -782,7 +924,7 @@ main() {
|
|||
var x = h.addVar('x', 'int?');
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
flow.ifStatement_thenBegin(h.eqNull(x)());
|
||||
flow.ifStatement_thenBegin(h.eqNull(x, _Type('int?'))());
|
||||
flow.handleExit();
|
||||
flow.ifStatement_end(false);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
|
@ -904,7 +1046,8 @@ main() {
|
|||
var x = h.addVar('x', 'int?');
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
flow.logicalBinaryOp_rightBegin(h.notNull(x)(), isAnd: true);
|
||||
flow.logicalBinaryOp_rightBegin(h.notNull(x, _Type('int?'))(),
|
||||
isAnd: true);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.logicalBinaryOp_end(_Expression(), _Expression(), isAnd: true);
|
||||
});
|
||||
|
@ -917,7 +1060,8 @@ main() {
|
|||
h.declare(x, initialized: true);
|
||||
flow.logicalBinaryOp_rightBegin(_Expression(), isAnd: true);
|
||||
var wholeExpr = _Expression();
|
||||
flow.logicalBinaryOp_end(wholeExpr, h.notNull(x)(), isAnd: true);
|
||||
flow.logicalBinaryOp_end(wholeExpr, h.notNull(x, _Type('int?'))(),
|
||||
isAnd: true);
|
||||
flow.ifStatement_thenBegin(wholeExpr);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.ifStatement_end(false);
|
||||
|
@ -932,7 +1076,8 @@ main() {
|
|||
h.declare(x, initialized: true);
|
||||
flow.logicalBinaryOp_rightBegin(_Expression(), isAnd: false);
|
||||
var wholeExpr = _Expression();
|
||||
flow.logicalBinaryOp_end(wholeExpr, h.eqNull(x)(), isAnd: false);
|
||||
flow.logicalBinaryOp_end(wholeExpr, h.eqNull(x, _Type('int?'))(),
|
||||
isAnd: false);
|
||||
flow.ifStatement_thenBegin(wholeExpr);
|
||||
flow.ifStatement_elseBegin();
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
|
@ -945,7 +1090,8 @@ main() {
|
|||
var x = h.addVar('x', 'int?');
|
||||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
flow.logicalBinaryOp_rightBegin(h.eqNull(x)(), isAnd: false);
|
||||
flow.logicalBinaryOp_rightBegin(h.eqNull(x, _Type('int?'))(),
|
||||
isAnd: false);
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.logicalBinaryOp_end(_Expression(), _Expression(), isAnd: false);
|
||||
});
|
||||
|
@ -961,7 +1107,8 @@ main() {
|
|||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
h.declare(y, initialized: true);
|
||||
h.if_(h.and(h.notNull(x), h.notNull(y)), () {
|
||||
h.if_(h.and(h.notNull(x, _Type('int?')), h.notNull(y, _Type('int?'))),
|
||||
() {
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
expect(flow.promotedType(y).type, 'int');
|
||||
});
|
||||
|
@ -978,7 +1125,9 @@ main() {
|
|||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
h.declare(y, initialized: true);
|
||||
h.ifElse(h.or(h.eqNull(x), h.eqNull(y)), () {}, () {
|
||||
h.ifElse(
|
||||
h.or(h.eqNull(x, _Type('int?')), h.eqNull(y, _Type('int?'))), () {},
|
||||
() {
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
expect(flow.promotedType(y).type, 'int');
|
||||
});
|
||||
|
@ -1046,8 +1195,11 @@ main() {
|
|||
var x = h.addVar('x', 'int?');
|
||||
h.run((flow) {
|
||||
h.if_(
|
||||
h.parenthesized(h.notEqual(h.parenthesized(h.variableRead(x)),
|
||||
h.parenthesized(h.nullLiteral))), () {
|
||||
h.parenthesized(h.notEqual(
|
||||
h.parenthesized(h.variableRead(x)),
|
||||
_Type('int?'),
|
||||
h.parenthesized(h.nullLiteral),
|
||||
_Type('Null'))), () {
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
});
|
||||
});
|
||||
|
@ -1625,7 +1777,7 @@ main() {
|
|||
h.run((flow) {
|
||||
h.declare(x, initialized: true);
|
||||
flow.whileStatement_conditionBegin(stmt);
|
||||
flow.whileStatement_bodyBegin(stmt, h.notNull(x)());
|
||||
flow.whileStatement_bodyBegin(stmt, h.notNull(x, _Type('int?'))());
|
||||
expect(flow.promotedType(x).type, 'int');
|
||||
flow.whileStatement_end();
|
||||
});
|
||||
|
@ -1646,7 +1798,8 @@ main() {
|
|||
h.declare(y, initialized: true);
|
||||
h.declare(z, initialized: true);
|
||||
flow.whileStatement_conditionBegin(stmt);
|
||||
flow.whileStatement_bodyBegin(stmt, h.or(h.eqNull(x), h.eqNull(z))());
|
||||
flow.whileStatement_bodyBegin(stmt,
|
||||
h.or(h.eqNull(x, _Type('int?')), h.eqNull(z, _Type('int?')))());
|
||||
h.if_(h.expr, () {
|
||||
h.promote(x, 'int');
|
||||
h.promote(y, 'int');
|
||||
|
@ -3133,6 +3286,7 @@ class _Harness extends TypeOperations<_Var, _Type> {
|
|||
'int <: int?': true,
|
||||
'int <: Iterable': false,
|
||||
'int <: List': false,
|
||||
'int <: Null': false,
|
||||
'int <: num': true,
|
||||
'int <: num?': true,
|
||||
'int <: num*': true,
|
||||
|
@ -3141,10 +3295,13 @@ class _Harness extends TypeOperations<_Var, _Type> {
|
|||
'int <: Object?': true,
|
||||
'int <: String': false,
|
||||
'int? <: int': false,
|
||||
'int? <: Null': false,
|
||||
'int? <: num': false,
|
||||
'int? <: num?': true,
|
||||
'int? <: Object': false,
|
||||
'int? <: Object?': true,
|
||||
'Null <: int': false,
|
||||
'Null <: Object': false,
|
||||
'num <: int': false,
|
||||
'num <: Iterable': false,
|
||||
'num <: List': false,
|
||||
|
@ -3175,6 +3332,7 @@ class _Harness extends TypeOperations<_Var, _Type> {
|
|||
'Never? <: int?': true,
|
||||
'Never? <: num?': true,
|
||||
'Never? <: Object?': true,
|
||||
'Null <: int?': true,
|
||||
'Object <: int': false,
|
||||
'Object <: int?': false,
|
||||
'Object <: List': false,
|
||||
|
@ -3204,6 +3362,7 @@ class _Harness extends TypeOperations<_Var, _Type> {
|
|||
'int? - int': _Type('Never?'),
|
||||
'int? - int?': _Type('Never'),
|
||||
'int? - String': _Type('int?'),
|
||||
'Null - int': _Type('Null'),
|
||||
'num - int': _Type('num'),
|
||||
'num? - num': _Type('Never?'),
|
||||
'num? - int': _Type('num?'),
|
||||
|
@ -3270,6 +3429,17 @@ class _Harness extends TypeOperations<_Var, _Type> {
|
|||
callback(_AssignedVariablesHarness(_assignedVariables));
|
||||
}
|
||||
|
||||
@override
|
||||
TypeClassification classifyType(_Type type) {
|
||||
if (isSubtypeOf(type, _Type('Object'))) {
|
||||
return TypeClassification.nonNullable;
|
||||
} else if (isSubtypeOf(type, _Type('Null'))) {
|
||||
return TypeClassification.nullOrEquivalent;
|
||||
} else {
|
||||
return TypeClassification.potentiallyNullable;
|
||||
}
|
||||
}
|
||||
|
||||
/// Given three [LazyExpression]s, produces a new [LazyExpression]
|
||||
/// representing the result of combining them with `?` and `:`.
|
||||
LazyExpression conditional(
|
||||
|
@ -3293,15 +3463,15 @@ class _Harness extends TypeOperations<_Var, _Type> {
|
|||
|
||||
/// Creates a [LazyExpression] representing an `== null` check performed on
|
||||
/// [variable].
|
||||
LazyExpression eqNull(_Var variable) {
|
||||
LazyExpression eqNull(_Var variable, _Type type) {
|
||||
return () {
|
||||
var varExpr = _Expression();
|
||||
_flow.variableRead(varExpr, variable);
|
||||
_flow.equalityOp_rightBegin(varExpr);
|
||||
_flow.equalityOp_rightBegin(varExpr, type);
|
||||
var nullExpr = _Expression();
|
||||
_flow.nullLiteral(nullExpr);
|
||||
var expr = _Expression();
|
||||
_flow.equalityOp_end(expr, nullExpr, notEqual: false);
|
||||
_flow.equalityOp_end(expr, nullExpr, _Type('Null'), notEqual: false);
|
||||
return expr;
|
||||
};
|
||||
}
|
||||
|
@ -3391,26 +3561,27 @@ class _Harness extends TypeOperations<_Var, _Type> {
|
|||
|
||||
/// Creates a [LazyExpression] representing an equality check between two
|
||||
/// other expressions.
|
||||
LazyExpression notEqual(LazyExpression lhs, LazyExpression rhs) {
|
||||
LazyExpression notEqual(
|
||||
LazyExpression lhs, _Type lhsType, LazyExpression rhs, _Type rhsType) {
|
||||
return () {
|
||||
var expr = _Expression();
|
||||
_flow.equalityOp_rightBegin(lhs());
|
||||
_flow.equalityOp_end(expr, rhs(), notEqual: true);
|
||||
_flow.equalityOp_rightBegin(lhs(), lhsType);
|
||||
_flow.equalityOp_end(expr, rhs(), rhsType, notEqual: true);
|
||||
return expr;
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a [LazyExpression] representing a `!= null` check performed on
|
||||
/// [variable].
|
||||
LazyExpression notNull(_Var variable) {
|
||||
LazyExpression notNull(_Var variable, _Type type) {
|
||||
return () {
|
||||
var varExpr = _Expression();
|
||||
_flow.variableRead(varExpr, variable);
|
||||
_flow.equalityOp_rightBegin(varExpr);
|
||||
_flow.equalityOp_rightBegin(varExpr, type);
|
||||
var nullExpr = _Expression();
|
||||
_flow.nullLiteral(nullExpr);
|
||||
var expr = _Expression();
|
||||
_flow.equalityOp_end(expr, nullExpr, notEqual: true);
|
||||
_flow.equalityOp_end(expr, nullExpr, _Type('Null'), notEqual: true);
|
||||
return expr;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
// 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.
|
||||
|
||||
Null nullExpr = null;
|
||||
|
||||
void var_eq_null(int? x) {
|
||||
if (x == null) {
|
||||
x;
|
||||
} else {
|
||||
/*nonNullable*/ x;
|
||||
}
|
||||
}
|
||||
|
||||
void var_notEq_null(int? x) {
|
||||
if (x != null) {
|
||||
/*nonNullable*/ x;
|
||||
} else {
|
||||
x;
|
||||
}
|
||||
}
|
||||
|
||||
void null_eq_var(int? x) {
|
||||
if (null == x) {
|
||||
x;
|
||||
} else {
|
||||
/*nonNullable*/ x;
|
||||
}
|
||||
}
|
||||
|
||||
void null_notEq_var(int? x) {
|
||||
if (null != x) {
|
||||
/*nonNullable*/ x;
|
||||
} else {
|
||||
x;
|
||||
}
|
||||
}
|
||||
|
||||
void var_eq_nullExpr(int? x) {
|
||||
if (x == nullExpr) {
|
||||
x;
|
||||
} else {
|
||||
x;
|
||||
}
|
||||
}
|
||||
|
||||
void var_notEq_nullExpr(int? x) {
|
||||
if (x != nullExpr) {
|
||||
x;
|
||||
} else {
|
||||
x;
|
||||
}
|
||||
}
|
||||
|
||||
void nullExpr_eq_var(int? x) {
|
||||
if (nullExpr == x) {
|
||||
x;
|
||||
} else {
|
||||
x;
|
||||
}
|
||||
}
|
||||
|
||||
void nullExpr_notEq_var(int? x) {
|
||||
if (nullExpr != x) {
|
||||
x;
|
||||
} else {
|
||||
x;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
// 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 nullValue(Null x) {
|
||||
if (x == null) {
|
||||
1;
|
||||
} else /*unreachable*/ {
|
||||
/*stmt: unreachable*/ 2;
|
||||
}
|
||||
}
|
||||
|
||||
void neverQuestionValue(Never? x) {
|
||||
if (x == null) {
|
||||
1;
|
||||
} else /*unreachable*/ {
|
||||
/*stmt: unreachable*/ 2;
|
||||
}
|
||||
}
|
||||
|
||||
void dynamicValue(dynamic x) {
|
||||
if (x == null) {
|
||||
1;
|
||||
} else {
|
||||
2;
|
||||
}
|
||||
}
|
||||
|
||||
void nullableValue(int? x) {
|
||||
if (x == null) {
|
||||
1;
|
||||
} else {
|
||||
2;
|
||||
}
|
||||
}
|
||||
|
||||
void nonNullableValue(int x) {
|
||||
if (x == null) /*unreachable*/ {
|
||||
/*stmt: unreachable*/ 1;
|
||||
} else {
|
||||
2;
|
||||
}
|
||||
}
|
||||
|
||||
void potentiallyNullableTypeVar_noBound<T>(T x) {
|
||||
if (x == null) {
|
||||
1;
|
||||
} else {
|
||||
2;
|
||||
}
|
||||
}
|
||||
|
||||
void potentiallyNullableTypeVar_dynamicBound<T extends dynamic>(T x) {
|
||||
if (x == null) {
|
||||
1;
|
||||
} else {
|
||||
2;
|
||||
}
|
||||
}
|
||||
|
||||
void potentiallyNullableTypeVar_nullableBound<T extends Object?>(T x) {
|
||||
if (x == null) {
|
||||
1;
|
||||
} else {
|
||||
2;
|
||||
}
|
||||
}
|
||||
|
||||
void nonNullableTypeVar<T extends Object>(T x) {
|
||||
if (x == null) /*unreachable*/ {
|
||||
/*stmt: unreachable*/ 1;
|
||||
} else {
|
||||
2;
|
||||
}
|
||||
}
|
||||
|
||||
void nullTypeVar<T extends Null>(T x) {
|
||||
if (x == null) {
|
||||
1;
|
||||
} else /*unreachable*/ {
|
||||
/*stmt: unreachable*/ 2;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ void ifNull(Object x) {
|
|||
/*num*/ x;
|
||||
}
|
||||
|
||||
void ifNull_rightUnPromote(Object x, Object y, Object z) {
|
||||
void ifNull_rightUnPromote(Object x, Object? y, Object z) {
|
||||
if (x is int) {
|
||||
/*int*/ x;
|
||||
y ?? (x = z);
|
||||
|
|
|
@ -18,9 +18,9 @@ promotesNullableType(int? x) {
|
|||
}
|
||||
}
|
||||
|
||||
promotesNullType(Null x) {
|
||||
doesNotPromoteNullType(Null x) {
|
||||
if (x != null) {
|
||||
/*Never*/ x;
|
||||
x;
|
||||
} else {
|
||||
x;
|
||||
}
|
||||
|
|
|
@ -136,13 +136,13 @@ class BinaryExpressionResolver {
|
|||
left = node.leftOperand;
|
||||
|
||||
var flow = _flowAnalysis?.flow;
|
||||
flow?.equalityOp_rightBegin(left);
|
||||
flow?.equalityOp_rightBegin(left, left.staticType);
|
||||
|
||||
var right = node.rightOperand;
|
||||
right.accept(_resolver);
|
||||
right = node.rightOperand;
|
||||
|
||||
flow?.equalityOp_end(node, right, notEqual: notEqual);
|
||||
flow?.equalityOp_end(node, right, right.staticType, notEqual: notEqual);
|
||||
|
||||
_resolveUserDefinableElement(
|
||||
node,
|
||||
|
|
|
@ -350,6 +350,17 @@ class TypeSystemTypeOperations
|
|||
|
||||
TypeSystemTypeOperations(this.typeSystem);
|
||||
|
||||
@override
|
||||
TypeClassification classifyType(DartType type) {
|
||||
if (isSubtypeOf(type, typeSystem.typeProvider.objectType)) {
|
||||
return TypeClassification.nonNullable;
|
||||
} else if (isSubtypeOf(type, typeSystem.typeProvider.nullType)) {
|
||||
return TypeClassification.nullOrEquivalent;
|
||||
} else {
|
||||
return TypeClassification.potentiallyNullable;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
DartType factor(DartType from, DartType what) {
|
||||
return typeSystem.factor(from, what);
|
||||
|
|
|
@ -505,33 +505,40 @@ class NullSafetyDeadCodeVerifier {
|
|||
return;
|
||||
}
|
||||
|
||||
// We know that [node] is the first dead node, or contains it.
|
||||
// So, technically the code code interval ends at the end of [node].
|
||||
// But we trim it to the last statement for presentation purposes.
|
||||
if (node != _firstDeadNode) {
|
||||
if (node is FunctionDeclaration) {
|
||||
node = (node as FunctionDeclaration).functionExpression.body;
|
||||
var parent = _firstDeadNode.parent;
|
||||
if (parent is Assertion && identical(_firstDeadNode, parent.message)) {
|
||||
// Don't report "dead code" for the message part of an assert statement,
|
||||
// because this causes nuisance warnings for redundant `!= null`
|
||||
// asserts.
|
||||
} else {
|
||||
// We know that [node] is the first dead node, or contains it.
|
||||
// So, technically the code code interval ends at the end of [node].
|
||||
// But we trim it to the last statement for presentation purposes.
|
||||
if (node != _firstDeadNode) {
|
||||
if (node is FunctionDeclaration) {
|
||||
node = (node as FunctionDeclaration).functionExpression.body;
|
||||
}
|
||||
if (node is FunctionExpression) {
|
||||
node = (node as FunctionExpression).body;
|
||||
}
|
||||
if (node is MethodDeclaration) {
|
||||
node = (node as MethodDeclaration).body;
|
||||
}
|
||||
if (node is BlockFunctionBody) {
|
||||
node = (node as BlockFunctionBody).block;
|
||||
}
|
||||
if (node is Block && node.statements.isNotEmpty) {
|
||||
node = (node as Block).statements.last;
|
||||
}
|
||||
if (node is SwitchMember && node.statements.isNotEmpty) {
|
||||
node = (node as SwitchMember).statements.last;
|
||||
}
|
||||
}
|
||||
if (node is FunctionExpression) {
|
||||
node = (node as FunctionExpression).body;
|
||||
}
|
||||
if (node is MethodDeclaration) {
|
||||
node = (node as MethodDeclaration).body;
|
||||
}
|
||||
if (node is BlockFunctionBody) {
|
||||
node = (node as BlockFunctionBody).block;
|
||||
}
|
||||
if (node is Block && node.statements.isNotEmpty) {
|
||||
node = (node as Block).statements.last;
|
||||
}
|
||||
if (node is SwitchMember && node.statements.isNotEmpty) {
|
||||
node = (node as SwitchMember).statements.last;
|
||||
}
|
||||
}
|
||||
|
||||
var offset = _firstDeadNode.offset;
|
||||
var length = node.end - offset;
|
||||
_errorReporter.reportErrorForOffset(HintCode.DEAD_CODE, offset, length);
|
||||
var offset = _firstDeadNode.offset;
|
||||
var length = node.end - offset;
|
||||
_errorReporter.reportErrorForOffset(HintCode.DEAD_CODE, offset, length);
|
||||
}
|
||||
|
||||
_firstDeadNode = null;
|
||||
}
|
||||
|
|
|
@ -846,6 +846,20 @@ void f(int a) {
|
|||
|
||||
@reflectiveTest
|
||||
class DeadCodeWithNullSafetyTest extends DeadCodeTest with WithNullSafetyMixin {
|
||||
test_assert_dead_message() async {
|
||||
// We don't warn if an assert statement is live but its message is dead,
|
||||
// because this results in nuisance warnings for desirable assertions (e.g.
|
||||
// a `!= null` assertion that is redundant with strong checking but still
|
||||
// useful with weak checking).
|
||||
await assertErrorsInCode('''
|
||||
void f(Object waldo) {
|
||||
assert(waldo != null, "Where's Waldo?");
|
||||
}
|
||||
''', [
|
||||
error(HintCode.UNNECESSARY_NULL_COMPARISON_TRUE, 38, 7),
|
||||
]);
|
||||
}
|
||||
|
||||
test_flowEnd_tryStatement_body() async {
|
||||
await assertErrorsInCode(r'''
|
||||
Never foo() => throw 0;
|
||||
|
|
|
@ -3520,7 +3520,7 @@ class InferenceVisitor
|
|||
int fileOffset, Expression left, DartType leftType, Expression right,
|
||||
{bool isNot}) {
|
||||
assert(isNot != null);
|
||||
inferrer.flowAnalysis.equalityOp_rightBegin(left);
|
||||
inferrer.flowAnalysis.equalityOp_rightBegin(left, leftType);
|
||||
ObjectAccessTarget equalsTarget = inferrer.findInterfaceMember(
|
||||
leftType, equalsName, fileOffset,
|
||||
includeExtensionMethods: true);
|
||||
|
@ -3557,7 +3557,9 @@ class InferenceVisitor
|
|||
if (isNot) {
|
||||
equals = new Not(equals)..fileOffset = fileOffset;
|
||||
}
|
||||
inferrer.flowAnalysis.equalityOp_end(equals, right, notEqual: isNot);
|
||||
inferrer.flowAnalysis.equalityOp_end(
|
||||
equals, right, rightResult.inferredType,
|
||||
notEqual: isNot);
|
||||
return new ExpressionInferenceResult(
|
||||
equalsTarget.isNever
|
||||
? const NeverType(Nullability.nonNullable)
|
||||
|
|
|
@ -263,6 +263,21 @@ class TypeOperationsCfe extends TypeOperations<VariableDeclaration, DartType> {
|
|||
|
||||
TypeOperationsCfe(this.typeEnvironment);
|
||||
|
||||
@override
|
||||
TypeClassification classifyType(DartType type) {
|
||||
if (type == null) {
|
||||
// Note: this can happen during top-level inference.
|
||||
return TypeClassification.potentiallyNullable;
|
||||
} else if (isSubtypeOf(
|
||||
type, typeEnvironment.coreTypes.objectNonNullableRawType)) {
|
||||
return TypeClassification.nonNullable;
|
||||
} else if (isSubtypeOf(type, typeEnvironment.coreTypes.nullType)) {
|
||||
return TypeClassification.nullOrEquivalent;
|
||||
} else {
|
||||
return TypeClassification.potentiallyNullable;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
DartType factor(DartType from, DartType what) {
|
||||
return factorType(typeEnvironment, from, what);
|
||||
|
|
|
@ -446,6 +446,10 @@ clashing
|
|||
class
|
||||
classes
|
||||
classic
|
||||
classification
|
||||
classifications
|
||||
classifies
|
||||
classify
|
||||
clause
|
||||
clauses
|
||||
clean
|
||||
|
|
|
@ -20,6 +20,15 @@ class DecoratedTypeOperations
|
|||
DecoratedTypeOperations(
|
||||
this._typeSystem, this._variableRepository, this._graph);
|
||||
|
||||
@override
|
||||
TypeClassification classifyType(DecoratedType type) {
|
||||
if (type.type.isDartCoreNull) {
|
||||
return TypeClassification.nullOrEquivalent;
|
||||
} else {
|
||||
return TypeClassification.potentiallyNullable;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
DecoratedType factor(DecoratedType from, DecoratedType what) {
|
||||
// TODO(scheglov): https://github.com/dart-lang/sdk/issues/41672
|
||||
|
|
|
@ -414,11 +414,12 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
|
|||
if (operatorType == TokenType.EQ_EQ || operatorType == TokenType.BANG_EQ) {
|
||||
var leftType = _dispatch(leftOperand);
|
||||
_graph.connectDummy(leftType.node, DummyOrigin(source, node));
|
||||
_flowAnalysis.equalityOp_rightBegin(leftOperand);
|
||||
_flowAnalysis.equalityOp_rightBegin(leftOperand, leftType);
|
||||
var rightType = _dispatch(rightOperand);
|
||||
_graph.connectDummy(rightType.node, DummyOrigin(source, node));
|
||||
bool notEqual = operatorType == TokenType.BANG_EQ;
|
||||
_flowAnalysis.equalityOp_end(node, rightOperand, notEqual: notEqual);
|
||||
_flowAnalysis.equalityOp_end(node, rightOperand, rightType,
|
||||
notEqual: notEqual);
|
||||
|
||||
void buildNullConditionInfo(NullLiteral nullLiteral,
|
||||
Expression otherOperand, NullabilityNode otherNode) {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// 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.
|
||||
|
||||
// The reason that `if (x == null)` doesn't promote x's type to `Null` is
|
||||
// because we lose type argument information that would be necessary for
|
||||
// inference. This test makes sure that the type argument information is
|
||||
// appropriately preserved.
|
||||
|
||||
// SharedOptions=--enable-experiment=non-nullable
|
||||
|
||||
void f(List<int>? x) {
|
||||
if (x == null) {
|
||||
// If x were promoted to `Null`, inference would not know that `[]` should
|
||||
// be considered to mean `<int>[]`, so either the line below would be a
|
||||
// compile-time error, or a runtime error would occur later when we try to
|
||||
// add an int to the list.
|
||||
x = [];
|
||||
}
|
||||
x.add(0);
|
||||
}
|
||||
|
||||
main() {
|
||||
f(null);
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// 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.
|
||||
|
||||
// Given an expression E having type `Null`, the reason that `if (x != E)`
|
||||
// doesn't promote x's type to non-nullable is because evaluation of the
|
||||
// expression may change the value of `x`. (Consider, for example, if E is the
|
||||
// expression `(x = null)`). This test demonstrates the problem with `(x =
|
||||
// null)` and checks a few other cases.
|
||||
|
||||
// SharedOptions=--enable-experiment=non-nullable
|
||||
|
||||
void assignNullRhs(int? x) {
|
||||
if (x != (x = null)) {
|
||||
x.isEven;
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
|
||||
//^
|
||||
// [cfe] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
|
||||
}
|
||||
}
|
||||
|
||||
void assignNullLhs(int? x) {
|
||||
// In theory it would be sound to promote x in this case, because the
|
||||
// assignment happens before the RHS is evaluated, but we prefer not to
|
||||
// promote in order to be consistent with the `assignNullRhs` case.
|
||||
if ((x = null) != x) {
|
||||
x.isEven;
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
|
||||
//^
|
||||
// [cfe] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
|
||||
}
|
||||
}
|
||||
|
||||
void unrelatedVarRhs(int? x, Null n) {
|
||||
if (x != n) {
|
||||
x.isEven;
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
|
||||
//^
|
||||
// [cfe] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
|
||||
}
|
||||
}
|
||||
|
||||
void unrelatedVarLhs(int? x, Null n) {
|
||||
if (n != x) {
|
||||
x.isEven;
|
||||
// ^
|
||||
// [analyzer] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
|
||||
//^
|
||||
// [cfe] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
assignNullRhs(0);
|
||||
assignNullLhs(0);
|
||||
unrelatedVarLhs(0, null);
|
||||
unrelatedVarRhs(0, null);
|
||||
}
|
Loading…
Reference in a new issue