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:
@@ -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),
|
||||
|
||||
@@ -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, _) {
|
||||
|
||||
@@ -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. "
|
||||
|
||||
@@ -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}) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(' ');
|
||||
}
|
||||
|
||||
@@ -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--
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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).",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user