blob: 83eb3ea2bd5027270520d915c918fef6b7e55cb8 [file] [log] [blame]
// Copyright (c) 2015, 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.
/// Mixins that implement convenience methods on [Element] subclasses.
library elements.common;
import '../common/names.dart' show Identifiers, Names, Uris;
import '../core_types.dart' show CoreClasses;
import '../dart_types.dart' show DartType, InterfaceType, FunctionType;
import '../util/util.dart' show Link;
import 'elements.dart';
abstract class ElementCommon implements Element {
@override
bool get isLibrary => kind == ElementKind.LIBRARY;
@override
bool get isCompilationUnit => kind == ElementKind.COMPILATION_UNIT;
@override
bool get isPrefix => kind == ElementKind.PREFIX;
@override
bool get isClass => kind == ElementKind.CLASS;
@override
bool get isTypeVariable => kind == ElementKind.TYPE_VARIABLE;
@override
bool get isTypedef => kind == ElementKind.TYPEDEF;
@override
bool get isFunction => kind == ElementKind.FUNCTION;
@override
bool get isAccessor => isGetter || isSetter;
@override
bool get isGetter => kind == ElementKind.GETTER;
@override
bool get isSetter => kind == ElementKind.SETTER;
@override
bool get isConstructor => isGenerativeConstructor || isFactoryConstructor;
@override
bool get isGenerativeConstructor =>
kind == ElementKind.GENERATIVE_CONSTRUCTOR;
@override
bool get isGenerativeConstructorBody =>
kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY;
bool get isFactoryConstructor => kind == ElementKind.FACTORY_CONSTRUCTOR;
@override
bool get isVariable => kind == ElementKind.VARIABLE;
@override
bool get isField => kind == ElementKind.FIELD;
@override
bool get isAbstractField => kind == ElementKind.ABSTRACT_FIELD;
@override
bool get isRegularParameter => kind == ElementKind.PARAMETER;
@override
bool get isInitializingFormal => kind == ElementKind.INITIALIZING_FORMAL;
@override
bool get isError => kind == ElementKind.ERROR;
@override
bool get isAmbiguous => kind == ElementKind.AMBIGUOUS;
@override
bool get isMalformed => false;
@override
bool get isWarnOnUse => kind == ElementKind.WARN_ON_USE;
@override
bool get impliesType => (kind.category & ElementCategory.IMPLIES_TYPE) != 0;
@override
bool get isAssignable {
if (isFinal || isConst) return false;
if (isFunction || isConstructor) return false;
return true;
}
@override
Element get declaration => this;
@override
Element get implementation => this;
@override
bool get isDeclaration => true;
@override
bool get isPatched => false;
@override
bool get isPatch => false;
@override
bool get isImplementation => true;
@override
bool get isInjected => !isPatch && implementationLibrary.isPatch;
@override
Element get patch {
throw new UnsupportedError('patch is not supported on $this');
}
@override
Element get origin {
throw new UnsupportedError('origin is not supported on $this');
}
@override
ClassElement get contextClass {
ClassElement cls;
for (Element e = this; e != null; e = e.enclosingElement) {
if (e.isClass) {
// Record [e] instead of returning it directly. We need the last class
// in the chain since the first classes might be closure classes.
cls = e.declaration;
}
}
return cls;
}
@override
Element get outermostEnclosingMemberOrTopLevel {
// TODO(lrn): Why is this called "Outermost"?
// TODO(johnniwinther): Clean up this method: This method does not return
// the outermost for elements in closure classses, but some call-sites rely
// on that behavior.
for (Element e = this; e != null; e = e.enclosingElement) {
if (e.isClassMember || e.isTopLevel) {
return e;
}
}
return null;
}
Element get enclosingClassOrCompilationUnit {
for (Element e = this; e != null; e = e.enclosingElement) {
if (e.isClass || e.isCompilationUnit) return e;
}
return null;
}
}
abstract class LibraryElementCommon implements LibraryElement {
@override
bool get isDartCore => canonicalUri == Uris.dart_core;
@override
bool get isPlatformLibrary => canonicalUri.scheme == 'dart';
@override
bool get isPackageLibrary => canonicalUri.scheme == 'package';
@override
bool get isInternalLibrary =>
isPlatformLibrary && canonicalUri.path.startsWith('_');
String get libraryOrScriptName {
if (hasLibraryName) {
return libraryName;
} else {
// Use the file name as script name.
String path = canonicalUri.path;
return path.substring(path.lastIndexOf('/') + 1);
}
}
}
abstract class CompilationUnitElementCommon implements CompilationUnitElement {}
abstract class ClassElementCommon implements ClassElement {
@override
Link<DartType> get allSupertypes => allSupertypesAndSelf.supertypes;
@override
int get hierarchyDepth => allSupertypesAndSelf.maxDepth;
@override
InterfaceType asInstanceOf(ClassElement cls) {
if (cls == this) return thisType;
return allSupertypesAndSelf.asInstanceOf(cls);
}
@override
ConstructorElement lookupConstructor(String name) {
Element result = localLookup(name);
return result != null && result.isConstructor ? result : null;
}
/**
* Find the first member in the class chain with the given [memberName].
*
* This method is NOT to be used for resolving
* unqualified sends because it does not implement the scoping
* rules, where library scope comes before superclass scope.
*
* When called on the implementation element both members declared in the
* origin and the patch class are returned.
*/
Element lookupByName(Name memberName, {ClassElement stopAt}) {
return internalLookupByName(memberName,
isSuperLookup: false, stopAtSuperclass: stopAt);
}
Element lookupSuperByName(Name memberName) {
return internalLookupByName(memberName, isSuperLookup: true);
}
Element internalLookupByName(Name memberName,
{bool isSuperLookup, ClassElement stopAtSuperclass}) {
String name = memberName.text;
bool isPrivate = memberName.isPrivate;
LibraryElement library = memberName.library;
for (ClassElement current = isSuperLookup ? superclass : this;
current != null && current != stopAtSuperclass;
current = current.superclass) {
Element member = current.lookupLocalMember(name);
if (member == null && current.isPatched) {
// Doing lookups on selectors is done after resolution, so it
// is safe to look in the patch class.
member = current.patch.lookupLocalMember(name);
}
if (member == null) continue;
// Private members from a different library are not visible.
if (isPrivate && !identical(library, member.library)) continue;
// Static members are not inherited.
if (member.isStatic && !identical(this, current)) continue;
// If we find an abstract field we have to make sure that it has
// the getter or setter part we're actually looking
// for. Otherwise, we continue up the superclass chain.
if (member.isAbstractField) {
AbstractFieldElement field = member;
FunctionElement getter = field.getter;
FunctionElement setter = field.setter;
if (memberName.isSetter) {
// Abstract members can be defined in a super class.
if (setter != null && !setter.isAbstract) {
return setter;
}
} else {
if (getter != null && !getter.isAbstract) {
return getter;
}
}
// Abstract members can be defined in a super class.
} else if (!member.isAbstract) {
return member;
}
}
return null;
}
/**
* Find the first member in the class chain with the given
* [memberName]. This method is NOT to be used for resolving
* unqualified sends because it does not implement the scoping
* rules, where library scope comes before superclass scope.
*/
@override
Element lookupMember(String memberName) {
Element localMember = lookupLocalMember(memberName);
return localMember == null ? lookupSuperMember(memberName) : localMember;
}
@override
Link<Element> get constructors {
// TODO(ajohnsen): See if we can avoid this method at some point.
Link<Element> result = const Link<Element>();
// TODO(johnniwinther): Should we include injected constructors?
forEachMember((_, Element member) {
if (member.isConstructor) result = result.prepend(member);
});
return result;
}
/**
* Lookup super members for the class. This will ignore constructors.
*/
@override
Element lookupSuperMember(String memberName) {
return lookupSuperMemberInLibrary(memberName, library);
}
/**
* Lookup super members for the class that is accessible in [library].
* This will ignore constructors.
*/
@override
Element lookupSuperMemberInLibrary(
String memberName, LibraryElement library) {
bool isPrivate = Name.isPrivateName(memberName);
for (ClassElement s = superclass; s != null; s = s.superclass) {
// Private members from a different library are not visible.
if (isPrivate && !identical(library, s.library)) continue;
Element e = s.lookupLocalMember(memberName);
if (e == null) continue;
// Static members are not inherited.
if (e.isStatic) continue;
return e;
}
return null;
}
/**
* Lookup local members in the class. This will ignore constructors.
*/
@override
Element lookupLocalMember(String memberName) {
var result = localLookup(memberName);
if (result != null && result.isConstructor) return null;
return result;
}
/**
* Runs through all members of this class.
*
* The enclosing class is passed to the callback. This is useful when
* [includeSuperAndInjectedMembers] is [:true:].
*
* When called on an implementation element both the members in the origin
* and patch class are included.
*/
// TODO(johnniwinther): Clean up lookup to get rid of the include predicates.
@override
void forEachMember(void f(ClassElement enclosingClass, Element member),
{includeBackendMembers: false, includeSuperAndInjectedMembers: false}) {
bool includeInjectedMembers = includeSuperAndInjectedMembers || isPatch;
ClassElement classElement = declaration;
do {
// Iterate through the members in textual order, which requires
// to reverse the data structure [localMembers] we created.
// Textual order may be important for certain operations, for
// example when emitting the initializers of fields.
classElement.forEachLocalMember((e) => f(classElement, e));
if (includeBackendMembers) {
classElement.forEachBackendMember((e) => f(classElement, e));
}
if (includeInjectedMembers) {
if (classElement.isPatched) {
classElement.patch.forEachLocalMember((e) {
if (!e.isPatch) f(classElement, e);
});
}
}
classElement =
includeSuperAndInjectedMembers ? classElement.superclass : null;
} while (classElement != null);
}
/**
* Runs through all instance-field members of this class.
*
* The enclosing class is passed to the callback. This is useful when
* [includeSuperAndInjectedMembers] is [:true:].
*
* When called on the implementation element both the fields declared in the
* origin and in the patch are included.
*/
@override
void forEachInstanceField(
void f(ClassElement enclosingClass, FieldElement field),
{bool includeSuperAndInjectedMembers: false}) {
// Filters so that [f] is only invoked with instance fields.
void fieldFilter(ClassElement enclosingClass, Element member) {
if (member.isInstanceMember && member.kind == ElementKind.FIELD) {
f(enclosingClass, member);
}
}
forEachMember(fieldFilter,
includeSuperAndInjectedMembers: includeSuperAndInjectedMembers);
}
/// Similar to [forEachInstanceField] but visits static fields.
@override
void forEachStaticField(void f(ClassElement enclosingClass, Element field)) {
// Filters so that [f] is only invoked with static fields.
void fieldFilter(ClassElement enclosingClass, Element member) {
if (!member.isInstanceMember && member.kind == ElementKind.FIELD) {
f(enclosingClass, member);
}
}
forEachMember(fieldFilter);
}
/**
* Returns true if the [fieldMember] shadows another field. The given
* [fieldMember] must be a member of this class, i.e. if there is a field of
* the same name in the superclass chain.
*
* This method also works if the [fieldMember] is private.
*/
@override
bool hasFieldShadowedBy(Element fieldMember) {
assert(fieldMember.isField);
String fieldName = fieldMember.name;
bool isPrivate = Name.isPrivateName(fieldName);
LibraryElement memberLibrary = fieldMember.library;
ClassElement lookupClass = this.superclass;
while (lookupClass != null) {
Element foundMember = lookupClass.lookupLocalMember(fieldName);
if (foundMember != null) {
if (foundMember.isField) {
if (!isPrivate || memberLibrary == foundMember.library) {
// Private fields can only be shadowed by a field declared in the
// same library.
return true;
}
}
}
lookupClass = lookupClass.superclass;
}
return false;
}
@override
bool implementsInterface(ClassElement intrface) {
return this != intrface &&
allSupertypesAndSelf.asInstanceOf(intrface) != null;
}
@override
bool implementsFunction(CoreClasses coreClasses) {
return asInstanceOf(coreClasses.functionClass) != null || callType != null;
}
@override
bool isSubclassOf(ClassElement cls) {
// Use [declaration] for both [this] and [cls], because
// declaration classes hold the superclass hierarchy.
cls = cls.declaration;
for (ClassElement s = declaration; s != null; s = s.superclass) {
if (identical(s, cls)) return true;
}
return false;
}
FunctionType get callType {
MemberSignature member = lookupInterfaceMember(Names.call);
return member != null && member.isMethod ? member.type : null;
}
@override
bool get isNamedMixinApplication {
return isMixinApplication && !isUnnamedMixinApplication;
}
// backendMembers are members that have been added by the backend to simplify
// compilation. They don't have any user-side counter-part.
Link<Element> backendMembers = const Link<Element>();
bool get hasBackendMembers => !backendMembers.isEmpty;
void addBackendMember(Element member) {
// TODO(ngeoffray): Deprecate this method.
assert(member.isGenerativeConstructorBody);
backendMembers = backendMembers.prepend(member);
}
void reverseBackendMembers() {
backendMembers = backendMembers.reverse();
}
/// Lookup a synthetic element created by the backend.
Element lookupBackendMember(String memberName) {
for (Element element in backendMembers) {
if (element.name == memberName) {
return element;
}
}
return null;
}
void forEachBackendMember(void f(Element member)) {
backendMembers.forEach(f);
}
}
abstract class FunctionSignatureCommon implements FunctionSignature {
DartType get returnType => type.returnType;
void forEachRequiredParameter(void function(Element parameter)) {
requiredParameters.forEach(function);
}
void forEachOptionalParameter(void function(Element parameter)) {
optionalParameters.forEach(function);
}
void forEachParameter(void function(Element parameter)) {
forEachRequiredParameter(function);
forEachOptionalParameter(function);
}
void orderedForEachParameter(void function(Element parameter)) {
forEachRequiredParameter(function);
orderedOptionalParameters.forEach(function);
}
int get parameterCount => requiredParameterCount + optionalParameterCount;
/**
* Check whether a function with this signature can be used instead of a
* function with signature [signature] without causing a `noSuchMethod`
* exception/call.
*/
bool isCompatibleWith(FunctionSignature signature) {
if (optionalParametersAreNamed) {
if (!signature.optionalParametersAreNamed) {
return requiredParameterCount == signature.parameterCount;
}
// If both signatures have named parameters, then they must have
// the same number of required parameters, and the names in
// [signature] must all be in [:this:].
if (requiredParameterCount != signature.requiredParameterCount) {
return false;
}
Set<String> names =
optionalParameters.map((Element element) => element.name).toSet();
for (Element namedParameter in signature.optionalParameters) {
if (!names.contains(namedParameter.name)) {
return false;
}
}
} else {
if (signature.optionalParametersAreNamed) return false;
// There must be at least as many arguments as in the other signature, but
// this signature must not have more required parameters. Having more
// optional parameters is not a problem, they simply are never provided
// by call sites of a call to a method with the other signature.
int otherTotalCount = signature.parameterCount;
return requiredParameterCount <= otherTotalCount &&
parameterCount >= otherTotalCount;
}
return true;
}
}
abstract class MixinApplicationElementCommon
implements MixinApplicationElement {
Link<ConstructorElement> get constructors {
throw new UnsupportedError('Unimplemented $this.constructors');
}
FunctionElement _lookupLocalConstructor(String name) {
for (Link<Element> link = constructors; !link.isEmpty; link = link.tail) {
if (link.head.name == name) return link.head;
}
return null;
}
@override
Element localLookup(String name) {
Element constructor = _lookupLocalConstructor(name);
if (constructor != null) return constructor;
if (mixin == null) return null;
Element mixedInElement = mixin.localLookup(name);
if (mixedInElement == null) return null;
return mixedInElement.isInstanceMember ? mixedInElement : null;
}
@override
void forEachLocalMember(void f(Element member)) {
constructors.forEach(f);
if (mixin != null)
mixin.forEachLocalMember((Element mixedInElement) {
if (mixedInElement.isInstanceMember) f(mixedInElement);
});
}
}
abstract class AbstractFieldElementCommon implements AbstractFieldElement {
@override
bool get isInstanceMember {
return isClassMember && !isStatic;
}
@override
bool get isAbstract {
return getter != null && getter.isAbstract ||
setter != null && setter.isAbstract;
}
}
enum _FromEnvironmentState {
NOT,
BOOL,
INT,
STRING,
}
abstract class ConstructorElementCommon implements ConstructorElement {
_FromEnvironmentState _fromEnvironmentState;
_FromEnvironmentState get fromEnvironmentState {
if (_fromEnvironmentState == null) {
_fromEnvironmentState = _FromEnvironmentState.NOT;
if (name == Identifiers.fromEnvironment && library.isDartCore) {
switch (enclosingClass.name) {
case 'bool':
_fromEnvironmentState = _FromEnvironmentState.BOOL;
break;
case 'int':
_fromEnvironmentState = _FromEnvironmentState.INT;
break;
case 'String':
_fromEnvironmentState = _FromEnvironmentState.STRING;
break;
}
}
}
return _fromEnvironmentState;
}
@override
bool get isFromEnvironmentConstructor {
return fromEnvironmentState != _FromEnvironmentState.NOT;
}
@override
bool get isIntFromEnvironmentConstructor {
return fromEnvironmentState == _FromEnvironmentState.INT;
}
@override
bool get isBoolFromEnvironmentConstructor {
return fromEnvironmentState == _FromEnvironmentState.BOOL;
}
@override
bool get isStringFromEnvironmentConstructor {
return fromEnvironmentState == _FromEnvironmentState.STRING;
}
}