blob: 9582f50d640f6acc59593b84d73bce99a452657d [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 kernel.analyzer.ast_from_analyzer;
import 'package:kernel/ast.dart' as ast;
import 'package:kernel/frontend/super_initializers.dart';
import 'package:kernel/log.dart';
import 'package:kernel/type_algebra.dart';
import 'package:kernel/transformations/flags.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/kernel/loader.dart';
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/dart/ast/standard_resolution_map.dart';
import 'package:analyzer/src/generated/parser.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:kernel/frontend/accessors.dart';
/// Provides reference-level access to libraries, classes, and members.
///
/// "Reference level" objects are incomplete nodes that have no children but
/// can be used for linking until the loader promotes the node to a higher
/// loading level.
///
/// The [ReferenceScope] is the most restrictive scope in a hierarchy of scopes
/// that provide increasing amounts of contextual information. [TypeScope] is
/// used when type parameters might be in scope, and [MemberScope] is used when
/// building the body of a [ast.Member].
class ReferenceScope {
final ReferenceLevelLoader loader;
ReferenceScope(this.loader);
bool get strongMode => loader.strongMode;
ast.Library getLibraryReference(LibraryElement element) {
if (element == null) return null;
return loader.getLibraryReference(getBaseElement(element));
}
ast.Class getRootClassReference() {
return loader.getRootClassReference();
}
ast.Class getClassReference(ClassElement element) {
return loader.getClassReference(getBaseElement(element));
}
ast.Member getMemberReference(Element element) {
return loader.getMemberReference(getBaseElement(element));
}
static Element getBaseElement(Element element) {
while (element is Member) {
element = (element as Member).baseElement;
}
return element;
}
static bool supportsConcreteGet(Element element) {
return (element is PropertyAccessorElement &&
element.isGetter &&
!element.isAbstract) ||
element is FieldElement ||
element is TopLevelVariableElement ||
element is MethodElement && !element.isAbstract ||
isTopLevelFunction(element);
}
static bool supportsInterfaceGet(Element element) {
return (element is PropertyAccessorElement && element.isGetter) ||
element is FieldElement ||
element is MethodElement;
}
static bool supportsConcreteSet(Element element) {
return (element is PropertyAccessorElement &&
element.isSetter &&
!element.isAbstract) ||
element is FieldElement && !element.isFinal && !element.isConst ||
element is TopLevelVariableElement &&
!element.isFinal &&
!element.isConst;
}
static bool supportsInterfaceSet(Element element) {
return (element is PropertyAccessorElement && element.isSetter) ||
element is FieldElement && !element.isFinal && !element.isConst;
}
static bool supportsConcreteIndexGet(Element element) {
return element is MethodElement &&
element.name == '[]' &&
!element.isAbstract;
}
static bool supportsInterfaceIndexGet(Element element) {
return element is MethodElement && element.name == '[]';
}
static bool supportsConcreteIndexSet(Element element) {
return element is MethodElement &&
element.name == '[]=' &&
!element.isAbstract;
}
static bool supportsInterfaceIndexSet(Element element) {
return element is MethodElement && element.name == '[]=';
}
static bool supportsConcreteMethodCall(Element element) {
// Note that local functions are not valid targets for method calls because
// they are not "methods" or even "procedures" in our AST.
return element is MethodElement && !element.isAbstract ||
isTopLevelFunction(element) ||
element is ConstructorElement && element.isFactory;
}
static bool supportsInterfaceMethodCall(Element element) {
return element is MethodElement;
}
static bool supportsConstructorCall(Element element) {
return element is ConstructorElement && !element.isFactory;
}
ast.Member _resolveGet(
Element element, Element auxiliary, bool predicate(Element element)) {
element = desynthesizeGetter(element);
if (predicate(element)) return getMemberReference(element);
if (element is PropertyAccessorElement && element.isSetter) {
// The getter is sometimes stored as the 'corresponding getter' instead
// of the 'auxiliary' element.
auxiliary ??= element.correspondingGetter;
}
auxiliary = desynthesizeGetter(auxiliary);
if (predicate(auxiliary)) return getMemberReference(auxiliary);
return null;
}
ast.Member resolveConcreteGet(Element element, Element auxiliary) {
return _resolveGet(element, auxiliary, supportsConcreteGet);
}
ast.Member resolveInterfaceGet(Element element, Element auxiliary) {
if (!strongMode) return null;
return _resolveGet(element, auxiliary, supportsInterfaceGet);
}
DartType getterTypeOfElement(Element element) {
if (element is VariableElement) {
return element.type;
} else if (element is PropertyAccessorElement && element.isGetter) {
return element.returnType;
} else {
return null;
}
}
/// Returns the interface target of a `call` dispatch to the given member.
///
/// For example, if the member is a field of type C, the target will be the
/// `call` method of class C, if it has such a method.
///
/// If the class C has a getter or field named `call`, this method returns
/// `null` - the static type system does support typed calls with indirection.
ast.Member resolveInterfaceFunctionCall(Element element) {
if (!strongMode || element == null) return null;
return resolveInterfaceFunctionCallOnType(getterTypeOfElement(element));
}
/// Returns the `call` method of [callee], if it has one, otherwise `null`.
ast.Member resolveInterfaceFunctionCallOnType(DartType callee) {
return callee is InterfaceType
? resolveInterfaceMethod(callee.getMethod('call'))
: null;
}
ast.Member _resolveSet(
Element element, Element auxiliary, bool predicate(Element element)) {
element = desynthesizeSetter(element);
if (predicate(element)) {
return getMemberReference(element);
}
if (element is PropertyAccessorElement && element.isSetter) {
// The setter is sometimes stored as the 'corresponding setter' instead
// of the 'auxiliary' element.
auxiliary ??= element.correspondingGetter;
}
auxiliary = desynthesizeSetter(auxiliary);
if (predicate(auxiliary)) {
return getMemberReference(auxiliary);
}
return null;
}
ast.Member resolveConcreteSet(Element element, Element auxiliary) {
return _resolveSet(element, auxiliary, supportsConcreteSet);
}
ast.Member resolveInterfaceSet(Element element, Element auxiliary) {
if (!strongMode) return null;
return _resolveSet(element, auxiliary, supportsInterfaceSet);
}
ast.Member resolveConcreteIndexGet(Element element, Element auxiliary) {
if (supportsConcreteIndexGet(element)) {
return getMemberReference(element);
}
if (supportsConcreteIndexGet(auxiliary)) {
return getMemberReference(auxiliary);
}
return null;
}
ast.Member resolveInterfaceIndexGet(Element element, Element auxiliary) {
if (!strongMode) return null;
if (supportsInterfaceIndexGet(element)) {
return getMemberReference(element);
}
if (supportsInterfaceIndexGet(auxiliary)) {
return getMemberReference(auxiliary);
}
return null;
}
ast.Member resolveConcreteIndexSet(Element element, Element auxiliary) {
if (supportsConcreteIndexSet(element)) {
return getMemberReference(element);
}
if (supportsConcreteIndexSet(auxiliary)) {
return getMemberReference(auxiliary);
}
return null;
}
ast.Member resolveInterfaceIndexSet(Element element, Element auxiliary) {
if (!strongMode) return null;
if (supportsInterfaceIndexSet(element)) {
return getMemberReference(element);
}
if (supportsInterfaceIndexSet(auxiliary)) {
return getMemberReference(auxiliary);
}
return null;
}
ast.Member resolveConcreteMethod(Element element) {
if (supportsConcreteMethodCall(element)) {
return getMemberReference(element);
}
return null;
}
ast.Member resolveInterfaceMethod(Element element) {
if (!strongMode) return null;
if (supportsInterfaceMethodCall(element)) {
return getMemberReference(element);
}
return null;
}
ast.Constructor resolveConstructor(Element element) {
if (supportsConstructorCall(element)) {
return getMemberReference(element);
}
return null;
}
ast.Field resolveField(Element element) {
if (element is FieldElement && !element.isSynthetic) {
return getMemberReference(element);
}
return null;
}
/// A static accessor that generates a 'throw NoSuchMethodError' when a
/// read or write access could not be resolved.
Accessor staticAccess(String name, Element element, [Element auxiliary]) {
return new _StaticAccessor(
this,
name,
resolveConcreteGet(element, auxiliary),
resolveConcreteSet(element, auxiliary));
}
/// An accessor that generates a 'throw NoSuchMethodError' on both read
/// and write access.
Accessor unresolvedAccess(String name) {
return new _StaticAccessor(this, name, null, null);
}
}
class TypeScope extends ReferenceScope {
final Map<TypeParameterElement, ast.TypeParameter> localTypeParameters =
<TypeParameterElement, ast.TypeParameter>{};
TypeAnnotationBuilder _typeBuilder;
TypeScope(ReferenceLevelLoader loader) : super(loader) {
_typeBuilder = new TypeAnnotationBuilder(this);
}
String get location => '?';
bool get allowClassTypeParameters => false;
ast.DartType get defaultTypeParameterBound => getRootClassReference().rawType;
ast.TypeParameter tryGetTypeParameterReference(TypeParameterElement element) {
return localTypeParameters[element] ??
loader.tryGetClassTypeParameter(element);
}
ast.TypeParameter getTypeParameterReference(TypeParameterElement element) {
return localTypeParameters[element] ??
loader.tryGetClassTypeParameter(element) ??
(localTypeParameters[element] = new ast.TypeParameter(element.name));
}
ast.TypeParameter makeTypeParameter(TypeParameterElement element,
{ast.DartType bound}) {
var typeParameter = getTypeParameterReference(element);
assert(bound != null);
typeParameter.bound = bound;
return typeParameter;
}
ast.DartType buildType(DartType type) {
return _typeBuilder.buildFromDartType(type);
}
ast.Supertype buildSupertype(DartType type) {
if (type is InterfaceType) {
var classElement = type.element;
if (classElement == null) return getRootClassReference().asRawSupertype;
var classNode = getClassReference(classElement);
if (classNode.typeParameters.isEmpty ||
classNode.typeParameters.length != type.typeArguments.length) {
return classNode.asRawSupertype;
} else {
return new ast.Supertype(classNode,
type.typeArguments.map(buildType).toList(growable: false));
}
}
return getRootClassReference().asRawSupertype;
}
ast.DartType buildTypeAnnotation(AstNode node) {
return _typeBuilder.build(node);
}
ast.DartType buildOptionalTypeAnnotation(AstNode node) {
return node == null ? null : _typeBuilder.build(node);
}
ast.DartType getInferredType(Expression node) {
if (!strongMode) return const ast.DynamicType();
// TODO: Is this official way to get the strong-mode inferred type?
return buildType(node.staticType);
}
ast.DartType getInferredTypeArgument(Expression node, int index) {
var type = getInferredType(node);
return type is ast.InterfaceType && index < type.typeArguments.length
? type.typeArguments[index]
: const ast.DynamicType();
}
ast.DartType getInferredReturnType(Expression node) {
var type = getInferredType(node);
return type is ast.FunctionType ? type.returnType : const ast.DynamicType();
}
List<ast.DartType> getInferredInvocationTypeArguments(
InvocationExpression node) {
if (!strongMode) return <ast.DartType>[];
ast.DartType inferredFunctionType = buildType(node.staticInvokeType);
ast.DartType genericFunctionType = buildType(node.function.staticType);
if (genericFunctionType is ast.FunctionType) {
if (genericFunctionType.typeParameters.isEmpty) return <ast.DartType>[];
// Attempt to unify the two types to obtain a substitution of the type
// variables. If successful, use the substituted types in the order
// they occur in the type parameter list.
var substitution = unifyTypes(genericFunctionType.withoutTypeParameters,
inferredFunctionType, genericFunctionType.typeParameters.toSet());
if (substitution != null) {
return genericFunctionType.typeParameters
.map((p) => substitution[p] ?? const ast.DynamicType())
.toList();
}
return new List<ast.DartType>.filled(
genericFunctionType.typeParameters.length, const ast.DynamicType(),
growable: true);
} else {
return <ast.DartType>[];
}
}
List<ast.DartType> buildOptionalTypeArgumentList(TypeArgumentList node) {
if (node == null) return null;
return _typeBuilder.buildList(node.arguments);
}
List<ast.DartType> buildTypeArgumentList(TypeArgumentList node) {
return _typeBuilder.buildList(node.arguments);
}
List<ast.TypeParameter> buildOptionalTypeParameterList(TypeParameterList node,
{bool strongModeOnly: false}) {
if (node == null) return <ast.TypeParameter>[];
if (strongModeOnly && !strongMode) return <ast.TypeParameter>[];
return node.typeParameters.map(buildTypeParameter).toList();
}
ast.TypeParameter buildTypeParameter(TypeParameter node) {
return makeTypeParameter(node.element,
bound: buildOptionalTypeAnnotation(node.bound) ??
defaultTypeParameterBound);
}
ConstructorElement findDefaultConstructor(ClassElement class_) {
for (var constructor in class_.constructors) {
// Note: isDefaultConstructor checks if the constructor is suitable for
// being invoked without arguments. It does not imply that it is
// synthetic.
if (constructor.isDefaultConstructor && !constructor.isFactory) {
return constructor;
}
}
return null;
}
ast.FunctionNode buildFunctionInterface(FunctionTypedElement element) {
var positional = <ast.VariableDeclaration>[];
var named = <ast.VariableDeclaration>[];
int requiredParameterCount = 0;
// Initialize type parameters in two passes: put them into scope,
// and compute the bounds afterwards while they are all in scope.
var typeParameters = <ast.TypeParameter>[];
var typeParameterElements =
element is ConstructorElement && element.isFactory
? element.enclosingElement.typeParameters
: element.typeParameters;
if (strongMode || element is ConstructorElement) {
for (var parameter in typeParameterElements) {
var parameterNode = new ast.TypeParameter(parameter.name);
typeParameters.add(parameterNode);
localTypeParameters[parameter] = parameterNode;
}
}
for (int i = 0; i < typeParameters.length; ++i) {
var parameter = typeParameterElements[i];
var parameterNode = typeParameters[i];
parameterNode.bound = parameter.bound == null
? defaultTypeParameterBound
: buildType(parameter.bound);
}
for (var parameter in element.parameters) {
var parameterNode = new ast.VariableDeclaration(parameter.name,
type: buildType(parameter.type));
switch (parameter.parameterKind) {
case ParameterKind.REQUIRED:
positional.add(parameterNode);
++requiredParameterCount;
break;
case ParameterKind.POSITIONAL:
positional.add(parameterNode);
break;
case ParameterKind.NAMED:
named.add(parameterNode);
break;
}
}
var returnType = element is ConstructorElement
? const ast.VoidType()
: buildType(element.returnType);
return new ast.FunctionNode(null,
typeParameters: typeParameters,
positionalParameters: positional,
namedParameters: named,
requiredParameterCount: requiredParameterCount,
returnType: returnType)
..fileOffset = element.nameOffset;
}
}
class ExpressionScope extends TypeScope {
ast.Library currentLibrary;
final Map<LocalElement, ast.VariableDeclaration> localVariables =
<LocalElement, ast.VariableDeclaration>{};
ExpressionBuilder _expressionBuilder;
StatementBuilder _statementBuilder;
ExpressionScope(ReferenceLevelLoader loader, this.currentLibrary)
: super(loader) {
assert(currentLibrary != null);
_expressionBuilder = new ExpressionBuilder(this);
_statementBuilder = new StatementBuilder(this);
}
bool get allowThis => false; // Overridden by MemberScope.
ast.Name buildName(SimpleIdentifier node) {
return new ast.Name(node.name, currentLibrary);
}
ast.Statement buildStatement(Statement node) {
return _statementBuilder.build(node);
}
ast.Statement buildOptionalFunctionBody(FunctionBody body) {
if (body == null ||
body is EmptyFunctionBody ||
body is NativeFunctionBody) {
return null;
}
return buildMandatoryFunctionBody(body);
}
ast.Statement buildMandatoryFunctionBody(FunctionBody body) {
try {
if (body is BlockFunctionBody) {
return buildStatement(body.block);
} else if (body is ExpressionFunctionBody) {
if (bodyHasVoidReturn(body)) {
return new ast.ExpressionStatement(buildExpression(body.expression));
} else {
return new ast.ReturnStatement(buildExpression(body.expression))
..fileOffset = body.expression.offset;
}
} else {
return internalError('Missing function body');
}
} on _CompilationError catch (e) {
return new ast.ExpressionStatement(buildThrowCompileTimeError(e.message));
}
}
ast.AsyncMarker getAsyncMarker({bool isAsync: false, bool isStar: false}) {
return ast.AsyncMarker.values[(isAsync ? 2 : 0) + (isStar ? 1 : 0)];
}
ast.FunctionNode buildFunctionNode(
FormalParameterList formalParameters, FunctionBody body,
{TypeName returnType,
List<ast.TypeParameter> typeParameters,
ast.DartType inferredReturnType}) {
// TODO(asgerf): This will in many cases rebuild the interface built by
// TypeScope.buildFunctionInterface.
var positional = <ast.VariableDeclaration>[];
var named = <ast.VariableDeclaration>[];
int requiredParameterCount = 0;
var formals = formalParameters?.parameters ?? const <FormalParameter>[];
for (var parameter in formals) {
var declaration = makeVariableDeclaration(parameter.element,
initializer: parameter is DefaultFormalParameter
? buildOptionalTopLevelExpression(parameter.defaultValue)
: null,
type: buildType(
resolutionMap.elementDeclaredByFormalParameter(parameter).type));
switch (parameter.kind) {
case ParameterKind.REQUIRED:
positional.add(declaration);
++requiredParameterCount;
declaration.initializer = null;
break;
case ParameterKind.POSITIONAL:
positional.add(declaration);
break;
case ParameterKind.NAMED:
named.add(declaration);
break;
}
}
int offset = formalParameters?.offset ?? body.offset;
int endOffset = body.endToken.offset;
ast.AsyncMarker asyncMarker =
getAsyncMarker(isAsync: body.isAsynchronous, isStar: body.isGenerator);
return new ast.FunctionNode(buildOptionalFunctionBody(body),
typeParameters: typeParameters,
positionalParameters: positional,
namedParameters: named,
requiredParameterCount: requiredParameterCount,
returnType: buildOptionalTypeAnnotation(returnType) ??
inferredReturnType ??
const ast.DynamicType(),
asyncMarker: asyncMarker,
dartAsyncMarker: asyncMarker)
..fileOffset = offset
..fileEndOffset = endOffset;
}
ast.Expression buildOptionalTopLevelExpression(Expression node) {
return node == null ? null : buildTopLevelExpression(node);
}
ast.Expression buildTopLevelExpression(Expression node) {
try {
return _expressionBuilder.build(node);
} on _CompilationError catch (e) {
return buildThrowCompileTimeError(e.message);
}
}
ast.Expression buildExpression(Expression node) {
return _expressionBuilder.build(node);
}
ast.Expression buildOptionalExpression(Expression node) {
return node == null ? null : _expressionBuilder.build(node);
}
Accessor buildLeftHandValue(Expression node) {
return _expressionBuilder.buildLeftHandValue(node);
}
ast.Expression buildStringLiteral(Expression node) {
List<ast.Expression> parts = <ast.Expression>[];
new StringLiteralPartBuilder(this, parts).build(node);
return parts.length == 1 && parts[0] is ast.StringLiteral
? parts[0]
: new ast.StringConcatenation(parts);
}
ast.Expression buildThis() {
return allowThis
? new ast.ThisExpression()
: emitCompileTimeError(CompileTimeErrorCode.INVALID_REFERENCE_TO_THIS);
}
ast.Initializer buildInitializer(ConstructorInitializer node) {
try {
return new InitializerBuilder(this).build(node);
} on _CompilationError catch (_) {
return new ast.InvalidInitializer();
}
}
bool isFinal(Element element) {
return element is VariableElement && element.isFinal ||
element is FunctionElement;
}
bool isConst(Element element) {
return element is VariableElement && element.isConst;
}
ast.VariableDeclaration getVariableReference(LocalElement element) {
return localVariables.putIfAbsent(element, () {
return new ast.VariableDeclaration(element.name,
isFinal: isFinal(element), isConst: isConst(element))
..fileOffset = element.nameOffset;
});
}
ast.DartType getInferredVariableType(Element element) {
if (!strongMode) return const ast.DynamicType();
if (element is FunctionTypedElement) {
return buildType(element.type);
} else if (element is VariableElement) {
return buildType(element.type);
} else {
log.severe('Unexpected variable element: $element');
return const ast.DynamicType();
}
}
ast.VariableDeclaration makeVariableDeclaration(LocalElement element,
{ast.DartType type, ast.Expression initializer, int equalsOffset}) {
var declaration = getVariableReference(element);
if (equalsOffset != null) declaration.fileEqualsOffset = equalsOffset;
declaration.type = type ?? getInferredVariableType(element);
if (initializer != null) {
declaration.initializer = initializer..parent = declaration;
}
return declaration;
}
/// Returns true if [arguments] can be accepted by [target]
/// (not taking type checks into account).
bool areArgumentsCompatible(
FunctionTypedElement target, ast.Arguments arguments) {
var positionals = arguments.positional;
var parameters = target.parameters;
const required = ParameterKind.REQUIRED; // For avoiding long lines.
const named = ParameterKind.NAMED;
// If the first unprovided parameter is required, there are too few
// positional arguments.
if (positionals.length < parameters.length &&
parameters[positionals.length].parameterKind == required) {
return false;
}
// If there are more positional arguments than parameters, or if the last
// positional argument corresponds to a named parameter, there are too many
// positional arguments.
if (positionals.length > parameters.length) return false;
if (positionals.isNotEmpty &&
parameters[positionals.length - 1].parameterKind == named) {
return false; // Too many positional arguments.
}
if (arguments.named.isEmpty) return true;
int firstNamedParameter = positionals.length;
while (firstNamedParameter < parameters.length &&
parameters[firstNamedParameter].parameterKind != ParameterKind.NAMED) {
++firstNamedParameter;
}
namedLoop:
for (int i = 0; i < arguments.named.length; ++i) {
String name = arguments.named[i].name;
for (int j = firstNamedParameter; j < parameters.length; ++j) {
if (parameters[j].parameterKind == ParameterKind.NAMED &&
parameters[j].name == name) {
continue namedLoop;
}
}
return false;
}
return true;
}
/// Throws a NoSuchMethodError corresponding to a call to [memberName] on
/// [receiver] with the given [arguments].
///
/// If provided, [candidateTarget] provides the expected arity and argument
/// names for the best candidate target.
ast.Expression buildThrowNoSuchMethodError(
ast.Expression receiver, String memberName, ast.Arguments arguments,
{Element candidateTarget}) {
// TODO(asgerf): When we have better integration with patch files, use
// the internal constructor that provides a more detailed error message.
ast.Expression candidateArgumentNames;
if (candidateTarget is FunctionTypedElement) {
candidateArgumentNames = new ast.ListLiteral(candidateTarget.parameters
.map((p) => new ast.StringLiteral(p.name))
.toList());
} else {
candidateArgumentNames = new ast.NullLiteral();
}
return new ast.Throw(new ast.ConstructorInvocation(
loader.getCoreClassConstructorReference('NoSuchMethodError'),
new ast.Arguments(<ast.Expression>[
receiver,
new ast.SymbolLiteral(memberName),
new ast.ListLiteral(arguments.positional),
new ast.MapLiteral(arguments.named.map((arg) {
return new ast.MapEntry(new ast.SymbolLiteral(arg.name), arg.value);
}).toList()),
candidateArgumentNames
])));
}
ast.Expression buildThrowCompileTimeError(String message) {
// The spec does not mandate a specific behavior in face of a compile-time
// error. We just throw a string. The VM throws an uncatchable exception
// for this case.
// TOOD(asgerf): Should we add uncatchable exceptions to kernel?
return new ast.Throw(new ast.StringLiteral(message));
}
ast.Expression buildThrowCompileTimeErrorFromCode(ErrorCode code,
[List arguments]) {
return buildThrowCompileTimeError(makeErrorMessage(code, arguments));
}
static final RegExp _errorMessagePattern = new RegExp(r'\{(\d+)\}');
String makeErrorMessage(ErrorCode error, [List arguments]) {
String message = error.message;
if (arguments != null) {
message = message.replaceAllMapped(_errorMessagePattern, (m) {
String numberString = m.group(1);
int index = int.parse(numberString);
return arguments[index];
});
}
return message;
}
/// Throws an exception that will be caught at the function level, to replace
/// the entire function with a throw.
emitCompileTimeError(ErrorCode error, [List arguments]) {
throw new _CompilationError(makeErrorMessage(error, arguments));
}
ast.Expression buildThrowAbstractClassInstantiationError(String name) {
return new ast.Throw(new ast.ConstructorInvocation(
loader.getCoreClassConstructorReference(
'AbstractClassInstantiationError'),
new ast.Arguments(<ast.Expression>[new ast.StringLiteral(name)])));
}
ast.Expression buildThrowFallThroughError() {
return new ast.Throw(new ast.ConstructorInvocation(
loader.getCoreClassConstructorReference('FallThroughError'),
new ast.Arguments.empty()));
}
emitInvalidConstant([ErrorCode error]) {
error ??= CompileTimeErrorCode.INVALID_CONSTANT;
return emitCompileTimeError(error);
}
internalError(String message) {
throw 'Internal error when compiling $location: $message';
}
unsupportedFeature(String feature) {
throw new _CompilationError('$feature is not supported');
}
ast.Expression buildAnnotation(Annotation annotation) {
Element element = annotation.element;
if (annotation.arguments == null) {
var target = resolveConcreteGet(element, null);
return target == null
? new ast.InvalidExpression(null)
: new ast.StaticGet(target);
} else if (element is ConstructorElement && element.isConst) {
var target = resolveConstructor(element);
return target == null
? new ast.InvalidExpression(null)
: new ast.ConstructorInvocation(
target, _expressionBuilder.buildArguments(annotation.arguments),
isConst: true);
} else {
return new ast.InvalidExpression(null);
}
}
void addTransformerFlag(int flags) {
// Overridden by MemberScope.
}
/// True if the body of the given method must return nothing.
bool hasVoidReturn(ExecutableElement element) {
return (strongMode && element.returnType.isVoid) ||
(element is PropertyAccessorElement && element.isSetter) ||
element.name == '[]=';
}
bool bodyHasVoidReturn(FunctionBody body) {
AstNode parent = body.parent;
return parent is MethodDeclaration && hasVoidReturn(parent.element) ||
parent is FunctionDeclaration && hasVoidReturn(parent.element);
}
}
/// A scope in which class type parameters are in scope, while not in scope
/// of a specific member.
class ClassScope extends ExpressionScope {
@override
bool get allowClassTypeParameters => true;
ClassScope(ReferenceLevelLoader loader, ast.Library library)
: super(loader, library);
}
/// Translates expressions, statements, and other constructs into [ast] nodes.
///
/// Naming convention:
/// - `buildX` may not be given null as argument (it may crash the compiler).
/// - `buildOptionalX` returns null or an empty list if given null
/// - `buildMandatoryX` returns an invalid node if given null.
class MemberScope extends ExpressionScope {
/// A reference to the member currently being upgraded to body level.
final ast.Member currentMember;
MemberScope(ReferenceLevelLoader loader, ast.Member currentMember)
: currentMember = currentMember,
super(loader, currentMember.enclosingLibrary) {
assert(currentMember != null);
}
ast.Class get currentClass => currentMember.enclosingClass;
bool get allowThis => _memberHasThis(currentMember);
@override
bool get allowClassTypeParameters {
return currentMember.isInstanceMember || currentMember is ast.Constructor;
}
/// Returns a string for debugging use, indicating the location of the member
/// being built.
String get location {
var library = currentMember.enclosingLibrary?.importUri ?? '<No Library>';
var className = currentMember.enclosingClass == null
? null
: (currentMember.enclosingClass?.name ?? '<Anonymous Class>');
var member =
currentMember.name?.name ?? '<Anonymous ${currentMember.runtimeType}>';
return [library, className, member].join('::');
}
bool _memberHasThis(ast.Member member) {
return member is ast.Procedure && !member.isStatic ||
member is ast.Constructor;
}
void addTransformerFlag(int flags) {
currentMember.transformerFlags |= flags;
}
}
class LabelStack {
final List<String> labels; // Contains null for unlabeled targets.
final LabelStack next;
final List<ast.Statement> jumps = <ast.Statement>[];
bool isSwitchTarget = false;
LabelStack(String label, this.next) : labels = <String>[label];
LabelStack.unlabeled(this.next) : labels = <String>[null];
LabelStack.switchCase(String label, this.next)
: isSwitchTarget = true,
labels = <String>[label];
LabelStack.many(this.labels, this.next);
}
class StatementBuilder extends GeneralizingAstVisitor<ast.Statement> {
final ExpressionScope scope;
LabelStack breakStack, continueStack;
StatementBuilder(this.scope, [this.breakStack, this.continueStack]);
ast.Statement build(Statement node) {
ast.Statement result = node.accept(this);
result.fileOffset = _getOffset(node);
return result;
}
ast.Statement buildOptional(Statement node) {
ast.Statement result = node?.accept(this);
result?.fileOffset = _getOffset(node);
return result;
}
int _getOffset(AstNode node) {
return node.offset;
}
ast.Statement buildInScope(
Statement node, LabelStack breakNode, LabelStack continueNode) {
var oldBreak = this.breakStack;
var oldContinue = this.continueStack;
breakStack = breakNode;
continueStack = continueNode;
var result = build(node);
this.breakStack = oldBreak;
this.continueStack = oldContinue;
return result;
}
void buildBlockMember(Statement node, List<ast.Statement> output) {
if (node is LabeledStatement &&
node.statement is VariableDeclarationStatement) {
// If a variable is labeled, its scope is part of the enclosing block.
LabeledStatement labeled = node;
node = labeled.statement;
}
if (node is VariableDeclarationStatement) {
VariableDeclarationList list = node.variables;
ast.DartType type = scope.buildOptionalTypeAnnotation(list.type);
for (VariableDeclaration decl in list.variables) {
LocalElement local = decl.element as dynamic; // Cross cast.
output.add(scope.makeVariableDeclaration(local,
type: type,
initializer: scope.buildOptionalExpression(decl.initializer),
equalsOffset: decl.equals?.offset));
}
} else {
output.add(build(node));
}
}
ast.Statement makeBreakTarget(ast.Statement node, LabelStack stackNode) {
if (stackNode.jumps.isEmpty) return node;
var labeled = new ast.LabeledStatement(node);
for (var jump in stackNode.jumps) {
(jump as ast.BreakStatement).target = labeled;
}
return labeled;
}
LabelStack findLabelTarget(String label, LabelStack stack) {
while (stack != null) {
if (stack.labels.contains(label)) return stack;
stack = stack.next;
}
return null;
}
ast.Statement visitAssertStatement(AssertStatement node) {
return new ast.AssertStatement(scope.buildExpression(node.condition),
message: scope.buildOptionalExpression(node.message));
}
ast.Statement visitBlock(Block node) {
List<ast.Statement> statements = <ast.Statement>[];
for (Statement statement in node.statements) {
buildBlockMember(statement, statements);
}
return new ast.Block(statements);
}
ast.Statement visitBreakStatement(BreakStatement node) {
var stackNode = findLabelTarget(node.label?.name, breakStack);
if (stackNode == null) {
return node.label == null
? scope.emitCompileTimeError(ParserErrorCode.BREAK_OUTSIDE_OF_LOOP)
: scope.emitCompileTimeError(
CompileTimeErrorCode.LABEL_UNDEFINED, [node.label.name]);
}
var result = new ast.BreakStatement(null);
stackNode.jumps.add(result);
return result;
}
ast.Statement visitContinueStatement(ContinueStatement node) {
var stackNode = findLabelTarget(node.label?.name, continueStack);
if (stackNode == null) {
return node.label == null
? scope.emitCompileTimeError(ParserErrorCode.CONTINUE_OUTSIDE_OF_LOOP)
: scope.emitCompileTimeError(
CompileTimeErrorCode.LABEL_UNDEFINED, [node.label.name]);
}
var result = stackNode.isSwitchTarget
? new ast.ContinueSwitchStatement(null)
: new ast.BreakStatement(null);
stackNode.jumps.add(result);
return result;
}
void addLoopLabels(Statement loop, LabelStack continueNode) {
AstNode parent = loop.parent;
if (parent is LabeledStatement) {
for (var label in parent.labels) {
continueNode.labels.add(label.label.name);
}
}
}
ast.Statement visitDoStatement(DoStatement node) {
LabelStack breakNode = new LabelStack.unlabeled(breakStack);
LabelStack continueNode = new LabelStack.unlabeled(continueStack);
addLoopLabels(node, continueNode);
var body = buildInScope(node.body, breakNode, continueNode);
var loop = new ast.DoStatement(makeBreakTarget(body, continueNode),
scope.buildExpression(node.condition));
return makeBreakTarget(loop, breakNode);
}
ast.Statement visitWhileStatement(WhileStatement node) {
LabelStack breakNode = new LabelStack.unlabeled(breakStack);
LabelStack continueNode = new LabelStack.unlabeled(continueStack);
addLoopLabels(node, continueNode);
var body = buildInScope(node.body, breakNode, continueNode);
var loop = new ast.WhileStatement(scope.buildExpression(node.condition),
makeBreakTarget(body, continueNode));
return makeBreakTarget(loop, breakNode);
}
ast.Statement visitEmptyStatement(EmptyStatement node) {
return new ast.EmptyStatement();
}
ast.Statement visitExpressionStatement(ExpressionStatement node) {
return new ast.ExpressionStatement(scope.buildExpression(node.expression));
}
static String _getLabelName(Label label) {
return label.label.name;
}
ast.Statement visitLabeledStatement(LabeledStatement node) {
// Only set up breaks here. Loops handle labeling on their own.
var breakNode = new LabelStack.many(
node.labels.map(_getLabelName).toList(), breakStack);
var body = buildInScope(node.statement, breakNode, continueStack);
return makeBreakTarget(body, breakNode);
}
static bool isBreakingExpression(ast.Expression node) {
return node is ast.Throw || node is ast.Rethrow;
}
static bool isBreakingStatement(ast.Statement node) {
return node is ast.BreakStatement ||
node is ast.ContinueSwitchStatement ||
node is ast.ReturnStatement ||
node is ast.ExpressionStatement &&
isBreakingExpression(node.expression);
}
ast.Statement visitSwitchStatement(SwitchStatement node) {
// Group all cases into case blocks. Use parallel lists to collect the
// intermediate terms until we are ready to create the AST nodes.
LabelStack breakNode = new LabelStack.unlabeled(breakStack);
LabelStack continueNode = continueStack;
var cases = <ast.SwitchCase>[];
var bodies = <List<Statement>>[];
var labelToNode = <String, ast.SwitchCase>{};
ast.SwitchCase currentCase = null;
for (var member in node.members) {
if (currentCase != null && currentCase.isDefault) {
var error = member is SwitchCase
? ParserErrorCode.SWITCH_HAS_CASE_AFTER_DEFAULT_CASE
: ParserErrorCode.SWITCH_HAS_MULTIPLE_DEFAULT_CASES;
return scope.emitCompileTimeError(error);
}
if (currentCase == null) {
currentCase = new ast.SwitchCase(<ast.Expression>[], <int>[], null);
cases.add(currentCase);
}
if (member is SwitchCase) {
var expression = scope.buildExpression(member.expression);
currentCase.expressions.add(expression..parent = currentCase);
currentCase.expressionOffsets.add(expression.fileOffset);
} else {
currentCase.isDefault = true;
}
for (Label label in member.labels) {
continueNode =
new LabelStack.switchCase(label.label.name, continueNode);
labelToNode[label.label.name] = currentCase;
}
if (member.statements?.isNotEmpty ?? false) {
bodies.add(member.statements);
currentCase = null;
}
}
if (currentCase != null) {
// Close off a trailing block.
bodies.add(const <Statement>[]);
currentCase = null;
}
// Now that the label environment is set up, build the bodies.
var oldBreak = this.breakStack;
var oldContinue = this.continueStack;
this.breakStack = breakNode;
this.continueStack = continueNode;
for (int i = 0; i < cases.length; ++i) {
var blockNodes = <ast.Statement>[];
for (var statement in bodies[i]) {
buildBlockMember(statement, blockNodes);
}
if (blockNodes.isEmpty || !isBreakingStatement(blockNodes.last)) {
if (i < cases.length - 1) {
blockNodes.add(
new ast.ExpressionStatement(scope.buildThrowFallThroughError()));
} else {
var jump = new ast.BreakStatement(null);
blockNodes.add(jump);
breakNode.jumps.add(jump);
}
}
cases[i].body = new ast.Block(blockNodes)..parent = cases[i];
}
// Unwind the stack of case labels and bind their jumps to the case target.
while (continueNode != oldContinue) {
for (var jump in continueNode.jumps) {
(jump as ast.ContinueSwitchStatement).target =
labelToNode[continueNode.labels.first];
}
continueNode = continueNode.next;
}
var expression = scope.buildExpression(node.expression);
var result = new ast.SwitchStatement(expression, cases);
this.breakStack = oldBreak;
this.continueStack = oldContinue;
return makeBreakTarget(result, breakNode);
}
ast.Statement visitForStatement(ForStatement node) {
List<ast.VariableDeclaration> variables = <ast.VariableDeclaration>[];
ast.Expression initialExpression;
if (node.variables != null) {
VariableDeclarationList list = node.variables;
var type = scope.buildOptionalTypeAnnotation(list.type);
for (var variable in list.variables) {
LocalElement local = variable.element as dynamic; // Cross cast.
variables.add(scope.makeVariableDeclaration(local,
initializer: scope.buildOptionalExpression(variable.initializer),
type: type,
equalsOffset: variable.equals?.offset));
}
} else if (node.initialization != null) {
initialExpression = scope.buildExpression(node.initialization);
}
var breakNode = new LabelStack.unlabeled(breakStack);
var continueNode = new LabelStack.unlabeled(continueStack);
addLoopLabels(node, continueNode);
var body = buildInScope(node.body, breakNode, continueNode);
var loop = new ast.ForStatement(
variables,
scope.buildOptionalExpression(node.condition),
node.updaters.map(scope.buildExpression).toList(),
makeBreakTarget(body, continueNode));
loop = makeBreakTarget(loop, breakNode);
if (initialExpression != null) {
return new ast.Block(<ast.Statement>[
new ast.ExpressionStatement(initialExpression),
loop
]);
}
return loop;
}
DartType iterableElementType(DartType iterable) {
if (iterable is InterfaceType) {
var iterator = iterable.lookUpInheritedGetter('iterator')?.returnType;
if (iterator is InterfaceType) {
return iterator.lookUpInheritedGetter('current')?.returnType;
}
}
return null;
}
DartType streamElementType(DartType stream) {
if (stream is InterfaceType) {
var class_ = stream.element;
if (class_.library.isDartAsync &&
class_.name == 'Stream' &&
stream.typeArguments.length == 1) {
return stream.typeArguments[0];
}
}
return null;
}
ast.Statement visitForEachStatement(ForEachStatement node) {
ast.VariableDeclaration variable;
Accessor leftHand;
if (node.loopVariable != null) {
DeclaredIdentifier loopVariable = node.loopVariable;
variable = scope.makeVariableDeclaration(loopVariable.element,
type: scope.buildOptionalTypeAnnotation(loopVariable.type));
} else if (node.identifier != null) {
leftHand = scope.buildLeftHandValue(node.identifier);
variable = new ast.VariableDeclaration(null, isFinal: true);
if (scope.strongMode) {
var containerType = node.iterable.staticType;
DartType elementType = node.awaitKeyword != null
? streamElementType(containerType)
: iterableElementType(containerType);
if (elementType != null) {
variable.type = scope.buildType(elementType);
}
}
}
var breakNode = new LabelStack.unlabeled(breakStack);
var continueNode = new LabelStack.unlabeled(continueStack);
addLoopLabels(node, continueNode);
var body = buildInScope(node.body, breakNode, continueNode);
if (leftHand != null) {
// Desugar
//
// for (x in e) BODY
//
// to
//
// for (var tmp in e) {
// x = tmp;
// BODY
// }
body = new ast.Block(<ast.Statement>[
new ast.ExpressionStatement(leftHand
.buildAssignment(new ast.VariableGet(variable), voidContext: true)),
body
]);
}
var loop = new ast.ForInStatement(
variable,
scope.buildExpression(node.iterable),
makeBreakTarget(body, continueNode),
isAsync: node.awaitKeyword != null)
..fileOffset = node.offset;
return makeBreakTarget(loop, breakNode);
}
ast.Statement visitIfStatement(IfStatement node) {
return new ast.IfStatement(scope.buildExpression(node.condition),
build(node.thenStatement), buildOptional(node.elseStatement));
}
ast.Statement visitReturnStatement(ReturnStatement node) {
return new ast.ReturnStatement(
scope.buildOptionalExpression(node.expression));
}
ast.Catch buildCatchClause(CatchClause node) {
var exceptionVariable = node.exceptionParameter == null
? null
: scope.makeVariableDeclaration(node.exceptionParameter.staticElement);
var stackTraceVariable = node.stackTraceParameter == null
? null
: scope.makeVariableDeclaration(node.stackTraceParameter.staticElement);
return new ast.Catch(exceptionVariable, build(node.body),
stackTrace: stackTraceVariable,
guard: scope.buildOptionalTypeAnnotation(node.exceptionType) ??
const ast.DynamicType());
}
ast.Statement visitTryStatement(TryStatement node) {
ast.Statement statement = build(node.body);
if (node.catchClauses.isNotEmpty) {
statement = new ast.TryCatch(
statement, node.catchClauses.map(buildCatchClause).toList());
}
if (node.finallyBlock != null) {
statement = new ast.TryFinally(statement, build(node.finallyBlock));
}
return statement;
}
ast.Statement visitVariableDeclarationStatement(
VariableDeclarationStatement node) {
// This is only reached when a variable is declared in non-block level,
// because visitBlock intercepts visits to its children.
// An example where we hit this case is:
//
// if (foo) var x = 5, y = x + 1;
//
// We wrap these in a block:
//
// if (foo) {
// var x = 5;
// var y = x + 1;
// }
//
// Note that the use of a block here is required by the kernel language,
// even if there is only one variable declaration.
List<ast.Statement> statements = <ast.Statement>[];
buildBlockMember(node, statements);
return new ast.Block(statements);
}
ast.Statement visitYieldStatement(YieldStatement node) {
return new ast.YieldStatement(scope.buildExpression(node.expression),
isYieldStar: node.star != null);
}
ast.Statement visitFunctionDeclarationStatement(
FunctionDeclarationStatement node) {
var declaration = node.functionDeclaration;
var expression = declaration.functionExpression;
LocalElement element = declaration.element as dynamic; // Cross cast.
return new ast.FunctionDeclaration(
scope.makeVariableDeclaration(element,
type: scope.buildType(declaration.element.type)),
scope.buildFunctionNode(expression.parameters, expression.body,
typeParameters: scope.buildOptionalTypeParameterList(
expression.typeParameters,
strongModeOnly: true),
returnType: declaration.returnType))
..fileOffset = node.offset;
}
@override
visitStatement(Statement node) {
return scope.internalError('Unhandled statement ${node.runtimeType}');
}
}
class ExpressionBuilder
extends GeneralizingAstVisitor /* <ast.Expression | Accessor> */ {
final ExpressionScope scope;
ast.VariableDeclaration cascadeReceiver;
ExpressionBuilder(this.scope);
ast.Expression build(Expression node) {
var result = node.accept(this);
if (result is Accessor) {
result = result.buildSimpleRead();
}
// For some method invocations we have already set a file offset to
// override the default behavior of _getOffset.
if (node is! MethodInvocation || result.fileOffset < 0) {
result.fileOffset = _getOffset(node);
}
return result;
}
int _getOffset(AstNode node) {
if (node is MethodInvocation) {
return node.methodName.offset;
} else if (node is InstanceCreationExpression) {
return node.constructorName.offset;
} else if (node is BinaryExpression) {
return node.operator.offset;
} else if (node is PrefixedIdentifier) {
return node.identifier.offset;
} else if (node is AssignmentExpression) {
return _getOffset(node.leftHandSide);
} else if (node is PropertyAccess) {
return node.propertyName.offset;
} else if (node is IsExpression) {
return node.isOperator.offset;
} else if (node is AsExpression) {
return node.asOperator.offset;
} else if (node is StringLiteral) {
// Use a catch-all for StringInterpolation and AdjacentStrings:
// the debugger stops at the end.
return node.end;
} else if (node is IndexExpression) {
return node.leftBracket.offset;
}
return node.offset;
}
Accessor buildLeftHandValue(Expression node) {
var result = node.accept(this);
if (result is Accessor) {
return result;
} else {
return new ReadOnlyAccessor(result, ast.TreeNode.noOffset);
}
}
ast.Expression visitAsExpression(AsExpression node) {
return new ast.AsExpression(
build(node.expression), scope.buildTypeAnnotation(node.type));
}
ast.Expression visitAssignmentExpression(AssignmentExpression node) {
bool voidContext = isInVoidContext(node);
String operator = node.operator.value();
var leftHand = buildLeftHandValue(node.leftHandSide);
var rightHand = build(node.rightHandSide);
if (operator == '=') {
return leftHand.buildAssignment(rightHand, voidContext: voidContext);
} else if (operator == '??=') {
return leftHand.buildNullAwareAssignment(
rightHand, scope.buildType(node.staticType),
voidContext: voidContext);
} else {
// Cut off the trailing '='.
var name = new ast.Name(operator.substring(0, operator.length - 1));
return leftHand.buildCompoundAssignment(name, rightHand,
offset: node.offset,
voidContext: voidContext,
interfaceTarget: scope.resolveInterfaceMethod(node.staticElement));
}
}
ast.Expression visitAwaitExpression(AwaitExpression node) {
return new ast.AwaitExpression(build(node.expression));
}
ast.Arguments buildSingleArgument(Expression node) {
return new ast.Arguments(<ast.Expression>[build(node)]);
}
ast.Expression visitBinaryExpression(BinaryExpression node) {
String operator = node.operator.value();
if (operator == '&&' || operator == '||') {
return new ast.LogicalExpression(
build(node.leftOperand), operator, build(node.rightOperand));
}
if (operator == '??') {
ast.Expression leftOperand = build(node.leftOperand);
if (leftOperand is ast.VariableGet) {
return new ast.ConditionalExpression(
buildIsNull(leftOperand, offset: node.leftOperand.offset),
build(node.rightOperand),
new ast.VariableGet(leftOperand.variable),
scope.getInferredType(node));
} else {
var variable = new ast.VariableDeclaration.forValue(leftOperand);
return new ast.Let(
variable,
new ast.ConditionalExpression(
buildIsNull(new ast.VariableGet(variable),
offset: leftOperand.fileOffset),
build(node.rightOperand),
new ast.VariableGet(variable),
scope.getInferredType(node)));
}
}
bool isNegated = false;
if (operator == '!=') {
isNegated = true;
operator = '==';
}
ast.Expression expression;
if (node.leftOperand is SuperExpression) {
scope.addTransformerFlag(TransformerFlag.superCalls);
expression = new ast.SuperMethodInvocation(
new ast.Name(operator),
buildSingleArgument(node.rightOperand),
scope.resolveConcreteMethod(node.staticElement));
} else {
expression = new ast.MethodInvocation(
build(node.leftOperand),
new ast.Name(operator),
buildSingleArgument(node.rightOperand),
scope.resolveInterfaceMethod(node.staticElement));
}
return isNegated ? new ast.Not(expression) : expression;
}
ast.Expression visitBooleanLiteral(BooleanLiteral node) {
return new ast.BoolLiteral(node.value);
}
ast.Expression visitDoubleLiteral(DoubleLiteral node) {
return new ast.DoubleLiteral(node.value);
}
ast.Expression visitIntegerLiteral(IntegerLiteral node) {
return new ast.IntLiteral(node.value);
}
ast.Expression visitNullLiteral(NullLiteral node) {
return new ast.NullLiteral();
}
ast.Expression visitSimpleStringLiteral(SimpleStringLiteral node) {
return new ast.StringLiteral(node.value);
}
ast.Expression visitStringLiteral(StringLiteral node) {
return scope.buildStringLiteral(node);
}
static Object _getTokenValue(Token token) {
return token.value();
}
ast.Expression visitSymbolLiteral(SymbolLiteral node) {
String value = node.components.map(_getTokenValue).join('.');
return new ast.SymbolLiteral(value);
}
ast.Expression visitCascadeExpression(CascadeExpression node) {
var receiver = build(node.target);
// If receiver is a variable it would be tempting to reuse it, but it
// might be reassigned in one of the cascade sections.
var receiverVariable = new ast.VariableDeclaration.forValue(receiver,
type: scope.getInferredType(node.target));
var oldReceiver = this.cascadeReceiver;
cascadeReceiver = receiverVariable;
ast.Expression result = new ast.VariableGet(receiverVariable);
for (var section in node.cascadeSections.reversed) {
var dummy = new ast.VariableDeclaration.forValue(build(section));
result = new ast.Let(dummy, result);
}
cascadeReceiver = oldReceiver;
return new ast.Let(receiverVariable, result);
}
ast.Expression makeCascadeReceiver() {
assert(cascadeReceiver != null);
return new ast.VariableGet(cascadeReceiver);
}
ast.Expression visitConditionalExpression(ConditionalExpression node) {
return new ast.ConditionalExpression(
build(node.condition),
build(node.thenExpression),
build(node.elseExpression),
scope.getInferredType(node));
}
ast.Expression visitFunctionExpression(FunctionExpression node) {
return new ast.FunctionExpression(scope.buildFunctionNode(
node.parameters, node.body,
typeParameters: scope.buildOptionalTypeParameterList(
node.typeParameters,
strongModeOnly: true),
inferredReturnType: scope.getInferredReturnType(node)));
}
ast.Arguments buildArguments(ArgumentList valueArguments,
{TypeArgumentList explicitTypeArguments,
List<ast.DartType> inferTypeArguments()}) {
var positional = <ast.Expression>[];
var named = <ast.NamedExpression>[];
for (var argument in valueArguments.arguments) {
if (argument is NamedExpression) {
named.add(new ast.NamedExpression(
argument.name.label.name, build(argument.expression)));
} else if (named.isNotEmpty) {
return scope.emitCompileTimeError(
ParserErrorCode.POSITIONAL_AFTER_NAMED_ARGUMENT);
} else {
positional.add(build(argument));
}
}
List<ast.DartType> typeArguments;
if (explicitTypeArguments != null) {
typeArguments = scope.buildTypeArgumentList(explicitTypeArguments);
} else if (inferTypeArguments != null) {
typeArguments = inferTypeArguments();
}
return new ast.Arguments(positional, named: named, types: typeArguments);
}
ast.Arguments buildArgumentsForInvocation(InvocationExpression node) {
if (scope.strongMode) {
return buildArguments(node.argumentList,
explicitTypeArguments: node.typeArguments,
inferTypeArguments: () =>
scope.getInferredInvocationTypeArguments(node));
} else {
return buildArguments(node.argumentList);
}
}
static final ast.Name callName = new ast.Name('call');
ast.Expression visitFunctionExpressionInvocation(
FunctionExpressionInvocation node) {
return new ast.MethodInvocation(
build(node.function),
callName,
buildArgumentsForInvocation(node),
scope.resolveInterfaceFunctionCallOnType(node.function.staticType));
}
visitPrefixedIdentifier(PrefixedIdentifier node) {
switch (ElementKind.of(node.prefix.staticElement)) {
case ElementKind.CLASS:
case ElementKind.LIBRARY:
case ElementKind.PREFIX:
case ElementKind.IMPORT:
if (node.identifier.staticElement != null) {
// Should be resolved to a static access.
// Do not invoke 'build', because the identifier should be seen as a
// left-hand value or an expression depending on the context.
return visitSimpleIdentifier(node.identifier);
}
// Unresolved access on a class or library.
return scope.unresolvedAccess(node.identifier.name);
case ElementKind.DYNAMIC:
case ElementKind.FUNCTION_TYPE_ALIAS:
case ElementKind.TYPE_PARAMETER:
// TODO: Check with the spec to see exactly when a type literal can be
// used in a property access without surrounding parentheses.
// For now, just fall through to the property access case.
case ElementKind.FIELD:
case ElementKind.TOP_LEVEL_VARIABLE:
case ElementKind.FUNCTION:
case ElementKind.METHOD:
case ElementKind.GETTER:
case ElementKind.SETTER:
case ElementKind.LOCAL_VARIABLE:
case ElementKind.PARAMETER:
case ElementKind.ERROR:
Element element = node.identifier.staticElement;
Element auxiliary = node.identifier.auxiliaryElements?.staticElement;
return PropertyAccessor.make(
build(node.prefix),
scope.buildName(node.identifier),
scope.resolveInterfaceGet(element, auxiliary),
scope.resolveInterfaceSet(element, auxiliary));
case ElementKind.UNIVERSE:
case ElementKind.NAME:
case ElementKind.CONSTRUCTOR:
case ElementKind.EXPORT:
case ElementKind.LABEL:
default:
return scope.internalError(
'Unexpected element kind: ${node.prefix.staticElement}');
}
}
bool isStatic(Element element) {
if (element is ClassMemberElement) {
return element.isStatic || element.enclosingElement == null;
}
if (element is PropertyAccessorElement) {
return element.isStatic || element.enclosingElement == null;
}
if (element is FunctionElement) {
return element.isStatic;
}
return false;
}
visitSimpleIdentifier(SimpleIdentifier node) {
Element element = node.staticElement;
switch (ElementKind.of(element)) {
case ElementKind.CLASS:
case ElementKind.DYNAMIC:
case ElementKind.FUNCTION_TYPE_ALIAS:
case ElementKind.TYPE_PARAMETER:
return new ast.TypeLiteral(scope.buildTypeAnnotation(node));
case ElementKind.ERROR: // This covers the case where nothing was found.
if (!scope.allowThis) {
return scope.unresolvedAccess(node.name);
}
return PropertyAccessor.make(
scope.buildThis(), scope.buildName(node), null, null);
case ElementKind.FIELD:
case ElementKind.TOP_LEVEL_VARIABLE:
case ElementKind.GETTER:
case ElementKind.SETTER:
case ElementKind.METHOD:
Element auxiliary = node.auxiliaryElements?.staticElement;
if (isStatic(element)) {
return scope.staticAccess(node.name, element, auxiliary);
}
if (!scope.allowThis) {
return scope.unresolvedAccess(node.name);
}
return PropertyAccessor.make(
scope.buildThis(),
scope.buildName(node),
scope.resolveInterfaceGet(element, auxiliary),
scope.resolveInterfaceSet(element, auxiliary));
case ElementKind.FUNCTION:
FunctionElement function = element;
if (isTopLevelFunction(function)) {
return scope.staticAccess(node.name, function);
}
if (function == function.library.loadLibraryFunction) {
return scope.unsupportedFeature('Deferred loading');
}
return new VariableAccessor(
scope.getVariableReference(function), null, ast.TreeNode.noOffset);
case ElementKind.LOCAL_VARIABLE:
case ElementKind.PARAMETER:
VariableElement variable = element;
var type = identical(node.staticType, variable.type)
? null
: scope.buildType(node.staticType);
return new VariableAccessor(
scope.getVariableReference(element), type, ast.TreeNode.noOffset);
case ElementKind.IMPORT:
case ElementKind.LIBRARY:
case ElementKind.PREFIX:
return scope.emitCompileTimeError(
CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT,
[node.name]);
case ElementKind.COMPILATION_UNIT:
case ElementKind.CONSTRUCTOR:
case ElementKind.EXPORT:
case ElementKind.LABEL:
case ElementKind.UNIVERSE:
case ElementKind.NAME:
default:
return scope.internalError('Unexpected element kind: $element');
}
}
visitIndexExpression(IndexExpression node) {
Element element = node.staticElement;
Element auxiliary = node.auxiliaryElements?.staticElement;
if (node.isCascaded) {
return IndexAccessor.make(
makeCascadeReceiver(),
build(node.index),
scope.resolveInterfaceIndexGet(element, auxiliary),
scope.resolveInterfaceIndexSet(element, auxiliary));
} else if (node.target is SuperExpression) {
scope.addTransformerFlag(TransformerFlag.superCalls);
return new SuperIndexAccessor(
build(node.index),
scope.resolveConcreteIndexGet(element, auxiliary),
scope.resolveConcreteIndexSet(element, auxiliary),
ast.TreeNode.noOffset);
} else {
return IndexAccessor.make(
build(node.target),
build(node.index),
scope.resolveInterfaceIndexGet(element, auxiliary),
scope.resolveInterfaceIndexSet(element, auxiliary));
}
}
/// Follows any number of redirecting factories, returning the effective
/// target or `null` if a cycle is found.
///
/// The returned element is a [Member] if the type arguments to the effective
/// target are different from the original arguments.
ConstructorElement getEffectiveFactoryTarget(ConstructorElement element) {
ConstructorElement anchor = null;
int n = 1;
while (element.isFactory && element.redirectedConstructor != null) {
element = element.redirectedConstructor;
var base = ReferenceScope.getBaseElement(element);
if (base == anchor) return null; // Cyclic redirection.
if (n & ++n == 0) {
anchor = base;
}
}
return element;
}
/// Forces the list of type arguments to have the specified length. If the
/// length was changed, all type arguments are changed to `dynamic`.
void _coerceTypeArgumentArity(List<ast.DartType> typeArguments, int arity) {
if (typeArguments.length != arity) {
typeArguments.length = arity;
typeArguments.fillRange(0, arity, const ast.DynamicType());
}
}
ast.Expression visitInstanceCreationExpression(
InstanceCreationExpression node) {
ConstructorElement element = node.staticElement;
ClassElement classElement = element?.enclosingElement;
List<ast.DartType> inferTypeArguments() {
var inferredType = scope.getInferredType(node);
if (inferredType is ast.InterfaceType) {
return inferredType.typeArguments.toList();
}
int numberOfTypeArguments =
classElement == null ? 0 : classElement.typeParameters.length;
return new List<ast.DartType>.filled(
numberOfTypeArguments, const ast.DynamicType(),
growable: true);
}
var arguments = buildArguments(node.argumentList,
explicitTypeArguments: node.constructorName.type.typeArguments,
inferTypeArguments: inferTypeArguments);
ast.Expression noSuchMethodError() {
return node.isConst
? scope.emitInvalidConstant()
: scope.buildThrowNoSuchMethodError(
new ast.NullLiteral(), '${node.constructorName}', arguments,
candidateTarget: element);
}
if (element == null) {
return noSuchMethodError();
}
assert(classElement != null);
var redirect = getEffectiveFactoryTarget(element);
if (redirect == null) {
return scope.buildThrowCompileTimeError(
CompileTimeErrorCode.RECURSIVE_FACTORY_REDIRECT.message);
}
if (redirect != element) {
ast.InterfaceType returnType = scope.buildType(redirect.returnType);
arguments.types
..clear()
..addAll(returnType.typeArguments);
element = redirect;
classElement = element.enclosingElement;
}
element = ReferenceScope.getBaseElement(element);
if (node.isConst && !element.isConst) {
return scope
.emitInvalidConstant(CompileTimeErrorCode.CONST_WITH_NON_CONST);
}
if (classElement.isEnum) {
return scope.emitCompileTimeError(CompileTimeErrorCode.INSTANTIATE_ENUM);
}
_coerceTypeArgumentArity(
arguments.types, classElement.typeParameters.length);
if (element.isFactory) {
ast.Member target = scope.resolveConcreteMethod(element);
if (target is ast.Procedure &&
scope.areArgumentsCompatible(element, arguments)) {
return new ast.StaticInvocation(target, arguments,
isConst: node.isConst);
} else {
return noSuchMethodError();
}
}
if (classElement.isAbstract) {
return node.isConst
? scope.emitInvalidConstant()
: scope.buildThrowAbstractClassInstantiationError(classElement.name);
}
ast.Constructor constructor = scope.resolveConstructor(element);
if (constructor != null &&
scope.areArgumentsCompatible(element, arguments)) {
return new ast.ConstructorInvocation(constructor, arguments,
isConst: node.isConst);
} else {
return noSuchMethodError();
}
}
ast.Expression visitIsExpression(IsExpression node) {
if (node.notOperator != null) {
// Put offset on the IsExpression for "is!" cases:
// As it is wrapped in a not, it won't get an offset otherwise.
return new ast.Not(new ast.IsExpression(
build(node.expression), scope.buildTypeAnnotation(node.type))
..fileOffset = _getOffset(node));
} else {
return new ast.IsExpression(
build(node.expression), scope.buildTypeAnnotation(node.type));
}
}
/// Emit a method invocation, either as a direct call `o.f(x)` or decomposed
/// into a getter and function invocation `o.f.call(x)`.
ast.Expression buildDecomposableMethodInvocation(ast.Expression receiver,
ast.Name name, ast.Arguments arguments, Element targetElement) {
// Try to emit a typed call to an interface method.
ast.Procedure targetMethod = scope.resolveInterfaceMethod(targetElement);
if (targetMethod != null) {
return new ast.MethodInvocation(receiver, name, arguments, targetMethod);
}
// Try to emit a typed call to getter or field and call the returned
// function.
ast.Member targetGetter = scope.resolveInterfaceGet(targetElement, null);
if (targetGetter != null) {
return new ast.MethodInvocation(
new ast.PropertyGet(receiver, name, targetGetter),
callName,
arguments,
scope.resolveInterfaceFunctionCall(targetElement));
}
// Emit a dynamic call.
return new ast.MethodInvocation(receiver, name, arguments);
}
ast.Expression visitMethodInvocation(MethodInvocation node) {
Element element = node.methodName.staticElement;
if (element != null && element == element.library?.loadLibraryFunction) {
return scope.unsupportedFeature('Deferred loading');
}
var target = node.target;
if (node.isCascaded) {
return buildDecomposableMethodInvocation(
makeCascadeReceiver(),
scope.buildName(node.methodName),
buildArgumentsForInvocation(node),
element);
} else if (target is SuperExpression) {
scope.addTransformerFlag(TransformerFlag.superCalls);
return new ast.SuperMethodInvocation(
scope.buildName(node.methodName),
buildArgumentsForInvocation(node),
scope.resolveConcreteMethod(element));
} else if (isLocal(element)) {
// Set the offset directly: Normally the offset is at the start of the
// method, but in this case, because we insert a '.call', we want it at
// the end instead.
return new ast.MethodInvocation(
new ast.VariableGet(scope.getVariableReference(element)),
callName,
buildArgumentsForInvocation(node),
scope.resolveInterfaceFunctionCall(element))
..fileOffset = node.methodName.end;
} else if (isStaticMethod(element)) {
var method = scope.resolveConcreteMethod(element);
var arguments = buildArgumentsForInvocation(node);
if (method == null || !scope.areArgumentsCompatible(element, arguments)) {
return scope.buildThrowNoSuchMethodError(
new ast.NullLiteral(), node.methodName.name, arguments,
candidateTarget: element);
}
return new ast.StaticInvocation(method, arguments);
} else if (isStaticVariableOrGetter(element)) {
var method = scope.resolveConcreteGet(element, null);
if (method == null) {
return scope.buildThrowNoSuchMethodError(
new ast.NullLiteral(), node.methodName.name, new ast.Arguments([]),
candidateTarget: element);
}
// Set the offset directly: Normally the offset is at the start of the
// method, but in this case, because we insert a '.call', we want it at
// the end instead.
return new ast.MethodInvocation(
new ast.StaticGet(method),
callName,
buildArgumentsForInvocation(node),
scope.resolveInterfaceFunctionCall(element))
..fileOffset = node.methodName.end;
} else if (target == null && !scope.allowThis ||
target is Identifier && target.staticElement is ClassElement ||
target is Identifier && target.staticElement is PrefixElement) {
return scope.buildThrowNoSuchMethodError(new ast.NullLiteral(),
node.methodName.name, buildArgumentsForInvocation(node),
candidateTarget: element);
} else if (target == null) {
return buildDecomposableMethodInvocation(
scope.buildThis(),
scope.buildName(node.methodName),
buildArgumentsForInvocation(node),
element);
} else if (node.operator.value() == '?.') {
var receiver = makeOrReuseVariable(build(target));
return makeLet(
receiver,
new ast.ConditionalExpression(
buildIsNull(new ast.VariableGet(receiver)),
new ast.NullLiteral(),
buildDecomposableMethodInvocation(
new ast.VariableGet(receiver),
scope.buildName(node.methodName),
buildArgumentsForInvocation(node),
element)
..fileOffset = node.methodName.offset,
scope.buildType(node.staticType)));
} else {
return buildDecomposableMethodInvocation(
build(node.target),
scope.buildName(node.methodName),
buildArgumentsForInvocation(node),
element);
}
}
ast.Expression visitNamedExpression(NamedExpression node) {
return scope.internalError('Unexpected named expression');
}
ast.Expression visitParenthesizedExpression(ParenthesizedExpression node) {
return build(node.expression);
}
bool isInVoidContext(Expression node) {
AstNode parent = node.parent;
return parent is ForStatement &&
(parent.updaters.contains(node) || parent.initialization == node) ||
parent is ExpressionStatement ||
parent is ExpressionFunctionBody && scope.bodyHasVoidReturn(parent);
}
ast.Expression visitPostfixExpression(PostfixExpression node) {
String operator = node.operator.value();
switch (operator) {
case '++':
case '--':
var leftHand = buildLeftHandValue(node.operand);
var binaryOperator = new ast.Name(operator[0]);
return leftHand.buildPostfixIncrement(binaryOperator,
offset: node.operator.offset,
voidContext: isInVoidContext(node),
interfaceTarget: scope.resolveInterfaceMethod(node.staticElement));
default:
return scope.internalError('Invalid postfix operator $operator');
}
}
ast.Expression visitPrefixExpression(PrefixExpression node) {
String operator = node.operator.value();
switch (operator) {
case '-':
case '~':
var name = new ast.Name(operator == '-' ? 'unary-' : '~');
if (node.operand is SuperExpression) {
scope.addTransformerFlag(TransformerFlag.superCalls);
return new ast.SuperMethodInvocation(name, new ast.Arguments.empty(),
scope.resolveConcreteMethod(node.staticElement));
}
return new ast.MethodInvocation(
build(node.operand),
name,
new ast.Arguments.empty(),
scope.resolveInterfaceMethod(node.staticElement));
case '!':
return new ast.Not(build(node.operand));
case '++':
case '--':
var leftHand = buildLeftHandValue(node.operand);
var binaryOperator = new ast.Name(operator[0]);
return leftHand.buildPrefixIncrement(binaryOperator,
offset: node.offset,
interfaceTarget: scope.resolveInterfaceMethod(node.staticElement));
default:
return scope.internalError('Invalid prefix operator $operator');
}
}
visitPropertyAccess(PropertyAccess node) {
Element element = node.propertyName.staticElement;
Element auxiliary = node.propertyName.auxiliaryElements?.staticElement;
var getter = scope.resolveInterfaceGet(element, auxiliary);
var setter = scope.resolveInterfaceSet(element, auxiliary);
Expression target = node.target;
if (node.isCascaded) {
return PropertyAccessor.make(makeCascadeReceiver(),
scope.buildName(node.propertyName), getter, setter);
} else if (node.target is SuperExpression) {
scope.addTransformerFlag(TransformerFlag.superCalls);
return new SuperPropertyAccessor(
scope.buildName(node.propertyName),
scope.resolveConcreteGet(element, auxiliary),
scope.resolveConcreteSet(element, auxiliary),
ast.TreeNode.noOffset);
} else if (target is Identifier && target.staticElement is ClassElement) {
// Note that this case also covers null-aware static access on a class,
// which is equivalent to a regular static access.
return scope.staticAccess(node.propertyName.name, element, auxiliary);
} else if (node.operator.value() == '?.') {
return new NullAwarePropertyAccessor(
build(target),
scope.buildName(node.propertyName),
getter,
setter,
scope.buildType(node.staticType),
ast.TreeNode.noOffset);
} else {
return PropertyAccessor.make(
build(target), scope.buildName(node.propertyName), getter, setter);
}
}
ast.Expression visitRethrowExpression(RethrowExpression node) {
return new ast.Rethrow();
}
ast.Expression visitSuperExpression(SuperExpression node) {
return scope
.emitCompileTimeError(CompileTimeErrorCode.SUPER_IN_INVALID_CONTEXT);
}
ast.Expression visitThisExpression(ThisExpression node) {
return scope.buildThis();
}
ast.Expression visitThrowExpression(ThrowExpression node) {
return new ast.Throw(build(node.expression));
}
ast.Expression visitListLiteral(ListLiteral node) {
ast.DartType type = node.typeArguments?.arguments?.isNotEmpty ?? false
? scope.buildTypeAnnotation(node.typeArguments.arguments[0])
: scope.getInferredTypeArgument(node, 0);
return new ast.ListLiteral(node.elements.map(build).toList(),
typeArgument: type, isConst: node.constKeyword != null);
}
ast.Expression visitMapLiteral(MapLiteral node) {
ast.DartType key, value;
if (node.typeArguments != null && node.typeArguments.arguments.length > 1) {
key = scope.buildTypeAnnotation(node.typeArguments.arguments[0]);
value = scope.buildTypeAnnotation(node.typeArguments.arguments[1]);
} else {
key = scope.getInferredTypeArgument(node, 0);
value = scope.getInferredTypeArgument(node, 1);
}
return new ast.MapLiteral(node.entries.map(buildMapEntry).toList(),
keyType: key, valueType: value, isConst: node.constKeyword != null);
}
ast.MapEntry buildMapEntry(MapLiteralEntry node) {
return new ast.MapEntry(build(node.key), build(node.value));
}
ast.Expression visitExpression(Expression node) {
return scope.internalError('Unhandled expression ${node.runtimeType}');
}
}
class StringLiteralPartBuilder extends GeneralizingAstVisitor<Null> {
final ExpressionScope scope;
final List<ast.Expression> output;
StringLiteralPartBuilder(this.scope, this.output);
void build(Expression node) {
node.accept(this);
}
void buildInterpolationElement(InterpolationElement node) {
node.accept(this);
}
visitSimpleStringLiteral(SimpleStringLiteral node) {
output.add(new ast.StringLiteral(node.value));
}
visitAdjacentStrings(AdjacentStrings node) {
node.strings.forEach(build);
}
visitStringInterpolation(StringInterpolation node) {
node.elements.forEach(buildInterpolationElement);
}
visitInterpolationString(InterpolationString node) {
output.add(new ast.StringLiteral(node.value));
}
visitInterpolationExpression(InterpolationExpression node) {
output.add(scope.buildExpression(node.expression));
}
}
class TypeAnnotationBuilder extends GeneralizingAstVisitor<ast.DartType> {
final TypeScope scope;
TypeAnnotationBuilder(this.scope);
ast.DartType build(AstNode node) {
return node.accept(this);
}
List<ast.DartType> buildList(Iterable<AstNode> node) {
return node.map(build).toList();
}
/// Replace unbound type variables in [type] with 'dynamic' and convert
/// to an [ast.DartType].
ast.DartType buildClosedTypeFromDartType(DartType type) {
return convertType(type, <TypeParameterElement>[]);
}
/// Convert to an [ast.DartType] and keep type variables.
ast.DartType buildFromDartType(DartType type) {
return convertType(type, null);
}
/// True if [parameter] should not be reified, because spec mode does not
/// currently reify generic method type parameters.
bool isUnreifiedTypeParameter(TypeParameterElement parameter) {
return !scope.strongMode && parameter.enclosingElement is! ClassElement;
}
/// Converts [type] to an [ast.DartType], while replacing unbound type
/// variables with 'dynamic'.
///
/// If [boundVariables] is null, no type variables are replaced, otherwise all
/// type variables except those in [boundVariables] are replaced. In other
/// words, it represents the bound variables, or "all variables" if omitted.
ast.DartType convertType(
DartType type, List<TypeParameterElement> boundVariables) {
if (type is TypeParameterType) {
if (isUnreifiedTypeParameter(type.element)) {
return const ast.DynamicType();
}
if (boundVariables == null || boundVariables.contains(type)) {
var typeParameter = scope.tryGetTypeParameterReference(type.element);
if (typeParameter == null) {
// The analyzer sometimes gives us a type parameter that was not
// bound anywhere. Make sure we do not emit a dangling reference.
if (type.element.bound != null) {
return convertType(type.element.bound, []);
}
return const ast.DynamicType();
}
if (!scope.allowClassTypeParameters &&
typeParameter.parent is ast.Class) {
return const ast.InvalidType();
}
return new ast.TypeParameterType(typeParameter);
} else {
return const ast.DynamicType();
}
} else if (type is InterfaceType) {
var classNode = scope.getClassReference(type.element);
if (type.typeArguments.length == 0) {
return classNode.rawType;
}
if (type.typeArguments.length != classNode.typeParameters.length) {
log.warning('Type parameter arity error in $type');
return const ast.InvalidType();
}
return new ast.InterfaceType(
classNode, convertTypeList(type.typeArguments, boundVariables));
} else if (type is FunctionType) {
// TODO: Avoid infinite recursion in case of illegal circular typedef.
boundVariables?.addAll(type.typeParameters);
var positionals =
concatenate(type.normalParameterTypes, type.optionalParameterTypes);
var result = new ast.FunctionType(
convertTypeList(positionals, boundVariables),
convertType(type.returnType, boundVariables),
typeParameters:
convertTypeParameterList(type.typeFormals, boundVariables),
namedParameters:
convertTypeMap(type.namedParameterTypes, boundVariables),
requiredParameterCount: type.normalParameterTypes.length);
boundVariables?.removeRange(
boundVariables.length - type.typeParameters.length,
boundVariables.length);
return result;
} else if (type.isUndefined) {
log.warning('Unresolved type found in ${scope.location}');
return const ast.InvalidType();
} else if (type.isVoid) {
return const ast.VoidType();
} else if (type.isDynamic) {
return const ast.DynamicType();
} else {
log.severe('Unexpected DartType: $type');
return const ast.InvalidType();
}
}
static Iterable<E> concatenate<E>(Iterable<E> x, Iterable<E> y) =>
<Iterable<E>>[x, y].expand((z) => z);
ast.TypeParameter convertTypeParameter(TypeParameterElement typeParameter,
List<TypeParameterElement> boundVariables) {
return scope.makeTypeParameter(typeParameter,
bound: typeParameter.bound == null
? scope.defaultTypeParameterBound
: convertType(typeParameter.bound, boundVariables));
}
List<ast.TypeParameter> convertTypeParameterList(
Iterable<TypeParameterElement> typeParameters,
List<TypeParameterElement> boundVariables) {
if (typeParameters.isEmpty) return const <ast.TypeParameter>[];
return typeParameters
.map((tp) => convertTypeParameter(tp, boundVariables))
.toList();
}
List<ast.DartType> convertTypeList(
Iterable<DartType> types, List<TypeParameterElement> boundVariables) {
if (types.isEmpty) return const <ast.DartType>[];
return types.map((t) => convertType(t, boundVariables)).toList();
}
List<ast.NamedType> convertTypeMap(
Map<String, DartType> types, List<TypeParameterElement> boundVariables) {
if (types.isEmpty) return const <ast.NamedType>[];
List<ast.NamedType> result = <ast.NamedType>[];
types.forEach((name, type) {
result.add(new ast.NamedType(name, convertType(type, boundVariables)));
});
sortAndRemoveDuplicates(result);
return result;
}
ast.DartType visitSimpleIdentifier(SimpleIdentifier node) {
Element element = node.staticElement;
switch (ElementKind.of(element)) {
case ElementKind.CLASS:
return scope.getClassReference(element).rawType;
case ElementKind.DYNAMIC:
return const ast.DynamicType();
case ElementKind.FUNCTION_TYPE_ALIAS:
FunctionTypeAliasElement functionType = element;
return buildClosedTypeFromDartType(functionType.type);
case ElementKind.TYPE_PARAMETER:
var typeParameter = scope.getTypeParameterReference(element);
if (!scope.allowClassTypeParameters &&
typeParameter.parent is ast.Class) {
return const ast.InvalidType();
}
if (isUnreifiedTypeParameter(element)) {
return const ast.DynamicType();
}
return new ast.TypeParameterType(typeParameter);
case ElementKind.COMPILATION_UNIT:
case ElementKind.CONSTRUCTOR:
case ElementKind.EXPORT:
case ElementKind.IMPORT:
case ElementKind.LABEL:
case ElementKind.LIBRARY:
case ElementKind.PREFIX:
case ElementKind.UNIVERSE:
case ElementKind.ERROR: // This covers the case where nothing was found.
case ElementKind.FIELD:
case ElementKind.TOP_LEVEL_VARIABLE:
case ElementKind.GETTER:
case ElementKind.SETTER:
case ElementKind.METHOD:
case ElementKind.LOCAL_VARIABLE:
case ElementKind.PARAMETER:
case ElementKind.FUNCTION:
case ElementKind.NAME:
default:
log.severe('Invalid type annotation: $element');
return const ast.InvalidType();
}
}
visitPrefixedIdentifier(PrefixedIdentifier node) {
return build(node.identifier);
}
visitTypeName(TypeName node) {
return buildFromDartType(node.type);
}
visitNode(AstNode node) {
log.severe('Unexpected type annotation: $node');
return new ast.InvalidType();
}
}
class InitializerBuilder extends GeneralizingAstVisitor<ast.Initializer> {
final MemberScope scope;
InitializerBuilder(this.scope);
ast.Initializer build(ConstructorInitializer node) {
return node.accept(this);
}
visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
var target = scope.resolveField(node.fieldName.staticElement);
if (target == null) {
return new ast.InvalidInitializer();
}
return new ast.FieldInitializer(
target, scope.buildExpression(node.expression));
}
visitSuperConstructorInvocation(SuperConstructorInvocation node) {
var target = scope.resolveConstructor(node.staticElement);
if (target == null) {
return new ast.InvalidInitializer();
}
scope.addTransformerFlag(TransformerFlag.superCalls);
return new ast.SuperInitializer(
target, scope._expressionBuilder.buildArguments(node.argumentList));
}
visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) {
var target = scope.resolveConstructor(node.staticElement);
if (target == null) {
return new ast.InvalidInitializer();
}
return new ast.RedirectingInitializer(
target, scope._expressionBuilder.buildArguments(node.argumentList));
}
visitNode(AstNode node) {
log.severe('Unexpected constructor initializer: ${node.runtimeType}');
return new ast.InvalidInitializer();
}
}
/// A utility to build a function with a static error in its body.
ast.FunctionNode _brokenFunction() {
return new ast.FunctionNode(
new ast.ExpressionStatement(new ast.InvalidExpression(null)));
}
/// Brings a class from hierarchy level to body level.
//
// TODO(asgerf): Error recovery during class construction is currently handled
// locally, but this can in theory break global invariants in the kernel IR.
// To safely compile code with compile-time errors, we may need a recovery
// pass to enforce all kernel invariants before it is given to the backend.
class ClassBodyBuilder extends GeneralizingAstVisitor<Null> {
final ClassScope scope;
final ExpressionScope annotationScope;
final ast.Class currentClass;
final ClassElement element;
ast.Library get currentLibrary => currentClass.enclosingLibrary;
ClassBodyBuilder(
ReferenceLevelLoader loader, ast.Class currentClass, this.element)
: this.currentClass = currentClass,
scope = new ClassScope(loader, currentClass.enclosingLibrary),
annotationScope =
new ExpressionScope(loader, currentClass.enclosingLibrary);
void build(CompilationUnitMember node) {
if (node == null) {
buildBrokenClass();
return;
}
node.accept(this);
}
/// Builds an empty class for broken classes that have no AST.
///
/// This should only be used to recover from a compile-time error.
void buildBrokenClass() {
currentClass.name = element.name;
currentClass.supertype = scope.getRootClassReference().asRawSupertype;
currentClass.constructors.add(new ast.Constructor(_brokenFunction())
..fileOffset = element.nameOffset);
}
void addAnnotations(List<Annotation> annotations) {
// Class type parameters are not in scope in the annotation list.
for (var annotation in annotations) {
currentClass.addAnnotation(annotationScope.buildAnnotation(annotation));
}
}
void _buildMemberBody(ast.Member member, Element element, AstNode node) {
new MemberBodyBuilder(scope.loader, member, element).build(node);
}
/// True if the given class member should not be emitted, and does not
/// correspond to any Kernel member.
///
/// This is true for redirecting factories with a resolved target. These are
/// always bypassed at the call site.
bool _isIgnoredMember(ClassMember node) {
if (node is ConstructorDeclaration && node.factoryKeyword != null) {
var element = resolutionMap.elementDeclaredByConstructorDeclaration(node);
return element.redirectedConstructor != null &&
(element.isSynthetic || scope.loader.ignoreRedirectingFactories);
} else {
return false;
}
}
visitClassDeclaration(ClassDeclaration node) {
addAnnotations(node.metadata);
ast.Class classNode = currentClass;
assert(classNode.members.isEmpty); // All members will be added here.
bool foundConstructor = false;
for (var member in node.members) {
if (_isIgnoredMember(member)) continue;
if (member is FieldDeclaration) {
for (var variable in member.fields.variables) {
// Ignore fields inserted through error recovery.
if (variable.isSynthetic || variable.length == 0) continue;
var field = scope.getMemberReference(variable.element);
classNode.addMember(field);
_buildMemberBody(field, variable.element, variable);
}
} else {
var memberNode = scope.getMemberReference(member.element);
classNode.addMember(memberNode);
_buildMemberBody(memberNode, member.element, member);
if (member is ConstructorDeclaration) {
foundConstructor = true;
}
}
}
if (!foundConstructor) {
var defaultConstructor = scope.findDefaultConstructor(node.element);
if (defaultConstructor != null) {
assert(defaultConstructor.enclosingElement == node.element);
if (!defaultConstructor.isSynthetic) {
throw 'Non-synthetic default constructor not in list of members. '
'$node $element $defaultConstructor';
}
var memberNode = scope.getMemberReference(defaultConstructor);
classNode.addMember(memberNode);
buildDefaultConstructor(memberNode, defaultConstructor);
}
}
addDefaultInstanceFieldInitializers(classNode);
}
void buildDefaultConstructor(
ast.Constructor constructor, ConstructorElement element) {
var function = constructor.function;
function.body = new ast.EmptyStatement()..parent = function;
var class_ = element.enclosingElement;
if (class_.supertype != null) {
// DESIGN TODO: If the super class is a mixin application, we will link to
// a constructor not in the immediate super class. This is a problem due
// to the fact that mixed-in fields come with initializers which need to
// be executed by a constructor. The mixin transformer takes care of
// this by making forwarding constructors and the super initializers will
// be rewritten to use them (see `transformations/mixin_full_resolution`).
var superConstructor =
scope.findDefaultConstructor(class_.supertype.element);
var target = scope.resolveConstructor(superConstructor);
if (target == null) {
constructor.initializers
.add(new ast.InvalidInitializer()..parent = constructor);
} else {
var arguments = new ast.Arguments.empty();
constructor.initializers.add(
new ast.SuperInitializer(target, arguments)..parent = constructor);
}
}
}
/// Adds initializers to instance fields that are have no initializer and are
/// not initialized by all constructors in the class.
void addDefaultInstanceFieldInitializers(ast.Class node) {
List<ast.Field> uninitializedFields = new List<ast.Field>();
for (var field in node.fields) {
if (field.initializer != null || field.isStatic) continue;
uninitializedFields.add(field);
}
if (uninitializedFields.isEmpty) return;
constructorLoop:
for (var constructor in node.constructors) {
var remainingFields = uninitializedFields.toSet();
for (var initializer in constructor.initializers) {
if (initializer is ast.FieldInitializer) {
remainingFields.remove(initializer.field);
} else if (initializer is ast.RedirectingInitializer) {
// The target constructor will be checked in another iteration.
continue constructorLoop;
}
}
for (var field in remainingFields) {
if (field.initializer == null) {
field.initializer = new ast.NullLiteral()..parent = field;
}
}
}
}
/// True for the `values` field of an `enum` class.
static bool _isValuesField(FieldElement field) => field.name == 'values';
/// True for the `index` field of an `enum` class.
static bool _isIndexField(FieldElement field) => field.name == 'index';
visitEnumDeclaration(EnumDeclaration node) {
addAnnotations(node.metadata);
ast.Class classNode = currentClass;
var intType = scope.loader.getCoreClassReference('int').rawType;
var indexFieldElement = element.fields.firstWhere(_isIndexField);
ast.Field indexField = scope.getMemberReference(indexFieldElement);
indexField.type = intType;
classNode.addMember(indexField);
var stringType = scope.loader.getCoreClassReference('String').rawType;
ast.Field nameField = new ast.Field(
new ast.Name('_name', scope.currentLibrary),
type: stringType,
isFinal: true,
fileUri: classNode.fileUri);
classNode.addMember(nameField);
var indexParameter = new ast.VariableDeclaration('index', type: intType);
var nameParameter = new ast.VariableDeclaration('name', type: stringType);
var function = new ast.FunctionNode(new ast.EmptyStatement(),
positionalParameters: [indexParameter, nameParameter]);
var superConstructor = scope.loader.getRootClassConstructorReference();
var constructor = new ast.Constructor(function,
name: new ast.Name(''),
isConst: true,
initializers: [
new ast.FieldInitializer(
indexField, new ast.VariableGet(indexParameter)),
new ast.FieldInitializer(
nameField, new ast.VariableGet(nameParameter)),
new ast.SuperInitializer(superConstructor, new ast.Arguments.empty())
])
..fileOffset = element.nameOffset;
classNode.addMember(constructor);
int index = 0;
var enumConstantFields = <ast.Field>[];
for (var constant in node.constants) {
ast.Field field = scope.getMemberReference(constant.element);
field.initializer = new ast.ConstructorInvocation(
constructor,
new ast.Arguments([
new ast.IntLiteral(index),
new ast.StringLiteral('${classNode.name}.${field.name.name}')
]),
isConst: true)
..parent = field;
field.type = classNode.rawType;
classNode.addMember(field);
++index;
enumConstantFields.add(field);
}
// Add the 'values' field.
var valuesFieldElement = element.fields.firstWhere(_isValuesField);
ast.Field valuesField = scope.getMemberReference(valuesFieldElement);
var enumType = classNode.rawType;
valuesField.type = new ast.InterfaceType(
scope.loader.getCoreClassReference('List'), <ast.DartType>[enumType]);
valuesField.initializer = new ast.ListLiteral(
enumConstantFields.map(_makeStaticGet).toList(),
isConst: true,
typeArgument: enumType)
..parent = valuesField;
classNode.addMember(valuesField);
// Add the 'toString()' method.
var body = new ast.ReturnStatement(
new ast.DirectPropertyGet(new ast.ThisExpression(), nameField));
var toStringFunction = new ast.FunctionNode(body, returnType: stringType);
var toStringMethod = new ast.Procedure(
new ast.Name('toString'), ast.ProcedureKind.Method, toStringFunction,
fileUri: classNode.fileUri);
classNode.addMember(toStringMethod);
}
visitClassTypeAlias(ClassTypeAlias node) {
addAnnotations(node.metadata);
assert(node.withClause != null && node.withClause.mixinTypes.isNotEmpty);
ast.Class classNode = currentClass;
for (var constructor in element.constructors) {
var constructorNode = scope.getMemberReference(constructor);
classNode.addMember(constructorNode);
buildMixinConstructor(constructorNode, constructor);
}
}
void buildMixinConstructor(
ast.Constructor constructor, ConstructorElement element) {
var function = constructor.function;
function.body = new ast.EmptyStatement()..parent = function;
// Call the corresponding constructor in super class.
ClassElement classElement = element.enclosingElement;
var targetConstructor = classElement.supertype.element.constructors
.firstWhere((c) => c.name == element.name);
var positionalArguments = constructor.function.positionalParameters
.map(_makeVariableGet)
.toList();
var namedArguments = constructor.function.namedParameters
.map(_makeNamedExpressionFrom)
.toList();
constructor.initializers.add(new ast.SuperInitializer(
scope.getMemberReference(targetConstructor),
new ast.Arguments(positionalArguments, named: namedArguments))
..parent = constructor);
}
visitNode(AstNode node) {
throw 'Unsupported class declaration: ${node.runtimeType}';
}
}
/// Brings a member from reference level to body level.
class MemberBodyBuilder extends GeneralizingAstVisitor<Null> {
final MemberScope scope;
final Element element;
ast.Member get currentMember => scope.currentMember;
MemberBodyBuilder(
ReferenceLevelLoader loader, ast.Member member, this.element)
: scope = new MemberScope(loader, member);
void build(AstNode node) {
if (node != null) {
currentMember.fileEndOffset = node.endToken.offset;
node.accept(this);
} else {
buildBrokenMember();
}
}
/// Builds an empty member.
///
/// This should only be used to recover from a compile-time error.
void buildBrokenMember() {
var member = currentMember;
member.name = new ast.Name(element.name, scope.currentLibrary);
if (member is ast.Procedure) {
member.function = _brokenFunction()..parent = member;
} else if (member is ast.Constructor) {
member.function = _brokenFunction()..parent = member;
}
}
void addAnnotations(List<Annotation> annotations) {
for (var annotation in annotations) {
currentMember.addAnnotation(scope.buildAnnotation(annotation));
}
}
void handleNativeBody(FunctionBody body) {
if (body is NativeFunctionBody) {
currentMember.isExternal = true;
currentMember.addAnnotation(new ast.ConstructorInvocation(
scope.loader.getCoreClassConstructorReference('ExternalName',
library: 'dart:_internal'),
new ast.Arguments(<ast.Expression>[
new ast.StringLiteral(body.stringLiteral.stringValue)
]),
isConst: true));
}
}
visitConstructorDeclaration(ConstructorDeclaration node) {
if (node.factoryKeyword != null) {
buildFactoryConstructor(node);
} else {
buildGenerativeConstructor(node);
}
}
void buildGenerativeConstructor(ConstructorDeclaration node) {
if (currentMember is! ast.Constructor) {
buildBrokenMember();
return;
}
addAnnotations(node.metadata);
ast.Constructor constructor = currentMember;
constructor.function = scope.buildFunctionNode(node.parameters, node.body,
inferredReturnType: const ast.VoidType())
..parent = constructor;
handleNativeBody(node.body);
if (node.body is EmptyFunctionBody && !constructor.isExternal) {
var function = constructor.function;
function.body = new ast.EmptyStatement()..parent = function;
}
for (var parameter in node.parameters.parameterElements) {
if (parameter is FieldFormalParameterElement) {
ast.Initializer initializer;
if (parameter.field == null) {
initializer = new ast.LocalInitializer(
new ast.VariableDeclaration.forValue(scope
.buildThrowCompileTimeErrorFromCode(
CompileTimeErrorCode.INITIALIZER_FOR_NON_EXISTENT_FIELD,
[parameter.name])));
} else {
initializer = new ast.FieldInitializer(
scope.getMemberReference(parameter.field),
new ast.VariableGet(scope.getVariableReference(parameter)));
}
constructor.initializers.add(initializer..parent = constructor);
}
}
bool hasExplicitConstructorCall = false;
for (var initializer in node.initializers) {
var node = scope.buildInitializer(initializer);
constructor.initializers.add(node..parent = constructor);
if (node is ast.SuperInitializer || node is ast.RedirectingInitializer) {
hasExplicitConstructorCall = true;
}
}
ClassElement classElement = resolutionMap
.elementDeclaredByConstructorDeclaration(node)
.enclosingElement;
if (classElement.supertype != null && !hasExplicitConstructorCall) {
ConstructorElement targetElement =
scope.findDefaultConstructor(classElement.supertype.element);
ast.Constructor target = scope.resolveConstructor(targetElement);
ast.Initializer initializer = target == null
? new ast.InvalidInitializer()
: new ast.SuperInitializer(
target, new ast.Arguments(<ast.Expression>[]));
constructor.initializers.add(initializer..parent = constructor);
} else {
moveSuperInitializerLast(constructor);
}
}
void buildFactoryConstructor(ConstructorDeclaration node) {
if (currentMember is! ast.Procedure) {
buildBrokenMember();
return;
}
addAnnotations(node.metadata);
ast.Procedure procedure = currentMember;
ClassElement classElement = resolutionMap
.elementDeclaredByConstructorDeclaration(node)
.enclosingElement;
ast.Class classNode = procedure.enclosingClass;
var types = getFreshTypeParameters(classNode.typeParameters);
for (int i = 0; i < classElement.typeParameters.length; ++i) {
scope.localTypeParameters[classElement.typeParameters[i]] =
types.freshTypeParameters[i];
}
var inferredReturnType = types.freshTypeParameters.isEmpty
? classNode.rawType
: new ast.InterfaceType(
classNode,
types.freshTypeParameters
.map(makeTypeParameterType)
.toList(growable: false));
var function = scope.buildFunctionNode(node.parameters, node.body,
typeParameters: types.freshTypeParameters,
inferredReturnType: inferredReturnType);
procedure.function = function..parent = procedure;
handleNativeBody(node.body);
if (node.redirectedConstructor != null) {
// Add a new synthetic field to [classNode] for representing factory
// constructors. This is used by the new frontend engine to support
// resolving source code.
//
// The synthetic field looks like this:
//
// final _redirecting# = [c1, ..., cn];
//
// Where each c1 ... cn are an instance of [StaticGet] whose target is
// the redirecting factory created above. The new frontend engine reads
// this field and rewrites them.
//
// TODO(ahe): Generate the correct factory body instead. This requires
// access to default values from other files, we'll probably never do
// that in this file, and instead rely on the new compiler for this.
var element = resolutionMap.elementDeclaredByConstructorDeclaration(node);
assert(!element.isSynthetic);
var expression;
if (node.element.redirectedConstructor != null) {
assert(!scope.loader.ignoreRedirectingFactories);
ConstructorElement element = node.element.redirectedConstructor;
while (element.isFactory && element.redirectedConstructor != null) {
element = element.redirectedConstructor;
}
ast.Member target = scope.getMemberReference(element);
assert(target != null);
expression = new ast.Let(
new ast.VariableDeclaration.forValue(new ast.StaticGet(target)),
new ast.InvalidExpression(null));
ast.Name constructors =
new ast.Name("_redirecting#", scope.currentLibrary);
ast.Field constructorsField;
for (ast.Field field in classNode.fields) {
if (field.name == constructors) {
constructorsField = field;
break;
}
}
if (constructorsField == null) {
ast.ListLiteral literal = new ast.ListLiteral(<ast.Expression>[]);
constructorsField = new ast.Field(constructors,
isStatic: true, initializer: literal, fileUri: classNode.fileUri)
..fileOffset = classNode.fileOffset;
classNode.addMember(constructorsField);
}
ast.ListLiteral literal = constructorsField.initializer;
literal.expressions.add(new ast.StaticGet(procedure)..parent = literal);
} else {
var name = node.redirectedConstructor.type.name.name;
if (node.redirectedConstructor.name != null) {
name += '.' + node.redirectedConstructor.name.name;
}
// TODO(asgerf): Sometimes a TypeError should be thrown.
expression = scope.buildThrowNoSuchMethodError(
new ast.NullLiteral(), name, new ast.Arguments.empty());
}
var function = procedure.function;
function.body = new ast.ExpressionStatement(expression)
..parent = function;
}
}
visitMethodDeclaration(MethodDeclaration node) {
addAnnotations(node.metadata);
ast.Procedure procedure = currentMember;
procedure.function = scope.buildFunctionNode(node.parameters, node.body,
returnType: node.returnType,
inferredReturnType: scope.buildType(
resolutionMap.elementDeclaredByMethodDeclaration(node).returnType),
typeParameters: scope.buildOptionalTypeParameterList(
node.typeParameters,
strongModeOnly: true))
..parent = procedure;
handleNativeBody(node.body);
}
visitVariableDeclaration(VariableDeclaration node) {
addAnnotations(node.metadata);
ast.Field field = currentMember;
field.type = scope.buildType(
resolutionMap.elementDeclaredByVariableDeclaration(node).type);
if (node.initializer != null) {
field.initializer = scope.buildTopLevelExpression(node.initializer)
..parent = field;
} else if (field.isStatic) {
// Add null initializer to static fields without an initializer.
// For instance fields, this is handled when building the class.
field.initializer = new ast.NullLiteral()..parent = field;
}
}
visitFunctionDeclaration(FunctionDeclaration node) {
addAnnotations(node.metadata);
var function = node.functionExpression;
ast.Procedure procedure = currentMember;
procedure.function = scope.buildFunctionNode(
function.parameters, function.body,
returnType: node.returnType,
typeParameters: scope.buildOptionalTypeParameterList(
function.typeParameters,
strongModeOnly: true))
..parent = procedure;
handleNativeBody(function.body);
}
visitNode(AstNode node) {
log.severe('Unexpected class or library member: $node');
}
}
/// Internal exception thrown from the expression or statement builder when a
/// compilation error is found.
///
/// This is then caught at the function level to replace the entire function
/// body (or field initializer) with a throw.
class _CompilationError {
String message;
_CompilationError(this.message);
}
/// Constructor alias for [ast.TypeParameterType], use instead of a closure.
ast.DartType makeTypeParameterType(ast.TypeParameter parameter) {
return new ast.TypeParameterType(parameter);
}
/// Constructor alias for [ast.VariableGet], use instead of a closure.
ast.VariableGet _makeVariableGet(ast.VariableDeclaration variable) {
return new ast.VariableGet(variable);
}
/// Constructor alias for [ast.StaticGet], use instead of a closure.
ast.StaticGet _makeStaticGet(ast.Field field) {
return new ast.StaticGet(field);
}
/// Create a named expression with the name and value of the given variable.
ast.NamedExpression _makeNamedExpressionFrom(ast.VariableDeclaration variable) {
return new ast.NamedExpression(variable.name, new ast.VariableGet(variable));
}
/// A [StaticAccessor] that throws a NoSuchMethodError when a suitable target
/// could not be resolved.
class _StaticAccessor extends StaticAccessor {
final ExpressionScope scope;
final String name;
_StaticAccessor(
this.scope, this.name, ast.Member readTarget, ast.Member writeTarget)
: super(readTarget, writeTarget, ast.TreeNode.noOffset);
@override
makeInvalidRead() {
return scope.buildThrowNoSuchMethodError(
new ast.NullLiteral(), name, new ast.Arguments([]));
}
@override
makeInvalidWrite(ast.Expression value) {
return scope.buildThrowNoSuchMethodError(
new ast.NullLiteral(), name, new ast.Arguments([value]));
}
}
bool isTopLevelFunction(Element element) {
return element is FunctionElement &&
element.enclosingElement is CompilationUnitElement;
}
bool isLocalFunction(Element element) {
return element is FunctionElement &&
element.enclosingElement is! CompilationUnitElement &&
element.enclosingElement is! LibraryElement;
}
bool isLocal(Element element) {
return isLocalFunction(element) ||
element is LocalVariableElement ||
element is ParameterElement;
}
bool isInstanceMethod(Element element) {
return element is MethodElement && !element.isStatic;
}
bool isStaticMethod(Element element) {
return element is MethodElement && element.isStatic ||
isTopLevelFunction(element);
}
bool isStaticVariableOrGetter(Element element) {
element = desynthesizeGetter(element);
return element is FieldElement && element.isStatic ||
element is TopLevelVariableElement;
}
Element desynthesizeGetter(Element element) {
if (element == null || !element.isSynthetic) return element;
if (element is PropertyAccessorElement) return element.variable;
if (element is FieldElement) return element.getter;
return element;
}
Element desynthesizeSetter(Element element) {
if (element == null || !element.isSynthetic) return element;
if (element is PropertyAccessorElement) return element.variable;
if (element is FieldElement) return element.setter;
return element;
}
void sortAndRemoveDuplicates<T extends Comparable<T>>(List<T> list) {
list.sort();
int deleted = 0;
for (int i = 1; i < list.length; ++i) {
var item = list[i];
if (list[i - 1].compareTo(item) == 0) {
++deleted;
} else if (deleted > 0) {
list[i - deleted] = item;
}
}
if (deleted > 0) {
list.length -= deleted;
}
}