8bfc4b47c0
This adds a class CanonicalName that can represent a library, class, or member. All references now go through a Reference object, which is linked to both the AST node and its CanonicalName, so either can be created first. dartk now accepts multiple input files: - If multiple dart files are given, they are all compiled. - If multiple binaries are given, they are linked together. Mixed dart and binary input is not supported by dartk. dartk now has a flag --include-sdk which includes the entire SDK in the output. This is so the SDK can be compiled alone and then linked. Example of compiling separately and then linking: dartk foo.dart -o foo.dill dartk main.dart -o main.dill dartk --include-sdk -o sdk.dill dartk main.dill foo.dill sdk.dill --target=vm --link -o program.dill dartk still has incredibly slow cold start due to the analyzer loading the dart sdk, so this does not actually speed things up at the moment. BUG= R=ahe@google.com, kmillikin@google.com, kustermann@google.com, sigmund@google.com Review-Url: https://codereview.chromium.org/2665723002 .
3056 lines
110 KiB
Dart
3056 lines
110 KiB
Dart
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
library kernel.analyzer.ast_from_analyzer;
|
|
|
|
import '../ast.dart' as ast;
|
|
import '../frontend/accessors.dart';
|
|
import '../frontend/super_initializers.dart';
|
|
import '../log.dart';
|
|
import '../type_algebra.dart';
|
|
import '../transformations/flags.dart';
|
|
import 'analyzer.dart';
|
|
import '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';
|
|
|
|
/// 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));
|
|
}
|
|
} 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}) {
|
|
var declaration = getVariableReference(element);
|
|
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, [candiateTarget] 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()
|
|
: new ast.StaticGet(target);
|
|
} else if (element is ConstructorElement && element.isConst) {
|
|
var target = resolveConstructor(element);
|
|
return target == null
|
|
? new ast.InvalidExpression()
|
|
: new ast.ConstructorInvocation(
|
|
target, _expressionBuilder.buildArguments(annotation.arguments),
|
|
isConst: true);
|
|
} else {
|
|
return new ast.InvalidExpression();
|
|
}
|
|
}
|
|
|
|
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)));
|
|
}
|
|
} 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),
|
|
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>[], null);
|
|
cases.add(currentCase);
|
|
}
|
|
if (member is SwitchCase) {
|
|
var expression = scope.buildExpression(member.expression);
|
|
currentCase.expressions.add(expression..parent = currentCase);
|
|
} 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));
|
|
}
|
|
} 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();
|
|
}
|
|
return result..fileOffset = _getOffset(node);
|
|
}
|
|
|
|
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 StringLiteral) {
|
|
// Use a catch-all for StringInterpolation and AdjacentStrings:
|
|
// the debugger stops at the end.
|
|
return node.end;
|
|
}
|
|
return node.offset;
|
|
}
|
|
|
|
Accessor buildLeftHandValue(Expression node) {
|
|
var result = node.accept(this);
|
|
if (result is Accessor) {
|
|
return result;
|
|
} else {
|
|
return new ReadOnlyAccessor(result);
|
|
}
|
|
}
|
|
|
|
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));
|
|
|
|
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);
|
|
|
|
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));
|
|
} 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) {
|
|
return new ast.Not(new ast.IsExpression(
|
|
build(node.expression), scope.buildTypeAnnotation(node.type)));
|
|
} 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)) {
|
|
return new ast.MethodInvocation(
|
|
new ast.VariableGet(scope.getVariableReference(element)),
|
|
callName,
|
|
buildArgumentsForInvocation(node),
|
|
scope.resolveInterfaceFunctionCall(element));
|
|
} 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);
|
|
}
|
|
return new ast.MethodInvocation(
|
|
new ast.StaticGet(method),
|
|
callName,
|
|
buildArgumentsForInvocation(node),
|
|
scope.resolveInterfaceFunctionCall(element));
|
|
} 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.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));
|
|
} 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));
|
|
} 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<dynamic/*=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();
|
|
}
|
|
}
|
|
|
|
/// 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(new ast.FunctionNode(new ast.InvalidStatement()))
|
|
..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 = new ast.FunctionNode(new ast.InvalidStatement())
|
|
..parent = member;
|
|
} else if (member is ast.Constructor) {
|
|
member.function = new ast.FunctionNode(new ast.InvalidStatement())
|
|
..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());
|
|
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);
|
|
|
|
@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;
|
|
}
|
|
}
|