blob: 50658378e4e55d873045e76eb43bf29f18fa089a [file] [log] [blame]
// 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.
library fasta.builder_accessors;
export 'frontend_accessors.dart' show wrapInvalid;
import 'frontend_accessors.dart' show Accessor;
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart' show CoreTypes;
import '../errors.dart' show internalError, printUnexpected;
import 'frontend_accessors.dart' as kernel
show
IndexAccessor,
NullAwarePropertyAccessor,
PropertyAccessor,
StaticAccessor,
SuperIndexAccessor,
SuperPropertyAccessor,
ThisIndexAccessor,
ThisPropertyAccessor,
VariableAccessor;
import 'frontend_accessors.dart' show buildIsNull, makeLet;
import 'kernel_builder.dart'
show Builder, KernelClassBuilder, PrefixBuilder, TypeDeclarationBuilder;
abstract class BuilderHelper {
Uri get uri;
CoreTypes get coreTypes;
Constructor lookupConstructor(Name name, {bool isSuper});
Expression toSuperMethodInvocation(MethodInvocation node);
Expression toValue(node);
Member lookupSuperMember(Name name, {bool isSetter: false});
builderToFirstExpression(Builder builder, String name, int charOffset);
finishSend(Object receiver, Arguments arguments, int charOffset);
Expression buildCompileTimeError(error, [int charOffset]);
Initializer buildCompileTimeErrorIntializer(error, [int charOffset]);
Expression buildStaticInvocation(Procedure target, Arguments arguments);
Expression buildProblemExpression(Builder builder, String name);
}
abstract class BuilderAccessor implements Accessor {
BuilderHelper get helper;
int get charOffset;
String get plainNameForRead;
Uri get uri => helper.uri;
CoreTypes get coreTypes => helper.coreTypes;
String get plainNameForWrite => plainNameForRead;
Expression buildForEffect() => buildSimpleRead();
Initializer buildFieldInitializer(
Map<String, FieldInitializer> initializers) {
// TODO(ahe): This error message is really bad.
return helper.buildCompileTimeErrorIntializer(
"Can't use $plainNameForRead here.", charOffset);
}
Expression makeInvalidRead() {
return throwNoSuchMethodError(
plainNameForRead, new Arguments.empty(), uri, charOffset, coreTypes,
isGetter: true);
}
Expression makeInvalidWrite(Expression value) {
return throwNoSuchMethodError(plainNameForWrite,
new Arguments(<Expression>[value]), uri, charOffset, coreTypes,
isSetter: true);
}
TreeNode doInvocation(int charOffset, Arguments arguments);
buildPropertyAccess(IncompleteSend send, bool isNullAware) {
if (send is SendAccessor) {
return buildMethodInvocation(
buildSimpleRead(), send.name, send.arguments, send.charOffset,
isNullAware: isNullAware);
} else {
return PropertyAccessor.make(helper, send.charOffset, buildSimpleRead(),
send.name, null, null, isNullAware);
}
}
Expression buildThrowNoSuchMethodError(Arguments arguments) {
bool isGetter = false;
if (arguments == null) {
arguments = new Arguments.empty();
isGetter = true;
}
return throwNoSuchMethodError(
plainNameForWrite, arguments, uri, charOffset, coreTypes,
isGetter: isGetter);
}
bool get isThisPropertyAccessor => false;
}
abstract class CompileTimeErrorAccessor implements Accessor {
Expression buildError();
Name get name => internalError("Unsupported operation.");
String get plainNameForRead => name.name;
withReceiver(Object receiver, {bool isNullAware}) => this;
Initializer buildFieldInitializer(
Map<String, FieldInitializer> initializers) {
return new LocalInitializer(new VariableDeclaration.forValue(buildError()));
}
doInvocation(int charOffset, Arguments arguments) => this;
buildPropertyAccess(IncompleteSend send, bool isNullAware) => this;
buildThrowNoSuchMethodError(Arguments arguments) => this;
Expression buildAssignment(Expression value, {bool voidContext: false}) {
return buildError();
}
Expression buildCompoundAssignment(
Name binaryOperator, Expression value, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return buildError();
}
Expression buildPrefixIncrement(Name binaryOperator, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return buildError();
}
Expression buildPostfixIncrement(Name binaryOperator, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return buildError();
}
Expression buildNullAwareAssignment(Expression value, DartType type,
{bool voidContext: false}) {
return buildError();
}
Expression buildSimpleRead() => buildError();
Expression makeInvalidRead() => buildError();
Expression makeInvalidWrite(Expression value) => buildError();
}
class ThisAccessor extends BuilderAccessor {
final BuilderHelper helper;
final int charOffset;
final bool isInitializer;
final bool isSuper;
ThisAccessor(this.helper, this.charOffset, this.isInitializer,
{this.isSuper: false});
String get plainNameForRead => internalError(isSuper ? "super" : "this");
Expression buildSimpleRead() {
if (!isSuper) {
return new ThisExpression();
} else {
return helper.buildCompileTimeError(
"Can't use `super` as an expression.", charOffset);
}
}
Initializer buildFieldInitializer(
Map<String, FieldInitializer> initializers) {
String keyword = isSuper ? "super" : "this";
return helper.buildCompileTimeErrorIntializer(
"Can't use '$keyword' here, did you mean '$keyword()'?", charOffset);
}
buildPropertyAccess(IncompleteSend send, bool isNullAware) {
if (isInitializer && send is SendAccessor) {
return buildConstructorInitializer(
send.charOffset, send.name, send.arguments);
}
if (send is SendAccessor) {
// Notice that 'this' or 'super' can't be null. So we can ignore the
// value of [isNullAware].
MethodInvocation result = buildMethodInvocation(
new ThisExpression(), send.name, send.arguments, charOffset);
return isSuper ? helper.toSuperMethodInvocation(result) : result;
} else {
if (isSuper) {
Member getter = helper.lookupSuperMember(send.name);
Member setter = helper.lookupSuperMember(send.name, isSetter: true);
return new SuperPropertyAccessor(
helper, charOffset, send.name, getter, setter);
} else {
return new ThisPropertyAccessor(
helper, charOffset, send.name, null, null);
}
}
}
doInvocation(int charOffset, Arguments arguments) {
if (isInitializer) {
return buildConstructorInitializer(charOffset, new Name(""), arguments);
} else {
return buildMethodInvocation(
new ThisExpression(), new Name("call"), arguments, charOffset);
}
}
Initializer buildConstructorInitializer(
int charOffset, Name name, Arguments arguments) {
Constructor constructor = helper.lookupConstructor(name, isSuper: isSuper);
Initializer result;
if (constructor == null) {
result = new LocalInitializer(new VariableDeclaration.forValue(
throwNoSuchMethodError(
name.name, arguments, uri, charOffset, coreTypes,
isSuper: isSuper)));
} else if (isSuper) {
result = new SuperInitializer(constructor, arguments);
} else {
result = new RedirectingInitializer(constructor, arguments);
}
return result..fileOffset = charOffset;
}
Expression buildAssignment(Expression value, {bool voidContext: false}) {
return buildAssignmentError();
}
Expression buildNullAwareAssignment(Expression value, DartType type,
{bool voidContext: false}) {
return buildAssignmentError();
}
Expression buildCompoundAssignment(
Name binaryOperator, Expression value, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return buildAssignmentError();
}
Expression buildPrefixIncrement(Name binaryOperator, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return buildAssignmentError();
}
Expression buildPostfixIncrement(Name binaryOperator, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return buildAssignmentError();
}
Expression buildAssignmentError() {
String message =
isSuper ? "Can't assign to 'super'." : "Can't assign to 'this'.";
return helper.buildCompileTimeError(message, charOffset);
}
toString() => "ThisAccessor($charOffset${isSuper ? ', super' : ''})";
}
abstract class IncompleteSend extends BuilderAccessor {
final BuilderHelper helper;
final int charOffset;
final Name name;
IncompleteSend(this.helper, this.charOffset, this.name);
withReceiver(Object receiver, {bool isNullAware});
}
class IncompleteError extends IncompleteSend with CompileTimeErrorAccessor {
final Object error;
IncompleteError(BuilderHelper helper, int charOffset, this.error)
: super(helper, charOffset, null);
Expression buildError() {
return helper.buildCompileTimeError(error, charOffset);
}
}
class SendAccessor extends IncompleteSend {
final Arguments arguments;
SendAccessor(BuilderHelper helper, int charOffset, Name name, this.arguments)
: super(helper, charOffset, name) {
assert(arguments != null);
}
String get plainNameForRead => name.name;
Expression buildSimpleRead() {
return internalError("Unhandled");
}
Expression buildAssignment(Expression value, {bool voidContext: false}) {
return internalError("Unhandled");
}
withReceiver(Object receiver, {bool isNullAware: false}) {
if (receiver is TypeDeclarationBuilder) {
/// `SomeType?.toString` is the same as `SomeType.toString`, not
/// `(SomeType).toString`.
isNullAware = false;
}
if (receiver is BuilderAccessor) {
return receiver.buildPropertyAccess(this, isNullAware);
}
if (receiver is PrefixBuilder) {
PrefixBuilder prefix = receiver;
receiver = helper.builderToFirstExpression(
prefix.exports[name.name], "${prefix.name}.${name.name}", charOffset);
return helper.finishSend(receiver, arguments, charOffset);
}
Expression result;
if (receiver is KernelClassBuilder) {
Builder builder = receiver.findStaticBuilder(name.name, charOffset, uri);
if (builder == null) {
return buildThrowNoSuchMethodError(arguments);
}
if (builder.hasProblem) {
result = helper.buildProblemExpression(builder, name.name);
} else {
Member target = builder.target;
if (target != null) {
if (target is Field) {
result = buildMethodInvocation(
new StaticGet(target), new Name("call"), arguments, charOffset,
isNullAware: isNullAware);
} else {
result = helper.buildStaticInvocation(target, arguments);
}
} else {
result = buildThrowNoSuchMethodError(arguments);
}
}
} else {
result = buildMethodInvocation(
helper.toValue(receiver), name, arguments, charOffset,
isNullAware: isNullAware);
}
return result..fileOffset = charOffset;
}
Expression buildNullAwareAssignment(Expression value, DartType type,
{bool voidContext: false}) {
return internalError("Unhandled");
}
Expression buildCompoundAssignment(
Name binaryOperator, Expression value, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return internalError("Unhandled");
}
Expression buildPrefixIncrement(Name binaryOperator, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return internalError("Unhandled");
}
Expression buildPostfixIncrement(Name binaryOperator, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return internalError("Unhandled");
}
Expression doInvocation(int charOffset, Arguments arguments) {
return internalError("Unhandled");
}
toString() => "SendAccessor($charOffset, $name, $arguments)";
}
class IncompletePropertyAccessor extends IncompleteSend {
IncompletePropertyAccessor(BuilderHelper helper, int charOffset, Name name)
: super(helper, charOffset, name);
String get plainNameForRead => name.name;
Expression buildSimpleRead() => internalError("Unhandled");
Expression buildAssignment(Expression value, {bool voidContext: false}) {
return internalError("Unhandled");
}
withReceiver(Object receiver, {bool isNullAware: false}) {
if (receiver is TypeDeclarationBuilder) {
/// For reasons beyond comprehension, `SomeType?.toString` is the same as
/// `SomeType.toString`, not `(SomeType).toString`. WTAF!?!
//
isNullAware = false;
}
if (receiver is BuilderAccessor) {
return receiver.buildPropertyAccess(this, isNullAware);
}
if (receiver is PrefixBuilder) {
PrefixBuilder prefix = receiver;
return helper.builderToFirstExpression(
prefix.exports[name.name], name.name, charOffset);
}
if (receiver is KernelClassBuilder) {
Builder builder = receiver.findStaticBuilder(name.name, charOffset, uri);
Member getter = builder?.target;
Member setter;
if (builder == null) {
builder = receiver.findStaticBuilder(name.name, charOffset, uri,
isSetter: true);
if (builder == null) {
return buildThrowNoSuchMethodError(null);
}
setter = builder.target;
}
if (builder.hasProblem) {
return helper.buildProblemExpression(builder, name.name)
..fileOffset = charOffset;
}
if (getter is Field) {
if (!getter.isFinal && !getter.isConst) {
setter = getter;
}
} else if (getter is Procedure) {
if (getter.isGetter) {
builder = receiver.findStaticBuilder(name.name, charOffset, uri,
isSetter: true);
if (builder != null && !builder.hasProblem) {
setter = builder.target;
}
}
}
if (getter == null && setter == null) {
return internalError("No accessor for '$name'.");
}
return new StaticAccessor(helper, charOffset, getter, setter);
}
return PropertyAccessor.make(helper, charOffset, helper.toValue(receiver),
name, null, null, isNullAware);
}
Expression buildNullAwareAssignment(Expression value, DartType type,
{bool voidContext: false}) {
return internalError("Unhandled");
}
Expression buildCompoundAssignment(
Name binaryOperator, Expression value, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return internalError("Unhandled");
}
Expression buildPrefixIncrement(Name binaryOperator, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return internalError("Unhandled");
}
Expression buildPostfixIncrement(Name binaryOperator, int charOffset,
{bool voidContext: false, Procedure interfaceTarget}) {
return internalError("Unhandled");
}
Expression doInvocation(int charOffset, Arguments arguments) {
return internalError("Unhandled");
}
toString() => "IncompletePropertyAccessor($charOffset, $name)";
}
class IndexAccessor extends kernel.IndexAccessor with BuilderAccessor {
final BuilderHelper helper;
IndexAccessor.internal(this.helper, int charOffset, Expression receiver,
Expression index, Procedure getter, Procedure setter)
: super.internal(receiver, index, getter, setter, charOffset);
String get plainNameForRead => "[]";
String get plainNameForWrite => "[]=";
Expression doInvocation(int charOffset, Arguments arguments) {
return buildMethodInvocation(
buildSimpleRead(), new Name("call"), arguments, charOffset);
}
toString() => "IndexAccessor()";
static BuilderAccessor make(
BuilderHelper helper,
int charOffset,
Expression receiver,
Expression index,
Procedure getter,
Procedure setter) {
if (receiver is ThisExpression) {
return new ThisIndexAccessor(helper, charOffset, index, getter, setter);
} else {
return new IndexAccessor.internal(
helper, charOffset, receiver, index, getter, setter);
}
}
}
class PropertyAccessor extends kernel.PropertyAccessor with BuilderAccessor {
final BuilderHelper helper;
PropertyAccessor.internal(this.helper, int charOffset, Expression receiver,
Name name, Member getter, Member setter)
: super.internal(receiver, name, getter, setter, charOffset);
String get plainNameForRead => name.name;
bool get isThisPropertyAccessor => receiver is ThisExpression;
Expression doInvocation(int charOffset, Arguments arguments) {
return buildMethodInvocation(receiver, name, arguments, charOffset);
}
toString() => "PropertyAccessor()";
static BuilderAccessor make(
BuilderHelper helper,
int charOffset,
Expression receiver,
Name name,
Member getter,
Member setter,
bool isNullAware) {
if (receiver is ThisExpression) {
return new ThisPropertyAccessor(helper, charOffset, name, getter, setter);
} else {
return isNullAware
? new NullAwarePropertyAccessor(
helper, charOffset, receiver, name, getter, setter, null)
: new PropertyAccessor.internal(
helper, charOffset, receiver, name, getter, setter);
}
}
}
class StaticAccessor extends kernel.StaticAccessor with BuilderAccessor {
final BuilderHelper helper;
final int charOffset;
StaticAccessor(
this.helper, this.charOffset, Member readTarget, Member writeTarget)
: super(readTarget, writeTarget) {
assert(readTarget != null || writeTarget != null);
}
String get plainNameForRead => (readTarget ?? writeTarget).name.name;
Expression doInvocation(int charOffset, Arguments arguments) {
if (readTarget == null || isFieldOrGetter(readTarget)) {
return buildMethodInvocation(
buildSimpleRead(), new Name("call"), arguments, charOffset);
} else {
return helper.buildStaticInvocation(readTarget, arguments)
..fileOffset = charOffset;
}
}
toString() => "StaticAccessor()";
}
class SuperPropertyAccessor extends kernel.SuperPropertyAccessor
with BuilderAccessor {
final BuilderHelper helper;
final int charOffset;
SuperPropertyAccessor(
this.helper, this.charOffset, Name name, Member getter, Member setter)
: super(name, getter, setter);
String get plainNameForRead => name.name;
Expression doInvocation(int charOffset, Arguments arguments) {
if (getter == null || isFieldOrGetter(getter)) {
return buildMethodInvocation(
buildSimpleRead(), new Name("call"), arguments, charOffset);
} else {
return new DirectMethodInvocation(new ThisExpression(), getter, arguments)
..fileOffset = charOffset;
}
}
toString() => "SuperPropertyAccessor()";
}
class ThisIndexAccessor extends kernel.ThisIndexAccessor with BuilderAccessor {
final BuilderHelper helper;
final int charOffset;
ThisIndexAccessor(this.helper, this.charOffset, Expression index,
Procedure getter, Procedure setter)
: super(index, getter, setter);
String get plainNameForRead => "[]";
String get plainNameForWrite => "[]=";
Expression doInvocation(int charOffset, Arguments arguments) {
return buildMethodInvocation(
buildSimpleRead(), new Name("call"), arguments, charOffset);
}
toString() => "ThisIndexAccessor()";
}
class SuperIndexAccessor extends kernel.SuperIndexAccessor
with BuilderAccessor {
final BuilderHelper helper;
final int charOffset;
SuperIndexAccessor(this.helper, this.charOffset, Expression index,
Member getter, Member setter)
: super(index, getter, setter);
String get plainNameForRead => "[]";
String get plainNameForWrite => "[]=";
Expression doInvocation(int charOffset, Arguments arguments) {
return buildMethodInvocation(
buildSimpleRead(), new Name("call"), arguments, charOffset);
}
toString() => "SuperIndexAccessor()";
}
class ThisPropertyAccessor extends kernel.ThisPropertyAccessor
with BuilderAccessor {
final BuilderHelper helper;
final int charOffset;
ThisPropertyAccessor(
this.helper, this.charOffset, Name name, Member getter, Member setter)
: super(name, getter, setter);
String get plainNameForRead => name.name;
bool get isThisPropertyAccessor => true;
Expression doInvocation(int charOffset, Arguments arguments) {
Member interfaceTarget = getter;
if (interfaceTarget is Field) {
// TODO(ahe): In strong mode we should probably rewrite this to
// `this.name.call(arguments)`.
interfaceTarget = null;
}
return buildMethodInvocation(
new ThisExpression(), name, arguments, charOffset);
}
toString() => "ThisPropertyAccessor()";
}
class NullAwarePropertyAccessor extends kernel.NullAwarePropertyAccessor
with BuilderAccessor {
final BuilderHelper helper;
final int charOffset;
NullAwarePropertyAccessor(this.helper, this.charOffset, Expression receiver,
Name name, Member getter, Member setter, DartType type)
: super(receiver, name, getter, setter, type);
String get plainNameForRead => name.name;
Expression doInvocation(int charOffset, Arguments arguments) {
return internalError("Not implemented yet.");
}
toString() => "NullAwarePropertyAccessor()";
}
class VariableAccessor extends kernel.VariableAccessor with BuilderAccessor {
final BuilderHelper helper;
VariableAccessor(this.helper, int charOffset, VariableDeclaration variable,
[DartType promotedType])
: super.internal(variable, charOffset, promotedType);
String get plainNameForRead => variable.name;
Expression doInvocation(int charOffset, Arguments arguments) {
// Normally the offset is at the start of the token, but in this case,
// because we insert a '.call', we want it at the end instead.
return buildMethodInvocation(buildSimpleRead(), new Name("call"), arguments,
charOffset + (variable.name?.length ?? 0));
}
toString() => "VariableAccessor()";
}
Expression throwNoSuchMethodError(String name, Arguments arguments, Uri uri,
int charOffset, CoreTypes coreTypes,
{bool isSuper: false, isGetter: false, isSetter: false}) {
printUnexpected(uri, charOffset, "Method not found: '$name'.");
Constructor constructor =
coreTypes.getClass("dart:core", "NoSuchMethodError").constructors.first;
return new Throw(new ConstructorInvocation(
constructor,
new Arguments(<Expression>[
new NullLiteral(),
new SymbolLiteral(name),
new ListLiteral(arguments.positional),
new MapLiteral(arguments.named.map((arg) {
return new MapEntry(new SymbolLiteral(arg.name), arg.value);
}).toList()),
new NullLiteral()
])));
}
bool isFieldOrGetter(Member member) {
return member is Field || (member is Procedure && member.isGetter);
}
Expression buildMethodInvocation(
Expression receiver, Name name, Arguments arguments, int charOffset,
{bool isNullAware: false}) {
if (isNullAware) {
VariableDeclaration variable = new VariableDeclaration.forValue(receiver);
return makeLet(
variable,
new ConditionalExpression(
buildIsNull(new VariableGet(variable)),
new NullLiteral(),
new MethodInvocation(new VariableGet(variable), name, arguments)
..fileOffset = charOffset,
const DynamicType()));
} else {
return new MethodInvocation(receiver, name, arguments)
..fileOffset = charOffset;
}
}