Implementation of null-aware operators.

This CL adds the implementation of all null-aware operators to dart2js. In
particular:

* a?.b: represented as 'isConditional' in the Send ast, and dispatched
  separately by the semantic visitor using visitIfNotNull* methods.

* a ?? b: represented as an operator, and dispatched separately by the semantic
  visitor using the `visitIfNull` method.

* a ??= b: represented as a compound operator.

All except 2 tests are passing with the SSA backend (those 2 tests fail for
unrelated reasons). I've marked the CPS ir tests as failing for now.

BUG=
R=johnniwinther@google.com, paulberry@google.com, sra@google.com

Review URL: https://codereview.chromium.org//1151163004
This commit is contained in:
Sigmund Cherem
2015-05-22 15:44:41 -07:00
parent ad2b8a43c8
commit a8db3278e2
39 changed files with 1187 additions and 141 deletions
+3 -1
View File
@@ -89,7 +89,9 @@ class Compiler extends leg.Compiler {
generateCodeWithCompileTimeErrors:
hasOption(options, '--generate-code-with-compile-time-errors'),
allowNativeExtensions:
hasOption(options, '--allow-native-extensions')) {
hasOption(options, '--allow-native-extensions'),
enableNullAwareOperators:
hasOption(options, '--enable-null-aware-operators')) {
tasks.addAll([
userHandlerTask = new leg.GenericTask('Diagnostic handler', this),
userProviderTask = new leg.GenericTask('Input provider', this),
+5
View File
@@ -781,6 +781,10 @@ abstract class Compiler implements DiagnosticListener {
/// If `true` native extension syntax is supported by the frontend.
final bool allowNativeExtensions;
/// Temporary flag to enable `?.`, `??`, and `??=` until it becomes part of
/// the spec.
final bool enableNullAwareOperators;
/// Output provider from user of Compiler API.
api.CompilerOutputProvider userOutputProvider;
@@ -1044,6 +1048,7 @@ abstract class Compiler implements DiagnosticListener {
bool hasIncrementalSupport: false,
this.enableExperimentalMirrors: false,
this.allowNativeExtensions: false,
this.enableNullAwareOperators: false,
this.generateCodeWithCompileTimeErrors: false,
api.CompilerOutputProvider outputProvider,
List<String> strips: const []})
@@ -843,6 +843,12 @@ abstract class IrBuilderVisitor extends ast.Visitor<ir.Primitive>
leftValue, buildRightValue, isLazyOr: isLazyOr);
}
@override
ir.Primitive visitIfNull(
ast.Send node, ast.Node left, ast.Node right, _) {
internalError(node, "If-null not yet implemented in cps_ir");
}
@override
ir.Primitive visitLogicalAnd(
ast.Send node, ast.Node left, ast.Node right, _) {
+1
View File
@@ -347,6 +347,7 @@ Future<api.CompilationResult> compile(List<String> argv) {
"Async-await is supported by default.",
api.Diagnostic.HINT);
}),
new OptionHandler('--enable-null-aware-operators', passThrough),
new OptionHandler('--enable-enum', (_) {
diagnosticHandler.info(
"Option '--enable-enum' is no longer needed. "
+5 -2
View File
@@ -532,7 +532,9 @@ class Elements {
static bool isInstanceSend(Send send, TreeElements elements) {
Element element = elements[send];
if (element == null) return !isClosureSend(send, element);
return isInstanceMethod(element) || isInstanceField(element);
return isInstanceMethod(element) ||
isInstanceField(element) ||
send.isConditional;
}
static bool isClosureSend(Send send, Element element) {
@@ -640,7 +642,7 @@ class Elements {
static String constructOperatorNameOrNull(String op, bool isUnary) {
if (isMinusOperator(op)) {
return isUnary ? 'unary-' : op;
} else if (isUserDefinableOperator(op)) {
} else if (isUserDefinableOperator(op) || op == '??') {
return op;
} else {
return null;
@@ -666,6 +668,7 @@ class Elements {
if (identical(op, '&=')) return '&';
if (identical(op, '^=')) return '^';
if (identical(op, '|=')) return '|';
if (identical(op, '??=')) return '??';
return null;
}
@@ -579,7 +579,11 @@ class ConcreteTypeSystem extends TypeSystem<ConcreteType> {
}
@override
ConcreteType refineReceiver(Selector selector, ConcreteType receiverType) {
ConcreteType refineReceiver(Selector selector, ConcreteType receiverType,
bool isConditional) {
if (isConditional) {
throw new UnimplementedError("isConditional in concrete type inferrer");
}
return receiverType.refine(selector, compiler);
}
@@ -115,8 +115,12 @@ abstract class TypeSystem<T> {
/**
* Returns a new receiver type for this [selector] applied to
* [receiverType].
*
* The option [isConditional] is true when [selector] was seen in a
* conditional send (e.g. `a?.selector`), in which case the returned type may
* be null.
*/
T refineReceiver(Selector selector, T receiverType);
T refineReceiver(Selector selector, T receiverType, bool isConditional);
/**
* Returns the internal inferrer representation for [mask].
@@ -441,6 +445,13 @@ class LocalsHandler<T> {
}
updateLocal() {
T currentType = locals[local];
SendSet send = node != null ? node.asSendSet() : null;
if (send != null && send.isIfNullAssignment && currentType != null) {
// If-null assignments may return either the new or the original value.
type = types.addPhiInput(
local, types.allocatePhi(locals.block, local, currentType), type);
}
locals[local] = type;
if (currentType != type) {
inferrer.recordLocalUpdate(local, type);
@@ -987,6 +998,16 @@ abstract class InferrerVisitor
return handleDynamicInvoke(node);
}
@override
T visitIfNotNullDynamicPropertyInvoke(
Send node,
Node receiver,
NodeList arguments,
Selector selector,
_) {
return handleDynamicInvoke(node);
}
@override
T visitThisPropertyInvoke(
Send node,
@@ -996,6 +1017,13 @@ abstract class InferrerVisitor
return handleDynamicInvoke(node);
}
@override
T visitIfNull(Send node, Node left, Node right, _) {
T firstType = visit(left);
T secondType = visit(right);
return types.allocateDiamondPhi(firstType, secondType);
}
@override
T visitLogicalAnd(Send node, Node left, Node right, _) {
conditionIsSimple = false;
@@ -155,7 +155,8 @@ class TypeMaskSystem implements TypeSystem<TypeMask> {
return type != selector.mask;
}
TypeMask refineReceiver(Selector selector, TypeMask receiverType) {
TypeMask refineReceiver(Selector selector, TypeMask receiverType,
bool isConditional) {
TypeMask newType = compiler.world.allFunctions.receiverType(selector);
return receiverType.intersection(newType, classWorld);
}
@@ -1758,6 +1759,15 @@ class SimpleTypeInferrerVisitor<T>
return handleDynamicGet(node);
}
@override
T visitIfNotNullDynamicPropertyGet(
ast.Send node,
ast.Node receiver,
Selector selector,
_) {
return handleDynamicGet(node);
}
@override
T visitLocalVariableGet(
ast.Send node,
@@ -1943,12 +1953,14 @@ class SimpleTypeInferrerVisitor<T>
// If the receiver of the call is a local, we may know more about
// its type by refining it with the potential targets of the
// calls.
if (node.asSend() != null) {
ast.Node receiver = node.asSend().receiver;
ast.Send send = node.asSend();
if (send != null) {
ast.Node receiver = send.receiver;
if (receiver != null) {
Element element = elements[receiver];
if (Elements.isLocal(element) && !capturedVariables.contains(element)) {
T refinedType = types.refineReceiver(selector, receiverType);
T refinedType = types.refineReceiver(selector, receiverType,
send.isConditional);
locals.update(element, refinedType, node);
}
}
@@ -27,6 +27,8 @@ import '../native/native.dart' as native;
import '../tree/tree.dart' as ast
show DartString,
Node,
Send,
SendSet,
TryStatement;
import '../types/types.dart'
show ContainerTypeMask,
@@ -248,9 +250,15 @@ class TypeInformationSystem extends TypeSystem<TypeInformation> {
return info.type != selector.mask;
}
TypeInformation refineReceiver(Selector selector, TypeInformation receiver) {
TypeInformation refineReceiver(Selector selector, TypeInformation receiver,
bool isConditional) {
if (receiver.type.isExact) return receiver;
TypeMask otherType = compiler.world.allFunctions.receiverType(selector);
// Conditional sends (a?.b) can still narrow the possible types of `a`,
// however, we still need to consider that `a` may be null.
if (isConditional && receiver.type.isNullable) {
otherType = otherType.nullable();
}
// If this is refining to nullable subtype of `Object` just return
// the receiver. We know the narrowing is useless.
if (otherType.isNullable && otherType.containsAll(classWorld)) {
@@ -897,7 +897,7 @@ class DynamicCallSiteTypeInformation extends CallSiteTypeInformation {
// Walk over the found targets, and compute the joined union type mask
// for all these targets.
return inferrer.types.joinTypeMasks(targets.map((element) {
TypeMask result = inferrer.types.joinTypeMasks(targets.map((element) {
// If [canReachAll] is true, then we are iterating over all
// targets that satisfy the untyped selector. We skip the return
// type of the targets that can only be reached through
@@ -942,6 +942,15 @@ class DynamicCallSiteTypeInformation extends CallSiteTypeInformation {
return inferrer.typeOfElementWithSelector(element, typedSelector).type;
}
}));
if (call is ast.Send) {
ast.Send send = call;
if (send.isConditional && receiver.type.isNullable) {
// Conditional sends (e.g. `a?.b`) may be null if the receiver is null.
result = result.nullable();
}
}
return result;
}
void giveUp(TypeGraphInferrerEngine inferrer, {bool clearAssignments: true}) {
+32 -4
View File
@@ -2615,7 +2615,26 @@ class ResolverVisitor extends MappingVisitor<ResolutionResult> {
var oldCategory = allowedCategory;
allowedCategory |= ElementCategory.PREFIX | ElementCategory.SUPER;
bool oldSendIsMemberAccess = sendIsMemberAccess;
int oldAllowedCategory = allowedCategory;
// Conditional sends like `e?.foo` treat the receiver as an expression. So
// `C?.foo` needs to be treated like `(C).foo`, not like C.foo. Prefixes and
// super are not allowed on their own in that context.
if (node.isConditional) {
sendIsMemberAccess = false;
allowedCategory =
ElementCategory.VARIABLE |
ElementCategory.FUNCTION |
ElementCategory.IMPLIES_TYPE;
}
ResolutionResult resolvedReceiver = visit(node.receiver);
if (node.isConditional) {
sendIsMemberAccess = oldSendIsMemberAccess;
allowedCategory = oldAllowedCategory;
}
allowedCategory = oldCategory;
Element target;
@@ -2740,7 +2759,7 @@ class ResolverVisitor extends MappingVisitor<ResolutionResult> {
if (identical(string, '!') ||
identical(string, '&&') || identical(string, '||') ||
identical(string, 'is') || identical(string, 'as') ||
identical(string, '?') ||
identical(string, '?') || identical(string, '??') ||
identical(string, '>>>')) {
return null;
}
@@ -2945,6 +2964,8 @@ class ResolverVisitor extends MappingVisitor<ResolutionResult> {
resolvedArguments = true;
} else if (operatorString == '||') {
sendStructure = const LogicalOrStructure();
} else if (operatorString == '??') {
sendStructure = const IfNullStructure();
}
if (sendStructure != null) {
registry.registerSendStructure(node, sendStructure);
@@ -3059,9 +3080,16 @@ class ResolverVisitor extends MappingVisitor<ResolutionResult> {
registry.registerThrowNoSuchMethod();
}
} else if (target.impliesType) {
setter = reportAndCreateErroneousElement(node.selector, target.name,
MessageKind.ASSIGNING_TYPE, const {});
registry.registerThrowNoSuchMethod();
if (node.isIfNullAssignment) {
setter = reportAndCreateErroneousElement(node.selector, target.name,
MessageKind.IF_NULL_ASSIGNING_TYPE, const {});
// In this case, no assignment happens, the rest of the compiler can
// treat the expression `C ??= e` as if it's just reading `C`.
} else {
setter = reportAndCreateErroneousElement(node.selector, target.name,
MessageKind.ASSIGNING_TYPE, const {});
registry.registerThrowNoSuchMethod();
}
registerTypeLiteralAccess(node, target);
} else if (target.isFinal || target.isConst) {
if (Elements.isStaticOrTopLevelField(target) || target.isLocal) {
+15 -1
View File
@@ -66,6 +66,7 @@ enum BinaryOperatorKind {
XOR,
LOGICAL_AND,
LOGICAL_OR,
IF_NULL,
}
class BinaryOperator {
@@ -161,6 +162,10 @@ class BinaryOperator {
static const BinaryOperator LOGICAL_OR =
const _LogicalOperator(BinaryOperatorKind.LOGICAL_OR, '||');
/// The if-null ?? operator.
static const BinaryOperator IF_NULL =
const _LogicalOperator(BinaryOperatorKind.IF_NULL, '??');
static BinaryOperator parse(String value) {
switch (value) {
case '==': return EQ;
@@ -183,6 +188,7 @@ class BinaryOperator {
case '|': return OR;
case '&&': return LOGICAL_AND;
case '||': return LOGICAL_OR;
case '??': return IF_NULL;
default: return null;
}
}
@@ -211,6 +217,7 @@ class _LogicalOperator extends BinaryOperator {
enum AssignmentOperatorKind {
ASSIGN,
IF_NULL,
ADD,
SUB,
MUL,
@@ -244,6 +251,12 @@ class AssignmentOperator {
const AssignmentOperator._(AssignmentOperatorKind.ASSIGN, '=',
null, isUserDefinable: false);
/// The ??= operator.
static const AssignmentOperator IF_NULL =
const AssignmentOperator._(AssignmentOperatorKind.IF_NULL, '??=',
BinaryOperator.IF_NULL,
isUserDefinable: false);
/// The += assignment operator.
static const AssignmentOperator ADD =
const AssignmentOperator._(AssignmentOperatorKind.ADD, '+=',
@@ -302,6 +315,7 @@ class AssignmentOperator {
static AssignmentOperator parse(String value) {
switch (value) {
case '=': return ASSIGN;
case '??=': return IF_NULL;
case '*=': return MUL;
case '/=': return DIV;
case '%=': return MOD;
@@ -349,4 +363,4 @@ class IncDecOperator {
default: return null;
}
}
}
}
@@ -304,6 +304,18 @@ abstract class SemanticSendVisitor<R, A> {
Selector selector,
A arg);
/// Conditional (if not null) getter call on [receiver] of the property
/// defined by [selector].
///
/// For instance
/// m(receiver) => receiver?.foo;
///
R visitIfNotNullDynamicPropertyGet(
Send node,
Node receiver,
Selector selector,
A arg);
/// Setter call on [receiver] with argument [rhs] of the property defined by
/// [selector].
///
@@ -319,6 +331,21 @@ abstract class SemanticSendVisitor<R, A> {
Node rhs,
A arg);
/// Conditional (if not null) setter call on [receiver] with argument [rhs] of
/// the property defined by [selector].
///
/// For instance
/// m(receiver) {
/// receiver?.foo = rhs;
/// }
///
R visitIfNotNullDynamicPropertySet(
SendSet node,
Node receiver,
Selector selector,
Node rhs,
A arg);
/// Invocation of the property defined by [selector] on [receiver] with
/// [arguments].
///
@@ -334,6 +361,21 @@ abstract class SemanticSendVisitor<R, A> {
Selector selector,
A arg);
/// Conditinal invocation of the property defined by [selector] on [receiver]
/// with [arguments], if [receiver] is not null.
///
/// For instance
/// m(receiver) {
/// receiver?.foo(null, 42);
/// }
///
R visitIfNotNullDynamicPropertyInvoke(
Send node,
Node receiver,
NodeList arguments,
Selector selector,
A arg);
/// Getter call on `this` of the property defined by [selector].
///
/// For instance
@@ -1627,6 +1669,17 @@ abstract class SemanticSendVisitor<R, A> {
Node rhs,
A arg);
/// If-null, ??, expression with operands [left] and [right].
///
/// For instance
/// m() => left ?? right;
///
R visitIfNull(
Send node,
Node left,
Node right,
A arg);
/// Logical and, &&, expression with operands [left] and [right].
///
/// For instance
@@ -1701,6 +1754,22 @@ abstract class SemanticSendVisitor<R, A> {
Selector setterSelector,
A arg);
/// Compound assignment expression of [rhs] with [operator] of the property on
/// a possibly null [receiver] whose getter and setter are defined by
/// [getterSelector] and [setterSelector], respectively.
///
/// For instance:
/// m(receiver, rhs) => receiver?.foo += rhs;
///
R visitIfNotNullDynamicPropertyCompound(
Send node,
Node receiver,
AssignmentOperator operator,
Node rhs,
Selector getterSelector,
Selector setterSelector,
A arg);
/// Compound assignment expression of [rhs] with [operator] of the property on
/// `this` whose getter and setter are defined by [getterSelector] and
/// [setterSelector], respectively.
@@ -2385,6 +2454,21 @@ abstract class SemanticSendVisitor<R, A> {
Selector setterSelector,
A arg);
/// Prefix expression with [operator] of the property on a possibly null
/// [receiver] whose getter and setter are defined by [getterSelector] and
/// [setterSelector], respectively.
///
/// For instance:
/// m(receiver) => ++receiver?.foo;
///
R visitIfNotNullDynamicPropertyPrefix(
Send node,
Node receiver,
IncDecOperator operator,
Selector getterSelector,
Selector setterSelector,
A arg);
/// Prefix expression with [operator] on a [parameter].
///
/// For instance:
@@ -2826,6 +2910,21 @@ abstract class SemanticSendVisitor<R, A> {
Selector setterSelector,
A arg);
/// Postfix expression with [operator] of the property on a possibly null
/// [receiver] whose getter and setter are defined by [getterSelector] and
/// [setterSelector], respectively.
///
/// For instance:
/// m(receiver) => receiver?.foo++;
///
R visitIfNotNullDynamicPropertyPostfix(
Send node,
Node receiver,
IncDecOperator operator,
Selector getterSelector,
Selector setterSelector,
A arg);
/// Postfix expression with [operator] on a [parameter].
///
/// For instance:
@@ -3288,6 +3387,10 @@ abstract class SemanticSendVisitor<R, A> {
/// m5() => unresolved.Foo.bar;
/// m6() => C.unresolved;
/// m7() => prefix.C.unresolved;
/// m8() => prefix?.unresolved;
/// m9() => Unresolved?.foo;
/// m10() => unresolved?.foo;
/// m11() => unresolved?.Foo?.bar;
///
// TODO(johnniwinther): Split the cases in which a prefix is resolved.
R visitUnresolvedGet(
@@ -3319,6 +3422,10 @@ abstract class SemanticSendVisitor<R, A> {
/// m5() => unresolved.Foo.bar = 42;
/// m6() => C.unresolved = 42;
/// m7() => prefix.C.unresolved = 42;
/// m8() => prefix?.unresolved = 42;
/// m9() => Unresolved?.foo = 42;
/// m10() => unresolved?.foo = 42;
/// m11() => unresolved?.Foo?.bar = 42;
///
// TODO(johnniwinther): Split the cases in which a prefix is resolved.
R visitUnresolvedSet(
@@ -3338,6 +3445,10 @@ abstract class SemanticSendVisitor<R, A> {
/// m5() => unresolved.Foo.bar(null, 42);
/// m6() => C.unresolved(null, 42);
/// m7() => prefix.C.unresolved(null, 42);
/// m8() => prefix?.unresolved(null, 42);
/// m9() => Unresolved?.foo(null, 42);
/// m10() => unresolved?.foo(null, 42);
/// m11() => unresolved?.Foo?.bar(null, 42);
///
// TODO(johnniwinther): Split the cases in which a prefix is resolved.
R visitUnresolvedInvoke(
@@ -3453,6 +3564,10 @@ abstract class SemanticSendVisitor<R, A> {
/// m5() => unresolved.Foo.bar += 42;
/// m6() => C.unresolved += 42;
/// m7() => prefix.C.unresolved += 42;
/// m8() => prefix?.unresolved += 42;
/// m9() => Unresolved?.foo += 42;
/// m10() => unresolved?.foo += 42;
/// m11() => unresolved?.Foo?.bar += 42;
///
// TODO(johnniwinther): Split the cases in which a prefix is resolved.
R visitUnresolvedCompound(
@@ -3563,6 +3678,10 @@ abstract class SemanticSendVisitor<R, A> {
/// m5() => ++unresolved.Foo.bar;
/// m6() => ++C.unresolved;
/// m7() => ++prefix.C.unresolved;
/// m8() => ++prefix?.unresolved;
/// m9() => ++Unresolved?.foo;
/// m10() => ++unresolved?.foo;
/// m11() => ++unresolved?.Foo?.bar;
///
// TODO(johnniwinther): Split the cases in which a prefix is resolved.
R visitUnresolvedPrefix(
@@ -3672,6 +3791,10 @@ abstract class SemanticSendVisitor<R, A> {
/// m5() => unresolved.Foo.bar++;
/// m6() => C.unresolved++;
/// m7() => prefix.C.unresolved++;
/// m8() => prefix?.unresolved++;
/// m9() => Unresolved?.foo++;
/// m10() => unresolved?.foo++;
/// m11() => unresolved?.Foo?.bar++;
///
// TODO(johnniwinther): Split the cases in which a prefix is resolved.
R visitUnresolvedPostfix(
@@ -86,6 +86,16 @@ abstract class PrefixBulkMixin<R, A>
return bulkHandlePrefix(node, arg);
}
R visitIfNotNullDynamicPropertyPrefix(
Send node,
Node receiver,
IncDecOperator operator,
Selector getterSelector,
Selector setterSelector,
A arg) {
return bulkHandlePrefix(node, arg);
}
@override
R visitIndexPrefix(
Send node,
@@ -504,6 +514,16 @@ abstract class PostfixBulkMixin<R, A>
}
@override
R visitIfNotNullDynamicPropertyPostfix(
Send node,
Node receiver,
IncDecOperator operator,
Selector getterSelector,
Selector setterSelector,
A arg) {
return bulkHandlePostfix(node, arg);
}
R visitIndexPostfix(
Send node,
Node receiver,
@@ -929,6 +949,18 @@ abstract class CompoundBulkMixin<R, A>
return bulkHandleCompound(node, arg);
}
@override
R visitIfNotNullDynamicPropertyCompound(
Send node,
Node receiver,
AssignmentOperator operator,
Node rhs,
Selector getterSelector,
Selector setterSelector,
A arg) {
return bulkHandleCompound(node, arg);
}
@override
R visitLocalVariableCompound(
Send node,
@@ -1336,6 +1368,16 @@ abstract class InvokeBulkMixin<R, A>
return bulkHandleInvoke(node, arg);
}
@override
R visitIfNotNullDynamicPropertyInvoke(
Send node,
Node receiver,
NodeList arguments,
Selector selector,
A arg) {
return bulkHandleInvoke(node, arg);
}
@override
R visitDynamicTypeLiteralInvoke(
Send node,
@@ -1634,6 +1676,15 @@ abstract class GetBulkMixin<R, A>
return bulkHandleGet(node, arg);
}
@override
R visitIfNotNullDynamicPropertyGet(
Send node,
Node receiver,
Selector selector,
A arg) {
return bulkHandleGet(node, arg);
}
@override
R visitDynamicTypeLiteralGet(
Send node,
@@ -1838,6 +1889,16 @@ abstract class SetBulkMixin<R, A>
return bulkHandleSet(node, arg);
}
@override
R visitIfNotNullDynamicPropertySet(
SendSet node,
Node receiver,
Selector selector,
Node rhs,
A arg) {
return bulkHandleSet(node, arg);
}
@override
R visitLocalVariableSet(
SendSet node,
@@ -2381,6 +2442,15 @@ abstract class BaseBulkMixin<R, A>
return bulkHandleNode(node, 'Is not test `#` unhandled.', arg);
}
@override
R visitIfNull(
Send node,
Node left,
Node right,
A arg) {
return bulkHandleNode(node, 'If-null (Lazy ?? `#`) unhandled.', arg);
}
@override
R visitLogicalAnd(
Send node,
@@ -3839,6 +3909,20 @@ class TraversalSendMixin<R, A> implements SemanticSendVisitor<R, A> {
return null;
}
@override
R visitIfNotNullDynamicPropertyCompound(
Send node,
Node receiver,
AssignmentOperator operator,
Node rhs,
Selector getterSelector,
Selector setterSelector,
A arg) {
apply(receiver, arg);
apply(rhs, arg);
return null;
}
@override
R visitDynamicPropertyGet(
Send node,
@@ -3849,6 +3933,16 @@ class TraversalSendMixin<R, A> implements SemanticSendVisitor<R, A> {
return null;
}
@override
R visitIfNotNullDynamicPropertyGet(
Send node,
Node receiver,
Selector selector,
A arg) {
apply(receiver, arg);
return null;
}
@override
R visitDynamicPropertyInvoke(
Send node,
@@ -3861,6 +3955,18 @@ class TraversalSendMixin<R, A> implements SemanticSendVisitor<R, A> {
return null;
}
@override
R visitIfNotNullDynamicPropertyInvoke(
Send node,
Node receiver,
NodeList arguments,
Selector selector,
A arg) {
apply(receiver, arg);
apply(arguments, arg);
return null;
}
@override
R visitDynamicPropertyPostfix(
Send node,
@@ -3873,6 +3979,18 @@ class TraversalSendMixin<R, A> implements SemanticSendVisitor<R, A> {
return null;
}
@override
R visitIfNotNullDynamicPropertyPostfix(
Send node,
Node receiver,
IncDecOperator operator,
Selector getterSelector,
Selector setterSelector,
A arg) {
apply(receiver, arg);
return null;
}
@override
R visitDynamicPropertyPrefix(
Send node,
@@ -3885,6 +4003,18 @@ class TraversalSendMixin<R, A> implements SemanticSendVisitor<R, A> {
return null;
}
@override
R visitIfNotNullDynamicPropertyPrefix(
Send node,
Node receiver,
IncDecOperator operator,
Selector getterSelector,
Selector setterSelector,
A arg) {
apply(receiver, arg);
return null;
}
@override
R visitDynamicPropertySet(
SendSet node,
@@ -3897,6 +4027,17 @@ class TraversalSendMixin<R, A> implements SemanticSendVisitor<R, A> {
return null;
}
@override
R visitIfNotNullDynamicPropertySet(
SendSet node,
Node receiver,
Selector selector,
Node rhs,
A arg) {
apply(rhs, arg);
return null;
}
@override
R visitDynamicTypeLiteralCompound(
Send node,
@@ -4089,6 +4230,17 @@ class TraversalSendMixin<R, A> implements SemanticSendVisitor<R, A> {
return null;
}
@override
R visitIfNull(
Send node,
Node left,
Node right,
A arg) {
apply(left, arg);
apply(right, arg);
return null;
}
@override
R visitLogicalAnd(
Send node,
@@ -7497,6 +7649,16 @@ abstract class BaseImplementationOfDynamicsMixin<R, A>
return handleDynamicGet(node, receiver, selector, arg);
}
@override
R visitIfNotNullDynamicPropertyGet(
Send node,
Node receiver,
Selector selector,
A arg) {
// TODO(johnniwinther): should these redirect to handleDynamicX?
return handleDynamicGet(node, receiver, selector, arg);
}
@override
R visitDynamicPropertyInvoke(
Send node,
@@ -7507,6 +7669,16 @@ abstract class BaseImplementationOfDynamicsMixin<R, A>
return handleDynamicInvoke(node, receiver, arguments, selector, arg);
}
@override
R visitIfNotNullDynamicPropertyInvoke(
Send node,
Node receiver,
NodeList arguments,
Selector selector,
A arg) {
return handleDynamicInvoke(node, receiver, arguments, selector, arg);
}
@override
R visitDynamicPropertySet(
SendSet node,
@@ -7517,6 +7689,16 @@ abstract class BaseImplementationOfDynamicsMixin<R, A>
return handleDynamicSet(node, receiver, selector, rhs, arg);
}
@override
R visitIfNotNullDynamicPropertySet(
SendSet node,
Node receiver,
Selector selector,
Node rhs,
A arg) {
return handleDynamicSet(node, receiver, selector, rhs, arg);
}
@override
R visitThisPropertyGet(
Send node,
@@ -7591,6 +7773,19 @@ abstract class BaseImplementationOfDynamicCompoundsMixin<R, A>
node, receiver, operator, rhs, getterSelector, setterSelector, arg);
}
@override
R visitIfNotNullDynamicPropertyCompound(
Send node,
Node receiver,
AssignmentOperator operator,
Node rhs,
Selector getterSelector,
Selector setterSelector,
A arg) {
return handleDynamicCompound(
node, receiver, operator, rhs, getterSelector, setterSelector, arg);
}
@override
R visitDynamicPropertyPostfix(
Send node,
@@ -7604,6 +7799,19 @@ abstract class BaseImplementationOfDynamicCompoundsMixin<R, A>
getterSelector, setterSelector, arg, isPrefix: false);
}
@override
R visitIfNotNullDynamicPropertyPostfix(
Send node,
Node receiver,
IncDecOperator operator,
Selector getterSelector,
Selector setterSelector,
A arg) {
return handleDynamicPostfixPrefix(
node, receiver, operator,
getterSelector, setterSelector, arg, isPrefix: false);
}
@override
R visitDynamicPropertyPrefix(
Send node,
@@ -7617,6 +7825,19 @@ abstract class BaseImplementationOfDynamicCompoundsMixin<R, A>
getterSelector, setterSelector, arg, isPrefix: true);
}
@override
R visitIfNotNullDynamicPropertyPrefix(
Send node,
Node receiver,
IncDecOperator operator,
Selector getterSelector,
Selector setterSelector,
A arg) {
return handleDynamicPostfixPrefix(
node, receiver, operator,
getterSelector, setterSelector, arg, isPrefix: true);
}
@override
R visitThisPropertyCompound(
Send node,
@@ -7829,6 +8050,23 @@ abstract class BaseImplementationOfCompoundsMixin<R, A>
arg);
}
R visitIfNotNullDynamicPropertyCompound(
Send node,
Node receiver,
AssignmentOperator operator,
Node rhs,
Selector getterSelector,
Selector setterSelector,
A arg) {
return handleDynamicCompounds(
node,
receiver,
new AssignmentCompound(operator, rhs),
getterSelector,
setterSelector,
arg);
}
@override
R visitThisPropertyCompound(
Send node,
@@ -8212,6 +8450,22 @@ abstract class BaseImplementationOfCompoundsMixin<R, A>
arg);
}
R visitIfNotNullDynamicPropertyPrefix(
Send node,
Node receiver,
IncDecOperator operator,
Selector getterSelector,
Selector setterSelector,
A arg) {
return handleDynamicCompounds(
node,
receiver,
new IncDecCompound(CompoundKind.PREFIX, operator),
getterSelector,
setterSelector,
arg);
}
@override
R visitParameterPrefix(
Send node,
@@ -8516,6 +8770,23 @@ abstract class BaseImplementationOfCompoundsMixin<R, A>
arg);
}
@override
R visitIfNotNullDynamicPropertyPostfix(
Send node,
Node receiver,
IncDecOperator operator,
Selector getterSelector,
Selector setterSelector,
A arg) {
return handleDynamicCompounds(
node,
receiver,
new IncDecCompound(CompoundKind.POSTFIX, operator),
getterSelector,
setterSelector,
arg);
}
@override
R visitParameterPostfix(
Send node,
@@ -225,9 +225,10 @@ abstract class SendResolverMixin {
return new AsStructure(elements.getType(node.arguments.single));
} else if (operatorText == '&&') {
return internalError(node, "Unexpected logical and.");
return const LogicalAndStructure();
} else if (operatorText == '||') {
return internalError(node, "Unexpected logical or.");
} else if (operatorText == '??') {
return internalError(node, "Unexpected if-null.");
}
}
@@ -514,7 +515,11 @@ abstract class SendResolverMixin {
} else {
return new StaticAccess.superMethod(element);
}
} else if (node.isOperator) {
} else if (node.isOperator || node.isConditional) {
// Conditional sends (e?.x) are treated as dynamic property reads because
// they are equivalent to do ((a) => a == null ? null : a.x)(e). If `e` is
// a type `A`, this is equivalent to write `(A).x`.
// TODO(johnniwinther): maybe add DynamicAccess.conditionalDynamicProperty
return new DynamicAccess.dynamicProperty(node.receiver);
} else if (Elements.isClosureSend(node, element)) {
if (element == null) {
@@ -59,6 +59,21 @@ class InvalidAssertStructure<R, A> implements SendStructure<R, A> {
String toString() => 'invalid assert';
}
/// The structure for a [Send] of the form `a ?? b`.
class IfNullStructure<R, A> implements SendStructure<R, A> {
const IfNullStructure();
R dispatch(SemanticSendVisitor<R, A> visitor, Send node, A arg) {
return visitor.visitIfNull(
node,
node.receiver,
node.arguments.single,
arg);
}
String toString() => '??';
}
/// The structure for a [Send] of the form `a && b`.
class LogicalAndStructure<R, A> implements SendStructure<R, A> {
const LogicalAndStructure();
@@ -161,6 +176,14 @@ class InvokeStructure<R, A> implements SendStructure<R, A> {
R dispatch(SemanticSendVisitor<R, A> visitor, Send node, A arg) {
switch (semantics.kind) {
case AccessKind.DYNAMIC_PROPERTY:
if (node.isConditional) {
return visitor.visitIfNotNullDynamicPropertyInvoke(
node,
node.receiver,
node.argumentsNode,
selector,
arg);
}
return visitor.visitDynamicPropertyInvoke(
node,
node.receiver,
@@ -424,6 +447,13 @@ class GetStructure<R, A> implements SendStructure<R, A> {
R dispatch(SemanticSendVisitor<R, A> visitor, Send node, A arg) {
switch (semantics.kind) {
case AccessKind.DYNAMIC_PROPERTY:
if (node.isConditional) {
return visitor.visitIfNotNullDynamicPropertyGet(
node,
node.receiver,
selector,
arg);
}
return visitor.visitDynamicPropertyGet(
node,
node.receiver,
@@ -578,12 +608,20 @@ class SetStructure<R, A> implements SendStructure<R, A> {
R dispatch(SemanticSendVisitor<R, A> visitor, Send node, A arg) {
switch (semantics.kind) {
case AccessKind.DYNAMIC_PROPERTY:
return visitor.visitDynamicPropertySet(
if (node.isConditional) {
return visitor.visitIfNotNullDynamicPropertySet(
node,
node.receiver,
selector,
node.arguments.single,
arg);
}
return visitor.visitDynamicPropertySet(
node,
node.receiver,
selector,
node.arguments.single,
arg);
case AccessKind.LOCAL_FUNCTION:
return visitor.visitLocalFunctionSet(
node,
@@ -1257,6 +1295,16 @@ class CompoundStructure<R, A> implements SendStructure<R, A> {
R dispatch(SemanticSendVisitor<R, A> visitor, Send node, A arg) {
switch (semantics.kind) {
case AccessKind.DYNAMIC_PROPERTY:
if (node.isConditional) {
return visitor.visitIfNotNullDynamicPropertyCompound(
node,
node.receiver,
operator,
node.arguments.single,
getterSelector,
setterSelector,
arg);
}
return visitor.visitDynamicPropertyCompound(
node,
node.receiver,
@@ -1674,6 +1722,15 @@ class PrefixStructure<R, A> implements SendStructure<R, A> {
R dispatch(SemanticSendVisitor<R, A> visitor, Send node, A arg) {
switch (semantics.kind) {
case AccessKind.DYNAMIC_PROPERTY:
if (node.isConditional) {
return visitor.visitIfNotNullDynamicPropertyPrefix(
node,
node.receiver,
operator,
getterSelector,
setterSelector,
arg);
}
return visitor.visitDynamicPropertyPrefix(
node,
node.receiver,
@@ -1982,6 +2039,15 @@ class PostfixStructure<R, A> implements SendStructure<R, A> {
R dispatch(SemanticSendVisitor<R, A> visitor, Send node, A arg) {
switch (semantics.kind) {
case AccessKind.DYNAMIC_PROPERTY:
if (node.isConditional) {
return visitor.visitIfNotNullDynamicPropertyPostfix(
node,
node.receiver,
operator,
getterSelector,
setterSelector,
arg);
}
return visitor.visitDynamicPropertyPostfix(
node,
node.receiver,
@@ -574,6 +574,15 @@ class ResolvedSemanticDispatcher<R> extends Object
return visitor.visitOperatorSend(node);
}
@override
R visitIfNull(
Send node,
Node left,
Node right,
ResolvedKindVisitor<R> visitor) {
return visitor.visitOperatorSend(node);
}
@override
R visitLogicalAnd(
Send node,
@@ -628,6 +637,16 @@ class ResolvedSemanticDispatcher<R> extends Object
return visitor.visitDynamicSend(node);
}
@override
R visitIfNotNullDynamicPropertyInvoke(
Send node,
Node receiver,
NodeList arguments,
Selector selector,
ResolvedKindVisitor<R> visitor) {
return visitor.visitDynamicSend(node);
}
@override
R visitThisPropertyInvoke(
Send node,
@@ -5,8 +5,9 @@
part of scanner;
abstract class ArrayBasedScanner extends AbstractScanner {
ArrayBasedScanner(SourceFile file, bool includeComments)
: super(file, includeComments);
ArrayBasedScanner(SourceFile file, bool includeComments,
bool enableNullAwareOperators)
: super(file, includeComments, enableNullAwareOperators);
/**
* The stack of open groups, e.g [: { ... ( .. :]
@@ -217,4 +218,4 @@ abstract class ArrayBasedScanner extends AbstractScanner {
groupingStack = groupingStack.tail;
}
}
}
}
+15 -6
View File
@@ -746,6 +746,9 @@ class Listener {
break;
}
reportError(token, kind, arguments);
} else if (token is UnsupportedNullAwareToken) {
reportError(token, MessageKind.NULL_AWARE_OPERATORS_DISABLED,
{'operator' : token.operator});
} else if (token is UnmatchedToken) {
String begin = token.begin.value;
String end = closeBraceFor(begin);
@@ -1568,7 +1571,7 @@ class NodeListener extends ElementListener {
if (name.asSend() == null) {
name = new Send(thisIdentifier, name);
} else {
name = name.asSend().copyWithReceiver(thisIdentifier);
name = name.asSend().copyWithReceiver(thisIdentifier, false);
}
}
TypeAnnotation type = popNode();
@@ -1698,7 +1701,9 @@ class NodeListener extends ElementListener {
Node argument = popNode();
Node receiver = popNode();
String tokenString = token.stringValue;
if (identical(tokenString, '.') || identical(tokenString, '..')) {
if (identical(tokenString, '.') ||
identical(tokenString, '..') ||
identical(tokenString, '?.')) {
Send argumentSend = argument.asSend();
if (argumentSend == null) {
// TODO(ahe): The parser should diagnose this problem, not
@@ -1708,7 +1713,8 @@ class NodeListener extends ElementListener {
}
if (argumentSend.receiver != null) internalError(node: argument);
if (argument is SendSet) internalError(node: argument);
pushNode(argument.asSend().copyWithReceiver(receiver));
pushNode(argument.asSend().copyWithReceiver(receiver,
identical(tokenString, '?.')));
} else {
NodeList arguments = new NodeList.singleton(argument);
pushNode(new Send(receiver, new Operator(token), arguments));
@@ -1755,7 +1761,8 @@ class NodeListener extends ElementListener {
arguments = new NodeList.singleton(arg);
}
Operator op = new Operator(token);
pushNode(new SendSet(send.receiver, send.selector, op, arguments));
pushNode(new SendSet(send.receiver, send.selector, op, arguments,
send.isConditional));
}
void reportNotAssignable(Node node) {
@@ -1922,9 +1929,11 @@ class NodeListener extends ElementListener {
Operator op = new Operator(token);
if (isPrefix) {
pushNode(new SendSet.prefix(send.receiver, send.selector, op, argument));
pushNode(new SendSet.prefix(send.receiver, send.selector, op, argument,
send.isConditional));
} else {
pushNode(new SendSet.postfix(send.receiver, send.selector, op, argument));
pushNode(new SendSet.postfix(send.receiver, send.selector, op, argument,
send.isConditional));
}
}
+2 -1
View File
@@ -1784,7 +1784,8 @@ class Parser {
token = parsePrecedenceExpression(token.next, level, allowCascades);
listener.handleAssignmentExpression(operator);
} else if (identical(tokenLevel, POSTFIX_PRECEDENCE)) {
if (identical(info, PERIOD_INFO)) {
if (identical(info, PERIOD_INFO) ||
identical(info, QUESTION_PERIOD_INFO)) {
// Left associative, so we recurse at the next higher
// precedence level. However, POSTFIX_PRECEDENCE is the
// highest level, so we just call parseUnaryExpression
+42 -6
View File
@@ -7,11 +7,14 @@ part of scanner;
abstract class Scanner {
Token tokenize();
factory Scanner(SourceFile file, {bool includeComments: false}) {
factory Scanner(SourceFile file,
{bool includeComments: false, bool enableNullAwareOperators: false}) {
if (file is Utf8BytesSourceFile) {
return new Utf8BytesScanner(file, includeComments: includeComments);
return new Utf8BytesScanner(file, includeComments: includeComments,
enableNullAwareOperators: enableNullAwareOperators);
} else {
return new StringScanner(file, includeComments: includeComments);
return new StringScanner(file, includeComments: includeComments,
enableNullAwareOperators: enableNullAwareOperators);
}
}
}
@@ -20,6 +23,7 @@ abstract class AbstractScanner implements Scanner {
// TODO(ahe): Move this class to implementation.
final bool includeComments;
final bool enableNullAwareOperators;
/**
* The string offset for the next token that will be created.
@@ -53,7 +57,8 @@ abstract class AbstractScanner implements Scanner {
final List<int> lineStarts = <int>[0];
AbstractScanner(this.file, this.includeComments) {
AbstractScanner(
this.file, this.includeComments, this.enableNullAwareOperators) {
this.tail = this.tokens;
}
@@ -330,8 +335,7 @@ abstract class AbstractScanner implements Scanner {
}
if (identical(next, $QUESTION)) {
appendPrecedenceToken(QUESTION_INFO);
return advance();
return tokenizeQuestion(next);
}
if (identical(next, $CLOSE_SQUARE_BRACKET)) {
@@ -450,6 +454,38 @@ abstract class AbstractScanner implements Scanner {
return select($EQ, CARET_EQ_INFO, CARET_INFO);
}
int tokenizeQuestion(int next) {
// ? ?. ?? ??=
next = advance();
if (identical(next, $QUESTION)) {
if (enableNullAwareOperators) {
return select($EQ, QUESTION_QUESTION_EQ_INFO, QUESTION_QUESTION_INFO);
} else {
next = advance();
PrecedenceInfo info;
if (identical(next, $EQ)) {
info = QUESTION_QUESTION_EQ_INFO;
next = advance();
} else {
info = QUESTION_QUESTION_INFO;
}
appendErrorToken(new UnsupportedNullAwareToken(info.value, tokenStart));
return next;
}
} else if (identical(next, $PERIOD)) {
if (enableNullAwareOperators) {
appendPrecedenceToken(QUESTION_PERIOD_INFO);
} else {
appendErrorToken(new UnsupportedNullAwareToken(
QUESTION_PERIOD_INFO.value, tokenStart));
}
return advance();
} else {
appendPrecedenceToken(QUESTION_INFO);
return next;
}
}
int tokenizeBar(int next) {
// | || |=
next = advance();
@@ -29,7 +29,8 @@ class ScannerTask extends CompilerTask {
void scanElements(CompilationUnitElement compilationUnit) {
Script script = compilationUnit.script;
Token tokens = new Scanner(script.file,
includeComments: compiler.preserveComments).tokenize();
includeComments: compiler.preserveComments,
enableNullAwareOperators: compiler.enableNullAwareOperators).tokenize();
if (compiler.preserveComments) {
tokens = compiler.processAndStripComments(tokens);
}
@@ -15,14 +15,16 @@ class StringScanner extends ArrayBasedScanner {
/** The current offset in [string]. */
int scanOffset = -1;
StringScanner(SourceFile file, {bool includeComments: false})
StringScanner(SourceFile file, {bool includeComments: false,
bool enableNullAwareOperators: false})
: string = file.slowText(),
super(file, includeComments) {
super(file, includeComments, enableNullAwareOperators) {
ensureZeroTermination();
}
StringScanner.fromString(this.string, {bool includeComments: false})
: super(null, includeComments) {
StringScanner.fromString(this.string, {bool includeComments: false,
bool enableNullAwareOperators: false})
: super(null, includeComments, enableNullAwareOperators) {
ensureZeroTermination();
}
+43 -16
View File
@@ -75,6 +75,9 @@ const int GT_GT_TOKEN = PERCENT_EQ_TOKEN + 1;
const int CARET_EQ_TOKEN = GT_GT_TOKEN + 1;
const int COMMENT_TOKEN = CARET_EQ_TOKEN + 1;
const int STRING_INTERPOLATION_IDENTIFIER_TOKEN = COMMENT_TOKEN + 1;
const int QUESTION_PERIOD_TOKEN = STRING_INTERPOLATION_IDENTIFIER_TOKEN + 1;
const int QUESTION_QUESTION_TOKEN = QUESTION_PERIOD_TOKEN + 1;
const int QUESTION_QUESTION_EQ_TOKEN = QUESTION_QUESTION_TOKEN + 1;
/**
* A token that doubles as a linked list.
@@ -259,6 +262,21 @@ class BadInputToken extends ErrorToken {
}
}
// TODO(sigmund): delete once we enable null-aware-operators by default.
class UnsupportedNullAwareToken extends ErrorToken {
final String operator;
UnsupportedNullAwareToken(this.operator, int charOffset)
: super(charOffset);
String toString() => "UnsupportedNullAwareToken($operator)";
String get assertionMessage => "'$opreator' isn't supported without "
"the --enable-null-aware-operators flag.";
int get charCount => operator.length;
}
class UnterminatedToken extends ErrorToken {
final String start;
final int endOffset;
@@ -589,27 +607,33 @@ const PrecedenceInfo STAR_EQ_INFO =
const PrecedenceInfo TILDE_SLASH_EQ_INFO =
const PrecedenceInfo('~/=',
ASSIGNMENT_PRECEDENCE, TILDE_SLASH_EQ_TOKEN);
const PrecedenceInfo QUESTION_QUESTION_EQ_INFO =
const PrecedenceInfo('??=',
ASSIGNMENT_PRECEDENCE, QUESTION_QUESTION_EQ_TOKEN);
const PrecedenceInfo QUESTION_INFO =
const PrecedenceInfo('?', 3, QUESTION_TOKEN);
const PrecedenceInfo QUESTION_QUESTION_INFO =
const PrecedenceInfo('??', 4, QUESTION_QUESTION_TOKEN);
const PrecedenceInfo BAR_BAR_INFO =
const PrecedenceInfo('||', 4, BAR_BAR_TOKEN);
const PrecedenceInfo('||', 5, BAR_BAR_TOKEN);
const PrecedenceInfo AMPERSAND_AMPERSAND_INFO =
const PrecedenceInfo('&&', 5, AMPERSAND_AMPERSAND_TOKEN);
const PrecedenceInfo('&&', 6, AMPERSAND_AMPERSAND_TOKEN);
const PrecedenceInfo BAR_INFO =
const PrecedenceInfo('|', 8, BAR_TOKEN);
const PrecedenceInfo('|', 9, BAR_TOKEN);
const PrecedenceInfo CARET_INFO =
const PrecedenceInfo('^', 9, CARET_TOKEN);
const PrecedenceInfo('^', 10, CARET_TOKEN);
const PrecedenceInfo AMPERSAND_INFO =
const PrecedenceInfo('&', 10, AMPERSAND_TOKEN);
const PrecedenceInfo('&', 11, AMPERSAND_TOKEN);
// Equality operators.
const int EQUALITY_PRECEDENCE = 6;
const int EQUALITY_PRECEDENCE = 7;
const PrecedenceInfo BANG_EQ_EQ_INFO =
const PrecedenceInfo('!==',
EQUALITY_PRECEDENCE, BANG_EQ_EQ_TOKEN);
@@ -624,7 +648,7 @@ const PrecedenceInfo EQ_EQ_INFO =
EQUALITY_PRECEDENCE, EQ_EQ_TOKEN);
// Relational operators.
const int RELATIONAL_PRECEDENCE = 7;
const int RELATIONAL_PRECEDENCE = 8;
const PrecedenceInfo GT_EQ_INFO =
const PrecedenceInfo('>=',
RELATIONAL_PRECEDENCE, GT_EQ_TOKEN);
@@ -646,30 +670,33 @@ const PrecedenceInfo LT_INFO =
// Shift operators.
const PrecedenceInfo GT_GT_INFO =
const PrecedenceInfo('>>', 11, GT_GT_TOKEN);
const PrecedenceInfo('>>', 12, GT_GT_TOKEN);
const PrecedenceInfo LT_LT_INFO =
const PrecedenceInfo('<<', 11, LT_LT_TOKEN);
const PrecedenceInfo('<<', 12, LT_LT_TOKEN);
// Additive operators.
const PrecedenceInfo MINUS_INFO =
const PrecedenceInfo('-', 12, MINUS_TOKEN);
const PrecedenceInfo('-', 13, MINUS_TOKEN);
const PrecedenceInfo PLUS_INFO =
const PrecedenceInfo('+', 12, PLUS_TOKEN);
const PrecedenceInfo('+', 13, PLUS_TOKEN);
// Multiplicative operators.
const PrecedenceInfo PERCENT_INFO =
const PrecedenceInfo('%', 13, PERCENT_TOKEN);
const PrecedenceInfo('%', 14, PERCENT_TOKEN);
const PrecedenceInfo SLASH_INFO =
const PrecedenceInfo('/', 13, SLASH_TOKEN);
const PrecedenceInfo('/', 14, SLASH_TOKEN);
const PrecedenceInfo STAR_INFO =
const PrecedenceInfo('*', 13, STAR_TOKEN);
const PrecedenceInfo('*', 14, STAR_TOKEN);
const PrecedenceInfo TILDE_SLASH_INFO =
const PrecedenceInfo('~/', 13, TILDE_SLASH_TOKEN);
const PrecedenceInfo('~/', 14, TILDE_SLASH_TOKEN);
const int POSTFIX_PRECEDENCE = 14;
const int POSTFIX_PRECEDENCE = 15;
const PrecedenceInfo PERIOD_INFO =
const PrecedenceInfo('.', POSTFIX_PRECEDENCE,
PERIOD_TOKEN);
const PrecedenceInfo QUESTION_PERIOD_INFO =
const PrecedenceInfo('?.', POSTFIX_PRECEDENCE,
QUESTION_PERIOD_TOKEN);
const PrecedenceInfo KEYWORD_INFO =
const PrecedenceInfo('keyword', 0, KEYWORD_TOKEN);
@@ -69,9 +69,10 @@ class Utf8BytesScanner extends ArrayBasedScanner {
* array whose last element is '0' to signal the end of the file. If this
* is not the case, the entire array is copied before scanning.
*/
Utf8BytesScanner(SourceFile file, {bool includeComments: false})
Utf8BytesScanner(SourceFile file, {bool includeComments: false,
bool enableNullAwareOperators: false})
: bytes = file.slowUtf8ZeroTerminatedBytes(),
super(file, includeComments) {
super(file, includeComments, enableNullAwareOperators) {
assert(bytes.last == 0);
// Skip a leading BOM.
if (_containsBomAt(0)) byteOffset += 3;
@@ -85,9 +86,10 @@ class Utf8BytesScanner extends ArrayBasedScanner {
* scanning.
*/
Utf8BytesScanner.fromBytes(List<int> zeroTerminatedBytes,
{bool includeComments: false})
{bool includeComments: false,
bool enableNullAwareOperators: false})
: this.bytes = zeroTerminatedBytes,
super(null, includeComments) {
super(null, includeComments, enableNullAwareOperators) {
assert(bytes.last == 0);
}
+221 -49
View File
@@ -3141,6 +3141,12 @@ class SsaBuilder extends NewResolvedVisitor {
branchBuilder.handleIf(visitCondition, visitThen, visitElse);
}
@override
void visitIfNull(ast.Send node, ast.Node left, ast.Node right, _) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleIfNull(() => visit(left), () => visit(right));
}
@override
void visitLogicalAnd(ast.Send node, ast.Node left, ast.Node right, _) {
SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, node);
@@ -3397,6 +3403,36 @@ class SsaBuilder extends NewResolvedVisitor {
generateDynamicGet(node);
}
@override
void visitIfNotNullDynamicPropertyGet(
ast.Send node,
ast.Node receiver,
Selector selector,
_) {
// exp?.x compiled as:
// t1 = exp;
// result = t1 == null ? t1 : t1.x;
// This is equivalent to t1 == null ? null : t1.x, but in the current form
// we will be able to later compress it as:
// t1 || t1.x
HInstruction expression;
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleConditional(
() {
expression = visitAndPop(receiver);
pushCheckNull(expression);
},
() => stack.add(expression),
() => generateInstanceGetterWithCompiledReceiver(
node, elements.getSelector(node), expression));
}
/// Pushes a boolean checking [expression] against null.
pushCheckNull(HInstruction expression) {
push(new HIdentity(expression, graph.addConstantNull(compiler),
null, backend.boolType));
}
@override
void visitLocalVariableGet(ast.Send node, LocalVariableElement variable, _) {
handleLocalGet(variable);
@@ -3468,6 +3504,37 @@ class SsaBuilder extends NewResolvedVisitor {
generateStaticGetterGet(node, getter);
}
void generatePossiblyConditionalInstanceSetter(ast.Send send,
HInstruction pushReceiver(),
HInstruction pushValue(),
{Selector selector,
ast.Node location}) {
if (send.isConditional) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this, send);
// compile e?.x = e2 to:
//
// t1 = e
// if (t1 == null)
// result = t1 // same as result = null
// else
// result = e.x = e2
HInstruction receiver;
brancher.handleConditional(
() {
receiver = pushReceiver();
pushCheckNull(receiver);
},
() => stack.add(receiver),
() => generateInstanceSetterWithCompiledReceiver(
send, receiver, pushValue(),
selector: selector, location: location));
} else {
generateInstanceSetterWithCompiledReceiver(
send, pushReceiver(), pushValue(),
selector: selector, location: location);
}
}
void generateInstanceSetterWithCompiledReceiver(ast.Send send,
HInstruction receiver,
HInstruction value,
@@ -3730,10 +3797,14 @@ class SsaBuilder extends NewResolvedVisitor {
/// Generate a dynamic method, getter or setter invocation.
void generateDynamicSend(ast.Send node) {
HInstruction receiver = generateInstanceSendReceiver(node);
_generateDynamicSend(node, receiver);
}
void _generateDynamicSend(ast.Send node, HInstruction receiver) {
Selector selector = elements.getSelector(node);
List<HInstruction> inputs = <HInstruction>[];
HInstruction receiver = generateInstanceSendReceiver(node);
inputs.add(receiver);
addDynamicSendArgumentsToList(node, inputs);
@@ -3754,6 +3825,25 @@ class SsaBuilder extends NewResolvedVisitor {
generateDynamicSend(node);
}
@override
visitIfNotNullDynamicPropertyInvoke(
ast.Send node,
ast.Node receiver,
ast.NodeList arguments,
Selector selector,
_) {
/// Desugar `exp?.m()` to `(t1 = exp) == null ? t1 : t1.m()`
HInstruction receiver;
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleConditional(
() {
receiver = generateInstanceSendReceiver(node);
pushCheckNull(receiver);
},
() => stack.add(receiver),
() => _generateDynamicSend(node, receiver));
}
@override
visitThisPropertyInvoke(
ast.Send node,
@@ -5505,11 +5595,22 @@ class SsaBuilder extends NewResolvedVisitor {
}
ast.Operator op = node.assignmentOperator;
if (node.isSuperCall) {
HInstruction result;
List<HInstruction> setterInputs = <HInstruction>[];
void generateSuperSendSet() {
Selector setterSelector = elements.getSelector(node);
if (Elements.isUnresolved(element)
|| !setterSelector.applies(element, compiler.world)) {
generateSuperNoSuchMethodSend(
node, setterSelector, setterInputs);
pop();
} else {
add(buildInvokeSuper(setterSelector, element, setterInputs));
}
}
if (identical(node.assignmentOperator.source, '=')) {
addDynamicSendArgumentsToList(node, setterInputs);
result = setterInputs.last;
generateSuperSendSet();
stack.add(setterInputs.last);
} else {
Element getter = elements[node.selector];
List<HInstruction> getterInputs = <HInstruction>[];
@@ -5539,25 +5640,22 @@ class SsaBuilder extends NewResolvedVisitor {
getterSelector, getter, getterInputs);
add(getterInstruction);
}
handleComplexOperatorSend(node, getterInstruction, arguments);
setterInputs.add(pop());
if (node.isPostfix) {
result = getterInstruction;
if (node.isIfNullAssignment) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleIfNull(() => stack.add(getterInstruction),
() {
addDynamicSendArgumentsToList(node, setterInputs);
generateSuperSendSet();
stack.add(setterInputs.last);
});
} else {
result = setterInputs.last;
handleComplexOperatorSend(node, getterInstruction, arguments);
setterInputs.add(pop());
generateSuperSendSet();
stack.add(node.isPostfix ? getterInstruction : setterInputs.last);
}
}
Selector setterSelector = elements.getSelector(node);
if (Elements.isUnresolved(element)
|| !setterSelector.applies(element, compiler.world)) {
generateSuperNoSuchMethodSend(
node, setterSelector, setterInputs);
pop();
} else {
add(buildInvokeSuper(setterSelector, element, setterInputs));
}
stack.add(result);
} else if (node.isIndex) {
if ("=" == op.source) {
generateDynamicSend(node);
@@ -5577,27 +5675,42 @@ class SsaBuilder extends NewResolvedVisitor {
elements.getGetterSelectorInComplexSendSet(node),
[receiver, index]);
HInstruction getterInstruction = pop();
handleComplexOperatorSend(node, getterInstruction, arguments);
HInstruction value = pop();
pushInvokeDynamic(
node, elements.getSelector(node), [receiver, index, value]);
pop();
if (node.isPostfix) {
stack.add(getterInstruction);
if (node.isIfNullAssignment) {
// Compile x[i] ??= e as:
// t1 = x[i]
// if (t1 == null)
// t1 = x[i] = e;
// result = t1
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleIfNull(() => stack.add(getterInstruction),
() {
visit(arguments.head);
HInstruction value = pop();
pushInvokeDynamic(
node, elements.getSelector(node), [receiver, index, value]);
pop();
stack.add(value);
});
} else {
stack.add(value);
handleComplexOperatorSend(node, getterInstruction, arguments);
HInstruction value = pop();
pushInvokeDynamic(
node, elements.getSelector(node), [receiver, index, value]);
pop();
if (node.isPostfix) {
stack.add(getterInstruction);
} else {
stack.add(value);
}
}
}
} else if ("=" == op.source) {
Link<ast.Node> link = node.arguments;
assert(!link.isEmpty && link.tail.isEmpty);
if (Elements.isInstanceSend(node, elements)) {
HInstruction receiver = generateInstanceSendReceiver(node);
visit(link.head);
generateInstanceSetterWithCompiledReceiver(node, receiver, pop());
generatePossiblyConditionalInstanceSetter(node,
() => generateInstanceSendReceiver(node),
() => visitAndPop(link.head));
} else {
visit(link.head);
generateNonInstanceSetter(node, element, pop());
@@ -5608,20 +5721,64 @@ class SsaBuilder extends NewResolvedVisitor {
assert("++" == op.source || "--" == op.source ||
node.assignmentOperator.source.endsWith("="));
// [receiver] is only used if the node is an instance send.
HInstruction receiver = null;
Element getter = elements[node.selector];
if (!Elements.isUnresolved(getter) && getter.impliesType) {
ast.Identifier selector = node.selector;
generateThrowNoSuchMethod(node, selector.source,
argumentNodes: node.arguments);
if (node.isIfNullAssignment) {
// C ??= x is compiled just as C.
stack.add(addConstant(node.selector));
} else {
ast.Identifier selector = node.selector;
generateThrowNoSuchMethod(node, selector.source,
argumentNodes: node.arguments);
}
return;
} else if (Elements.isInstanceSend(node, elements)) {
receiver = generateInstanceSendReceiver(node);
generateInstanceGetterWithCompiledReceiver(
node, elements.getGetterSelectorInComplexSendSet(node), receiver);
} else if (getter.isErroneous) {
}
if (Elements.isInstanceSend(node, elements)) {
void generateAssignment(HInstruction receiver) {
// desugars `e.x op= e2` to `e.x = e.x op e2`
generateInstanceGetterWithCompiledReceiver(
node, elements.getGetterSelectorInComplexSendSet(node), receiver);
HInstruction getterInstruction = pop();
if (node.isIfNullAssignment) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleIfNull(() => stack.add(getterInstruction),
() {
visit(node.arguments.head);
generateInstanceSetterWithCompiledReceiver(
node, receiver, pop());
});
} else {
handleComplexOperatorSend(node, getterInstruction, node.arguments);
HInstruction value = pop();
generateInstanceSetterWithCompiledReceiver(node, receiver, value);
}
if (node.isPostfix) {
pop();
stack.add(getterInstruction);
}
}
if (node.isConditional) {
// generate `e?.x op= e2` as:
// t1 = e
// t1 == null ? t1 : (t1.x = t1.x op e2);
HInstruction receiver;
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleConditional(
() {
receiver = generateInstanceSendReceiver(node);
pushCheckNull(receiver);
},
() => stack.add(receiver),
() => generateAssignment(receiver));
} else {
generateAssignment(generateInstanceSendReceiver(node));
}
return;
}
if (getter.isErroneous) {
generateStaticUnresolvedGet(node, getter);
} else if (getter.isField) {
generateStaticFieldGet(node, getter);
@@ -5635,14 +5792,16 @@ class SsaBuilder extends NewResolvedVisitor {
internalError(node, "Unexpected getter: $getter");
}
HInstruction getterInstruction = pop();
handleComplexOperatorSend(node, getterInstruction, node.arguments);
HInstruction value = pop();
assert(value != null);
if (Elements.isInstanceSend(node, elements)) {
assert(receiver != null);
generateInstanceSetterWithCompiledReceiver(node, receiver, value);
if (node.isIfNullAssignment) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleIfNull(() => stack.add(getterInstruction),
() {
visit(node.arguments.head);
generateNonInstanceSetter(node, element, pop());
});
} else {
assert(receiver == null);
handleComplexOperatorSend(node, getterInstruction, node.arguments);
HInstruction value = pop();
generateNonInstanceSetter(node, element, value);
}
if (node.isPostfix) {
@@ -7451,6 +7610,19 @@ class SsaBranchBuilder {
_handleDiamondBranch(visitCondition, visitThen, visitElse, true);
}
handleIfNull(void left(), void right()) {
// x ?? y is transformed into: x == null ? y : x
HInstruction leftExpression;
handleConditional(
() {
left();
leftExpression = builder.pop();
builder.pushCheckNull(leftExpression);
},
right,
() => builder.stack.add(leftExpression));
}
void handleLogicalAndOr(void left(), void right(), {bool isAnd}) {
// x && y is transformed into:
// t0 = boolify(x);
+30 -14
View File
@@ -357,14 +357,21 @@ class Send extends Expression with StoredTreeElementMixin {
final Node receiver;
final Node selector;
final NodeList argumentsNode;
/// Whether this is a conditinal send of the form `a?.b`.
final bool isConditional;
Link<Node> get arguments => argumentsNode.nodes;
Send([this.receiver, this.selector, this.argumentsNode]);
Send.postfix(this.receiver, this.selector, [Node argument = null])
Send([this.receiver, this.selector, this.argumentsNode,
this.isConditional = false]);
Send.postfix(this.receiver, this.selector,
[Node argument = null, this.isConditional = false])
: argumentsNode = (argument == null)
? new Postfix()
: new Postfix.singleton(argument);
Send.prefix(this.receiver, this.selector, [Node argument = null])
Send.prefix(this.receiver, this.selector,
[Node argument = null, this.isConditional = false])
: argumentsNode = (argument == null)
? new Prefix()
: new Prefix.singleton(argument);
@@ -398,6 +405,8 @@ class Send extends Expression with StoredTreeElementMixin {
isOperator && identical(selector.asOperator().source, '&&');
bool get isLogicalOr =>
isOperator && identical(selector.asOperator().source, '||');
bool get isIfNull =>
isOperator && identical(selector.asOperator().source, '??');
bool get isTypeCast {
return isOperator
@@ -441,9 +450,9 @@ class Send extends Expression with StoredTreeElementMixin {
return getBeginToken();
}
Send copyWithReceiver(Node newReceiver) {
Send copyWithReceiver(Node newReceiver, bool isConditional) {
assert(receiver == null);
return new Send(newReceiver, selector, argumentsNode);
return new Send(newReceiver, selector, argumentsNode, isConditional);
}
}
@@ -459,32 +468,37 @@ class Prefix extends NodeList {
class SendSet extends Send {
final Operator assignmentOperator;
SendSet(receiver, selector, this.assignmentOperator, argumentsNode)
: super(receiver, selector, argumentsNode);
SendSet(receiver, selector, this.assignmentOperator, argumentsNode,
[bool isConditional = false])
: super(receiver, selector, argumentsNode, isConditional);
SendSet.postfix(receiver,
selector,
this.assignmentOperator,
[Node argument = null])
: super.postfix(receiver, selector, argument);
[Node argument = null, bool isConditional = false])
: super.postfix(receiver, selector, argument, isConditional);
SendSet.prefix(receiver,
selector,
this.assignmentOperator,
[Node argument = null])
: super.prefix(receiver, selector, argument);
[Node argument = null, bool isConditional = false])
: super.prefix(receiver, selector, argument, isConditional);
SendSet asSendSet() => this;
accept(Visitor visitor) => visitor.visitSendSet(this);
/// Whether this is an if-null assignment of the form `a ??= b`.
bool get isIfNullAssignment =>
identical(assignmentOperator.source, '??=');
visitChildren(Visitor visitor) {
super.visitChildren(visitor);
if (assignmentOperator != null) assignmentOperator.accept(visitor);
}
Send copyWithReceiver(Node newReceiver) {
Send copyWithReceiver(Node newReceiver, bool isConditional) {
assert(receiver == null);
return new SendSet(newReceiver, selector, assignmentOperator,
argumentsNode);
argumentsNode, isConditional);
}
Token getBeginToken() {
@@ -1047,7 +1061,7 @@ class Identifier extends Expression with StoredTreeElementMixin {
class Operator extends Identifier {
static const COMPLEX_OPERATORS =
const ["--", "++", '+=', "-=", "*=", "/=", "%=", "&=", "|=", "~/=", "^=",
">>=", "<<="];
">>=", "<<=", "??="];
static const INCREMENT_OPERATORS = const <String>["++", "--"];
@@ -2250,6 +2264,7 @@ class Initializers {
return (node.receiver == null && node.selector.isSuper()) ||
(node.receiver != null &&
node.receiver.isSuper() &&
!node.isConditional &&
node.selector.asIdentifier() != null);
}
@@ -2257,6 +2272,7 @@ class Initializers {
return (node.receiver == null && node.selector.isThis()) ||
(node.receiver != null &&
node.receiver.isThis() &&
!node.isConditional &&
node.selector.asIdentifier() != null);
}
}
+1 -1
View File
@@ -424,7 +424,7 @@ class Unparser extends Indentation implements Visitor {
indentLess();
indentLess();
} else if (node.selector.asOperator() == null) {
write('.');
write(node.isConditional ? '?.' : '.');
} else if (spacesNeeded) {
write(' ');
}
+11 -4
View File
@@ -1237,6 +1237,10 @@ class TypeCheckerVisitor extends Visitor<DartType> {
return boolType;
} else if (identical(name, '?')) {
return boolType;
} else if (identical(name, '??')) {
final Node argument = node.arguments.head;
final DartType argumentType = analyze(argument);
return types.computeLeastUpperBound(receiverType, argumentType);
}
String operatorName = selector.source;
if (identical(name, '-') && node.arguments.isEmpty) {
@@ -1405,7 +1409,7 @@ class TypeCheckerVisitor extends Visitor<DartType> {
Element element = elements[node];
Identifier selector = node.selector;
final name = node.assignmentOperator.source;
if (identical(name, '=')) {
if (identical(name, '=') || identical(name, '??=')) {
// e1 = value
if (node.isIndex) {
// base[key] = value
@@ -1416,14 +1420,16 @@ class TypeCheckerVisitor extends Visitor<DartType> {
final DartType value = analyze(valueNode);
DartType indexSet = lookupMemberType(
node, base, '[]=', MemberKind.OPERATOR);
DartType indexSetValue = const DynamicType();
if (indexSet is FunctionType) {
FunctionType indexSetType = indexSet;
DartType indexSetKey = firstType(indexSetType.parameterTypes);
checkAssignable(keyNode, key, indexSetKey);
DartType indexSetValue = secondType(indexSetType.parameterTypes);
indexSetValue = secondType(indexSetType.parameterTypes);
checkAssignable(node.assignmentOperator, value, indexSetValue);
}
return value;
return identical(name, '=') ? value
: types.computeLeastUpperBound(value, indexSetValue);
} else {
// target = value
DartType target;
@@ -1441,7 +1447,8 @@ class TypeCheckerVisitor extends Visitor<DartType> {
final Node valueNode = node.arguments.head;
final DartType value = analyze(valueNode);
checkAssignable(node.assignmentOperator, value, target);
return value;
return identical(name, '=') ? value
: types.computeLeastUpperBound(value, target);
}
} else if (identical(name, '++') || identical(name, '--')) {
// e++ or e--
+4 -2
View File
@@ -544,11 +544,13 @@ class Selector {
(memberName != INDEX_NAME && memberName != INDEX_SET_NAME));
assert(kind == SelectorKind.OPERATOR ||
kind == SelectorKind.INDEX ||
!Elements.isOperatorName(memberName.text));
!Elements.isOperatorName(memberName.text) ||
identical(memberName.text, '??'));
assert(kind == SelectorKind.CALL ||
kind == SelectorKind.GETTER ||
kind == SelectorKind.SETTER ||
Elements.isOperatorName(memberName.text));
Elements.isOperatorName(memberName.text) ||
identical(memberName.text, '??'));
}
// TODO(johnniwinther): Extract caching.
+13
View File
@@ -1175,6 +1175,15 @@ main() => new C();"""]);
static const MessageKind ASSIGNING_TYPE = const MessageKind(
"Cannot assign a value to a type.");
static const MessageKind IF_NULL_ASSIGNING_TYPE = const MessageKind(
"Cannot assign a value to a type. Note that types are never null, "
"so this ??= assignment has no effect.",
howToFix: "Try removing the '??=' assignment.",
options: const ['--enable-null-aware-operators'],
examples: const [
"class A {} main() { print(A ??= 3);}",
]);
static const MessageKind VOID_NOT_ALLOWED = const MessageKind(
"Type 'void' can't be used here because it isn't a return type.",
howToFix: "Try removing 'void' keyword or replace it with 'var', 'final',"
@@ -1969,6 +1978,10 @@ main() => r\"\"\"
// This is a fall-back message that shouldn't happen.
"Incomplete token.");
static const MessageKind NULL_AWARE_OPERATORS_DISABLED = const MessageKind(
"Null-aware operators like '#{operator}' are currently experimental. "
"You can enable them using the --enable-null-aware-operators flag.");
static const MessageKind EXPONENT_MISSING = const MessageKind(
"Numbers in exponential notation should always contain an exponent"
" (an integer number with an optional sign).",
+27
View File
@@ -144,6 +144,32 @@ void testConditionalExpression() {
conditional = node.expression;
Expect.isNotNull(conditional.thenExpression.asSendSet());
Expect.isNotNull(conditional.elseExpression.asSendSet());
node = parseStatement("a ?? b ? c : d;");
// Should parse as: (a ?? b) ? c : d;
conditional = node.expression;
Expect.isNotNull(conditional.condition.asSend());
Expect.isTrue(conditional.condition.asSend().isIfNull);
Expect.isNotNull(conditional.thenExpression.asSend());
Expect.isNotNull(conditional.elseExpression.asSend());
}
void testNullOperators() {
Expression node = parseStatement("a ?? b;").expression;
Expect.isNotNull(node.asSend());
Expect.isTrue(node.asSend().isIfNull);
node = parseStatement("a ??= b;").expression;
Expect.isNotNull(node.asSendSet());
Expect.isTrue(node.asSendSet().isIfNullAssignment);
node = parseStatement("a?.b;").expression;
Expect.isNotNull(node.asSend());
Expect.isTrue(node.asSend().isConditional);
node = parseStatement("a?.m();").expression;
Expect.isNotNull(node.asSend());
Expect.isTrue(node.asSend().isConditional);
}
void testAssignment() {
@@ -338,6 +364,7 @@ void main() {
testDoStatement();
testWhileStatement();
testConditionalExpression();
testNullOperators();
testAssignment();
testIndex();
testPostfix();
@@ -0,0 +1,20 @@
// Copyright (c) 2015, 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.
// SharedOptions=--enable-null-aware-operators
import "package:expect/expect.dart";
@NoInline() @AssumeDynamic()
confuse(x) => x;
class A {
int x;
m() => "a";
}
main(args) {
var a = confuse(true) ? null : new A();
a?.x = 3;
Expect.throws(() => a.m());
}
@@ -0,0 +1,51 @@
// Copyright (c) 2015, 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.
// SharedOptions=--enable-null-aware-operators
import "package:expect/expect.dart";
@NoInline() @AssumeDynamic()
confuse(x) => x;
main(args) {
var x = new A();
var y;
// Checks that inference doesn't incorrectly treat this as a normal
// assignment (where only B is a possible value after the assignment).
var c = x ??= new B();
var z = x;
Expect.equals('a', x.m());
Expect.equals('a', z.m());
Expect.equals('a', c.m());
if (confuse(true)) y = x;
Expect.equals('a', y.m());
// Similar test, within fields.
new C();
new D();
}
class A { m() => 'a'; }
class B { m() => 'b'; }
class C {
var y;
C() {
y = new A();
var c = y ??= new B();
Expect.equals('a', y.m());
Expect.equals('a', c.m());
}
}
class D {
var y;
D() {
this.y = new A();
var c = this.y ??= new B();
Expect.equals('a', y.m());
Expect.equals('a', c.m());
}
}
@@ -11,6 +11,9 @@ library lib;
var topLevelVar;
void topLevelFunction() {}
class C {
static var staticField;
static void staticMethod() {}
}
@@ -20,6 +20,7 @@ class B {}
class C extends B {
f(callback()) => callback();
int g(int callback()) => callback();
static void staticMethod() {}
}
C nullC() => null;
+1
View File
@@ -27,6 +27,7 @@ if_null_assignment_static_test: Fail
conditional_property_assignment_test: Fail
conditional_property_access_test: Fail
conditional_method_invocation_test: Fail
this_conditional_operator_test/none: Fail
deferred_redirecting_factory_test: Fail # Issue 23408
redirecting_constructor_initializer_test: RuntimeError # Issue 23488
+31 -11
View File
@@ -59,17 +59,8 @@ ref_before_declaration_test/06: MissingCompileTimeError
regress_22976_test/01: CompileTimeError # Issue 23132
regress_22976_test/02: CompileTimeError # Issue 23132
# Null-aware operators aren't implemented in dart2js yet.
if_null_evaluation_order_test: CompileTimeError
if_null_precedence_test: CompileTimeError
if_null_behavior_test: CompileTimeError
if_null_assignment_behavior_test: CompileTimeError
if_null_assignment_static_test: CompileTimeError
conditional_property_assignment_test: CompileTimeError
conditional_property_access_test: CompileTimeError
conditional_method_invocation_test: CompileTimeError
super_conditional_operator_test/none: CompileTimeError
if_null_assignment_behavior_test/13: Crash # Issue 23491
if_null_assignment_behavior_test/14: Crash # Issue 23491
# VM specific tests that should not be run by dart2js.
vm/*: Skip # Issue 12699
@@ -1045,3 +1036,32 @@ value_range2_test: Crash # Instance of 'TypeOperator': type check unimplemented
value_range3_test: Crash # Instance of 'TypeOperator': type check unimplemented for _Nullary.
value_range_test: Crash # Instance of 'TypeOperator': type check unimplemented for _Nullary.
void_type_test: Crash # Unhandled node
# Null-aware operators aren't implemented in the CPS IR yet.
if_null_evaluation_order_test: Crash
if_null_precedence_test: Crash
if_null_behavior_test: Crash
if_null_assignment_behavior_test: Crash
if_null_assignment_static_test: Crash
conditional_property_assignment_test: Fail
conditional_property_assignment_test/06: Crash
conditional_property_assignment_test/11: Crash
conditional_property_assignment_test/14: Crash
conditional_property_assignment_test/15: Crash
conditional_property_assignment_test/16: Crash
conditional_property_assignment_test/17: Crash
conditional_property_assignment_test/18: Crash
conditional_property_assignment_test/19: Crash
conditional_property_assignment_test/20: Crash
conditional_property_assignment_test/21: Crash
conditional_property_assignment_test/22: Crash
conditional_property_access_test: Fail
conditional_property_access_test/07: Crash
conditional_property_access_test/08: Crash
conditional_property_access_test/09: Crash
conditional_method_invocation_test: Fail
conditional_method_invocation_test/none: Pass
conditional_method_invocation_test/09: Crash
conditional_method_invocation_test/10: Crash
conditional_method_invocation_test/11: Crash
@@ -0,0 +1,30 @@
// Copyright (c) 2015, 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.
// Verify that the ?. operator cannot be used for forwarding "this"
// constructors.
// SharedOptions=--enable-null-aware-operators
class B {
B();
B.namedConstructor();
var field = 1;
method() => 1;
B.forward()
: this?.namedConstructor() /// 01: compile-time error
;
test() {
this?.field = 1;
this?.field += 1;
this?.field;
this?.method();
}
}
main() {
new B.forward().test();
}