[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:
committed by
commit-bot@chromium.org
parent
96f1abdaca
commit
94dd49cdb6
@@ -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)!)!;
|
||||
}
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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 { &&, || }
|
||||
*/
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 '&&':
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) \
|
||||
|
||||
Reference in New Issue
Block a user