5ac1789d6c
TBR=antonm@google.com Review URL: https://chromiumcodereview.appspot.com//10827442 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@11051 260f80e4-7a28-3924-810f-c04153c831b5
2536 lines
87 KiB
Dart
2536 lines
87 KiB
Dart
// Copyright (c) 2012, 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.
|
|
|
|
interface TreeElements {
|
|
Element operator[](Node node);
|
|
Selector getSelector(Send send);
|
|
Type getType(TypeAnnotation annotation);
|
|
}
|
|
|
|
class TreeElementMapping implements TreeElements {
|
|
Map<Node, Element> map;
|
|
Map<Node, Selector> selectors;
|
|
Map<TypeAnnotation, Type> types;
|
|
|
|
TreeElementMapping()
|
|
: map = new LinkedHashMap<Node, Element>(),
|
|
selectors = new LinkedHashMap<Node, Selector>(),
|
|
types = new LinkedHashMap<TypeAnnotation, Type>();
|
|
|
|
operator []=(Node node, Element element) => map[node] = element;
|
|
operator [](Node node) => map[node];
|
|
void remove(Node node) { map.remove(node); }
|
|
|
|
void setType(TypeAnnotation annotation, Type type) {
|
|
types[annotation] = type;
|
|
}
|
|
|
|
Type getType(TypeAnnotation annotation) => types[annotation];
|
|
|
|
void setSelector(Node node, Selector selector) {
|
|
selectors[node] = selector;
|
|
}
|
|
|
|
Selector getSelector(Node node) => selectors[node];
|
|
}
|
|
|
|
class ResolverTask extends CompilerTask {
|
|
ResolverTask(Compiler compiler) : super(compiler);
|
|
|
|
String get name() => 'Resolver';
|
|
|
|
TreeElements resolve(Element element) {
|
|
return measure(() {
|
|
ElementKind kind = element.kind;
|
|
if (kind === ElementKind.GENERATIVE_CONSTRUCTOR ||
|
|
kind === ElementKind.FUNCTION ||
|
|
kind === ElementKind.GETTER ||
|
|
kind === ElementKind.SETTER) {
|
|
return resolveMethodElement(element);
|
|
}
|
|
|
|
if (kind === ElementKind.FIELD) return resolveField(element);
|
|
|
|
if (kind === ElementKind.PARAMETER ||
|
|
kind === ElementKind.FIELD_PARAMETER) {
|
|
return resolveParameter(element);
|
|
}
|
|
|
|
compiler.unimplemented("resolve($element)",
|
|
node: element.parseNode(compiler));
|
|
});
|
|
}
|
|
|
|
SourceString getConstructorName(Send node) {
|
|
if (node.receiver !== null) {
|
|
return node.selector.asIdentifier().source;
|
|
} else {
|
|
return const SourceString('');
|
|
}
|
|
}
|
|
|
|
FunctionElement resolveConstructorRedirection(FunctionElement constructor) {
|
|
FunctionExpression node = constructor.parseNode(compiler);
|
|
// A synthetic constructor does not have a node.
|
|
if (node === null) return null;
|
|
if (node.initializers === null) return null;
|
|
Link<Node> initializers = node.initializers.nodes;
|
|
if (!initializers.isEmpty() &&
|
|
Initializers.isConstructorRedirect(initializers.head)) {
|
|
final ClassElement classElement = constructor.getEnclosingClass();
|
|
final SourceString constructorName =
|
|
getConstructorName(initializers.head);
|
|
final SourceString className = classElement.name;
|
|
return classElement.lookupConstructor(className, constructorName);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void resolveRedirectingConstructor(InitializerResolver resolver,
|
|
Node node,
|
|
FunctionElement constructor,
|
|
FunctionElement redirection) {
|
|
Set<FunctionElement> seen = new Set<FunctionElement>();
|
|
seen.add(constructor);
|
|
while (redirection !== null) {
|
|
if (seen.contains(redirection)) {
|
|
resolver.visitor.error(node, MessageKind.REDIRECTING_CONSTRUCTOR_CYCLE);
|
|
return;
|
|
}
|
|
seen.add(redirection);
|
|
redirection = resolveConstructorRedirection(redirection);
|
|
}
|
|
}
|
|
|
|
TreeElements resolveMethodElement(FunctionElement element) {
|
|
return compiler.withCurrentElement(element, () {
|
|
bool isConstructor = element.kind === ElementKind.GENERATIVE_CONSTRUCTOR;
|
|
TreeElements elements =
|
|
compiler.enqueuer.resolution.getCachedElements(element);
|
|
if (elements !== null) {
|
|
assert(isConstructor);
|
|
return elements;
|
|
}
|
|
FunctionExpression tree = element.parseNode(compiler);
|
|
if (isConstructor) {
|
|
resolveConstructorImplementation(element, tree);
|
|
}
|
|
ResolverVisitor visitor = new ResolverVisitor(compiler, element);
|
|
visitor.useElement(tree, element);
|
|
visitor.setupFunction(tree, element);
|
|
|
|
if (isConstructor) {
|
|
// Even if there is no initializer list we still have to do the
|
|
// resolution in case there is an implicit super constructor call.
|
|
InitializerResolver resolver = new InitializerResolver(visitor);
|
|
FunctionElement redirection =
|
|
resolver.resolveInitializers(element, tree);
|
|
if (redirection !== null) {
|
|
resolveRedirectingConstructor(resolver, tree, element, redirection);
|
|
}
|
|
} else if (tree.initializers != null) {
|
|
error(tree, MessageKind.FUNCTION_WITH_INITIALIZER);
|
|
}
|
|
visitBody(visitor, tree.body);
|
|
|
|
return visitor.mapping;
|
|
});
|
|
}
|
|
|
|
void visitBody(ResolverVisitor visitor, Statement body) {
|
|
visitor.visit(body);
|
|
}
|
|
|
|
void resolveConstructorImplementation(FunctionElement constructor,
|
|
FunctionExpression node) {
|
|
if (constructor.defaultImplementation !== constructor) return;
|
|
ClassElement intrface = constructor.getEnclosingClass();
|
|
if (!intrface.isInterface()) return;
|
|
Type defaultType = intrface.defaultClass;
|
|
if (defaultType === null) {
|
|
error(node, MessageKind.NO_DEFAULT_CLASS, [intrface.name]);
|
|
}
|
|
ClassElement defaultClass = defaultType.element;
|
|
defaultClass.ensureResolved(compiler);
|
|
assert(defaultClass.resolutionState == ClassElement.STATE_DONE);
|
|
assert(defaultClass.supertypeLoadState == ClassElement.STATE_DONE);
|
|
if (defaultClass.isInterface()) {
|
|
error(node, MessageKind.CANNOT_INSTANTIATE_INTERFACE,
|
|
[defaultClass.name]);
|
|
}
|
|
// We have now established the following:
|
|
// [intrface] is an interface, let's say "MyInterface".
|
|
// [defaultClass] is a class, let's say "MyClass".
|
|
|
|
// If the default class implements the interface then we must use the
|
|
// default class' name. Otherwise we look for a factory with the name
|
|
// of the interface.
|
|
SourceString name;
|
|
if (defaultClass.implementsInterface(intrface)) {
|
|
// TODO(ahe): Don't use string replacement here.
|
|
name = new SourceString(constructor.name.slowToString().replaceFirst(
|
|
intrface.name.slowToString(),
|
|
defaultClass.name.slowToString()));
|
|
} else {
|
|
name = constructor.name;
|
|
}
|
|
constructor.defaultImplementation = defaultClass.lookupConstructor(name);
|
|
|
|
if (constructor.defaultImplementation === null) {
|
|
// We failed to find a constructor named either
|
|
// "MyInterface.name" or "MyClass.name".
|
|
error(node,
|
|
MessageKind.CANNOT_FIND_CONSTRUCTOR2,
|
|
[name, defaultClass.name]);
|
|
}
|
|
}
|
|
|
|
TreeElements resolveField(Element element) {
|
|
Node tree = element.parseNode(compiler);
|
|
ResolverVisitor visitor = new ResolverVisitor(compiler, element);
|
|
initializerDo(tree, visitor.visit);
|
|
return visitor.mapping;
|
|
}
|
|
|
|
TreeElements resolveParameter(Element element) {
|
|
Node tree = element.parseNode(compiler);
|
|
ResolverVisitor visitor =
|
|
new ResolverVisitor(compiler, element.enclosingElement);
|
|
initializerDo(tree, visitor.visit);
|
|
return visitor.mapping;
|
|
}
|
|
|
|
Type resolveTypeAnnotation(Element element, TypeAnnotation annotation) {
|
|
if (annotation === null) return compiler.types.dynamicType;
|
|
ResolverVisitor visitor = new ResolverVisitor(compiler, element);
|
|
Type result = visitor.resolveTypeAnnotation(annotation);
|
|
if (result === null) {
|
|
// TODO(karklose): warning.
|
|
return compiler.types.dynamicType;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Load and resolve the supertypes of [cls].
|
|
*
|
|
* Warning: do not call this method directly. It should only be
|
|
* called by [resolveClass] and [ClassSupertypeResolver].
|
|
*/
|
|
void loadSupertypes(ClassElement cls, Node from) {
|
|
compiler.withCurrentElement(cls, () => measure(() {
|
|
if (cls.supertypeLoadState == ClassElement.STATE_DONE) return;
|
|
if (cls.supertypeLoadState == ClassElement.STATE_STARTED) {
|
|
compiler.reportMessage(
|
|
compiler.spanFromNode(from),
|
|
MessageKind.CYCLIC_CLASS_HIERARCHY.error([cls.name]),
|
|
api.Diagnostic.ERROR);
|
|
cls.supertypeLoadState = ClassElement.STATE_DONE;
|
|
cls.allSupertypes = const EmptyLink<Type>().prepend(
|
|
compiler.objectClass.computeType(compiler));
|
|
// TODO(ahe): We should also set cls.supertype here to avoid
|
|
// creating a malformed class hierarchy.
|
|
return;
|
|
}
|
|
cls.supertypeLoadState = ClassElement.STATE_STARTED;
|
|
compiler.withCurrentElement(cls, () {
|
|
// TODO(ahe): Cache the node in cls.
|
|
cls.parseNode(compiler).accept(new ClassSupertypeResolver(compiler,
|
|
cls));
|
|
if (cls.supertypeLoadState != ClassElement.STATE_DONE) {
|
|
cls.supertypeLoadState = ClassElement.STATE_DONE;
|
|
}
|
|
});
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Resolve the class [element].
|
|
*
|
|
* Before calling this method, [element] was constructed by the
|
|
* scanner and most fields are null or empty. This method fills in
|
|
* these fields and also ensure that the supertypes of [element] are
|
|
* resolved.
|
|
*
|
|
* Warning: Do not call this method directly. Instead use
|
|
* [:element.ensureResolved(compiler):].
|
|
*/
|
|
void resolveClass(ClassElement element) {
|
|
assert(element.resolutionState == ClassElement.STATE_NOT_STARTED);
|
|
element.resolutionState = ClassElement.STATE_STARTED;
|
|
compiler.withCurrentElement(element, () => measure(() {
|
|
ClassNode tree = element.parseNode(compiler);
|
|
loadSupertypes(element, tree);
|
|
|
|
ClassResolverVisitor visitor =
|
|
new ClassResolverVisitor(compiler, element);
|
|
visitor.visit(tree);
|
|
element.resolutionState = ClassElement.STATE_DONE;
|
|
}));
|
|
}
|
|
|
|
void checkMembers(ClassElement cls) {
|
|
if (cls === compiler.objectClass) return;
|
|
cls.forEachMember((holder, member) {
|
|
checkAbstractField(member);
|
|
checkValidOverride(member, cls.lookupSuperMember(member.name));
|
|
});
|
|
}
|
|
|
|
void checkAbstractField(Element member) {
|
|
if (member is !AbstractFieldElement) return;
|
|
if (member.getter === null) return;
|
|
if (member.setter === null) return;
|
|
int getterFlags = member.getter.modifiers.flags | Modifiers.FLAG_ABSTRACT;
|
|
int setterFlags = member.setter.modifiers.flags | Modifiers.FLAG_ABSTRACT;
|
|
if (getterFlags !== setterFlags) {
|
|
final mismatchedFlags =
|
|
new Modifiers.withFlags(null, getterFlags ^ setterFlags);
|
|
compiler.reportMessage(
|
|
compiler.spanFromElement(member.getter),
|
|
MessageKind.GETTER_MISMATCH.error([mismatchedFlags]),
|
|
api.Diagnostic.ERROR);
|
|
compiler.reportMessage(
|
|
compiler.spanFromElement(member.setter),
|
|
MessageKind.SETTER_MISMATCH.error([mismatchedFlags]),
|
|
api.Diagnostic.ERROR);
|
|
}
|
|
}
|
|
|
|
reportErrorWithContext(Element errorneousElement,
|
|
MessageKind errorMessage,
|
|
Element contextElement,
|
|
MessageKind contextMessage) {
|
|
compiler.reportMessage(
|
|
compiler.spanFromElement(errorneousElement),
|
|
errorMessage.error([contextElement.name,
|
|
contextElement.getEnclosingClass().name]),
|
|
api.Diagnostic.ERROR);
|
|
compiler.reportMessage(
|
|
compiler.spanFromElement(contextElement),
|
|
contextMessage.error(),
|
|
api.Diagnostic.INFO);
|
|
}
|
|
|
|
void checkValidOverride(Element member, Element superMember) {
|
|
if (superMember === null) return;
|
|
if (member.modifiers.isStatic()) {
|
|
reportErrorWithContext(
|
|
member, MessageKind.NO_STATIC_OVERRIDE,
|
|
superMember, MessageKind.NO_STATIC_OVERRIDE_CONT);
|
|
} else {
|
|
FunctionElement superFunction = superMember.asFunctionElement();
|
|
FunctionElement function = member.asFunctionElement();
|
|
if (superFunction === null || superFunction.isAccessor()) {
|
|
// Field or accessor in super.
|
|
if (function !== null && !function.isAccessor()) {
|
|
// But a plain method in this class.
|
|
reportErrorWithContext(
|
|
member, MessageKind.CANNOT_OVERRIDE_FIELD_WITH_METHOD,
|
|
superMember, MessageKind.CANNOT_OVERRIDE_FIELD_WITH_METHOD_CONT);
|
|
}
|
|
} else {
|
|
// Instance method in super.
|
|
if (function === null || function.isAccessor()) {
|
|
// But a field (or accessor) in this class.
|
|
reportErrorWithContext(
|
|
member, MessageKind.CANNOT_OVERRIDE_METHOD_WITH_FIELD,
|
|
superMember, MessageKind.CANNOT_OVERRIDE_METHOD_WITH_FIELD_CONT);
|
|
} else {
|
|
// Both are plain instance methods.
|
|
if (superFunction.requiredParameterCount(compiler) !=
|
|
function.requiredParameterCount(compiler)) {
|
|
reportErrorWithContext(
|
|
member,
|
|
MessageKind.BAD_ARITY_OVERRIDE,
|
|
superMember,
|
|
MessageKind.BAD_ARITY_OVERRIDE_CONT);
|
|
}
|
|
// TODO(ahe): Check optional parameters.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FunctionSignature resolveSignature(FunctionElement element) {
|
|
return compiler.withCurrentElement(element, () {
|
|
FunctionExpression node =
|
|
compiler.parser.measure(() => element.parseNode(compiler));
|
|
return measure(() => SignatureResolver.analyze(
|
|
compiler, node.parameters, node.returnType, element));
|
|
});
|
|
}
|
|
|
|
FunctionSignature resolveFunctionExpression(Element element,
|
|
FunctionExpression node) {
|
|
return measure(() => SignatureResolver.analyze(
|
|
compiler, node.parameters, node.returnType, element));
|
|
}
|
|
|
|
void resolveTypedef(TypedefElement element) {
|
|
if (element.isResolved || element.isBeingResolved) return;
|
|
element.isBeingResolved = true;
|
|
return compiler.withCurrentElement(element, () {
|
|
measure(() {
|
|
Typedef node =
|
|
compiler.parser.measure(() => element.parseNode(compiler));
|
|
TypedefResolverVisitor visitor =
|
|
new TypedefResolverVisitor(compiler, element);
|
|
visitor.visit(node);
|
|
|
|
element.isBeingResolved = false;
|
|
element.isResolved = true;
|
|
});
|
|
});
|
|
}
|
|
|
|
FunctionType computeFunctionType(Element element,
|
|
FunctionSignature signature) {
|
|
LinkBuilder<Type> parameterTypes = new LinkBuilder<Type>();
|
|
for (Link<Element> link = signature.requiredParameters;
|
|
!link.isEmpty();
|
|
link = link.tail) {
|
|
parameterTypes.addLast(link.head.computeType(compiler));
|
|
// TODO(karlklose): optional parameters.
|
|
}
|
|
return new FunctionType(signature.returnType,
|
|
parameterTypes.toLink(),
|
|
element);
|
|
}
|
|
|
|
error(Node node, MessageKind kind, [arguments = const []]) {
|
|
ResolutionError message = new ResolutionError(kind, arguments);
|
|
compiler.reportError(node, message);
|
|
}
|
|
}
|
|
|
|
class InitializerResolver {
|
|
final ResolverVisitor visitor;
|
|
final Map<SourceString, Node> initialized;
|
|
Link<Node> initializers;
|
|
bool hasSuper;
|
|
|
|
InitializerResolver(this.visitor)
|
|
: initialized = new Map<SourceString, Node>(), hasSuper = false;
|
|
|
|
error(Node node, MessageKind kind, [arguments = const []]) {
|
|
visitor.error(node, kind, arguments);
|
|
}
|
|
|
|
warning(Node node, MessageKind kind, [arguments = const []]) {
|
|
visitor.warning(node, kind, arguments);
|
|
}
|
|
|
|
bool isFieldInitializer(SendSet node) {
|
|
if (node.selector.asIdentifier() == null) return false;
|
|
if (node.receiver == null) return true;
|
|
if (node.receiver.asIdentifier() == null) return false;
|
|
return node.receiver.asIdentifier().isThis();
|
|
}
|
|
|
|
void checkForDuplicateInitializers(SourceString name, Node init) {
|
|
if (initialized.containsKey(name)) {
|
|
error(init, MessageKind.DUPLICATE_INITIALIZER, [name]);
|
|
warning(initialized[name], MessageKind.ALREADY_INITIALIZED, [name]);
|
|
}
|
|
initialized[name] = init;
|
|
}
|
|
|
|
void resolveFieldInitializer(FunctionElement constructor, SendSet init) {
|
|
// init is of the form [this.]field = value.
|
|
final Node selector = init.selector;
|
|
final SourceString name = selector.asIdentifier().source;
|
|
// Lookup target field.
|
|
Element target;
|
|
if (isFieldInitializer(init)) {
|
|
final ClassElement classElement = constructor.getEnclosingClass();
|
|
target = classElement.lookupLocalMember(name);
|
|
if (target === null) {
|
|
error(selector, MessageKind.CANNOT_RESOLVE, [name]);
|
|
} else if (target.kind != ElementKind.FIELD) {
|
|
error(selector, MessageKind.NOT_A_FIELD, [name]);
|
|
} else if (!target.isInstanceMember()) {
|
|
error(selector, MessageKind.INIT_STATIC_FIELD, [name]);
|
|
}
|
|
} else {
|
|
error(init, MessageKind.INVALID_RECEIVER_IN_INITIALIZER);
|
|
}
|
|
visitor.useElement(init, target);
|
|
visitor.world.registerStaticUse(target);
|
|
checkForDuplicateInitializers(name, init);
|
|
// Resolve initializing value.
|
|
visitor.visitInStaticContext(init.arguments.head);
|
|
}
|
|
|
|
Element resolveSuperOrThisForSend(FunctionElement constructor,
|
|
FunctionExpression functionNode,
|
|
Send call) {
|
|
// Resolve the selector and the arguments.
|
|
ResolverTask resolver = visitor.compiler.resolver;
|
|
visitor.inStaticContext(() {
|
|
visitor.resolveSelector(call);
|
|
visitor.resolveArguments(call.argumentsNode);
|
|
});
|
|
Selector selector = visitor.mapping.getSelector(call);
|
|
bool isSuperCall = Initializers.isSuperConstructorCall(call);
|
|
SourceString constructorName = resolver.getConstructorName(call);
|
|
Element result = resolveSuperOrThis(
|
|
constructor, isSuperCall, false, constructorName, selector, call);
|
|
visitor.useElement(call, result);
|
|
visitor.world.registerStaticUse(result);
|
|
return result;
|
|
}
|
|
|
|
void resolveImplicitSuperConstructorSend(FunctionElement constructor,
|
|
FunctionExpression functionNode) {
|
|
// If the class has a super resolve the implicit super call.
|
|
ClassElement classElement = constructor.getEnclosingClass();
|
|
ClassElement superClass = classElement.superclass;
|
|
if (classElement != visitor.compiler.objectClass) {
|
|
assert(superClass !== null);
|
|
assert(superClass.resolutionState == ClassElement.STATE_DONE);
|
|
SourceString name = const SourceString('');
|
|
Selector call = new Selector.call(name, classElement.getLibrary(), 0);
|
|
var element = resolveSuperOrThis(constructor, true, true,
|
|
name, call, functionNode);
|
|
visitor.world.registerStaticUse(element);
|
|
}
|
|
}
|
|
|
|
Element resolveSuperOrThis(FunctionElement constructor,
|
|
bool isSuperCall,
|
|
bool isImplicitSuperCall,
|
|
SourceString constructorName,
|
|
Selector selector,
|
|
Node diagnosticNode) {
|
|
ClassElement lookupTarget = constructor.getEnclosingClass();
|
|
bool validTarget = true;
|
|
FunctionElement result;
|
|
if (isSuperCall) {
|
|
// Calculate correct lookup target and constructor name.
|
|
if (lookupTarget === visitor.compiler.objectClass) {
|
|
error(diagnosticNode, MessageKind.SUPER_INITIALIZER_IN_OBJECT);
|
|
} else {
|
|
lookupTarget = lookupTarget.supertype.element;
|
|
}
|
|
}
|
|
|
|
// Lookup constructor and try to match it to the selector.
|
|
ResolverTask resolver = visitor.compiler.resolver;
|
|
final SourceString className = lookupTarget.name;
|
|
result = lookupTarget.lookupConstructor(className, constructorName);
|
|
if (result === null || !result.isGenerativeConstructor()) {
|
|
String classNameString = className.slowToString();
|
|
String constructorNameString = constructorName.slowToString();
|
|
String name = (constructorName === const SourceString(''))
|
|
? classNameString
|
|
: "$classNameString.$constructorNameString";
|
|
MessageKind kind = isImplicitSuperCall
|
|
? MessageKind.CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT
|
|
: MessageKind.CANNOT_RESOLVE_CONSTRUCTOR;
|
|
error(diagnosticNode, kind, [name]);
|
|
} else {
|
|
if (!selector.applies(result, visitor.compiler)) {
|
|
MessageKind kind = isImplicitSuperCall
|
|
? MessageKind.NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT
|
|
: MessageKind.NO_MATCHING_CONSTRUCTOR;
|
|
error(diagnosticNode, kind);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
FunctionElement resolveRedirection(FunctionElement constructor,
|
|
FunctionExpression functionNode) {
|
|
if (functionNode.initializers === null) return null;
|
|
Link<Node> link = functionNode.initializers.nodes;
|
|
if (!link.isEmpty() && Initializers.isConstructorRedirect(link.head)) {
|
|
return resolveSuperOrThisForSend(constructor, functionNode, link.head);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Resolve all initializers of this constructor. In the case of a redirecting
|
|
* constructor, the resolved constructor's function element is returned.
|
|
*/
|
|
FunctionElement resolveInitializers(FunctionElement constructor,
|
|
FunctionExpression functionNode) {
|
|
// Keep track of all "this.param" parameters specified for constructor so
|
|
// that we can ensure that fields are initialized only once.
|
|
FunctionSignature functionParameters =
|
|
constructor.computeSignature(visitor.compiler);
|
|
functionParameters.forEachParameter((Element element) {
|
|
if (element.kind === ElementKind.FIELD_PARAMETER) {
|
|
checkForDuplicateInitializers(element.name,
|
|
element.parseNode(visitor.compiler));
|
|
}
|
|
});
|
|
|
|
if (functionNode.initializers === null) {
|
|
initializers = const EmptyLink<Node>();
|
|
} else {
|
|
initializers = functionNode.initializers.nodes;
|
|
}
|
|
FunctionElement result;
|
|
bool resolvedSuper = false;
|
|
for (Link<Node> link = initializers;
|
|
!link.isEmpty();
|
|
link = link.tail) {
|
|
if (link.head.asSendSet() != null) {
|
|
final SendSet init = link.head.asSendSet();
|
|
resolveFieldInitializer(constructor, init);
|
|
} else if (link.head.asSend() !== null) {
|
|
final Send call = link.head.asSend();
|
|
if (Initializers.isSuperConstructorCall(call)) {
|
|
if (resolvedSuper) {
|
|
error(call, MessageKind.DUPLICATE_SUPER_INITIALIZER);
|
|
}
|
|
resolveSuperOrThisForSend(constructor, functionNode, call);
|
|
resolvedSuper = true;
|
|
} else if (Initializers.isConstructorRedirect(call)) {
|
|
// Check that there is no body (Language specification 7.5.1).
|
|
if (functionNode.hasBody()) {
|
|
error(functionNode, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_BODY);
|
|
}
|
|
// Check that there are no other initializers.
|
|
if (!initializers.tail.isEmpty()) {
|
|
error(call, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER);
|
|
}
|
|
return resolveSuperOrThisForSend(constructor, functionNode, call);
|
|
} else {
|
|
visitor.error(call, MessageKind.CONSTRUCTOR_CALL_EXPECTED);
|
|
return null;
|
|
}
|
|
} else {
|
|
error(link.head, MessageKind.INVALID_INITIALIZER);
|
|
}
|
|
}
|
|
if (!resolvedSuper) {
|
|
resolveImplicitSuperConstructorSend(constructor, functionNode);
|
|
}
|
|
return null; // If there was no redirection always return null.
|
|
}
|
|
}
|
|
|
|
class CommonResolverVisitor<R> extends AbstractVisitor<R> {
|
|
final Compiler compiler;
|
|
|
|
CommonResolverVisitor(Compiler this.compiler);
|
|
|
|
R visitNode(Node node) {
|
|
cancel(node,
|
|
'internal error: Unhandled node: ${node.getObjectDescription()}');
|
|
}
|
|
|
|
R visitEmptyStatement(Node node) => null;
|
|
|
|
/** Convenience method for visiting nodes that may be null. */
|
|
R visit(Node node) => (node == null) ? null : node.accept(this);
|
|
|
|
void error(Node node, MessageKind kind, [arguments = const []]) {
|
|
ResolutionError message = new ResolutionError(kind, arguments);
|
|
compiler.reportError(node, message);
|
|
}
|
|
|
|
void warning(Node node, MessageKind kind, [arguments = const []]) {
|
|
ResolutionWarning message = new ResolutionWarning(kind, arguments);
|
|
compiler.reportWarning(node, message);
|
|
}
|
|
|
|
void cancel(Node node, String message) {
|
|
compiler.cancel(message, node: node);
|
|
}
|
|
|
|
void internalError(Node node, String message) {
|
|
compiler.internalError(message, node: node);
|
|
}
|
|
|
|
void unimplemented(Node node, String message) {
|
|
compiler.unimplemented(message, node: node);
|
|
}
|
|
}
|
|
|
|
interface LabelScope {
|
|
LabelScope get outer();
|
|
LabelElement lookup(String label);
|
|
}
|
|
|
|
class LabeledStatementLabelScope implements LabelScope {
|
|
final LabelScope outer;
|
|
final Map<String, LabelElement> labels;
|
|
LabeledStatementLabelScope(this.outer, this.labels);
|
|
LabelElement lookup(String labelName) {
|
|
LabelElement label = labels[labelName];
|
|
if (label !== null) return label;
|
|
return outer.lookup(labelName);
|
|
}
|
|
}
|
|
|
|
class SwitchLabelScope implements LabelScope {
|
|
final LabelScope outer;
|
|
final Map<String, LabelElement> caseLabels;
|
|
|
|
SwitchLabelScope(this.outer, this.caseLabels);
|
|
|
|
LabelElement lookup(String labelName) {
|
|
LabelElement result = caseLabels[labelName];
|
|
if (result !== null) return result;
|
|
return outer.lookup(labelName);
|
|
}
|
|
}
|
|
|
|
class EmptyLabelScope implements LabelScope {
|
|
const EmptyLabelScope();
|
|
LabelElement lookup(String label) => null;
|
|
LabelScope get outer() {
|
|
throw 'internal error: empty label scope has no outer';
|
|
}
|
|
}
|
|
|
|
class StatementScope {
|
|
LabelScope labels;
|
|
Link<TargetElement> breakTargetStack;
|
|
Link<TargetElement> continueTargetStack;
|
|
// Used to provide different numbers to statements if one is inside the other.
|
|
// Can be used to make otherwise duplicate labels unique.
|
|
int nestingLevel = 0;
|
|
|
|
StatementScope()
|
|
: labels = const EmptyLabelScope(),
|
|
breakTargetStack = const EmptyLink<TargetElement>(),
|
|
continueTargetStack = const EmptyLink<TargetElement>();
|
|
|
|
LabelElement lookupLabel(String label) {
|
|
return labels.lookup(label);
|
|
}
|
|
|
|
TargetElement currentBreakTarget() =>
|
|
breakTargetStack.isEmpty() ? null : breakTargetStack.head;
|
|
|
|
TargetElement currentContinueTarget() =>
|
|
continueTargetStack.isEmpty() ? null : continueTargetStack.head;
|
|
|
|
void enterLabelScope(Map<String, LabelElement> elements) {
|
|
labels = new LabeledStatementLabelScope(labels, elements);
|
|
nestingLevel++;
|
|
}
|
|
|
|
void exitLabelScope() {
|
|
nestingLevel--;
|
|
labels = labels.outer;
|
|
}
|
|
|
|
void enterLoop(TargetElement element) {
|
|
breakTargetStack = breakTargetStack.prepend(element);
|
|
continueTargetStack = continueTargetStack.prepend(element);
|
|
nestingLevel++;
|
|
}
|
|
|
|
void exitLoop() {
|
|
nestingLevel--;
|
|
breakTargetStack = breakTargetStack.tail;
|
|
continueTargetStack = continueTargetStack.tail;
|
|
}
|
|
|
|
void enterSwitch(TargetElement breakElement,
|
|
Map<String, LabelElement> continueElements) {
|
|
breakTargetStack = breakTargetStack.prepend(breakElement);
|
|
labels = new SwitchLabelScope(labels, continueElements);
|
|
nestingLevel++;
|
|
}
|
|
|
|
void exitSwitch() {
|
|
nestingLevel--;
|
|
breakTargetStack = breakTargetStack.tail;
|
|
labels = labels.outer;
|
|
}
|
|
}
|
|
|
|
class TypeResolver {
|
|
final Compiler compiler;
|
|
|
|
TypeResolver(this.compiler);
|
|
|
|
Element resolveTypeName(Scope scope, TypeAnnotation node) {
|
|
Identifier typeName = node.typeName.asIdentifier();
|
|
Send send = node.typeName.asSend();
|
|
return resolveTypeNameInternal(scope, typeName, send);
|
|
}
|
|
|
|
Element resolveTypeNameInternal(Scope scope, Identifier typeName, Send send) {
|
|
if (send !== null) {
|
|
typeName = send.selector;
|
|
}
|
|
if (typeName.source.stringValue === 'void') {
|
|
return compiler.types.voidType.element;
|
|
} else if (send !== null) {
|
|
Element e = scope.lookup(send.receiver.asIdentifier().source);
|
|
if (e !== null && e.kind === ElementKind.PREFIX) {
|
|
// The receiver is a prefix. Lookup in the imported members.
|
|
PrefixElement prefix = e;
|
|
return prefix.lookupLocalMember(typeName.source);
|
|
} else if (e !== null && e.kind === ElementKind.CLASS) {
|
|
// The receiver is the class part of a named constructor.
|
|
return e;
|
|
} else {
|
|
return null;
|
|
}
|
|
} else {
|
|
return scope.lookup(typeName.source);
|
|
}
|
|
}
|
|
|
|
// TODO(johnniwinther): Change [onFailure] and [whenResolved] to use boolean
|
|
// flags instead of closures.
|
|
// TODO(johnniwinther): Should never return [null] but instead an erroneous
|
|
// type.
|
|
Type resolveTypeAnnotation(TypeAnnotation node,
|
|
[Scope inScope, ClassElement inClass,
|
|
onFailure(Node, MessageKind, [List arguments]),
|
|
whenResolved(Node, Type)]) {
|
|
if (onFailure === null) {
|
|
onFailure = (n, k, [arguments]) {};
|
|
}
|
|
if (whenResolved === null) {
|
|
whenResolved = (n, t) {};
|
|
}
|
|
if (inClass !== null) {
|
|
inScope = inClass.buildScope();
|
|
}
|
|
if (inScope === null) {
|
|
compiler.internalError('resolveTypeAnnotation: no scope specified');
|
|
}
|
|
return resolveTypeAnnotationInContext(inScope, node, onFailure,
|
|
whenResolved);
|
|
}
|
|
|
|
Type resolveTypeAnnotationInContext(Scope scope, TypeAnnotation node,
|
|
onFailure, whenResolved) {
|
|
Element element = resolveTypeName(scope, node);
|
|
Type type;
|
|
if (element === null) {
|
|
onFailure(node, MessageKind.CANNOT_RESOLVE_TYPE, [node.typeName]);
|
|
} else if (!element.impliesType()) {
|
|
onFailure(node, MessageKind.NOT_A_TYPE, [node.typeName]);
|
|
} else {
|
|
if (element === compiler.types.voidType.element ||
|
|
element === compiler.types.dynamicType.element) {
|
|
type = element.computeType(compiler);
|
|
} else if (element.isClass()) {
|
|
ClassElement cls = element;
|
|
cls.ensureResolved(compiler);
|
|
Link<Type> arguments =
|
|
resolveTypeArguments(node, cls.typeVariables, scope,
|
|
onFailure, whenResolved);
|
|
if (cls.typeVariables.isEmpty() && arguments.isEmpty()) {
|
|
// Use the canonical type if it has no type parameters.
|
|
type = cls.computeType(compiler);
|
|
} else {
|
|
type = new InterfaceType(cls, arguments);
|
|
}
|
|
} else if (element.isTypedef()) {
|
|
TypedefElement typdef = element;
|
|
// TODO(ahe): Should be [ensureResolved].
|
|
compiler.resolveTypedef(typdef);
|
|
typdef.computeType(compiler);
|
|
Link<Type> arguments = resolveTypeArguments(
|
|
node, typdef.typeVariables,
|
|
scope, onFailure, whenResolved);
|
|
if (typdef.typeVariables.isEmpty() && arguments.isEmpty()) {
|
|
// Return the canonical type if it has no type parameters.
|
|
type = typdef.computeType(compiler);
|
|
} else {
|
|
type = new TypedefType(typdef, arguments);
|
|
}
|
|
} else if (element.isTypeVariable()) {
|
|
type = element.computeType(compiler);
|
|
} else {
|
|
compiler.cancel("unexpected element kind ${element.kind}",
|
|
node: node);
|
|
}
|
|
}
|
|
whenResolved(node, type);
|
|
return type;
|
|
}
|
|
|
|
Link<Type> resolveTypeArguments(TypeAnnotation node,
|
|
Link<Type> typeVariables,
|
|
Scope scope, onFailure, whenResolved) {
|
|
if (node.typeArguments == null) {
|
|
return const EmptyLink<Type>();
|
|
}
|
|
var arguments = new LinkBuilder<Type>();
|
|
for (Link<Node> typeArguments = node.typeArguments.nodes;
|
|
!typeArguments.isEmpty();
|
|
typeArguments = typeArguments.tail) {
|
|
if (typeVariables.isEmpty()) {
|
|
onFailure(typeArguments.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT);
|
|
}
|
|
Type argType = resolveTypeAnnotationInContext(scope,
|
|
typeArguments.head,
|
|
onFailure,
|
|
whenResolved);
|
|
arguments.addLast(argType);
|
|
if (!typeVariables.isEmpty()) {
|
|
typeVariables = typeVariables.tail;
|
|
}
|
|
}
|
|
if (!typeVariables.isEmpty()) {
|
|
onFailure(node.typeArguments, MessageKind.MISSING_TYPE_ARGUMENT);
|
|
}
|
|
return arguments.toLink();
|
|
}
|
|
}
|
|
|
|
class ResolverVisitor extends CommonResolverVisitor<Element> {
|
|
final TreeElementMapping mapping;
|
|
final Element enclosingElement;
|
|
final TypeResolver typeResolver;
|
|
bool inInstanceContext;
|
|
Scope scope;
|
|
ClassElement currentClass;
|
|
bool typeRequired = false;
|
|
StatementScope statementScope;
|
|
int allowedCategory = ElementCategory.VARIABLE | ElementCategory.FUNCTION;
|
|
|
|
ResolverVisitor(Compiler compiler, Element element)
|
|
: this.mapping = new TreeElementMapping(),
|
|
this.enclosingElement = element,
|
|
inInstanceContext = element.isInstanceMember()
|
|
|| element.isGenerativeConstructor(),
|
|
this.currentClass = element.isMember() ?
|
|
element.getEnclosingClass() :
|
|
null,
|
|
this.statementScope = new StatementScope(),
|
|
typeResolver = new TypeResolver(compiler),
|
|
scope = element.buildEnclosingScope(),
|
|
super(compiler) {
|
|
}
|
|
|
|
Enqueuer get world() => compiler.enqueuer.resolution;
|
|
|
|
Element lookup(Node node, SourceString name) {
|
|
Element result = scope.lookup(name);
|
|
if (!inInstanceContext && result != null && result.isInstanceMember()) {
|
|
error(node, MessageKind.NO_INSTANCE_AVAILABLE, [node]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Create, or reuse an already created, statement element for a statement.
|
|
TargetElement getOrCreateTargetElement(Node statement) {
|
|
TargetElement element = mapping[statement];
|
|
if (element === null) {
|
|
element = new TargetElement(statement,
|
|
statementScope.nestingLevel,
|
|
enclosingElement);
|
|
mapping[statement] = element;
|
|
}
|
|
return element;
|
|
}
|
|
|
|
inStaticContext(action()) {
|
|
bool wasInstanceContext = inInstanceContext;
|
|
inInstanceContext = false;
|
|
var result = action();
|
|
inInstanceContext = wasInstanceContext;
|
|
return result;
|
|
}
|
|
|
|
visitInStaticContext(Node node) {
|
|
inStaticContext(() => visit(node));
|
|
}
|
|
|
|
Element visitIdentifier(Identifier node) {
|
|
if (node.isThis()) {
|
|
if (!inInstanceContext) {
|
|
error(node, MessageKind.NO_INSTANCE_AVAILABLE, [node]);
|
|
}
|
|
return null;
|
|
} else if (node.isSuper()) {
|
|
if (!inInstanceContext) error(node, MessageKind.NO_SUPER_IN_STATIC);
|
|
if ((ElementCategory.SUPER & allowedCategory) == 0) {
|
|
error(node, MessageKind.INVALID_USE_OF_SUPER);
|
|
}
|
|
return null;
|
|
} else {
|
|
Element element = lookup(node, node.source);
|
|
if (element === null) {
|
|
if (!inInstanceContext) error(node, MessageKind.CANNOT_RESOLVE, [node]);
|
|
} else {
|
|
if ((element.kind.category & allowedCategory) == 0) {
|
|
// TODO(ahe): Improve error message. Need UX input.
|
|
error(node, MessageKind.GENERIC, ["is not an expression $element"]);
|
|
}
|
|
}
|
|
return useElement(node, element);
|
|
}
|
|
}
|
|
|
|
Element visitTypeAnnotation(TypeAnnotation node) {
|
|
Type type = resolveTypeAnnotation(node);
|
|
if (type !== null) return type.element;
|
|
return null;
|
|
}
|
|
|
|
Element defineElement(Node node, Element element,
|
|
[bool doAddToScope = true]) {
|
|
compiler.ensure(element !== null);
|
|
mapping[node] = element;
|
|
if (doAddToScope) {
|
|
Element existing = scope.add(element);
|
|
if (existing != element) {
|
|
error(node, MessageKind.DUPLICATE_DEFINITION, [node]);
|
|
}
|
|
}
|
|
return element;
|
|
}
|
|
|
|
Element useElement(Node node, Element element) {
|
|
if (element === null) return null;
|
|
return mapping[node] = element;
|
|
}
|
|
|
|
Type useType(TypeAnnotation annotation, Type type) {
|
|
if (type !== null) {
|
|
mapping.setType(annotation, type);
|
|
useElement(annotation, type.element);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
void setupFunction(FunctionExpression node, FunctionElement function) {
|
|
scope = new MethodScope(scope, function);
|
|
// Put the parameters in scope.
|
|
FunctionSignature functionParameters =
|
|
function.computeSignature(compiler);
|
|
Link<Node> parameterNodes = (node.parameters === null)
|
|
? const EmptyLink<Node>() : node.parameters.nodes;
|
|
functionParameters.forEachParameter((Element element) {
|
|
if (element == functionParameters.optionalParameters.head) {
|
|
NodeList nodes = parameterNodes.head;
|
|
parameterNodes = nodes.nodes;
|
|
}
|
|
VariableDefinitions variableDefinitions = parameterNodes.head;
|
|
Node parameterNode = variableDefinitions.definitions.nodes.head;
|
|
initializerDo(parameterNode, (n) => n.accept(this));
|
|
// Field parameters (this.x) are not visible inside the constructor. The
|
|
// fields they reference are visible, but must be resolved independently.
|
|
if (element.kind == ElementKind.FIELD_PARAMETER) {
|
|
useElement(parameterNode, element);
|
|
} else {
|
|
defineElement(variableDefinitions.definitions.nodes.head, element);
|
|
}
|
|
parameterNodes = parameterNodes.tail;
|
|
});
|
|
}
|
|
|
|
visitCascade(Cascade node) {
|
|
visit(node.expression);
|
|
}
|
|
|
|
visitCascadeReceiver(CascadeReceiver node) {
|
|
visit(node.expression);
|
|
}
|
|
|
|
Element visitClassNode(ClassNode node) {
|
|
cancel(node, "shouldn't be called");
|
|
}
|
|
|
|
visitIn(Node node, Scope nestedScope) {
|
|
scope = nestedScope;
|
|
Element element = visit(node);
|
|
scope = scope.parent;
|
|
return element;
|
|
}
|
|
|
|
/**
|
|
* Introduces new default targets for break and continue
|
|
* before visiting the body of the loop
|
|
*/
|
|
visitLoopBodyIn(Node loop, Node body, Scope bodyScope) {
|
|
TargetElement element = getOrCreateTargetElement(loop);
|
|
statementScope.enterLoop(element);
|
|
visitIn(body, bodyScope);
|
|
statementScope.exitLoop();
|
|
if (!element.isTarget) {
|
|
mapping.remove(loop);
|
|
}
|
|
}
|
|
|
|
visitBlock(Block node) {
|
|
visitIn(node.statements, new BlockScope(scope));
|
|
}
|
|
|
|
visitDoWhile(DoWhile node) {
|
|
visitLoopBodyIn(node, node.body, new BlockScope(scope));
|
|
visit(node.condition);
|
|
}
|
|
|
|
visitEmptyStatement(EmptyStatement node) { }
|
|
|
|
visitExpressionStatement(ExpressionStatement node) {
|
|
visit(node.expression);
|
|
}
|
|
|
|
visitFor(For node) {
|
|
Scope blockScope = new BlockScope(scope);
|
|
visitIn(node.initializer, blockScope);
|
|
visitIn(node.condition, blockScope);
|
|
visitIn(node.update, blockScope);
|
|
visitLoopBodyIn(node, node.body, blockScope);
|
|
}
|
|
|
|
visitFunctionDeclaration(FunctionDeclaration node) {
|
|
assert(node.function.name !== null);
|
|
visit(node.function);
|
|
FunctionElement functionElement = mapping[node.function];
|
|
// TODO(floitsch): this might lead to two errors complaining about
|
|
// shadowing.
|
|
defineElement(node, functionElement);
|
|
}
|
|
|
|
visitFunctionExpression(FunctionExpression node) {
|
|
visit(node.returnType);
|
|
SourceString name;
|
|
if (node.name === null) {
|
|
name = const SourceString("");
|
|
} else {
|
|
name = node.name.asIdentifier().source;
|
|
}
|
|
FunctionElement enclosing = new FunctionElement.node(
|
|
name, node, ElementKind.FUNCTION, new Modifiers.empty(),
|
|
scope.element);
|
|
setupFunction(node, enclosing);
|
|
defineElement(node, enclosing, doAddToScope: node.name !== null);
|
|
|
|
// Run the body in a fresh statement scope.
|
|
StatementScope oldScope = statementScope;
|
|
statementScope = new StatementScope();
|
|
visit(node.body);
|
|
statementScope = oldScope;
|
|
|
|
scope = scope.parent;
|
|
}
|
|
|
|
visitIf(If node) {
|
|
visit(node.condition);
|
|
visit(node.thenPart);
|
|
visit(node.elsePart);
|
|
}
|
|
|
|
static bool isLogicalOperator(Identifier op) {
|
|
String str = op.source.stringValue;
|
|
return (str === '&&' || str == '||' || str == '!');
|
|
}
|
|
|
|
Element resolveSend(Send node) {
|
|
Selector selector = resolveSelector(node);
|
|
|
|
if (node.receiver === null) {
|
|
return node.selector.accept(this);
|
|
}
|
|
|
|
var oldCategory = allowedCategory;
|
|
allowedCategory |=
|
|
ElementCategory.CLASS | ElementCategory.PREFIX | ElementCategory.SUPER;
|
|
Element resolvedReceiver = visit(node.receiver);
|
|
allowedCategory = oldCategory;
|
|
|
|
Element target;
|
|
SourceString name = node.selector.asIdentifier().source;
|
|
if (name.stringValue === 'this') {
|
|
error(node.selector, MessageKind.GENERIC, ["expected an identifier"]);
|
|
} else if (node.isSuperCall) {
|
|
if (node.isOperator) {
|
|
if (isUserDefinableOperator(name.stringValue)) {
|
|
name = selector.name;
|
|
} else {
|
|
error(node.selector, MessageKind.ILLEGAL_SUPER_SEND, [name]);
|
|
}
|
|
}
|
|
if (!inInstanceContext) {
|
|
error(node.receiver, MessageKind.NO_INSTANCE_AVAILABLE, [name]);
|
|
return null;
|
|
}
|
|
if (currentClass.supertype === null) {
|
|
// This is just to guard against internal errors, so no need
|
|
// for a real error message.
|
|
error(node.receiver, MessageKind.GENERIC, ["Object has no superclass"]);
|
|
}
|
|
target = currentClass.lookupSuperMember(name);
|
|
// [target] may be null which means invoking noSuchMethod on
|
|
// super.
|
|
} else if (resolvedReceiver === null) {
|
|
return null;
|
|
} else if (resolvedReceiver.kind === ElementKind.CLASS) {
|
|
ClassElement receiverClass = resolvedReceiver;
|
|
target = receiverClass.ensureResolved(compiler).lookupLocalMember(name);
|
|
if (target === null) {
|
|
error(node, MessageKind.METHOD_NOT_FOUND, [receiverClass.name, name]);
|
|
} else if (target.isInstanceMember()) {
|
|
error(node, MessageKind.MEMBER_NOT_STATIC, [receiverClass.name, name]);
|
|
}
|
|
} else if (resolvedReceiver.kind === ElementKind.PREFIX) {
|
|
PrefixElement prefix = resolvedReceiver;
|
|
target = prefix.lookupLocalMember(name);
|
|
if (target == null) {
|
|
error(node, MessageKind.NO_SUCH_LIBRARY_MEMBER, [prefix.name, name]);
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
|
|
Type resolveTypeTest(Node argument) {
|
|
TypeAnnotation node = argument.asTypeAnnotation();
|
|
if (node === null) {
|
|
// node is of the form !Type.
|
|
node = argument.asSend().receiver.asTypeAnnotation();
|
|
if (node === null) compiler.cancel("malformed send");
|
|
}
|
|
return resolveTypeRequired(node);
|
|
}
|
|
|
|
static Selector computeSendSelector(Send node, LibraryElement library) {
|
|
// First determine if this is part of an assignment.
|
|
bool isSet = node.asSendSet() !== null;
|
|
|
|
if (node.isIndex) {
|
|
return isSet ? new Selector.indexSet() : new Selector.index();
|
|
}
|
|
|
|
if (node.isOperator) {
|
|
SourceString source = node.selector.asOperator().source;
|
|
String string = source.stringValue;
|
|
if (string === '!' || string === '&&' || string == '||' ||
|
|
string === 'is' || string === 'as' ||
|
|
string === '===' || string === '!==' ||
|
|
string === '>>>') {
|
|
return null;
|
|
}
|
|
return node.arguments.isEmpty()
|
|
? new Selector.unaryOperator(source)
|
|
: new Selector.binaryOperator(source);
|
|
}
|
|
|
|
Identifier identifier = node.selector.asIdentifier();
|
|
if (node.isPropertyAccess) {
|
|
assert(!isSet);
|
|
return new Selector.getter(identifier.source, library);
|
|
} else if (isSet) {
|
|
return new Selector.setter(identifier.source, library);
|
|
}
|
|
|
|
// Compute the arity and the list of named arguments.
|
|
int arity = 0;
|
|
List<SourceString> named = <SourceString>[];
|
|
for (Link<Node> link = node.argumentsNode.nodes;
|
|
!link.isEmpty();
|
|
link = link.tail) {
|
|
Expression argument = link.head;
|
|
NamedArgument namedArgument = argument.asNamedArgument();
|
|
if (namedArgument !== null) {
|
|
named.add(namedArgument.name.source);
|
|
}
|
|
arity++;
|
|
}
|
|
|
|
// If we're invoking a closure, we do not have an identifier.
|
|
return (identifier === null)
|
|
? new Selector.callClosure(arity, named)
|
|
: new Selector.call(identifier.source, library, arity, named);
|
|
}
|
|
|
|
Selector resolveSelector(Send node) {
|
|
LibraryElement library = enclosingElement.getLibrary();
|
|
Selector selector = computeSendSelector(node, library);
|
|
if (selector != null) mapping.setSelector(node, selector);
|
|
return selector;
|
|
}
|
|
|
|
void resolveArguments(NodeList list) {
|
|
if (list === null) return;
|
|
bool seenNamedArgument = false;
|
|
for (Link<Node> link = list.nodes; !link.isEmpty(); link = link.tail) {
|
|
Expression argument = link.head;
|
|
visit(argument);
|
|
if (argument.asNamedArgument() != null) {
|
|
seenNamedArgument = true;
|
|
} else if (seenNamedArgument) {
|
|
error(argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED);
|
|
}
|
|
}
|
|
}
|
|
|
|
visitSend(Send node) {
|
|
Element target = resolveSend(node);
|
|
if (target != null && target.kind == ElementKind.ABSTRACT_FIELD) {
|
|
AbstractFieldElement field = target;
|
|
target = field.getter;
|
|
}
|
|
|
|
bool resolvedArguments = false;
|
|
if (node.isOperator) {
|
|
String operatorString = node.selector.asOperator().source.stringValue;
|
|
if (operatorString === 'is' || operatorString === 'as') {
|
|
assert(node.arguments.tail.isEmpty());
|
|
resolveTypeTest(node.arguments.head);
|
|
resolvedArguments = true;
|
|
}
|
|
}
|
|
|
|
if (!resolvedArguments) {
|
|
resolveArguments(node.argumentsNode);
|
|
}
|
|
|
|
// If the selector is null, it means that we will not be generating
|
|
// code for this as a send.
|
|
Selector selector = mapping.getSelector(node);
|
|
if (selector === null) return;
|
|
|
|
// If we don't know what we're calling or if we are calling a getter,
|
|
// we need to register that fact that we may be calling a closure
|
|
// with the same arguments.
|
|
if (node.isCall &&
|
|
(target === null ||
|
|
target.isGetter() ||
|
|
Elements.isClosureSend(node, target))) {
|
|
Selector call = new Selector.callClosureFrom(selector);
|
|
world.registerDynamicInvocation(call.name, call);
|
|
}
|
|
|
|
// TODO(ngeoffray): We should do the check in
|
|
// visitExpressionStatement instead.
|
|
if (target === compiler.assertMethod && !node.isCall) {
|
|
// We can only use assert by calling it.
|
|
if (!inInstanceContext) {
|
|
error(node, MessageKind.MISSING_ARGUMENTS_TO_ASSERT, [node]);
|
|
}
|
|
target = null;
|
|
}
|
|
|
|
// TODO(ngeoffray): Warn if target is null and the send is
|
|
// unqualified.
|
|
useElement(node, target);
|
|
registerSend(selector, target);
|
|
return node.isPropertyAccess ? target : null;
|
|
}
|
|
|
|
visitSendSet(SendSet node) {
|
|
Element target = resolveSend(node);
|
|
Element setter = target;
|
|
Element getter = target;
|
|
if (target != null && target.kind == ElementKind.ABSTRACT_FIELD) {
|
|
AbstractFieldElement field = target;
|
|
setter = field.setter;
|
|
getter = field.getter;
|
|
}
|
|
|
|
visit(node.argumentsNode);
|
|
|
|
// TODO(ngeoffray): Check if the target can be assigned.
|
|
// TODO(ngeoffray): Warn if target is null and the send is
|
|
// unqualified.
|
|
|
|
Selector selector = mapping.getSelector(node);
|
|
String source = node.assignmentOperator.source.stringValue;
|
|
bool isComplex = source !== '=';
|
|
if (isComplex) {
|
|
if (selector.isSetter()) {
|
|
// TODO(kasperl): We're registering the getter selector for
|
|
// compound assignments on the AST selector node. In the code
|
|
// generator, we then fetch it from there when generating the
|
|
// getter for a SendSet node.
|
|
Selector getterSelector = new Selector.getterFrom(selector);
|
|
registerSend(getterSelector, getter);
|
|
mapping.setSelector(node.selector, getterSelector);
|
|
useElement(node.selector, getter);
|
|
} else {
|
|
// TODO(kasperl): If [getter] is resolved, it will actually
|
|
// refer to the []= operator which isn't the one we want to
|
|
// register here. We should consider using some notion of
|
|
// abstract indexable element that we can resolve to so we can
|
|
// distinguish the two.
|
|
assert(selector.isIndexSet());
|
|
registerSend(new Selector.index(), null);
|
|
}
|
|
|
|
// Make sure we include the + and - operators if we are using
|
|
// the ++ and -- ones.
|
|
void registerBinaryOperator(SourceString name) {
|
|
Selector binop = new Selector.binaryOperator(name);
|
|
world.registerDynamicInvocation(binop.name, binop);
|
|
}
|
|
if (source === '++') registerBinaryOperator(const SourceString('+'));
|
|
if (source === '--') registerBinaryOperator(const SourceString('-'));
|
|
}
|
|
|
|
registerSend(selector, setter);
|
|
return useElement(node, setter);
|
|
}
|
|
|
|
void registerSend(Selector selector, Element target) {
|
|
if (target === null || target.isInstanceMember()) {
|
|
if (selector.isGetter()) {
|
|
world.registerDynamicGetter(selector.name, selector);
|
|
} else if (selector.isSetter()) {
|
|
world.registerDynamicSetter(selector.name, selector);
|
|
} else {
|
|
world.registerDynamicInvocation(selector.name, selector);
|
|
}
|
|
} else if (Elements.isStaticOrTopLevel(target)) {
|
|
// TODO(kasperl): It seems like we're not supposed to register
|
|
// the use of classes. Wouldn't it be simpler if we just did?
|
|
if (!target.isClass()) world.registerStaticUse(target);
|
|
}
|
|
|
|
// TODO(kasperl): Pass the selector directly.
|
|
var interceptor = new Interceptors(compiler).getStaticInterceptor(
|
|
selector.name,
|
|
selector.argumentCount);
|
|
if (interceptor !== null) {
|
|
world.registerStaticUse(interceptor);
|
|
}
|
|
}
|
|
|
|
visitLiteralInt(LiteralInt node) {
|
|
}
|
|
|
|
visitLiteralDouble(LiteralDouble node) {
|
|
}
|
|
|
|
visitLiteralBool(LiteralBool node) {
|
|
}
|
|
|
|
visitLiteralString(LiteralString node) {
|
|
}
|
|
|
|
visitLiteralNull(LiteralNull node) {
|
|
}
|
|
|
|
visitStringJuxtaposition(StringJuxtaposition node) {
|
|
node.visitChildren(this);
|
|
}
|
|
|
|
visitNodeList(NodeList node) {
|
|
for (Link<Node> link = node.nodes; !link.isEmpty(); link = link.tail) {
|
|
visit(link.head);
|
|
}
|
|
}
|
|
|
|
visitOperator(Operator node) {
|
|
unimplemented(node, 'operator');
|
|
}
|
|
|
|
visitReturn(Return node) {
|
|
visit(node.expression);
|
|
}
|
|
|
|
visitThrow(Throw node) {
|
|
visit(node.expression);
|
|
}
|
|
|
|
visitVariableDefinitions(VariableDefinitions node) {
|
|
visit(node.type);
|
|
VariableDefinitionsVisitor visitor =
|
|
new VariableDefinitionsVisitor(compiler, node, this,
|
|
ElementKind.VARIABLE);
|
|
visitor.visit(node.definitions);
|
|
}
|
|
|
|
visitWhile(While node) {
|
|
visit(node.condition);
|
|
visitLoopBodyIn(node, node.body, new BlockScope(scope));
|
|
}
|
|
|
|
visitParenthesizedExpression(ParenthesizedExpression node) {
|
|
visit(node.expression);
|
|
}
|
|
|
|
visitNewExpression(NewExpression node) {
|
|
Node selector = node.send.selector;
|
|
FunctionElement constructor = resolveConstructor(node);
|
|
resolveSelector(node.send);
|
|
resolveArguments(node.send.argumentsNode);
|
|
useElement(node.send, constructor);
|
|
if (Element.isInvalid(constructor)) return constructor;
|
|
// TODO(karlklose): handle optional arguments.
|
|
if (node.send.argumentCount() != constructor.parameterCount(compiler)) {
|
|
// TODO(ngeoffray): resolution error with wrong number of
|
|
// parameters. We cannot do this rigth now because of the
|
|
// List constructor.
|
|
}
|
|
world.registerStaticUse(constructor);
|
|
compiler.withCurrentElement(constructor, () {
|
|
FunctionExpression tree = constructor.parseNode(compiler);
|
|
compiler.resolver.resolveConstructorImplementation(constructor, tree);
|
|
});
|
|
world.registerStaticUse(constructor.defaultImplementation);
|
|
ClassElement cls = constructor.defaultImplementation.getEnclosingClass();
|
|
world.registerInstantiatedClass(cls);
|
|
cls.forEachInstanceField(
|
|
includeBackendMembers: false,
|
|
includeSuperMembers: true,
|
|
f: (ClassElement enclosingClass, Element member) {
|
|
world.addToWorkList(member);
|
|
});
|
|
return null;
|
|
}
|
|
|
|
TypeAnnotation getTypeAnnotationFromSend(Send send) {
|
|
if (send.selector.asTypeAnnotation() !== null) {
|
|
return send.selector;
|
|
} else if (send.selector.asSend() !== null) {
|
|
Send selector = send.selector;
|
|
if (selector.receiver.asTypeAnnotation() !== null) {
|
|
return selector.receiver;
|
|
}
|
|
} else {
|
|
compiler.internalError("malformed send in new expression");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Try to resolve the constructor that is referred to by [node].
|
|
* Note: this function may return an ErroneousFunctionElement instead of
|
|
* [null], if there is no corresponding constructor, class or library.
|
|
*/
|
|
FunctionElement resolveConstructor(NewExpression node) {
|
|
FunctionElement constructor =
|
|
node.accept(new ConstructorResolver(compiler, this));
|
|
TypeAnnotation annotation = getTypeAnnotationFromSend(node.send);
|
|
// TODO(karlklose): clean up: the type should be resolved in the
|
|
// constructor resolver visitor to avoid visiting the node twice.
|
|
resolveTypeRequired(annotation);
|
|
return constructor;
|
|
}
|
|
|
|
Type resolveTypeRequired(TypeAnnotation node) {
|
|
bool old = typeRequired;
|
|
typeRequired = true;
|
|
Type result = resolveTypeAnnotation(node);
|
|
typeRequired = old;
|
|
return result;
|
|
}
|
|
|
|
Type resolveTypeAnnotation(TypeAnnotation node) {
|
|
Function report = typeRequired ? error : warning;
|
|
return typeResolver.resolveTypeAnnotation(node, inScope: scope,
|
|
onFailure: report,
|
|
whenResolved: useType);
|
|
}
|
|
|
|
visitModifiers(Modifiers node) {
|
|
// TODO(ngeoffray): Implement this.
|
|
unimplemented(node, 'modifiers');
|
|
}
|
|
|
|
visitLiteralList(LiteralList node) {
|
|
visit(node.elements);
|
|
}
|
|
|
|
visitConditional(Conditional node) {
|
|
node.visitChildren(this);
|
|
}
|
|
|
|
visitStringInterpolation(StringInterpolation node) {
|
|
node.visitChildren(this);
|
|
}
|
|
|
|
visitStringInterpolationPart(StringInterpolationPart node) {
|
|
SourceString name = const SourceString('toString');
|
|
LibraryElement library = enclosingElement.getLibrary();
|
|
Selector selector = new Selector.call(name, library, 0);
|
|
world.registerDynamicInvocation(name, selector);
|
|
node.visitChildren(this);
|
|
}
|
|
|
|
visitBreakStatement(BreakStatement node) {
|
|
TargetElement target;
|
|
if (node.target === null) {
|
|
target = statementScope.currentBreakTarget();
|
|
if (target === null) {
|
|
error(node, MessageKind.NO_BREAK_TARGET);
|
|
return;
|
|
}
|
|
target.isBreakTarget = true;
|
|
} else {
|
|
String labelName = node.target.source.slowToString();
|
|
LabelElement label = statementScope.lookupLabel(labelName);
|
|
if (label === null) {
|
|
error(node.target, MessageKind.UNBOUND_LABEL, [labelName]);
|
|
return;
|
|
}
|
|
target = label.target;
|
|
if (!target.statement.isValidBreakTarget()) {
|
|
error(node.target, MessageKind.INVALID_BREAK, [labelName]);
|
|
return;
|
|
}
|
|
label.setBreakTarget();
|
|
mapping[node.target] = label;
|
|
}
|
|
mapping[node] = target;
|
|
}
|
|
|
|
visitContinueStatement(ContinueStatement node) {
|
|
TargetElement target;
|
|
if (node.target === null) {
|
|
target = statementScope.currentContinueTarget();
|
|
if (target === null) {
|
|
error(node, MessageKind.NO_CONTINUE_TARGET);
|
|
return;
|
|
}
|
|
target.isContinueTarget = true;
|
|
} else {
|
|
String labelName = node.target.source.slowToString();
|
|
LabelElement label = statementScope.lookupLabel(labelName);
|
|
if (label === null) {
|
|
error(node.target, MessageKind.UNBOUND_LABEL, [labelName]);
|
|
return;
|
|
}
|
|
target = label.target;
|
|
if (!target.statement.isValidContinueTarget()) {
|
|
error(node.target, MessageKind.INVALID_CONTINUE, [labelName]);
|
|
}
|
|
// TODO(lrn): Handle continues to switch cases.
|
|
if (target.statement is SwitchCase) {
|
|
unimplemented(node, "continue to switch case");
|
|
}
|
|
label.setContinueTarget();
|
|
mapping[node.target] = label;
|
|
}
|
|
mapping[node] = target;
|
|
}
|
|
|
|
visitForIn(ForIn node) {
|
|
visit(node.expression);
|
|
Scope blockScope = new BlockScope(scope);
|
|
Node declaration = node.declaredIdentifier;
|
|
visitIn(declaration, blockScope);
|
|
visitLoopBodyIn(node, node.body, blockScope);
|
|
|
|
// TODO(lrn): Also allow a single identifier.
|
|
if ((declaration is !Send || declaration.asSend().selector is !Identifier)
|
|
&& (declaration is !VariableDefinitions ||
|
|
!declaration.asVariableDefinitions().definitions.nodes.tail.isEmpty()))
|
|
{
|
|
// The variable declaration is either not an identifier, not a
|
|
// declaration, or it's declaring more than one variable.
|
|
error(node.declaredIdentifier, MessageKind.INVALID_FOR_IN, []);
|
|
}
|
|
}
|
|
|
|
visitLabel(Label node) {
|
|
// Labels are handled by their containing statements/cases.
|
|
}
|
|
|
|
visitLabeledStatement(LabeledStatement node) {
|
|
Statement body = node.statement;
|
|
TargetElement targetElement = getOrCreateTargetElement(body);
|
|
Map<String, LabelElement> labelElements = <String, LabelElement>{};
|
|
for (Label label in node.labels) {
|
|
String labelName = label.slowToString();
|
|
if (labelElements.containsKey(labelName)) continue;
|
|
LabelElement element = targetElement.addLabel(label, labelName);
|
|
labelElements[labelName] = element;
|
|
}
|
|
statementScope.enterLabelScope(labelElements);
|
|
visit(node.statement);
|
|
statementScope.exitLabelScope();
|
|
labelElements.forEach((String labelName, LabelElement element) {
|
|
if (element.isTarget) {
|
|
mapping[element.label] = element;
|
|
} else {
|
|
warning(element.label, MessageKind.UNUSED_LABEL, [labelName]);
|
|
}
|
|
});
|
|
if (!targetElement.isTarget && mapping[body] === targetElement) {
|
|
// If the body is itself a break or continue for another target, it
|
|
// might have updated its mapping to the target it actually does target.
|
|
mapping.remove(body);
|
|
}
|
|
}
|
|
|
|
visitLiteralMap(LiteralMap node) {
|
|
node.visitChildren(this);
|
|
}
|
|
|
|
visitLiteralMapEntry(LiteralMapEntry node) {
|
|
node.visitChildren(this);
|
|
}
|
|
|
|
visitNamedArgument(NamedArgument node) {
|
|
visit(node.expression);
|
|
}
|
|
|
|
visitSwitchStatement(SwitchStatement node) {
|
|
node.expression.accept(this);
|
|
|
|
TargetElement breakElement = getOrCreateTargetElement(node);
|
|
Map<String, LabelElement> continueLabels = <String, LabelElement>{};
|
|
Link<Node> cases = node.cases.nodes;
|
|
while (!cases.isEmpty()) {
|
|
SwitchCase switchCase = cases.head;
|
|
for (Node labelOrCase in switchCase.labelsAndCases) {
|
|
if (labelOrCase is! Label) continue;
|
|
Label label = labelOrCase;
|
|
String labelName = label.slowToString();
|
|
|
|
LabelElement existingElement = continueLabels[labelName];
|
|
if (existingElement !== null) {
|
|
// It's an error if the same label occurs twice in the same switch.
|
|
warning(label, MessageKind.DUPLICATE_LABEL, [labelName]);
|
|
error(existingElement.label, MessageKind.EXISTING_LABEL, [labelName]);
|
|
} else {
|
|
// It's only a warning if it shadows another label.
|
|
existingElement = statementScope.lookupLabel(labelName);
|
|
if (existingElement !== null) {
|
|
warning(label, MessageKind.DUPLICATE_LABEL, [labelName]);
|
|
warning(existingElement.label,
|
|
MessageKind.EXISTING_LABEL, [labelName]);
|
|
}
|
|
}
|
|
|
|
TargetElement targetElement =
|
|
new TargetElement(switchCase,
|
|
statementScope.nestingLevel,
|
|
enclosingElement);
|
|
mapping[switchCase] = targetElement;
|
|
|
|
LabelElement labelElement =
|
|
new LabelElement(label, labelName,
|
|
targetElement, enclosingElement);
|
|
mapping[label] = labelElement;
|
|
continueLabels[labelName] = labelElement;
|
|
}
|
|
cases = cases.tail;
|
|
// Test that only the last case, if any, is a default case.
|
|
if (switchCase.defaultKeyword !== null && !cases.isEmpty()) {
|
|
error(switchCase, MessageKind.INVALID_CASE_DEFAULT);
|
|
}
|
|
}
|
|
|
|
statementScope.enterSwitch(breakElement, continueLabels);
|
|
node.cases.accept(this);
|
|
statementScope.exitSwitch();
|
|
|
|
// Clean-up unused labels.
|
|
continueLabels.forEach((String key, LabelElement label) {
|
|
if (!label.isContinueTarget) {
|
|
TargetElement targetElement = label.target;
|
|
SwitchCase switchCase = targetElement.statement;
|
|
mapping.remove(switchCase);
|
|
mapping.remove(label.label);
|
|
}
|
|
});
|
|
}
|
|
|
|
visitSwitchCase(SwitchCase node) {
|
|
node.labelsAndCases.accept(this);
|
|
visitIn(node.statements, new BlockScope(scope));
|
|
}
|
|
|
|
visitCaseMatch(CaseMatch node) {
|
|
visit(node.expression);
|
|
}
|
|
|
|
visitTryStatement(TryStatement node) {
|
|
visit(node.tryBlock);
|
|
if (node.catchBlocks.isEmpty() && node.finallyBlock == null) {
|
|
// TODO(ngeoffray): The precise location is
|
|
// node.getEndtoken.next. Adjust when issue #1581 is fixed.
|
|
error(node, MessageKind.NO_CATCH_NOR_FINALLY);
|
|
}
|
|
visit(node.catchBlocks);
|
|
visit(node.finallyBlock);
|
|
}
|
|
|
|
visitCatchBlock(CatchBlock node) {
|
|
Scope blockScope = new BlockScope(scope);
|
|
if (node.formals.isEmpty()) {
|
|
error(node, MessageKind.EMPTY_CATCH_DECLARATION);
|
|
} else if (!node.formals.nodes.tail.isEmpty()
|
|
&& !node.formals.nodes.tail.tail.isEmpty()) {
|
|
for (Node extra in node.formals.nodes.tail.tail) {
|
|
error(extra, MessageKind.EXTRA_CATCH_DECLARATION);
|
|
}
|
|
}
|
|
visitIn(node.type, blockScope);
|
|
visitIn(node.formals, blockScope);
|
|
visitIn(node.block, blockScope);
|
|
}
|
|
|
|
visitTypedef(Typedef node) {
|
|
unimplemented(node, 'typedef');
|
|
}
|
|
}
|
|
|
|
class TypeDefinitionVisitor extends CommonResolverVisitor<Type> {
|
|
Scope scope;
|
|
TypeDeclarationElement element;
|
|
TypeResolver typeResolver;
|
|
|
|
TypeDefinitionVisitor(Compiler compiler, TypeDeclarationElement element)
|
|
: this.element = element,
|
|
scope = element.buildEnclosingScope(),
|
|
typeResolver = new TypeResolver(compiler),
|
|
super(compiler);
|
|
|
|
void resolveTypeVariableBounds(NodeList node) {
|
|
if (node === null) return;
|
|
|
|
var nameSet = new Set<SourceString>();
|
|
// Resolve the bounds of type variables.
|
|
Link<Type> typeLink = element.typeVariables;
|
|
Link<Node> nodeLink = node.nodes;
|
|
while (!nodeLink.isEmpty()) {
|
|
TypeVariableType typeVariable = typeLink.head;
|
|
SourceString typeName = typeVariable.name;
|
|
TypeVariable typeNode = nodeLink.head;
|
|
if (nameSet.contains(typeName)) {
|
|
error(typeNode, MessageKind.DUPLICATE_TYPE_VARIABLE_NAME, [typeName]);
|
|
}
|
|
nameSet.add(typeName);
|
|
|
|
TypeVariableElement variableElement = typeVariable.element;
|
|
if (typeNode.bound !== null) {
|
|
Type boundType = typeResolver.resolveTypeAnnotation(
|
|
typeNode.bound, inScope: scope, onFailure: warning);
|
|
if (boundType !== null && boundType.element == variableElement) {
|
|
// TODO(johnniwinther): Check for more general cycles, like
|
|
// [: <A extends B, B extends C, C extends B> :].
|
|
warning(node, MessageKind.CYCLIC_TYPE_VARIABLE,
|
|
[variableElement.name]);
|
|
} else if (boundType !== null) {
|
|
variableElement.bound = boundType;
|
|
} else {
|
|
// TODO(johnniwinther): Should be an erroneous type.
|
|
variableElement.bound = compiler.objectClass.computeType(compiler);
|
|
}
|
|
} else {
|
|
variableElement.bound = compiler.objectClass.computeType(compiler);
|
|
}
|
|
nodeLink = nodeLink.tail;
|
|
typeLink = typeLink.tail;
|
|
}
|
|
assert(typeLink.isEmpty());
|
|
}
|
|
}
|
|
|
|
class TypedefResolverVisitor extends TypeDefinitionVisitor {
|
|
TypedefElement get element() => super.element;
|
|
|
|
TypedefResolverVisitor(Compiler compiler, TypedefElement typedefElement)
|
|
: super(compiler, typedefElement);
|
|
|
|
visitTypedef(Typedef node) {
|
|
TypedefType type = element.computeType(compiler);
|
|
scope = new TypeDeclarationScope(scope, element);
|
|
resolveTypeVariableBounds(node.typeParameters);
|
|
|
|
element.functionSignature = SignatureResolver.analyze(
|
|
compiler, node.formals, node.returnType, element);
|
|
|
|
element.alias = compiler.computeFunctionType(
|
|
element, element.functionSignature);
|
|
|
|
// TODO(johnniwinther): Check for cyclic references in the typedef alias.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The implementation of [ResolverTask.resolveClass].
|
|
*
|
|
* This visitor has to be extra careful as it is building the basic
|
|
* element information, and cannot safely look at other elements as
|
|
* this may lead to cycles.
|
|
*
|
|
* This visitor can assume that the supertypes have already been
|
|
* resolved, but it cannot call [ResolverTask.resolveClass] directly
|
|
* or indirectly (through [ClassElement.ensureResolved]) for any other
|
|
* types.
|
|
*/
|
|
class ClassResolverVisitor extends TypeDefinitionVisitor {
|
|
ClassElement get element() => super.element;
|
|
|
|
ClassResolverVisitor(Compiler compiler, ClassElement classElement)
|
|
: super(compiler, classElement);
|
|
|
|
Type visitClassNode(ClassNode node) {
|
|
compiler.ensure(element !== null);
|
|
compiler.ensure(element.resolutionState == ClassElement.STATE_STARTED);
|
|
|
|
InterfaceType type = element.computeType(compiler);
|
|
scope = new TypeDeclarationScope(scope, element);
|
|
// TODO(ahe): It is not safe to call resolveTypeVariableBounds yet.
|
|
// As a side-effect, this may get us back here trying to
|
|
// resolve this class again.
|
|
resolveTypeVariableBounds(node.typeParameters);
|
|
|
|
// Find super type.
|
|
Type supertype = visit(node.superclass);
|
|
if (supertype !== null && supertype.element.isExtendable()) {
|
|
element.supertype = supertype;
|
|
if (isBlackListed(supertype)) {
|
|
error(node.superclass, MessageKind.CANNOT_EXTEND, [supertype]);
|
|
}
|
|
} else if (supertype !== null) {
|
|
error(node.superclass, MessageKind.TYPE_NAME_EXPECTED);
|
|
}
|
|
final objectElement = compiler.objectClass;
|
|
if (element !== objectElement && element.supertype === null) {
|
|
if (objectElement === null) {
|
|
compiler.internalError("Internal error: cannot resolve Object",
|
|
node: node);
|
|
} else {
|
|
objectElement.ensureResolved(compiler);
|
|
}
|
|
// TODO(ahe): This should be objectElement.computeType(...).
|
|
element.supertype = new InterfaceType(objectElement);
|
|
}
|
|
assert(element.interfaces === null);
|
|
Link<Type> interfaces = const EmptyLink<Type>();
|
|
for (Link<Node> link = node.interfaces.nodes;
|
|
!link.isEmpty();
|
|
link = link.tail) {
|
|
Type interfaceType = visit(link.head);
|
|
if (interfaceType !== null && interfaceType.element.isExtendable()) {
|
|
interfaces = interfaces.prepend(interfaceType);
|
|
if (isBlackListed(interfaceType)) {
|
|
error(link.head, MessageKind.CANNOT_IMPLEMENT, [interfaceType]);
|
|
}
|
|
} else {
|
|
error(link.head, MessageKind.TYPE_NAME_EXPECTED);
|
|
}
|
|
}
|
|
element.interfaces = interfaces;
|
|
calculateAllSupertypes(element);
|
|
|
|
if (node.defaultClause !== null) {
|
|
element.defaultClass = visit(node.defaultClause);
|
|
}
|
|
addDefaultConstructorIfNeeded(element);
|
|
return element.computeType(compiler);
|
|
}
|
|
|
|
Type visitTypeAnnotation(TypeAnnotation node) {
|
|
return visit(node.typeName);
|
|
}
|
|
|
|
Type visitIdentifier(Identifier node) {
|
|
Element element = scope.lookup(node.source);
|
|
if (element === null) {
|
|
error(node, MessageKind.CANNOT_RESOLVE_TYPE, [node]);
|
|
return null;
|
|
} else if (!element.impliesType() && !element.isTypeVariable()) {
|
|
error(node, MessageKind.NOT_A_TYPE, [node]);
|
|
return null;
|
|
} else {
|
|
if (element.isTypeVariable()) {
|
|
TypeVariableElement variableElement = element;
|
|
return variableElement.type;
|
|
} else if (element.isTypedef()) {
|
|
compiler.unimplemented('visitIdentifier for typedefs', node: node);
|
|
} else {
|
|
// TODO(ngeoffray): Use type variables.
|
|
return element.computeType(compiler);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Type visitSend(Send node) {
|
|
Identifier prefix = node.receiver.asIdentifier();
|
|
if (prefix === null) {
|
|
error(node.receiver, MessageKind.NOT_A_PREFIX, [node.receiver]);
|
|
return null;
|
|
}
|
|
Element element = scope.lookup(prefix.source);
|
|
if (element === null || element.kind !== ElementKind.PREFIX) {
|
|
error(node.receiver, MessageKind.NOT_A_PREFIX, [node.receiver]);
|
|
return null;
|
|
}
|
|
PrefixElement prefixElement = element;
|
|
Identifier selector = node.selector.asIdentifier();
|
|
var e = prefixElement.lookupLocalMember(selector.source);
|
|
if (e === null || !e.impliesType()) {
|
|
error(node.selector, MessageKind.CANNOT_RESOLVE_TYPE, [node.selector]);
|
|
return null;
|
|
}
|
|
return e.computeType(compiler);
|
|
}
|
|
|
|
void calculateAllSupertypes(ClassElement cls) {
|
|
// TODO(karlklose): substitute type variables.
|
|
// TODO(karlklose): check if type arguments match, if a classelement occurs
|
|
// more than once in the supertypes.
|
|
if (cls.allSupertypes !== null) return;
|
|
final Type supertype = cls.supertype;
|
|
if (supertype != null) {
|
|
ClassElement superElement = supertype.element;
|
|
Link<Type> superSupertypes = superElement.allSupertypes;
|
|
assert(superSupertypes !== null);
|
|
Link<Type> supertypes = new Link<Type>(supertype, superSupertypes);
|
|
for (Link<Type> interfaces = cls.interfaces;
|
|
!interfaces.isEmpty();
|
|
interfaces = interfaces.tail) {
|
|
ClassElement element = interfaces.head.element;
|
|
Link<Type> interfaceSupertypes = element.allSupertypes;
|
|
assert(interfaceSupertypes !== null);
|
|
supertypes = supertypes.reversePrependAll(interfaceSupertypes);
|
|
supertypes = supertypes.prepend(interfaces.head);
|
|
}
|
|
cls.allSupertypes = supertypes;
|
|
} else {
|
|
assert(cls === compiler.objectClass);
|
|
cls.allSupertypes = const EmptyLink<Type>();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a synthetic nullary constructor if there are no other
|
|
* constructors.
|
|
*/
|
|
void addDefaultConstructorIfNeeded(ClassElement element) {
|
|
if (element.hasConstructor) return;
|
|
SynthesizedConstructorElement constructor =
|
|
new SynthesizedConstructorElement(element);
|
|
element.addToScope(constructor, compiler);
|
|
Type returnType = compiler.types.voidType;
|
|
constructor.type = new FunctionType(returnType, const EmptyLink<Type>(),
|
|
constructor);
|
|
constructor.cachedNode =
|
|
new FunctionExpression(new Identifier(element.position()),
|
|
new NodeList.empty(),
|
|
new Block(new NodeList.empty()),
|
|
null, null, null, null);
|
|
}
|
|
|
|
isBlackListed(Type type) {
|
|
LibraryElement lib = element.getLibrary();
|
|
return
|
|
lib !== compiler.coreLibrary &&
|
|
lib !== compiler.coreImplLibrary &&
|
|
lib !== compiler.jsHelperLibrary &&
|
|
(type.element === compiler.dynamicClass ||
|
|
type.element === compiler.boolClass ||
|
|
type.element === compiler.numClass ||
|
|
type.element === compiler.intClass ||
|
|
type.element === compiler.doubleClass ||
|
|
type.element === compiler.stringClass ||
|
|
type.element === compiler.nullClass ||
|
|
type.element === compiler.functionClass);
|
|
}
|
|
}
|
|
|
|
class ClassSupertypeResolver extends CommonResolverVisitor {
|
|
Scope context;
|
|
ClassElement classElement;
|
|
|
|
ClassSupertypeResolver(Compiler compiler, ClassElement cls)
|
|
: context = new TopScope(cls.getLibrary()),
|
|
this.classElement = cls,
|
|
super(compiler);
|
|
|
|
void loadSupertype(ClassElement element, Node from) {
|
|
compiler.resolver.loadSupertypes(element, from);
|
|
element.ensureResolved(compiler);
|
|
}
|
|
|
|
void visitClassNode(ClassNode node) {
|
|
if (node.superclass === null) {
|
|
if (classElement !== compiler.objectClass) {
|
|
loadSupertype(compiler.objectClass, node);
|
|
}
|
|
} else {
|
|
node.superclass.accept(this);
|
|
}
|
|
for (Link<Node> link = node.interfaces.nodes;
|
|
!link.isEmpty();
|
|
link = link.tail) {
|
|
link.head.accept(this);
|
|
}
|
|
}
|
|
|
|
void visitTypeAnnotation(TypeAnnotation node) {
|
|
node.typeName.accept(this);
|
|
}
|
|
|
|
void visitIdentifier(Identifier node) {
|
|
Element element = context.lookup(node.source);
|
|
if (element === null) {
|
|
error(node, MessageKind.CANNOT_RESOLVE_TYPE, [node]);
|
|
} else if (!element.impliesType()) {
|
|
error(node, MessageKind.NOT_A_TYPE, [node]);
|
|
} else {
|
|
if (element.isClass()) {
|
|
loadSupertype(element, node);
|
|
} else {
|
|
compiler.reportMessage(
|
|
compiler.spanFromNode(node),
|
|
MessageKind.TYPE_NAME_EXPECTED.error([]),
|
|
api.Diagnostic.ERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
void visitSend(Send node) {
|
|
Identifier prefix = node.receiver.asIdentifier();
|
|
if (prefix === null) {
|
|
error(node.receiver, MessageKind.NOT_A_PREFIX, [node.receiver]);
|
|
return;
|
|
}
|
|
Element element = context.lookup(prefix.source);
|
|
if (element === null || element.kind !== ElementKind.PREFIX) {
|
|
error(node.receiver, MessageKind.NOT_A_PREFIX, [node.receiver]);
|
|
return;
|
|
}
|
|
PrefixElement prefixElement = element;
|
|
Identifier selector = node.selector.asIdentifier();
|
|
var e = prefixElement.lookupLocalMember(selector.source);
|
|
if (e === null || !e.impliesType()) {
|
|
error(node.selector, MessageKind.CANNOT_RESOLVE_TYPE, [node.selector]);
|
|
return;
|
|
}
|
|
loadSupertype(e, node);
|
|
}
|
|
}
|
|
|
|
class VariableDefinitionsVisitor extends CommonResolverVisitor<SourceString> {
|
|
VariableDefinitions definitions;
|
|
ResolverVisitor resolver;
|
|
ElementKind kind;
|
|
VariableListElement variables;
|
|
|
|
VariableDefinitionsVisitor(Compiler compiler,
|
|
this.definitions, this.resolver, this.kind)
|
|
: super(compiler) {
|
|
variables = new VariableListElement.node(
|
|
definitions, ElementKind.VARIABLE_LIST, resolver.scope.element);
|
|
}
|
|
|
|
SourceString visitSendSet(SendSet node) {
|
|
assert(node.arguments.tail.isEmpty()); // Sanity check
|
|
resolver.visit(node.arguments.head);
|
|
return visit(node.selector);
|
|
}
|
|
|
|
SourceString visitIdentifier(Identifier node) => node.source;
|
|
|
|
visitNodeList(NodeList node) {
|
|
for (Link<Node> link = node.nodes; !link.isEmpty(); link = link.tail) {
|
|
SourceString name = visit(link.head);
|
|
VariableElement element = new VariableElement(
|
|
name, variables, kind, resolver.scope.element, node: link.head);
|
|
resolver.defineElement(link.head, element);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* [SignatureResolver] resolves function signatures.
|
|
*/
|
|
class SignatureResolver extends CommonResolverVisitor<Element> {
|
|
final Element enclosingElement;
|
|
Link<Element> optionalParameters = const EmptyLink<Element>();
|
|
int optionalParameterCount = 0;
|
|
VariableDefinitions currentDefinitions;
|
|
|
|
SignatureResolver(Compiler compiler, this.enclosingElement) : super(compiler);
|
|
|
|
Element visitNodeList(NodeList node) {
|
|
// This must be a list of optional arguments.
|
|
if (node.beginToken.stringValue !== '[') {
|
|
internalError(node, "expected optional parameters");
|
|
}
|
|
LinkBuilder<Element> elements = analyzeNodes(node.nodes);
|
|
optionalParameterCount = elements.length;
|
|
optionalParameters = elements.toLink();
|
|
return null;
|
|
}
|
|
|
|
Element visitVariableDefinitions(VariableDefinitions node) {
|
|
Link<Node> definitions = node.definitions.nodes;
|
|
if (definitions.isEmpty()) {
|
|
cancel(node, 'internal error: no parameter definition');
|
|
return null;
|
|
}
|
|
if (!definitions.tail.isEmpty()) {
|
|
cancel(definitions.tail.head, 'internal error: extra definition');
|
|
return null;
|
|
}
|
|
Node definition = definitions.head;
|
|
if (definition is NodeList) {
|
|
cancel(node, 'optional parameters are not implemented');
|
|
}
|
|
|
|
if (currentDefinitions != null) {
|
|
cancel(node, 'function type parameters not supported');
|
|
}
|
|
currentDefinitions = node;
|
|
Element element = definition.accept(this);
|
|
currentDefinitions = null;
|
|
return element;
|
|
}
|
|
|
|
Element visitIdentifier(Identifier node) {
|
|
Element variables = new VariableListElement.node(currentDefinitions,
|
|
ElementKind.VARIABLE_LIST, enclosingElement);
|
|
return new VariableElement(node.source, variables,
|
|
ElementKind.PARAMETER, enclosingElement, node: node);
|
|
}
|
|
|
|
SourceString getParameterName(Send node) {
|
|
var identifier = node.selector.asIdentifier();
|
|
if (identifier !== null) {
|
|
// Normal parameter: [:Type name:].
|
|
return identifier.source;
|
|
} else {
|
|
// Function type parameter: [:void name(Type arg):].
|
|
var functionExpression = node.selector.asFunctionExpression();
|
|
if (functionExpression !== null &&
|
|
functionExpression.name.asIdentifier() !== null) {
|
|
return functionExpression.name.asIdentifier().source;
|
|
} else {
|
|
cancel(node,
|
|
'internal error: unimplemented receiver on parameter send');
|
|
}
|
|
}
|
|
}
|
|
|
|
// The only valid [Send] can be in constructors and must be of the form
|
|
// [:this.x:] (where [:x:] represents an instance field).
|
|
FieldParameterElement visitSend(Send node) {
|
|
FieldParameterElement element;
|
|
if (node.receiver.asIdentifier() === null ||
|
|
!node.receiver.asIdentifier().isThis()) {
|
|
error(node, MessageKind.INVALID_PARAMETER, []);
|
|
} else if (enclosingElement.kind !== ElementKind.GENERATIVE_CONSTRUCTOR) {
|
|
error(node, MessageKind.FIELD_PARAMETER_NOT_ALLOWED, []);
|
|
} else {
|
|
SourceString name = getParameterName(node);
|
|
Element fieldElement = currentClass.lookupLocalMember(name);
|
|
if (fieldElement === null || fieldElement.kind !== ElementKind.FIELD) {
|
|
error(node, MessageKind.NOT_A_FIELD, [name]);
|
|
} else if (!fieldElement.isInstanceMember()) {
|
|
error(node, MessageKind.NOT_INSTANCE_FIELD, [name]);
|
|
}
|
|
Element variables = new VariableListElement.node(currentDefinitions,
|
|
ElementKind.VARIABLE_LIST, enclosingElement);
|
|
element = new FieldParameterElement(name,
|
|
fieldElement, variables, enclosingElement, node);
|
|
}
|
|
return element;
|
|
}
|
|
|
|
Element visitSendSet(SendSet node) {
|
|
Element element;
|
|
if (node.receiver != null) {
|
|
element = visitSend(node);
|
|
} else if (node.selector.asIdentifier() != null) {
|
|
Element variables = new VariableListElement.node(currentDefinitions,
|
|
ElementKind.VARIABLE_LIST, enclosingElement);
|
|
element = new VariableElement(node.selector.asIdentifier().source,
|
|
variables, ElementKind.PARAMETER, enclosingElement, node: node);
|
|
}
|
|
// Visit the value. The compile time constant handler will
|
|
// make sure it's a compile time constant.
|
|
resolveExpression(node.arguments.head);
|
|
return element;
|
|
}
|
|
|
|
Element visitFunctionExpression(FunctionExpression node) {
|
|
// This is a function typed parameter.
|
|
// TODO(ahe): Resolve the function type.
|
|
return visit(node.name);
|
|
}
|
|
|
|
LinkBuilder<Element> analyzeNodes(Link<Node> link) {
|
|
LinkBuilder<Element> elements = new LinkBuilder<Element>();
|
|
for (; !link.isEmpty(); link = link.tail) {
|
|
Element element = link.head.accept(this);
|
|
if (element != null) {
|
|
elements.addLast(element);
|
|
} else {
|
|
// If parameter is null, the current node should be the last,
|
|
// and a list of optional named parameters.
|
|
if (!link.tail.isEmpty() || (link.head is !NodeList)) {
|
|
internalError(link.head, "expected optional parameters");
|
|
}
|
|
}
|
|
}
|
|
return elements;
|
|
}
|
|
|
|
/**
|
|
* Resolves formal parameters and return type to a [FunctionSignature].
|
|
*/
|
|
static FunctionSignature analyze(Compiler compiler,
|
|
NodeList formalParameters,
|
|
Node returnNode,
|
|
Element element) {
|
|
SignatureResolver visitor = new SignatureResolver(compiler, element);
|
|
Link<Element> parameters = const EmptyLink<Element>();
|
|
int requiredParameterCount = 0;
|
|
if (formalParameters === null) {
|
|
if (!element.isGetter()) {
|
|
compiler.reportMessage(compiler.spanFromElement(element),
|
|
MessageKind.MISSING_FORMALS.error([]),
|
|
api.Diagnostic.ERROR);
|
|
}
|
|
} else {
|
|
if (element.isGetter()) {
|
|
if (!element.getLibrary().isPlatformLibrary) {
|
|
// TODO(ahe): Remove the isPlatformLibrary check.
|
|
if (formalParameters.getEndToken().next.stringValue !== 'native') {
|
|
// TODO(ahe): Remove the check for native keyword.
|
|
compiler.reportMessage(compiler.spanFromNode(formalParameters),
|
|
MessageKind.EXTRA_FORMALS.error([]),
|
|
api.Diagnostic.WARNING);
|
|
}
|
|
}
|
|
}
|
|
LinkBuilder<Element> parametersBuilder =
|
|
visitor.analyzeNodes(formalParameters.nodes);
|
|
requiredParameterCount = parametersBuilder.length;
|
|
parameters = parametersBuilder.toLink();
|
|
}
|
|
Type returnType = compiler.resolveTypeAnnotation(element, returnNode);
|
|
return new FunctionSignature(parameters,
|
|
visitor.optionalParameters,
|
|
requiredParameterCount,
|
|
visitor.optionalParameterCount,
|
|
returnType);
|
|
}
|
|
|
|
// TODO(ahe): This is temporary.
|
|
void resolveExpression(Node node) {
|
|
if (node == null) return;
|
|
node.accept(new ResolverVisitor(compiler, enclosingElement));
|
|
}
|
|
|
|
// TODO(ahe): This is temporary.
|
|
ClassElement get currentClass() {
|
|
return enclosingElement.isMember()
|
|
? enclosingElement.getEnclosingClass() : null;
|
|
}
|
|
}
|
|
|
|
class ConstructorResolver extends CommonResolverVisitor<Element> {
|
|
final ResolverVisitor resolver;
|
|
|
|
ConstructorResolver(Compiler compiler, this.resolver) : super(compiler);
|
|
|
|
visitNode(Node node) {
|
|
throw 'not supported';
|
|
}
|
|
|
|
FunctionElement lookupConstructor(ClassElement cls,
|
|
Node diagnosticNode,
|
|
SourceString constructorName) {
|
|
cls.ensureResolved(compiler);
|
|
Element result = cls.lookupConstructor(cls.name, constructorName);
|
|
if (result === null) {
|
|
String fullConstructorName = cls.name.slowToString();
|
|
if (constructorName !== const SourceString('')) {
|
|
fullConstructorName = '$fullConstructorName'
|
|
'.${constructorName.slowToString()}';
|
|
}
|
|
ResolutionWarning warning =
|
|
new ResolutionWarning(MessageKind.CANNOT_FIND_CONSTRUCTOR,
|
|
[fullConstructorName]);
|
|
compiler.reportWarning(diagnosticNode, warning);
|
|
return new ErroneousFunctionElement(warning.message, cls);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
visitNewExpression(NewExpression node) {
|
|
Node selector = node.send.selector;
|
|
Element e = visit(selector);
|
|
if (!Element.isInvalid(e) && e.kind === ElementKind.CLASS) {
|
|
ClassElement cls = e;
|
|
cls.ensureResolved(compiler);
|
|
if (cls.isInterface() && (cls.defaultClass === null)) {
|
|
error(selector, MessageKind.CANNOT_INSTANTIATE_INTERFACE, [cls.name]);
|
|
}
|
|
e = lookupConstructor(cls, selector, const SourceString(''));
|
|
}
|
|
return e;
|
|
}
|
|
|
|
visitTypeAnnotation(TypeAnnotation node) {
|
|
// TODO(ahe): Do not ignore type arguments.
|
|
return visit(node.typeName);
|
|
}
|
|
|
|
visitSend(Send node) {
|
|
Element e = visit(node.receiver);
|
|
if (e === null) return null; // TODO(ahe): Return erroneous element.
|
|
|
|
Identifier name = node.selector.asIdentifier();
|
|
if (name === null) internalError(node.selector, 'unexpected node');
|
|
|
|
if (e.kind === ElementKind.CLASS) {
|
|
ClassElement cls = e;
|
|
cls.ensureResolved(compiler);
|
|
if (cls.isInterface() && (cls.defaultClass === null)) {
|
|
error(node.receiver, MessageKind.CANNOT_INSTANTIATE_INTERFACE,
|
|
[cls.name]);
|
|
}
|
|
return lookupConstructor(cls, name, name.source);
|
|
} else if (e.kind === ElementKind.PREFIX) {
|
|
PrefixElement prefix = e;
|
|
e = prefix.lookupLocalMember(name.source);
|
|
if (e === null) {
|
|
error(name, MessageKind.CANNOT_RESOLVE, [name]);
|
|
// TODO(ahe): Return erroneous element.
|
|
} else if (e.kind !== ElementKind.CLASS) {
|
|
error(node, MessageKind.NOT_A_TYPE, [name]);
|
|
}
|
|
} else {
|
|
internalError(node.receiver, 'unexpected element $e');
|
|
}
|
|
return e;
|
|
}
|
|
|
|
Element visitIdentifier(Identifier node) {
|
|
SourceString name = node.source;
|
|
Element e = resolver.lookup(node, name);
|
|
if (e === null) {
|
|
error(node, MessageKind.CANNOT_RESOLVE, [name]);
|
|
// TODO(ahe): Return erroneous element.
|
|
} else if (e.kind === ElementKind.TYPEDEF) {
|
|
error(node, MessageKind.CANNOT_INSTANTIATE_TYPEDEF, [name]);
|
|
} else if (e.kind !== ElementKind.CLASS && e.kind !== ElementKind.PREFIX) {
|
|
error(node, MessageKind.NOT_A_TYPE, [name]);
|
|
}
|
|
return e;
|
|
}
|
|
}
|
|
|
|
class Scope {
|
|
final Element element;
|
|
final Scope parent;
|
|
|
|
Scope(this.parent, this.element);
|
|
abstract Element add(Element element);
|
|
abstract Element lookup(SourceString name);
|
|
}
|
|
|
|
/**
|
|
* [TypeDeclarationScope] defines the outer scope of a type declaration in
|
|
* which the declared type variables and the entities in the enclosing scope are
|
|
* available but where declared and inherited members are not available. This
|
|
* scope is only used for class/interface declarations during resolution of the
|
|
* class hierarchy. In all other cases [ClassScope] is used.
|
|
*/
|
|
class TypeDeclarationScope extends Scope {
|
|
TypeDeclarationElement get element() => super.element;
|
|
|
|
TypeDeclarationScope(parent, TypeDeclarationElement element)
|
|
: super(parent, element) {
|
|
assert(parent !== null);
|
|
}
|
|
|
|
Element add(Element newElement) {
|
|
throw "Cannot add element to TypeDeclarationScope";
|
|
}
|
|
|
|
/**
|
|
* Looks up [name] within the type variables declared in [element].
|
|
*/
|
|
Element lookupTypeVariable(SourceString name) {
|
|
return null;
|
|
}
|
|
|
|
Element lookup(SourceString name) {
|
|
Link<Type> typeVariableLink = element.typeVariables;
|
|
while (!typeVariableLink.isEmpty()) {
|
|
TypeVariableType typeVariable = typeVariableLink.head;
|
|
if (typeVariable.name == name) {
|
|
return typeVariable.element;
|
|
}
|
|
typeVariableLink = typeVariableLink.tail;
|
|
}
|
|
|
|
return parent.lookup(name);
|
|
}
|
|
|
|
String toString() =>
|
|
'$element${element.typeVariables} > $parent';
|
|
}
|
|
|
|
class MethodScope extends Scope {
|
|
final Map<SourceString, Element> elements;
|
|
|
|
MethodScope(Scope parent, Element element)
|
|
: super(parent, element),
|
|
this.elements = new Map<SourceString, Element>() {
|
|
assert(parent !== null);
|
|
}
|
|
|
|
Element lookup(SourceString name) {
|
|
Element found = elements[name];
|
|
if (found !== null) return found;
|
|
return parent.lookup(name);
|
|
}
|
|
|
|
Element add(Element newElement) {
|
|
if (elements.containsKey(newElement.name)) {
|
|
return elements[newElement.name];
|
|
}
|
|
elements[newElement.name] = newElement;
|
|
return newElement;
|
|
}
|
|
|
|
String toString() => '$element${elements.getKeys()} > $parent';
|
|
}
|
|
|
|
class BlockScope extends MethodScope {
|
|
BlockScope(Scope parent) : super(parent, parent.element);
|
|
|
|
String toString() => 'block${elements.getKeys()} > $parent';
|
|
}
|
|
|
|
/**
|
|
* [ClassScope] defines the inner scope of a class/interface declaration in
|
|
* which declared members, declared type variables, entities in the enclosing
|
|
* scope and inherited members are available, in the given order.
|
|
*/
|
|
class ClassScope extends TypeDeclarationScope {
|
|
ClassScope(Scope parentScope, ClassElement element)
|
|
: super(parentScope, element);
|
|
|
|
Element lookup(SourceString name) {
|
|
ClassElement cls = element;
|
|
Element result = cls.lookupLocalMember(name);
|
|
if (result !== null) return result;
|
|
result = super.lookup(name);
|
|
if (result != null) return result;
|
|
return cls.lookupSuperMember(name);
|
|
}
|
|
|
|
Element add(Element newElement) {
|
|
throw "Cannot add an element in a class scope";
|
|
}
|
|
|
|
String toString() => '$element > $parent';
|
|
}
|
|
|
|
class TopScope extends Scope {
|
|
LibraryElement get library() => element;
|
|
|
|
TopScope(LibraryElement library) : super(null, library);
|
|
Element lookup(SourceString name) {
|
|
return library.find(name);
|
|
}
|
|
|
|
Element add(Element newElement) {
|
|
throw "Cannot add an element in the top scope";
|
|
}
|
|
String toString() => '$element';
|
|
}
|