6fa813d818
Before: Kernel static errors were represented by throwing a distinguished error or else by calling a distinguished library const constructor. Now: Kernel static errors are represented by InvalidExpression. To support error reporting, InvalidExpression has an optional message and a file offset. A back end can choose to signal these errors at any time; for example, when deserializing the binary, or when compiling the procedure containing the static error, or when the erroneous expression is evaluated at run time. InvalidStatement is removed because it can be encoded as ExpressionStatement of InvalidExpression. Future work: * supporting static errors where an expression cannot appear in the AST * allowing InvalidExpression to contain an Expression for error recovery * adding a top-level list of static errors and warnings to the binary Bug: https://github.com/dart-lang/sdk/issues/29840 Change-Id: Ifdfe9a76cee6cefed28061bf245be70531d2f413 Reviewed-on: https://dart-review.googlesource.com/31320 Commit-Queue: Kevin Millikin <kmillikin@google.com> Reviewed-by: Dan Rubel <danrubel@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
511 lines
17 KiB
Dart
511 lines
17 KiB
Dart
// Copyright (c) 2016, 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.
|
|
|
|
/// A library to help transform compounds and null-aware accessors into
|
|
/// let expressions.
|
|
library kernel.frontend.accessors;
|
|
|
|
import '../ast.dart';
|
|
|
|
final Name indexGetName = new Name("[]");
|
|
|
|
final Name indexSetName = new Name("[]=");
|
|
|
|
/// An [Accessor] represents a subexpression for which we can't yet build a
|
|
/// kernel [Expression] because we don't yet know the context in which it is
|
|
/// used.
|
|
///
|
|
/// Once the context is known, an [Accessor] can be converted into an
|
|
/// [Expression] by calling a "build" method.
|
|
///
|
|
/// For example, when building a kernel representation for `a[x] = b`, after
|
|
/// parsing `a[x]` but before parsing `= b`, we don't yet know whether to
|
|
/// generate an invocation of `operator[]` or `operator[]=`, so we generate an
|
|
/// [Accessor] object. Later, after `= b` is parsed, [buildAssignment] will be
|
|
/// called.
|
|
abstract class Accessor {
|
|
final int offset;
|
|
|
|
// [builtBinary] and [builtGetter] capture the inner nodes. Used by
|
|
// dart2js+rasta for determining how subexpressions map to legacy dart2js Ast
|
|
// nodes. This will be removed once dart2js type analysis (aka inference) is
|
|
// reimplemented on kernel.
|
|
Expression builtBinary;
|
|
Expression builtGetter;
|
|
|
|
Accessor(this.offset);
|
|
|
|
/// Builds an [Expression] representing a read from the accessor.
|
|
Expression buildSimpleRead() {
|
|
return _finish(_makeSimpleRead());
|
|
}
|
|
|
|
/// Builds an [Expression] representing an assignment with the accessor on
|
|
/// the LHS and [value] on the RHS.
|
|
///
|
|
/// The returned expression evaluates to the assigned value, unless
|
|
/// [voidContext] is true, in which case it may evaluate to anything.
|
|
Expression buildAssignment(Expression value, {bool voidContext: false}) {
|
|
return _finish(_makeSimpleWrite(value, voidContext));
|
|
}
|
|
|
|
/// Returns an [Expression] representing a null-aware assignment (`??=`) with
|
|
/// the accessor on the LHS and [value] on the RHS.
|
|
///
|
|
/// The returned expression evaluates to the assigned value, unless
|
|
/// [voidContext] is true, in which case it may evaluate to anything.
|
|
///
|
|
/// [type] is the static type of the RHS.
|
|
Expression buildNullAwareAssignment(Expression value, DartType type,
|
|
{bool voidContext: false}) {
|
|
if (voidContext) {
|
|
return _finish(new ConditionalExpression(buildIsNull(_makeRead()),
|
|
_makeWrite(value, false), new NullLiteral(), type));
|
|
}
|
|
var tmp = new VariableDeclaration.forValue(_makeRead());
|
|
return _finish(makeLet(
|
|
tmp,
|
|
new ConditionalExpression(buildIsNull(new VariableGet(tmp)),
|
|
_makeWrite(value, false), new VariableGet(tmp), type)));
|
|
}
|
|
|
|
/// Returns an [Expression] representing a compound assignment (e.g. `+=`)
|
|
/// with the accessor on the LHS and [value] on the RHS.
|
|
Expression buildCompoundAssignment(Name binaryOperator, Expression value,
|
|
{int offset: TreeNode.noOffset,
|
|
bool voidContext: false,
|
|
Procedure interfaceTarget}) {
|
|
return _finish(_makeWrite(
|
|
builtBinary = makeBinary(
|
|
_makeRead(), binaryOperator, interfaceTarget, value,
|
|
offset: offset),
|
|
voidContext));
|
|
}
|
|
|
|
/// Returns an [Expression] representing a pre-increment or pre-decrement
|
|
/// of the accessor.
|
|
Expression buildPrefixIncrement(Name binaryOperator,
|
|
{int offset: TreeNode.noOffset,
|
|
bool voidContext: false,
|
|
Procedure interfaceTarget}) {
|
|
return buildCompoundAssignment(binaryOperator, new IntLiteral(1),
|
|
offset: offset,
|
|
voidContext: voidContext,
|
|
interfaceTarget: interfaceTarget);
|
|
}
|
|
|
|
/// Returns an [Expression] representing a post-increment or post-decrement
|
|
/// of the accessor.
|
|
Expression buildPostfixIncrement(Name binaryOperator,
|
|
{int offset: TreeNode.noOffset,
|
|
bool voidContext: false,
|
|
Procedure interfaceTarget}) {
|
|
if (voidContext) {
|
|
return buildPrefixIncrement(binaryOperator,
|
|
offset: offset, voidContext: true, interfaceTarget: interfaceTarget);
|
|
}
|
|
var value = new VariableDeclaration.forValue(_makeRead());
|
|
valueAccess() => new VariableGet(value);
|
|
var dummy = new VariableDeclaration.forValue(_makeWrite(
|
|
builtBinary = makeBinary(
|
|
valueAccess(), binaryOperator, interfaceTarget, new IntLiteral(1),
|
|
offset: offset),
|
|
true));
|
|
return _finish(makeLet(value, makeLet(dummy, valueAccess())));
|
|
}
|
|
|
|
Expression _makeSimpleRead() => _makeRead();
|
|
|
|
Expression _makeSimpleWrite(Expression value, bool voidContext) {
|
|
return _makeWrite(value, voidContext);
|
|
}
|
|
|
|
Expression _makeRead();
|
|
|
|
Expression _makeWrite(Expression value, bool voidContext);
|
|
|
|
Expression _finish(Expression body) => body;
|
|
|
|
/// Returns an [Expression] representing a compile-time error.
|
|
makeInvalidRead() => new InvalidExpression(null);
|
|
|
|
/// Returns an [Expression] representing a compile-time error wrapping
|
|
/// [value].
|
|
///
|
|
/// The expression will be a compile-time error but will contain [value] as a
|
|
/// subexpression before the compile-time error.
|
|
makeInvalidWrite(Expression value) => wrapInvalid(value);
|
|
}
|
|
|
|
class VariableAccessor extends Accessor {
|
|
VariableDeclaration variable;
|
|
DartType promotedType;
|
|
|
|
VariableAccessor(this.variable, this.promotedType, int offset)
|
|
: super(offset);
|
|
|
|
_makeRead() => new VariableGet(variable, promotedType)..fileOffset = offset;
|
|
|
|
_makeWrite(Expression value, bool voidContext) {
|
|
return variable.isFinal || variable.isConst
|
|
? makeInvalidWrite(value)
|
|
: new VariableSet(variable, value)
|
|
..fileOffset = offset;
|
|
}
|
|
}
|
|
|
|
class PropertyAccessor extends Accessor {
|
|
VariableDeclaration _receiverVariable;
|
|
Expression receiver;
|
|
Name name;
|
|
Member getter, setter;
|
|
|
|
static Accessor make(
|
|
Expression receiver, Name name, Member getter, Member setter,
|
|
{int offset: TreeNode.noOffset}) {
|
|
if (receiver is ThisExpression) {
|
|
return new ThisPropertyAccessor(name, getter, setter, offset);
|
|
} else {
|
|
return new PropertyAccessor.internal(
|
|
receiver, name, getter, setter, offset);
|
|
}
|
|
}
|
|
|
|
PropertyAccessor.internal(
|
|
this.receiver, this.name, this.getter, this.setter, int offset)
|
|
: super(offset);
|
|
|
|
_makeSimpleRead() =>
|
|
new PropertyGet(receiver, name, getter)..fileOffset = offset;
|
|
|
|
_makeSimpleWrite(Expression value, bool voidContext) {
|
|
return new PropertySet(receiver, name, value, setter)..fileOffset = offset;
|
|
}
|
|
|
|
receiverAccess() {
|
|
_receiverVariable ??= new VariableDeclaration.forValue(receiver);
|
|
return new VariableGet(_receiverVariable)..fileOffset = offset;
|
|
}
|
|
|
|
_makeRead() => builtGetter = new PropertyGet(receiverAccess(), name, getter)
|
|
..fileOffset = offset;
|
|
|
|
_makeWrite(Expression value, bool voidContext) {
|
|
return new PropertySet(receiverAccess(), name, value, setter)
|
|
..fileOffset = offset;
|
|
}
|
|
|
|
_finish(Expression body) => makeLet(_receiverVariable, body);
|
|
}
|
|
|
|
/// Special case of [PropertyAccessor] to avoid creating an indirect access to
|
|
/// 'this'.
|
|
class ThisPropertyAccessor extends Accessor {
|
|
Name name;
|
|
Member getter, setter;
|
|
|
|
ThisPropertyAccessor(this.name, this.getter, this.setter, int offset)
|
|
: super(offset);
|
|
|
|
_makeRead() => builtGetter =
|
|
new PropertyGet(new ThisExpression(), name, getter)..fileOffset = offset;
|
|
|
|
_makeWrite(Expression value, bool voidContext) {
|
|
return new PropertySet(new ThisExpression(), name, value, setter)
|
|
..fileOffset = offset;
|
|
}
|
|
}
|
|
|
|
class NullAwarePropertyAccessor extends Accessor {
|
|
VariableDeclaration receiver;
|
|
Name name;
|
|
Member getter, setter;
|
|
DartType type;
|
|
|
|
NullAwarePropertyAccessor(Expression receiver, this.name, this.getter,
|
|
this.setter, this.type, int offset)
|
|
: this.receiver = makeOrReuseVariable(receiver),
|
|
super(offset);
|
|
|
|
receiverAccess() => new VariableGet(receiver);
|
|
|
|
_makeRead() => builtGetter = new PropertyGet(receiverAccess(), name, getter);
|
|
|
|
_makeWrite(Expression value, bool voidContext) {
|
|
return new PropertySet(receiverAccess(), name, value, setter);
|
|
}
|
|
|
|
_finish(Expression body) => makeLet(
|
|
receiver,
|
|
new ConditionalExpression(
|
|
buildIsNull(receiverAccess()), new NullLiteral(), body, type));
|
|
}
|
|
|
|
class SuperPropertyAccessor extends Accessor {
|
|
Name name;
|
|
Member getter, setter;
|
|
|
|
SuperPropertyAccessor(this.name, this.getter, this.setter, int offset)
|
|
: super(offset);
|
|
|
|
_makeRead() {
|
|
if (getter == null) return makeInvalidRead();
|
|
// TODO(ahe): Use [DirectPropertyGet] when possible.
|
|
return builtGetter = new SuperPropertyGet(name, getter)
|
|
..fileOffset = offset;
|
|
}
|
|
|
|
_makeWrite(Expression value, bool voidContext) {
|
|
if (setter == null) return makeInvalidWrite(value);
|
|
// TODO(ahe): Use [DirectPropertySet] when possible.
|
|
return new SuperPropertySet(name, value, setter)..fileOffset = offset;
|
|
}
|
|
}
|
|
|
|
class IndexAccessor extends Accessor {
|
|
Expression receiver;
|
|
Expression index;
|
|
VariableDeclaration receiverVariable;
|
|
VariableDeclaration indexVariable;
|
|
Procedure getter, setter;
|
|
|
|
static Accessor make(
|
|
Expression receiver, Expression index, Procedure getter, Procedure setter,
|
|
{int offset: TreeNode.noOffset}) {
|
|
if (receiver is ThisExpression) {
|
|
return new ThisIndexAccessor(index, getter, setter, offset);
|
|
} else {
|
|
return new IndexAccessor.internal(
|
|
receiver, index, getter, setter, offset);
|
|
}
|
|
}
|
|
|
|
IndexAccessor.internal(
|
|
this.receiver, this.index, this.getter, this.setter, int offset)
|
|
: super(offset);
|
|
|
|
_makeSimpleRead() => new MethodInvocation(
|
|
receiver, indexGetName, new Arguments(<Expression>[index]), getter)
|
|
..fileOffset = offset;
|
|
|
|
_makeSimpleWrite(Expression value, bool voidContext) {
|
|
if (!voidContext) return _makeWriteAndReturn(value);
|
|
return new MethodInvocation(receiver, indexSetName,
|
|
new Arguments(<Expression>[index, value]), setter)
|
|
..fileOffset = offset;
|
|
}
|
|
|
|
receiverAccess() {
|
|
// We cannot reuse the receiver if it is a variable since it might be
|
|
// reassigned in the index expression.
|
|
receiverVariable ??= new VariableDeclaration.forValue(receiver);
|
|
return new VariableGet(receiverVariable)..fileOffset = offset;
|
|
}
|
|
|
|
indexAccess() {
|
|
indexVariable ??= new VariableDeclaration.forValue(index);
|
|
return new VariableGet(indexVariable)..fileOffset = offset;
|
|
}
|
|
|
|
_makeRead() {
|
|
return builtGetter = new MethodInvocation(receiverAccess(), indexGetName,
|
|
new Arguments(<Expression>[indexAccess()]), getter)
|
|
..fileOffset = offset;
|
|
}
|
|
|
|
_makeWrite(Expression value, bool voidContext) {
|
|
if (!voidContext) return _makeWriteAndReturn(value);
|
|
return new MethodInvocation(receiverAccess(), indexSetName,
|
|
new Arguments(<Expression>[indexAccess(), value]), setter)
|
|
..fileOffset = offset;
|
|
}
|
|
|
|
// TODO(dmitryas): remove this method after the "[]=" operator of the Context
|
|
// class is made to return a value.
|
|
_makeWriteAndReturn(Expression value) {
|
|
// The call to []= does not return the value like direct-style assignments
|
|
// do. We need to bind the value in a let.
|
|
var valueVariable = new VariableDeclaration.forValue(value);
|
|
var dummy = new VariableDeclaration.forValue(new MethodInvocation(
|
|
receiverAccess(),
|
|
indexSetName,
|
|
new Arguments(
|
|
<Expression>[indexAccess(), new VariableGet(valueVariable)]),
|
|
setter)
|
|
..fileOffset = offset);
|
|
return makeLet(
|
|
valueVariable, makeLet(dummy, new VariableGet(valueVariable)));
|
|
}
|
|
|
|
Expression _finish(Expression body) {
|
|
return makeLet(receiverVariable, makeLet(indexVariable, body));
|
|
}
|
|
}
|
|
|
|
/// Special case of [IndexAccessor] to avoid creating an indirect access to
|
|
/// 'this'.
|
|
class ThisIndexAccessor extends Accessor {
|
|
Expression index;
|
|
VariableDeclaration indexVariable;
|
|
Procedure getter, setter;
|
|
|
|
ThisIndexAccessor(this.index, this.getter, this.setter, int offset)
|
|
: super(offset);
|
|
|
|
_makeSimpleRead() {
|
|
return new MethodInvocation(new ThisExpression(), indexGetName,
|
|
new Arguments(<Expression>[index]), getter);
|
|
}
|
|
|
|
_makeSimpleWrite(Expression value, bool voidContext) {
|
|
if (!voidContext) return _makeWriteAndReturn(value);
|
|
return new MethodInvocation(new ThisExpression(), indexSetName,
|
|
new Arguments(<Expression>[index, value]), setter);
|
|
}
|
|
|
|
indexAccess() {
|
|
indexVariable ??= new VariableDeclaration.forValue(index);
|
|
return new VariableGet(indexVariable);
|
|
}
|
|
|
|
_makeRead() => builtGetter = new MethodInvocation(new ThisExpression(),
|
|
indexGetName, new Arguments(<Expression>[indexAccess()]), getter);
|
|
|
|
_makeWrite(Expression value, bool voidContext) {
|
|
if (!voidContext) return _makeWriteAndReturn(value);
|
|
return new MethodInvocation(new ThisExpression(), indexSetName,
|
|
new Arguments(<Expression>[indexAccess(), value]), setter);
|
|
}
|
|
|
|
_makeWriteAndReturn(Expression value) {
|
|
var valueVariable = new VariableDeclaration.forValue(value);
|
|
var dummy = new VariableDeclaration.forValue(new MethodInvocation(
|
|
new ThisExpression(),
|
|
indexSetName,
|
|
new Arguments(
|
|
<Expression>[indexAccess(), new VariableGet(valueVariable)]),
|
|
setter));
|
|
return makeLet(
|
|
valueVariable, makeLet(dummy, new VariableGet(valueVariable)));
|
|
}
|
|
|
|
Expression _finish(Expression body) => makeLet(indexVariable, body);
|
|
}
|
|
|
|
class SuperIndexAccessor extends Accessor {
|
|
Expression index;
|
|
VariableDeclaration indexVariable;
|
|
Member getter, setter;
|
|
|
|
SuperIndexAccessor(this.index, this.getter, this.setter, int offset)
|
|
: super(offset);
|
|
|
|
indexAccess() {
|
|
indexVariable ??= new VariableDeclaration.forValue(index);
|
|
return new VariableGet(indexVariable);
|
|
}
|
|
|
|
_makeSimpleRead() => new SuperMethodInvocation(
|
|
indexGetName, new Arguments(<Expression>[index]), getter);
|
|
|
|
_makeSimpleWrite(Expression value, bool voidContext) {
|
|
if (!voidContext) return _makeWriteAndReturn(value);
|
|
return new SuperMethodInvocation(
|
|
indexSetName, new Arguments(<Expression>[index, value]), setter);
|
|
}
|
|
|
|
_makeRead() {
|
|
return builtGetter = new SuperMethodInvocation(
|
|
indexGetName, new Arguments(<Expression>[indexAccess()]), getter);
|
|
}
|
|
|
|
_makeWrite(Expression value, bool voidContext) {
|
|
if (!voidContext) return _makeWriteAndReturn(value);
|
|
return new SuperMethodInvocation(indexSetName,
|
|
new Arguments(<Expression>[indexAccess(), value]), setter);
|
|
}
|
|
|
|
_makeWriteAndReturn(Expression value) {
|
|
var valueVariable = new VariableDeclaration.forValue(value);
|
|
var dummy = new VariableDeclaration.forValue(new SuperMethodInvocation(
|
|
indexSetName,
|
|
new Arguments(
|
|
<Expression>[indexAccess(), new VariableGet(valueVariable)]),
|
|
setter));
|
|
return makeLet(
|
|
valueVariable, makeLet(dummy, new VariableGet(valueVariable)));
|
|
}
|
|
|
|
Expression _finish(Expression body) {
|
|
return makeLet(indexVariable, body);
|
|
}
|
|
}
|
|
|
|
class StaticAccessor extends Accessor {
|
|
Member readTarget;
|
|
Member writeTarget;
|
|
|
|
StaticAccessor(this.readTarget, this.writeTarget, int offset) : super(offset);
|
|
|
|
_makeRead() => builtGetter =
|
|
readTarget == null ? makeInvalidRead() : new StaticGet(readTarget)
|
|
..fileOffset = offset;
|
|
|
|
_makeWrite(Expression value, bool voidContext) {
|
|
return writeTarget == null
|
|
? makeInvalidWrite(value)
|
|
: new StaticSet(writeTarget, value)
|
|
..fileOffset = offset;
|
|
}
|
|
}
|
|
|
|
class ReadOnlyAccessor extends Accessor {
|
|
Expression expression;
|
|
VariableDeclaration value;
|
|
|
|
ReadOnlyAccessor(this.expression, int offset) : super(offset);
|
|
|
|
_makeSimpleRead() => expression;
|
|
|
|
_makeRead() {
|
|
value ??= new VariableDeclaration.forValue(expression);
|
|
return new VariableGet(value);
|
|
}
|
|
|
|
_makeWrite(Expression value, bool voidContext) => makeInvalidWrite(value);
|
|
|
|
Expression _finish(Expression body) => makeLet(value, body);
|
|
}
|
|
|
|
Expression makeLet(VariableDeclaration variable, Expression body) {
|
|
if (variable == null) return body;
|
|
return new Let(variable, body);
|
|
}
|
|
|
|
Expression makeBinary(
|
|
Expression left, Name operator, Procedure interfaceTarget, Expression right,
|
|
{int offset: TreeNode.noOffset}) {
|
|
return new MethodInvocation(
|
|
left, operator, new Arguments(<Expression>[right]), interfaceTarget)
|
|
..fileOffset = offset;
|
|
}
|
|
|
|
final Name _equalOperator = new Name('==');
|
|
|
|
Expression buildIsNull(Expression value, {int offset: TreeNode.noOffset}) {
|
|
return makeBinary(value, _equalOperator, null, new NullLiteral(),
|
|
offset: offset);
|
|
}
|
|
|
|
VariableDeclaration makeOrReuseVariable(Expression value) {
|
|
// TODO: Devise a way to remember if a variable declaration was reused
|
|
// or is fresh (hence needs a let binding).
|
|
return new VariableDeclaration.forValue(value);
|
|
}
|
|
|
|
Expression wrapInvalid(Expression e) {
|
|
return new Let(
|
|
new VariableDeclaration.forValue(e), new InvalidExpression(null));
|
|
}
|