[cfe] Add NullCheck expression node

Change-Id: I17223bffa5dec7b1ce7652b21bf1a3381137f4f4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/118380
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Jens Johansen <jensj@google.com>
This commit is contained in:
Johnni Winther
2019-09-23 10:04:48 +00:00
committed by commit-bot@chromium.org
parent 96f1abdaca
commit 94dd49cdb6
27 changed files with 251 additions and 3 deletions
@@ -4894,6 +4894,11 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
return js.call('!#', _visitTest(operand));
}
@override
js_ast.Expression visitNullCheck(NullCheck node) {
throw UnimplementedError('Unimplemented null check expression: $node');
}
@override
js_ast.Expression visitLogicalExpression(LogicalExpression node) {
// The operands of logical boolean operators are subject to boolean
@@ -2746,9 +2746,21 @@ class BodyBuilder extends ScopeListener<JumpTarget>
@override
void handleNonNullAssertExpression(Token bang) {
assert(checkState(bang, [
unionOfKinds([ValueKind.Expression, ValueKind.Generator])
]));
if (!libraryBuilder.loader.target.enableNonNullable) {
reportNonNullAssertExpressionNotEnabled(bang);
}
Object operand = pop();
Expression expression;
if (operand is Generator) {
expression = operand.buildSimpleRead();
} else {
assert(operand is Expression);
expression = operand;
}
push(forest.createNullCheck(offsetForToken(bang), expression));
}
@override
@@ -723,6 +723,10 @@ class Forest {
return new SuperMethodInvocation(name, arguments, procedure)
..fileOffset = fileOffset ?? TreeNode.noOffset;
}
NullCheck createNullCheck(int fileOffset, Expression expression) {
return new NullCheck(expression)..fileOffset = fileOffset;
}
}
class _VariablesDeclaration extends Statement {
@@ -1880,6 +1880,20 @@ class InferenceVisitor
return new ExpressionInferenceResult(boolType);
}
@override
ExpressionInferenceResult visitNullCheck(
NullCheck node, DartType typeContext) {
// TODO(johnniwinther): Should the typeContext for the operand be
// `Nullable(typeContext)`?
DartType inferredType = inferrer
.inferExpression(node.operand, typeContext, !inferrer.isTopLevel)
.inferredType;
// TODO(johnniwinther): Check that the inferred type is potentially
// nullable.
// TODO(johnniwinther): Return `NonNull(inferredType)`.
return new ExpressionInferenceResult(inferredType);
}
ExpressionInferenceResult visitNullAwareMethodInvocation(
NullAwareMethodInvocation node, DartType typeContext) {
inferrer.inferStatement(node.variable);
@@ -1360,6 +1360,12 @@ class TypePromotionLookAheadListener extends Listener {
state.popPushNull("%UnaryPrefixExpression%", token);
}
@override
void handleNonNullAssertExpression(Token token) {
debugEvent("NonNullAssertExpression", token);
state.popPushNull("%NonNullAssertExpression%", token);
}
@override
void handleUnescapeError(
Message message, Token location, int stringOffset, int length) {
@@ -0,0 +1,28 @@
// Copyright (c) 2019, 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 Class {
int? field;
int? method() => field;
Class operator +(Class other) => new Class();
}
main() {
Class? c = new Class();
c!;
c!.field;
c!.field = 42;
c!.method;
c!.method();
c!.field!.toString();
c!.method()!.toString();
c! + c;
c! + c!;
c + c!;
(c + c)!;
bool? o = true;
!o! ? !o! : !!o!!;
!(o!) ? (!o)! : (!(!o)!)!;
}
@@ -0,0 +1,15 @@
library;
import self as self;
import "dart:core" as core;
class Class extends core::Object {
field core::int? field;
synthetic constructor •() → self::Class*
;
method method() → core::int?
;
operator +(self::Class other) → self::Class
;
}
static method main() → dynamic
;
@@ -0,0 +1,31 @@
library;
import self as self;
import "dart:core" as core;
class Class extends core::Object {
field core::int? field = null;
synthetic constructor •() → self::Class*
: super core::Object::•()
;
method method() → core::int?
return this.{self::Class::field};
operator +(self::Class other) → self::Class
return new self::Class::•();
}
static method main() → dynamic {
self::Class? c = new self::Class::•();
c!;
c!.{self::Class::field};
c!.{self::Class::field} = 42;
c!.{self::Class::method};
c!.{self::Class::method}();
c!.{self::Class::field}!.{core::int::toString}();
c!.{self::Class::method}()!.{core::int::toString}();
c!.{self::Class::+}(c);
c!.{self::Class::+}(c!);
c.{self::Class::+}(c!);
c.{self::Class::+}(c)!;
core::bool? o = true;
!o! ?{core::bool} !o! : !!o!!;
!o! ?{core::bool} (!o)! : (!(!o)!)!;
}
@@ -0,0 +1,31 @@
library;
import self as self;
import "dart:core" as core;
class Class extends core::Object {
field core::int? field = null;
synthetic constructor •() → self::Class*
: super core::Object::•()
;
method method() → core::int?
return this.{self::Class::field};
operator +(self::Class other) → self::Class
return new self::Class::•();
}
static method main() → dynamic {
self::Class? c = new self::Class::•();
c!;
c!.{self::Class::field};
c!.{self::Class::field} = 42;
c!.{self::Class::method};
c!.{self::Class::method}();
c!.{self::Class::field}!.{core::int::toString}();
c!.{self::Class::method}()!.{core::int::toString}();
c!.{self::Class::+}(c);
c!.{self::Class::+}(c!);
c.{self::Class::+}(c!);
c.{self::Class::+}(c)!;
core::bool? o = true;
!o! ?{core::bool} !o! : !!o!!;
!o! ?{core::bool} (!o)! : (!(!o)!)!;
}
@@ -838,6 +838,7 @@ instantiate_to_bound/typedef_super_bounded_type: TextSerializationFailure # Was:
new_const_insertion/simple: TextSerializationFailure # Was: Pass
nnbd/function_types: TextSerializationFailure
nnbd/late: TextSerializationFailure
nnbd/null_check: TextSerializationFailure
nnbd/nullable_param: TextSerializationFailure
nnbd/required: TextSerializationFailure
no_such_method_forwarders/abstract_accessors_from_field: TextSerializationFailure # Was: Pass
+7 -1
View File
@@ -143,7 +143,7 @@ type CanonicalName {
type ComponentFile {
UInt32 magic = 0x90ABCDEF;
UInt32 formatVersion = 32;
UInt32 formatVersion = 33;
List<String> problemsAsJson; // Described in problems.md.
Library[] libraries;
UriSource sourceMap;
@@ -703,6 +703,12 @@ type Not extends Expression {
Expression operand;
}
type NullCheck extends Expression {
Byte tag = 117;
FileOffset fileOffset;
Expression operand;
}
/*
enum LogicalOperator { &&, || }
*/
+31
View File
@@ -3901,6 +3901,37 @@ class AsExpression extends Expression {
}
}
/// Null check expression of form `x!`.
///
/// This expression was added as part of NNBD and is currently only created when
/// the 'non-nullable' experimental feature is enabled.
class NullCheck extends Expression {
Expression operand;
NullCheck(this.operand) {
operand?.parent = this;
}
DartType getStaticType(TypeEnvironment types) =>
// TODO(johnniwinther): Return `NonNull(operand.getStaticType(types))`.
operand.getStaticType(types);
R accept<R>(ExpressionVisitor<R> v) => v.visitNullCheck(this);
R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) =>
v.visitNullCheck(this, arg);
visitChildren(Visitor v) {
operand?.accept(v);
}
transformChildren(Transformer v) {
if (operand != null) {
operand = operand.accept<TreeNode>(v);
operand?.parent = this;
}
}
}
/// An integer, double, boolean, string, or null constant.
abstract class BasicLiteral extends Expression {
Object get value;
@@ -1661,6 +1661,9 @@ class BinaryBuilder {
..fileOffset = offset;
case Tag.Not:
return new Not(readExpression());
case Tag.NullCheck:
int offset = readOffset();
return new NullCheck(readExpression())..fileOffset = offset;
case Tag.LogicalExpression:
return new LogicalExpression(readExpression(),
logicalOperatorToString(readByte()), readExpression());
+7
View File
@@ -1495,6 +1495,13 @@ class BinaryPrinter implements Visitor<void>, BinarySink {
writeNode(node.operand);
}
@override
void visitNullCheck(NullCheck node) {
writeByte(Tag.NullCheck);
writeOffset(node.fileOffset);
writeNode(node.operand);
}
int logicalOperatorIndex(String operator) {
switch (operator) {
case '&&':
+3 -1
View File
@@ -48,6 +48,7 @@ class Tag {
static const int ConstructorInvocation = 31;
static const int ConstConstructorInvocation = 32;
static const int Not = 33;
static const int NullCheck = 117;
static const int LogicalExpression = 34;
static const int ConditionalExpression = 35;
static const int StringConcatenation = 36;
@@ -130,6 +131,7 @@ class Tag {
/// 114 is occupied by [InstanceCreation] (expression).
/// 115 is occupied by [Extension].
/// 116 is occupied by [FileUriExpression] (expression).
/// 117 is occupied by [NullCheck] (expression).
static const int SpecializedTagHighBit = 0x80; // 10000000
static const int SpecializedTagMask = 0xF8; // 11111000
@@ -146,7 +148,7 @@ class Tag {
/// Internal version of kernel binary format.
/// Bump it when making incompatible changes in kernel binaries.
/// Keep in sync with runtime/vm/kernel_binary.h, pkg/kernel/binary.md.
static const int BinaryFormatVersion = 32;
static const int BinaryFormatVersion = 33;
}
abstract class ConstantTag {
+4
View File
@@ -180,6 +180,10 @@ class CloneVisitor implements TreeVisitor<TreeNode> {
return new Not(clone(node.operand));
}
visitNullCheck(NullCheck node) {
return new NullCheck(clone(node.operand));
}
visitLogicalExpression(LogicalExpression node) {
return new LogicalExpression(
clone(node.left), node.operator, clone(node.right));
+6
View File
@@ -1292,6 +1292,11 @@ class Printer extends Visitor<Null> {
writeExpression(node.operand, Precedence.PREFIX);
}
visitNullCheck(NullCheck node) {
writeExpression(node.operand, Precedence.POSTFIX);
writeSymbol('!');
}
visitLogicalExpression(LogicalExpression node) {
int precedence = Precedence.binaryPrecedence[node.operator];
writeExpression(node.left, precedence);
@@ -2329,6 +2334,7 @@ class Precedence extends ExpressionVisitor<int> {
int visitStaticInvocation(StaticInvocation node) => CALLEE;
int visitConstructorInvocation(ConstructorInvocation node) => CALLEE;
int visitNot(Not node) => PREFIX;
int visitNullCheck(NullCheck node) => PRIMARY;
int visitLogicalExpression(LogicalExpression node) =>
binaryPrecedence[node.operator];
int visitConditionalExpression(ConditionalExpression node) => CONDITIONAL;
@@ -941,6 +941,12 @@ class TextSerializationVerifier implements Visitor<void> {
makeExpressionRoundTrip(node);
}
@override
void visitNullCheck(NullCheck node) {
storeLastSeenUriAndOffset(node);
makeExpressionRoundTrip(node);
}
@override
void visitConstructorInvocation(ConstructorInvocation node) {
storeLastSeenUriAndOffset(node);
+6
View File
@@ -649,6 +649,12 @@ class TypeCheckingVisitor
return environment.boolType;
}
@override
DartType visitNullCheck(NullCheck node) {
// TODO(johnniwinther): Return `NonNull(visitExpression(types))`.
return visitExpression(node.operand);
}
@override
DartType visitNullLiteral(NullLiteral node) {
return const BottomType();
+3
View File
@@ -34,6 +34,7 @@ abstract class ExpressionVisitor<R> {
R visitConstructorInvocation(ConstructorInvocation node) =>
defaultExpression(node);
R visitNot(Not node) => defaultExpression(node);
R visitNullCheck(NullCheck node) => defaultExpression(node);
R visitLogicalExpression(LogicalExpression node) => defaultExpression(node);
R visitConditionalExpression(ConditionalExpression node) =>
defaultExpression(node);
@@ -162,6 +163,7 @@ class TreeVisitor<R>
R visitConstructorInvocation(ConstructorInvocation node) =>
defaultExpression(node);
R visitNot(Not node) => defaultExpression(node);
R visitNullCheck(NullCheck node) => defaultExpression(node);
R visitLogicalExpression(LogicalExpression node) => defaultExpression(node);
R visitConditionalExpression(ConditionalExpression node) =>
defaultExpression(node);
@@ -673,6 +675,7 @@ abstract class ExpressionVisitor1<R, T> {
R visitConstructorInvocation(ConstructorInvocation node, T arg) =>
defaultExpression(node, arg);
R visitNot(Not node, T arg) => defaultExpression(node, arg);
R visitNullCheck(NullCheck node, T arg) => defaultExpression(node, arg);
R visitLogicalExpression(LogicalExpression node, T arg) =>
defaultExpression(node, arg);
R visitConditionalExpression(ConditionalExpression node, T arg) =>
@@ -1157,6 +1157,8 @@ Fragment StreamingFlowGraphBuilder::BuildExpression(TokenPosition* position) {
return BuildConstructorInvocation(true, position);
case kNot:
return BuildNot(position);
case kNullCheck:
return BuildNullCheck(position);
case kLogicalExpression:
return BuildLogicalExpression(position);
case kConditionalExpression:
@@ -3245,6 +3247,17 @@ Fragment StreamingFlowGraphBuilder::BuildNot(TokenPosition* position) {
return instructions;
}
Fragment StreamingFlowGraphBuilder::BuildNullCheck(TokenPosition* p) {
const TokenPosition position = ReadPosition(); // read position.
if (p != nullptr) *p = position;
TokenPosition operand_position = TokenPosition::kNoSource;
Fragment instructions =
BuildExpression(&operand_position); // read expression.
// TODO(37479): Implement null-check semantics.
return instructions;
}
// Translate the logical expression (lhs && rhs or lhs || rhs) in a context
// where a value is required.
//
@@ -301,6 +301,7 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
Fragment BuildStaticInvocation(bool is_const, TokenPosition* position);
Fragment BuildConstructorInvocation(bool is_const, TokenPosition* position);
Fragment BuildNot(TokenPosition* position);
Fragment BuildNullCheck(TokenPosition* position);
Fragment BuildLogicalExpression(TokenPosition* position);
Fragment TranslateLogicalExpressionForValue(bool negated,
TestFragment* side_exits);
@@ -442,6 +442,10 @@ void KernelFingerprintHelper::CalculateExpressionFingerprint() {
case kNot:
CalculateExpressionFingerprint(); // read expression.
return;
case kNullCheck:
ReadPosition(); // read position.
CalculateExpressionFingerprint(); // read expression.
return;
case kLogicalExpression:
CalculateExpressionFingerprint(); // read left.
SkipBytes(1); // read operator.
@@ -2229,6 +2229,10 @@ void KernelReaderHelper::SkipExpression() {
case kNot:
SkipExpression(); // read expression.
return;
case kNullCheck:
ReadPosition(); // read position.
SkipExpression(); // read expression.
return;
case kLogicalExpression:
SkipExpression(); // read left.
SkipBytes(1); // read operator.
@@ -763,6 +763,10 @@ void ScopeBuilder::VisitExpression() {
case kNot:
VisitExpression(); // read expression.
return;
case kNullCheck:
helper_.ReadPosition(); // read position.
VisitExpression(); // read expression.
return;
case kLogicalExpression:
needs_expr_temp_ = true;
VisitExpression(); // read left.
+2 -1
View File
@@ -20,7 +20,7 @@ static const uint32_t kMagicProgramFile = 0x90ABCDEFu;
// Both version numbers are inclusive.
static const uint32_t kMinSupportedKernelFormatVersion = 18;
static const uint32_t kMaxSupportedKernelFormatVersion = 32;
static const uint32_t kMaxSupportedKernelFormatVersion = 33;
// Keep in sync with package:kernel/lib/binary/tag.dart
#define KERNEL_TAG_LIST(V) \
@@ -60,6 +60,7 @@ static const uint32_t kMaxSupportedKernelFormatVersion = 32;
V(ConstructorInvocation, 31) \
V(ConstConstructorInvocation, 32) \
V(Not, 33) \
V(NullCheck, 117) \
V(LogicalExpression, 34) \
V(ConditionalExpression, 35) \
V(StringConcatenation, 36) \