Implement type inference for if-null expressions (a ?? b).

R=sigmund@google.com

Review-Url: https://codereview.chromium.org/2944903002 .
This commit is contained in:
Paul Berry 2017-06-19 13:46:06 -07:00
parent 22042b691f
commit fdf77ba36f
6 changed files with 74 additions and 3 deletions

View file

@ -864,7 +864,7 @@ class BodyBuilder extends ScopeListener<JumpTarget> implements BuilderHelper {
Expression b = popForValue();
Expression a = popForValue();
VariableDeclaration variable = new VariableDeclaration.forValue(a);
push(makeLet(
push(new KernelIfNullExpression(
variable,
new KernelConditionalExpression(
buildIsNull(new VariableGet(variable), offsetForToken(token)),

View file

@ -935,6 +935,56 @@ class KernelFunctionExpression extends FunctionExpression
}
}
/// Concrete shadow object representing an if-null expression.
///
/// An if-null expression of the form `a ?? b` is represented as the kernel
/// expression:
///
/// let v = a in v == null ? b : v
class KernelIfNullExpression extends Let implements KernelExpression {
KernelIfNullExpression(VariableDeclaration variable, Expression body)
: super(variable, body);
@override
ConditionalExpression get body => super.body;
/// Returns the expression to the left of `??`.
Expression get _lhs => variable.initializer;
/// Returns the expression to the right of `??`.
Expression get _rhs => body.then;
@override
void _collectDependencies(KernelDependencyCollector collector) {
// If-null expressions are not immediately evident expressions.
collector.recordNotImmediatelyEvident(fileOffset);
}
@override
DartType _inferExpression(
KernelTypeInferrer inferrer, DartType typeContext, bool typeNeeded) {
typeNeeded = inferrer.listener.ifNullEnter(this, typeContext) || typeNeeded;
// To infer `e0 ?? e1` in context K:
// - Infer e0 in context K to get T0
var lhsType = inferrer.inferExpression(_lhs, typeContext, true);
variable.type = lhsType;
// - Let J = T0 if K is `_` else K.
var rhsContext = typeContext ?? lhsType;
// - Infer e1 in context J to get T1
var rhsType =
inferrer.inferExpression(_rhs, rhsContext, typeContext == null);
// - Let T = greatest closure of K with respect to `?` if K is not `_`, else
// UP(t0, t1)
// - Then the inferred type is T.
var inferredType = typeContext == null
? inferrer.typeSchemaEnvironment.getLeastUpperBound(lhsType, rhsType)
: greatestClosure(inferrer.coreTypes, typeContext);
body.staticType = inferredType;
inferrer.listener.ifNullExit(this, inferredType);
return inferredType;
}
}
/// Concrete shadow object representing an if statement in kernel form.
class KernelIfStatement extends IfStatement implements KernelStatement {
KernelIfStatement(Expression condition, Statement then, Statement otherwise)

View file

@ -163,6 +163,12 @@ class TypeInferenceListener
FunctionExpression expression, DartType inferredType) =>
debugExpressionExit("functionExpression", expression, inferredType);
bool ifNullEnter(Expression expression, DartType typeContext) =>
debugExpressionEnter('ifNull', expression, typeContext);
void ifNullExit(Expression expression, DartType inferredType) =>
debugExpressionExit('ifNull', expression, inferredType);
void ifStatementEnter(IfStatement statement) =>
debugStatementEnter('ifStatement', statement);

View file

@ -67,8 +67,6 @@ inference/infer_types_on_loop_indices_for_loop_with_inference: Fail
inference/lambda_does_not_have_propagated_type_hint: Fail
inference/list_literals_can_infer_null_top_level: Fail
inference/map_literals_can_infer_null_top_level: Fail
inference/null_coalescing_operator: Fail
inference/null_coalescing_operator_2: Fail
inference/property_set: Fail
inference/property_set_bad_setter: Crash
inference/reference_to_typedef: Fail

View file

@ -0,0 +1,9 @@
library test;
import self as self;
import "dart:core" as core;
static method main() → dynamic {
core::List<core::int> x;
core::List<core::int> y = let final core::List<core::int> #t1 = x in #t1.==(null) ? <core::int>[] : #t1;
core::List<core::int> z = y;
}

View file

@ -0,0 +1,8 @@
library test;
import self as self;
import "dart:core" as core;
static method main() → dynamic {
core::List<core::int> x;
core::List<core::num> y = let final core::List<core::int> #t1 = x in #t1.==(null) ? <core::num>[] : #t1;
}