blob: de6bdbc715f853ae9226d8293758da47482ccd85 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.royale.compiler.internal.definitions;
import static org.apache.royale.compiler.common.ISourceLocation.UNKNOWN;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.royale.abc.ABCConstants;
import org.apache.royale.abc.semantics.Name;
import org.apache.royale.abc.semantics.Namespace;
import org.apache.royale.abc.semantics.Nsset;
import org.apache.royale.compiler.asdoc.IASDocComment;
import org.apache.royale.compiler.common.ASModifier;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.common.ModifiersSet;
import org.apache.royale.compiler.common.NodeReference;
import org.apache.royale.compiler.config.CompilerDiagnosticsConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.constants.IMetaAttributeConstants;
import org.apache.royale.compiler.constants.INamespaceConstants;
import org.apache.royale.compiler.definitions.IAccessorDefinition;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IDocumentableDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.IPackageDefinition;
import org.apache.royale.compiler.definitions.IScopedDefinition;
import org.apache.royale.compiler.definitions.metadata.IDeprecationInfo;
import org.apache.royale.compiler.definitions.metadata.IMetaTag;
import org.apache.royale.compiler.definitions.metadata.IMetaTagAttribute;
import org.apache.royale.compiler.definitions.references.INamespaceReference;
import org.apache.royale.compiler.definitions.references.INamespaceResolvedReference;
import org.apache.royale.compiler.definitions.references.IReference;
import org.apache.royale.compiler.filespecs.IFileSpecification;
import org.apache.royale.compiler.internal.common.Counter;
import org.apache.royale.compiler.internal.definitions.metadata.DeprecationInfo;
import org.apache.royale.compiler.internal.definitions.metadata.MetaTag;
import org.apache.royale.compiler.internal.parsing.as.OffsetLookup;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.scopes.ASFileScope;
import org.apache.royale.compiler.internal.scopes.ASProjectScope;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.scopes.ASScopeBase;
import org.apache.royale.compiler.internal.scopes.SWCFileScopeProvider.SWCFileScope;
import org.apache.royale.compiler.mxml.IMXMLTagData;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.scopes.IDefinitionSet;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IDefinitionNode;
import org.apache.royale.compiler.tree.as.IDocumentableDefinitionNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.units.ICompilationUnit;
/**
* This class is the abstract base class for all ActionScript definitions in the
* symbol table.
* <p>
* After definitions are added to the symbol table, they should always be
* accessed through the read-only definition interfaces (which are
* <code>IDefinition</code> and its subinterfaces) rather than through these
* implementation classes.
* <p>
* Note that this class also implements {@link IDefinitionSet} so that a
* scope's map can point to a single definition acting as it own
* definition-set-of-size-1.
*/
public abstract class DefinitionBase implements IDocumentableDefinition, IDefinitionSet
{
// Mask constants for bitflags in the 'flags' field,
// a short value that we use in place of multiple boolean fields.
protected static final short FLAG_CAST_FUNCTION = 1 << 0;
protected static final short FLAG_CONSTRUCTOR = 1 << 1;
protected static final short FLAG_DYNAMIC = 1 << 2;
protected static final short FLAG_FINAL = 1 << 3;
protected static final short FLAG_IMPLICIT = 1 << 4;
protected static final short FLAG_NATIVE = 1 << 5;
protected static final short FLAG_OVERRIDE = 1 << 6;
protected static final short FLAG_REST = 1 << 7;
protected static final short FLAG_STATIC = 1 << 8;
protected static final short FLAG_DEFAULT = 1 << 9;
protected static final short FLAG_CONTINGENT = 1 << 10;
protected static final short FLAG_GENERATED_EMBED_CLASS = 1 << 11;
protected static final short FLAG_HAS_INIT = 1 << 12;
protected static final short FLAG_DEPRECATED = 1 << 13;
protected static final short FLAG_DECLARED_IN_CONTROL_FLOW = 1 << 14;
/**
* Constructor.
*
* @param name The "constructor name" of the definition.
* This is currently in an inconsistent form; for example,
* for the <code>flash.display.Sprite</code> class definition
* constructed from source code it is <code>"Sprite"</code>
* but for the same definition constructed from ABC it is
* <code>"flash.display.Sprite"</code>.
* The "constructor name" will be converted to a "storage name"
* and stored in the <code>storageName</code> field.
*/
// TODO Fix construction of definitions so that
// the storage name is always passed in.
public DefinitionBase(String name)
{
assert name != null;
this.storageName = toStorageName(name);
if (Counter.COUNT_DEFINITIONS)
countDefinitions();
}
// The scope that contains this definition.
// This field is set when the addDefinition() method of ASScope
// calls the package-private setContainingScope() method of this class.
private IASScope containingScope;
// Bit flags for this definition, such as whether it is implicit.
protected short flags;
// The namespace reference for this definition.
// For most definitions this will be non-null, even if no namespace was specified
// in the source code. For example, if the namespace is omitted on a member of a class,
// "internal" is implied; therefore this field will be a reference to the internal
// namespace for the package containing the class. However, some types of definitions,
// such as a for a function parameter, do have a null namespace reference.
private INamespaceResolvedReference namespaceReference;
// The name stored for this definition. See getStorageName() for details.
private final String storageName;
// The type reference for this definition. See getTypeReference() for details.
// Note that this is an IReference, which is basically a reference-by-name
// to a definition, which can resolve to different definitions in different projects.
private IReference typeReference;
// metaTags is never null. As an optimization, it re-uses a singleton empty array when
// it has nothing in it
private IMetaTag[] metaTags = singletonEmptyMetaTags;
protected static final IMetaTag[] singletonEmptyMetaTags = new IMetaTag[0];
// Hold a reference to the node this definition came from
// (NodeReference only holds onto the node weakly, so we don't have to worry about leaks).
protected NodeReference nodeRef = NodeReference.noReference;
private int absoluteNameStart = 0;
private int absoluteNameEnd = 0;
private IDefinition parentDef = null;
/**
* Called by {@code MXMLScopeBuilder} when building definitions from
* {@code MXMLData}. Creates a nodeless {@code NodeReference} for this
* definition that keeps track of the file and offset for the MXML tag that
* produced this definition. Later, after the MXML tree is produced, this
* {@code NodeReference} can be used to locate the appropriate tree node.
*
* @param tag The {@code MXMLTagData} that is producing this definition.
*/
public void setLocation(IMXMLTagData tag)
{
nodeRef = new NodeReference(tag.getSource(), tag.getAbsoluteStart());
}
/**
* Attaches a weak reference to the source node that produced this
* definition, and copies over enough source-location information to
* re-associate the node if it is garbage-collected.
*
* @param node The {@link IDefinitionNode} that is producing
* this definition.
*/
public void setNode(IDefinitionNode node)
{
if (node == null)
{
nodeRef = NodeReference.noReference;
absoluteNameStart = UNKNOWN;
absoluteNameEnd = UNKNOWN;
}
else
{
nodeRef = new NodeReference(node);
IExpressionNode nameNode = node.getNameExpressionNode();
if (nameNode != null)
{
absoluteNameStart = nameNode.getAbsoluteStart();
absoluteNameEnd = nameNode.getAbsoluteEnd();
}
else
{
absoluteNameStart = UNKNOWN;
absoluteNameEnd = UNKNOWN;
}
}
}
/**
* This method is used to set the name location when there is no name node
* from which it can be determined.
*/
public void setNameLocation(int absoluteNameStart, int absoluteNameEnd)
{
this.absoluteNameStart = absoluteNameStart;
this.absoluteNameEnd = absoluteNameEnd;
}
@Override
public IASScope getContainingScope()
{
return containingScope;
}
public void setContainingScope(IASScope scope)
{
containingScope = scope;
}
@Override
public IDefinition getParent()
{
if (parentDef != null)
return parentDef;
IASScope scope = getContainingScope();
// Walk up the scope chain until we find a scope that has
// a containing definition. The following types of scope do
// not have an immediate containing definition:
// CatchScope's
// WithScope's
// FunctionScope's for MXML event handler's.
while ((scope != null) && (scope.getDefinition() == null))
scope = scope.getContainingScope();
parentDef = scope != null ? scope.getDefinition() : null;
return parentDef;
}
@Override
public IDefinition getAncestorOfType(Class<? extends IDefinition> ancestorType)
{
IDefinition definition = getParent();
while (definition != null && !(ancestorType.isInstance(definition)))
definition = definition.getParent();
return definition;
}
// TODO Eliminate this method and its overrides
// by constructing the definition with its storage name.
protected String toStorageName(String name)
{
// Most constructors pass in either a base name or a qname,
// which we convert to a base name.
// However, this method is overridin in DefinitionPromise,
// PackageDefinition, and AppliedVectorDefinition to NOT
// reduce the constructor name to a base name.
int i = name.lastIndexOf('.');
return i == -1 ? name : name.substring(i + 1);
}
/**
* Gets the name that is actually stored for this definition.
* <p>
* This is not a public API, and is not in the {@link IDefinition}
* interface, because different types of definitions store different
* types of names. However, the storage name of a definition does
* not depend on whether it came from source code or from ABC.
* <p>
* For class, interface, function, variable, constant, and namespace
* definitions -- whose base name and the qualified name can differ
* when the definition is at package scope -- the storage name is the
* base name (e.g., <code>"Sprite"</code>,
* not <code>"flash.display.Sprite"</code>).
* <p>
* In the case of vector types, the storage name is a base name
* like <code>"Vector.&lt;Sprite&gt;"</code>
* (not <code>"Vector.&lt;flash.display.Sprite&gt;"</code>),
* which is considered a base name despite technically having a dot.
* <p>
* For parameter definitions -- which cannot be at package scope -
* and for event, style, and effect definitions -- which as metadata
* don't live in any scope -- there is no distinction between the base
* name and the qualified name; the storage name is the same as both
* of these. E.g., <code>i</code> for a parameter definition or
* <code>"click"</code> for an event definition.
* <p>
* For package definitions, the storage name is the package name
* (e.g., <code>"flash.display"</code>), which is considered
* both the base name and the qualified name of a package definition.
* <p>
* Finally, for definition promises, the storage name is a dotted,
* fully-qualified name (e.g., <code>"flash.display.Sprite"</code>,
* not <code>"Sprite"</code>).
*/
protected final String getStorageName()
{
return storageName;
}
/**
*/
public IFileSpecification getFileSpecification()
{
return nodeRef.getFileSpecification();
}
@Override
public String getSourcePath()
{
IFileSpecification fileSpec = getFileSpecification();
return fileSpec != null ? fileSpec.getPath() : null;
}
private OffsetLookup getOffsetLookup()
{
final ASFileScope fileScope = getFileScope();
if (fileScope == null)
return null;
return fileScope.getOffsetLookup();
}
@Override
public int getStart()
{
// Because the starting offset is what is used
// connect a node to a definition,
// we can't just call getStart() on getNode();
// because this leads to deadlock.
// Instead, we have to use the starting offset
// stored in the NodeReference.
if (nodeRef == null)
return UNKNOWN;
final int absoluteStart = nodeRef.getAbsoluteStart();
if (absoluteStart == UNKNOWN)
return UNKNOWN;
final OffsetLookup offsetLookup = getOffsetLookup();
if (offsetLookup == null)
return absoluteStart;
return offsetLookup.getLocalOffset(absoluteStart);
}
@Override
public int getEnd()
{
final IDefinitionNode node = getNode();
if (node == null)
return UNKNOWN;
return node.getEnd();
}
@Override
public int getLine()
{
final IDefinitionNode node = getNode();
if (node == null)
return UNKNOWN;
return node.getLine();
}
@Override
public int getColumn()
{
final IDefinitionNode node = getNode();
if (node == null)
return UNKNOWN;
return node.getColumn();
}
@Override
public int getAbsoluteStart()
{
// Because the starting offset is what is used
// connect a node to a definition,
// we can't just call getAbsoluteStart() on getNode();
// because this leads to deadlock.
// Instead, we have to use the starting offset
// stored in the NodeReference.
if (nodeRef == null)
return UNKNOWN;
return nodeRef.getAbsoluteStart();
}
@Override
public int getAbsoluteEnd()
{
final IDefinitionNode node = getNode();
if (node == null)
return UNKNOWN;
return node.getAbsoluteEnd();
}
private IExpressionNode getNameNode()
{
IDefinitionNode node = getNode();
if (node == null)
return null;
return node.getNameExpressionNode();
}
@Override
public int getNameStart()
{
if (absoluteNameStart == UNKNOWN)
return UNKNOWN;
OffsetLookup offsetLookup = getOffsetLookup();
if (offsetLookup == null)
return absoluteNameStart;
return offsetLookup.getLocalOffset(absoluteNameStart);
}
@Override
public int getNameEnd()
{
if (absoluteNameEnd == UNKNOWN)
return UNKNOWN;
OffsetLookup offsetLookup = getOffsetLookup();
if (offsetLookup == null)
return absoluteNameEnd;
return offsetLookup.getLocalOffset(absoluteNameEnd);
}
/**
* Get line number for the name of this definition, if it can be determined.
*
* @return the line number, or -1 if we couldn't figure it out
*/
@Override
public int getNameLine()
{
final IExpressionNode nameNode = getNameNode();
if (nameNode == null)
return UNKNOWN;
return nameNode.getLine();
}
/**
* Get column number for the name of this definition, if it can be
* determined.
*
* @return the column number, or -1 if we couldn't figure it out
*/
@Override
public int getNameColumn()
{
final IExpressionNode nameNode = getNameNode();
if (nameNode == null)
return UNKNOWN;
return nameNode.getColumn();
}
@Override
public String getContainingFilePath()
{
ASFileScope fileScope = getFileScope();
if (fileScope == null)
return null;
return fileScope.getContainingPath();
}
@Override
public String getContainingSourceFilePath(final ICompilerProject project)
{
// If node reference is set, find the source file name from
// offset lookup
if (nodeRef != NodeReference.noReference)
{
final ASFileScope fileScope = getFileScope();
if (fileScope != null && fileScope.getOffsetLookup() != null)
{
return fileScope.getOffsetLookup().getFilename(getAbsoluteStart());
}
}
ASScope containingScope = (ASScope)getContainingScope();
if (containingScope != null)
return containingScope.getContainingSourcePath(getQualifiedName(), project);
return null;
}
private static String namespaceReferenceToPackageName(INamespaceReference nsRef)
{
if (nsRef instanceof NamespaceDefinition.INamespaceWithPackageName)
return ((NamespaceDefinition.INamespaceWithPackageName)nsRef).getNamespacePackageName();
return null;
}
/**
*
* @param definition is the definition whose containing top level definition we want
* @return the top level deinition, or null if none exists.
*/
private static DefinitionBase getContainingToplevelDefinition(DefinitionBase definition)
{
ASScope currentContainingScope = definition.getContainingASScope();
DefinitionBase currentDefinition = definition;
IScopedDefinition containingDefinition = currentContainingScope.getContainingDefinition();
while (containingDefinition != null)
{
currentDefinition = (DefinitionBase)containingDefinition;
currentContainingScope = currentDefinition.getContainingASScope();
// With some synthetic definitions you can't find a containint top level definition.
// This happens with Vector<T>, for example. In this case, return null
if (currentContainingScope == null)
return null;
containingDefinition = currentContainingScope.getContainingDefinition();
}
assert currentDefinition != null;
return currentDefinition;
}
@Override
public String getPackageName()
{
// Ugghhh!! This method is supposed to return the fully qualified name
// of the package that contains this definition. The method works even
// if this definition is not a top level definition.
// Sub-classes that will never have a containing scope ( like DefinitionPromise's )
// should overload this method.
assert getContainingScope() != null : "This method should not be called until we have a containing scope.!";
// If we get here, we have a containing scope, so we can just walk the scope
// chain until we get to a definition that is a direct child of a package or file
// scope.
DefinitionBase containingToplevelDefinition = getContainingToplevelDefinition(this);
if (containingToplevelDefinition == null)
{
// If there is no containing top level definition they return the top level package.
// In the case of member functions of the Vector<T> class, this will be correct
return "";
}
assert (!(containingToplevelDefinition instanceof FunctionDefinition)) || (!((FunctionDefinition)containingToplevelDefinition).isConstructor()) : "Constructors should always be contained by class definitions!";
INamespaceReference toplevelDefinitionNSRef =
containingToplevelDefinition.getNamespaceReference();
assert toplevelDefinitionNSRef != null;
String packageNameFromNSRef = namespaceReferenceToPackageName(toplevelDefinitionNSRef);
if (packageNameFromNSRef != null)
return packageNameFromNSRef;
assert getBaseName().indexOf('.') == -1;
return "";
}
@Override
public String getBaseName()
{
// Most definitions store their base name, so this base method
// simply returns it. However, a few subclasses override this.
return storageName;
}
@Override
public String getQualifiedName()
{
// For top-level definitions, report the package name concatenated
// with the base name. If it's not in a package (e.g. a file private
// definition), then the package name will be "".
if (isTopLevelDefinition())
{
String packageName = getPackageName();
if (packageName != null && !packageName.isEmpty())
return packageName + "." + storageName;
}
return storageName;
}
/**
* Is this definition a toplevel definition
*
* @return true if this definition is declared at file scope, or package
* scope.
*/
public boolean isTopLevelDefinition()
{
return getParent() instanceof IPackageDefinition || getParent() == null;
}
@Override
public boolean isDynamic()
{
return (flags & FLAG_DYNAMIC) != 0;
}
public void setDynamic()
{
flags |= FLAG_DYNAMIC;
}
@Override
public boolean isFinal()
{
return (flags & FLAG_FINAL) != 0;
}
public void setFinal()
{
flags |= FLAG_FINAL;
}
@Override
public boolean isNative()
{
return (flags & FLAG_NATIVE) != 0;
}
public void setNative()
{
flags |= FLAG_NATIVE;
}
@Override
public boolean isOverride()
{
return (flags & FLAG_OVERRIDE) != 0;
}
public void setOverride()
{
flags |= FLAG_OVERRIDE;
}
public void unsetOverride()
{
if (isOverride())
flags -= FLAG_OVERRIDE;
}
@Override
public boolean isStatic()
{
return (flags & FLAG_STATIC) != 0;
}
public void setStatic()
{
flags |= FLAG_STATIC;
}
@Override
public boolean hasModifier(ASModifier modifier)
{
// Note: These get checked in decreasing order of frequency of use.
if (modifier == ASModifier.OVERRIDE)
{
return (flags & FLAG_OVERRIDE) != 0;
}
else if (modifier == ASModifier.STATIC)
{
return (flags & FLAG_STATIC) != 0;
}
else if (modifier == ASModifier.FINAL)
{
return (flags & FLAG_FINAL) != 0;
}
else if (modifier == ASModifier.DYNAMIC)
{
return (flags & FLAG_DYNAMIC) != 0;
}
else if (modifier == ASModifier.NATIVE)
{
return (flags & FLAG_NATIVE) != 0;
}
else if (modifier == ASModifier.VIRTUAL)
{
// Ignore "virtual" modifier.
return false;
}
else
{
assert false : "Unknown modifier: " + modifier;
return false;
}
}
@Override
public ModifiersSet getModifiers()
{
ModifiersSet result = new ModifiersSet();
if ((flags & FLAG_OVERRIDE) != 0)
result.addModifier(ASModifier.OVERRIDE);
if ((flags & FLAG_STATIC) != 0)
result.addModifier(ASModifier.STATIC);
if ((flags & FLAG_FINAL) != 0)
result.addModifier(ASModifier.FINAL);
if ((flags & FLAG_DYNAMIC) != 0)
result.addModifier(ASModifier.DYNAMIC);
if ((flags & FLAG_NATIVE) != 0)
result.addModifier(ASModifier.NATIVE);
return result;
}
public void setModifier(ASModifier modifier)
{
// Note: These get checked in decreasing order of frequency of use.
if (modifier == ASModifier.OVERRIDE)
flags |= FLAG_OVERRIDE;
else if (modifier == ASModifier.STATIC)
flags |= FLAG_STATIC;
else if (modifier == ASModifier.FINAL)
flags |= FLAG_FINAL;
else if (modifier == ASModifier.DYNAMIC)
flags |= FLAG_DYNAMIC;
else if (modifier == ASModifier.NATIVE)
flags |= FLAG_NATIVE;
else
assert false;
}
@Override
public boolean isPublic()
{
return namespaceReference instanceof INamespaceDefinition.IPublicNamespaceDefinition;
}
@Override
public boolean isPrivate()
{
return namespaceReference instanceof INamespaceDefinition.IPrivateNamespaceDefinition;
}
@Override
public boolean isProtected()
{
return namespaceReference instanceof INamespaceDefinition.IProtectedNamespaceDefinition;
}
@Override
public boolean isInternal()
{
return namespaceReference instanceof INamespaceDefinition.IInternalNamespaceDefinition;
}
@Override
public boolean hasNamespace(INamespaceReference namespace, ICompilerProject project)
{
// If the namespaces being compared are language namespaces
// (which have singleton NamespaceReferences)
// then we can just compare the NamespaceReferences.
if (namespace.isLanguageNamespace() &&
this.namespaceReference.isLanguageNamespace())
{
return namespace == this.namespaceReference;
}
// Otherwise, the namespaces are equivalent if they resolve to
// equivalent namespaceReference definitions (i.e., two with the same URI).
INamespaceDefinition ns1 = namespace.resolveNamespaceReference(project);
INamespaceDefinition ns2 = this.namespaceReference.resolveNamespaceReference(project);
return ((ns1 != null && ns2 != null) ? ns1.equals(ns2) : false);
}
@Override
public INamespaceReference getNamespaceReference()
{
return namespaceReference;
}
public void setNamespaceReference(INamespaceReference value)
{
namespaceReference = (INamespaceResolvedReference)value;
NamespaceDefinition.setContainingDefinitionOfReference(value, this);
}
public void setPublic()
{
setNamespaceReference(NamespaceDefinition.getPublicNamespaceDefinition());
}
/**
* Utility to mark a definition as bindable. This method should only ever be
* called during construction or initialization of a definition.
*/
public void setBindable()
{
MetaTag bindableMetaTag = new MetaTag(this, IMetaAttributeConstants.ATTRIBUTE_BINDABLE, new IMetaTagAttribute[0]);
addMetaTag(bindableMetaTag);
}
@Override
public INamespaceDefinition resolveNamespace(ICompilerProject project)
{
return namespaceReference != null ? namespaceReference.resolveNamespaceReference(project) : null;
}
@Override
public String getTypeAsDisplayString()
{
return typeReference != null ? typeReference.getDisplayString() : "";
}
@Override
public IReference getTypeReference()
{
return typeReference;
}
/**
* Sets the type reference for this definition.
*
* @param typeReference An [@Link IReference} to a class or interface.
*/
public void setTypeReference(IReference typeReference)
{
this.typeReference = typeReference;
}
/**
* Gets the {@link DependencyType} that should be used when resolving the
* type of this definition.
* <p>
* This method is intended to be overridden by sub-classes.
*
* @return The {@link DependencyType} that should be used when resolving the
* type of this variable definition
*/
protected DependencyType getTypeDependencyType()
{
return DependencyType.SIGNATURE;
}
@Override
public TypeDefinitionBase resolveType(ICompilerProject project)
{
DependencyType dt = getTypeDependencyType();
return resolveType(typeReference, project, dt);
}
@Override
public boolean isImplicit()
{
return (flags & FLAG_IMPLICIT) != 0;
}
public void setImplicit()
{
flags |= FLAG_IMPLICIT;
}
@Override
public IMetaTag[] getAllMetaTags()
{
return metaTags;
}
protected void addMetaTag(IMetaTag metaTag)
{
IMetaTag[] newMetaTags = new IMetaTag[metaTags.length + 1];
System.arraycopy(metaTags, 0, newMetaTags, 0, metaTags.length);
newMetaTags[metaTags.length] = metaTag;
setMetaTags(newMetaTags);
}
public void setMetaTags(IMetaTag[] newMetaTags)
{
if (newMetaTags == null)
{
metaTags = singletonEmptyMetaTags;
}
else
{
this.metaTags = newMetaTags;
// Use a flag bit to keep track of whether there is a [Deprecated] tag.
for (IMetaTag metaTag : metaTags)
{
String tagName = metaTag.getTagName();
if (tagName.equals(IMetaAttributeConstants.ATTRIBUTE_DEPRECATED))
setDeprecated();
}
}
}
@Override
public IMetaTag[] getMetaTagsByName(String name)
{
// We should never return null from this function - callers do not expect it.
// So allocate an empty array here, even if metaTags is null
List<IMetaTag> list = new ArrayList<IMetaTag>();
for (IMetaTag tag : metaTags)
{
if (tag.getTagName().equals(name))
list.add(tag);
}
return list.toArray(new IMetaTag[0]);
}
@Override
public boolean hasMetaTagByName(String name)
{
return getMetaTagByName(name) != null;
}
@Override
public IMetaTag getMetaTagByName(String name)
{
for (IMetaTag tag : metaTags)
{
if (tag.getTagName().equals(name))
return tag;
}
return null;
}
@Override
public IASDocComment getExplicitSourceComment()
{
IDefinitionNode node = getNode();
if (node instanceof IDocumentableDefinitionNode)
return ((IDocumentableDefinitionNode)node).getASDocComment();
return null;
}
@Override
public boolean hasExplicitComment()
{
IDefinitionNode node = getNode();
if (node instanceof IDocumentableDefinitionNode)
return ((IDocumentableDefinitionNode)node).hasExplicitComment();
return false;
}
public ASFileScope getFileScope()
{
ASScope s = getContainingASScope();
if (s == null)
return null;
return s.getFileScope();
}
@Override
public IDefinitionNode getNode()
{
// If this definition didn't come from source, return null.
if (nodeRef == NodeReference.noReference)
return null;
ASScope containingScope = getContainingASScope();
if (containingScope == null)
return null;
ASFileScope fileScope = containingScope.getFileScope();
if (fileScope == null)
return null;
IASNode node = nodeRef.getNode(fileScope.getWorkspace(), getContainingASScope());
if (!(node instanceof IDefinitionNode))
return null;
return (IDefinitionNode)node;
}
/**
* Static version of {@code resolveType()} to help sub-classes implement
* their overrides of {@link #resolveType(String, ICompilerProject, DependencyType)}.
*
* @param context {@link DefinitionBase} relative to which the resolve should
* be done.
*/
protected static TypeDefinitionBase resolveType(DefinitionBase context, String typeName, ICompilerProject project, DependencyType dt)
{
// TODO - should probably return the Definition for "*" once we have an easy way to get that
// TODO - definition - this works for now to prevent NPEs
if (typeName == null)
return null;
CompilerProject compilerProject = (CompilerProject)project;
ASScope containingScope = (ASScope)(context.containingScope);
// TODO at some point this method should take some sort of name object.
if (containingScope != null)
{
int lastIndexOfDot = typeName.lastIndexOf('.');
IDefinition foundDefinition = null;
if (lastIndexOfDot != -1)
{
String unqualifiedName = typeName.substring(lastIndexOfDot + 1);
String packageName = typeName.substring(0, lastIndexOfDot);
INamespaceDefinition packageNS = ((CompilerProject)project).getWorkspace().getPackageNamespaceDefinitionCache().get(packageName, false);
foundDefinition = containingScope.findPropertyQualified(compilerProject, packageNS, unqualifiedName, dt, true);
}
else
{
foundDefinition = containingScope.findProperty(compilerProject, typeName, dt, true);
}
assert (foundDefinition == null) || foundDefinition.isInProject(project);
if (foundDefinition instanceof TypeDefinitionBase)
return (TypeDefinitionBase)foundDefinition;
}
return null;
}
protected static TypeDefinitionBase resolveType(DefinitionBase context, IReference typeRef, ICompilerProject project, DependencyType dt)
{
// No type means '*'
if (typeRef == null)
return (TypeDefinitionBase)project.getBuiltinType(IASLanguageConstants.BuiltinType.ANY_TYPE);
IDefinition foundDefinition = typeRef.resolve(project, (ASScope)context.containingScope, dt, true);
assert (foundDefinition == null) || foundDefinition.isInProject(project);
return foundDefinition instanceof TypeDefinitionBase ? (TypeDefinitionBase)foundDefinition : null;
}
/**
* This helper method resolves type references in definitions (i.e., Strings
* such as "int", "Sprite", or "flash.events.IEventDispatcher" that are
* stored as references to a class or interface) to the definition of the
* specified type.
* <p>
* It is called by
* <ul>
* <li><code>resolveType()</code> in DefinitionBase to resolve a type
* annotation such as the "int" on a declaration like
* <code>public var foo:int</code>;</li>
* <li><code>resolveReturnType()</code> in FunctionDefinition to resolve the
* return type of a function declaration;</li>
* <li><code>resolveBaseClass()</code> in ClassDefinition to resolve
* <code>extends</code> clause of a class declaration;</li>
* <li><code>resolveImplementedInterfaces()</code> in ClassDefinition to
* resolve the <code>implements</code> clause of a class declaration;</li>
* <li><code>resolveTypeParameters()</code> in ParameterizedClassDefinition
* to resolve the type of a Vector;</li>
* <li><code>resolveExtendedInterfaces()</code> in InterfaceDefinition to
* resolve the <code>extends</code> clause of an interface declaration.</li>
* </ul>
*
* @param typeName The name of the type to resolve.
* @param project The compiler project.
* @param dt The type of dependency to be created.
* @return The definition of the resolved type.
*/
protected TypeDefinitionBase resolveType(String typeName, ICompilerProject project, DependencyType dt)
{
return resolveType(this, typeName, project, dt);
}
public TypeDefinitionBase resolveType(IReference typeRef, ICompilerProject project, DependencyType dt)
{
return resolveType(this, typeRef, project, dt);
}
protected String getLocationString()
{
StringBuilder sb = new StringBuilder();
int start = getStart();
int end = getEnd();
if (start != -1 && end != -1)
{
sb.append(start);
sb.append('-');
sb.append(end);
}
sb.append(' ');
String path = getContainingFilePath();
if (path != null)
sb.append(path);
return sb.toString();
}
/**
* Generate an AET Name object for this definition, and log any Problems
* encountered while constructing the Name, to the list of problems passed
* in.
*
* @param project the Project to use when resolving the Definitions
* Namespace
* @return An AET Name object that represents the qualified name of this
* definition. Will return null if errors are encountered while constructing
* the name (such as, the Namespace of the Definition is unresolved).
*/
public Name getMName(ICompilerProject project)
{
Name name = null;
Namespace qual = null;
if (namespaceReference != null)
{
qual = namespaceReference.resolveAETNamespace(project);
}
else
{
assert false : "All definitions should have a namespaceReference qualifier.";
ASScope scope = getContainingASScope();
if (scope != null)
qual = ((NamespaceDefinition)NamespaceDefinition.getDefaultNamespaceDefinition(scope)).getAETNamespace();
}
// if we can't figure out the namespaceReference, we can't generate a valid name
if (qual != null)
name = new Name(ABCConstants.CONSTANT_Qname, new Nsset(qual), getBaseName());
return name;
}
protected final ASScope getContainingASScope()
{
IASScope s = getContainingScope();
ASScope scope = s instanceof ASScope ? (ASScope)s : null;
return scope;
}
protected String getNamespaceReferenceAsString()
{
INamespaceReference namespaceReference = getNamespaceReference();
return namespaceReference != null ?
namespaceReference.toString() :
INamespaceConstants.internal_;
}
/**
*
* @return false if and only if we can reject the match due to one or the other or both
* being vectors.
*/
private boolean checkVectorMatch(DefinitionBase node)
{
IASScope thisScope = getContainingScope();
IASScope nodeScope = node.getContainingScope();
if (thisScope != null && nodeScope != null)
{
IScopedDefinition thisDef = thisScope.getDefinition();
IScopedDefinition nodeDef = nodeScope.getDefinition();
if (thisDef instanceof AppliedVectorDefinition && nodeDef instanceof AppliedVectorDefinition)
{
String thisElementQName = ((AppliedVectorDefinition)thisDef).getQualifiedName();
String nodeElementQName = ((AppliedVectorDefinition)nodeDef).getQualifiedName();
if (thisElementQName==null && nodeElementQName==null)
return true; // I don't know if this can happen, but it does then they "match"
if (thisElementQName == null)
return false;
return thisElementQName.equals(nodeElementQName);
}
}
return true; // we could not reject this match due to vector specific issues
}
public boolean matches(DefinitionBase node)
{
if (node == null)
return false;
if (node.getClass() != getClass())
return false;
if (node == this)
return true;
INamespaceReference namespace = getNamespaceReference();
if (namespace == null && node.getNamespaceReference() != null)
return false;
if (namespace != null && namespace.getBaseName().compareTo(node.getNamespaceReference().getBaseName()) != 0)
{
return false;
}
if (node.getBaseName().compareTo(getBaseName()) != 0)
return false;
if (!checkVectorMatch(node))
return false;
String packageName = node.getPackageName();
String packageName2 = getPackageName();
if (packageName == null && packageName2 != null)
return false;
if (packageName != null && packageName2 != null && packageName.compareTo(packageName2) != 0)
return false;
ASScope leftScope = node.getContainingASScope();
if (leftScope != null)
{
leftScope = leftScope.getFileScope();
}
ASScope rightScope = getContainingASScope();
if (rightScope != null)
{
rightScope = rightScope.getFileScope();
}
if (leftScope instanceof SWCFileScope || rightScope instanceof SWCFileScope)
{
return true; //we can't verify path because we might be a definition from a library
}
else if (leftScope instanceof ASFileScope && rightScope instanceof ASFileScope)
{
if (((ASFileScope)leftScope).getContainingPath().compareTo(((ASFileScope)rightScope).getContainingPath()) != 0)
{
return false;
}
}
else
{
//For normal case, just compare the full containing file path
//Note that for some synthetic definitions (like Vector), this path will be null
String nodePath = node.getContainingFilePath();
String thisPath = this.getContainingFilePath();
if (nodePath == null)
{
if (thisPath != null) return false;
}
else
if (nodePath.compareTo(thisPath) != 0)
return false;
}
if (node.getTypeAsDisplayString().compareTo(getTypeAsDisplayString()) != 0)
return false;
return true;
}
/**
* Whether this definition was specified as "Bindable" in its metadata
*/
private boolean isBindableLocally()
{
return getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_BINDABLE) != null;
}
/**
* Whether this definition was specified as "Bindable" in its metadata, or
* inherits bindability from the containing class
*
* @return true if this definition is Bindable.
*/
@Override
public boolean isBindable()
{
// A variable is considered bindable if it is marked bindable,
// or if it is a public var in a class marked bindable
boolean bindable = isBindableLocally();
if (!bindable)
{
IDefinition containing = getParent();
if (containing instanceof IClassDefinition
&& this.getNamespaceReference() == NamespaceDefinition.getPublicNamespaceDefinition())
{
DefinitionBase containingBase = (DefinitionBase)containing;
bindable = containingBase.isBindableLocally();
}
}
return bindable;
}
/**
* @return a collection of Bindable event names, if any were specified in
* the metadata, as well as any inherited from the class
*/
@Override
public List<String> getBindableEventNames()
{
// First get any from this definition
// Use a Set<> temporarily, so we can do an easy union
Set<String> eventNames = new HashSet<String>(getBindableEventNamesLocally());
// Now "or" in any from the containing class
IDefinition containing = getParent();
if (containing instanceof IClassDefinition
&& this.getNamespaceReference() == NamespaceDefinition.getPublicNamespaceDefinition())
{
DefinitionBase containingBase = (DefinitionBase)containing;
eventNames.addAll(containingBase.getBindableEventNamesLocally());
}
// Convert to list, as per the return type in IDefinition
return new ArrayList<String>(eventNames);
}
/**
* @return a collection of Bindable event names, if any were specified in
* the metadata
*/
private List<String> getBindableEventNamesLocally()
{
IMetaTag[] bindableTags = getMetaTagsByName(IMetaAttributeConstants.ATTRIBUTE_BINDABLE);
if (bindableTags != null)
{
List<String> events = new ArrayList<String>();
for (IMetaTag bindableTag : bindableTags)
{
String eventName = bindableTag.getAttributeValue(IMetaAttributeConstants.NAME_BINDABLE_EVENT);
if (eventName == null && bindableTag.getAllAttributes().length == 1)
{
Boolean isStyle = false;
eventName = bindableTag.getAllAttributes()[0].getKey();
if (eventName != null && eventName.equals("style"))
isStyle = true;
eventName = bindableTag.getAllAttributes()[0].getValue();
if (isStyle && eventName.equals("true"))
eventName = "isStyle"; // hack: fake event name "isStyle"
}
if (eventName != null)
events.add(eventName);
}
return events;
}
return Collections.emptyList();
}
/**
* @return a collection of Bindable event names, if any were specified in
* the metadata
*/
@Override
public boolean isBindableStyle()
{
IMetaTag[] bindableTags = getMetaTagsByName(IMetaAttributeConstants.ATTRIBUTE_BINDABLE);
if (bindableTags != null)
{
for (IMetaTag bindableTag : bindableTags)
{
String style = bindableTag.getAttributeValue(IMetaAttributeConstants.NAME_BINDABLE_STYLE);
if (style != null && IASLanguageConstants.TRUE.equals(style))
return true;
}
}
return false;
}
@Override
public boolean isContingent()
{
return (flags & FLAG_CONTINGENT) != 0;
}
/**
* Marks the definition as contingent. Meaning that it's added to the symbol
* table as a placeholder, but codegen will decide whether or not it needs
* to actually be created.
*/
protected void setContingent()
{
flags |= FLAG_CONTINGENT;
}
@Override
public boolean isGeneratedEmbedClass()
{
return (flags & FLAG_GENERATED_EMBED_CLASS) != 0;
}
/**
* Marks a class definition as being auto-generated for an embedded asset
* (i.e., one created for a <code>var</code> with <code>[Embed(...)]</code>
* metadata).
* <p>
* Adding such a class to the project scope in the middle of a compilation
* will not cause the <code>validImports</code> set to be rebuilt.
*/
public void setGeneratedEmbedClass()
{
flags |= FLAG_GENERATED_EMBED_CLASS;
}
@Override
public boolean isContingentNeeded(ICompilerProject project)
{
return ((ASScopeBase)getContainingScope()).isContingentDefinitionNeeded(project, this);
}
protected IMetaTag getSkinPart()
{
return getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_SKIN_PART);
}
protected boolean isRequiredSkinPart(IMetaTag skinPart)
{
// if the required value is not set, it's not required, as skin parts are
// optional by default.
String isRequired = skinPart.getAttributeValue(IMetaAttributeConstants.NAME_SKIN_PART_REQUIRED);
if (isRequired == null)
return false;
if (!isRequired.equals(IMetaAttributeConstants.VALUE_SKIN_PART_REQUIRED_TRUE))
return false;
return true;
}
/**
* Returns the type specified by <code>[ArrayElementType("...")]</code>
* metadata on a variable/getter/setter of type Array. </p> This method is
* in DefinitionBase because it is common to VariableDefinition,
* GetterDefinition, and SetterDefinition.
*/
public String getArrayElementType(ICompilerProject project)
{
if (getTypeAsDisplayString().equals(IASLanguageConstants.Array))
{
return getPropertyMetaTagValue(
(RoyaleProject)project, IMetaAttributeConstants.ATTRIBUTE_ARRAYELEMENTTYPE);
}
return null;
}
/**
* Returns the type specified by <code>[InstanceType("...")]</code> metadata
* on a variable/getter/setter of type mx.core.IDeferredInstance.
* <p>
* This method is in DefinitionBase because it is common to
* VariableDefinition, GetterDefinition, and SetterDefinition.
*/
public String getInstanceType(ICompilerProject project)
{
if (getTypeAsDisplayString().equals(((RoyaleProject)project).getDeferredInstanceInterface()))
{
return getPropertyMetaTagValue(
(RoyaleProject)project, IMetaAttributeConstants.ATTRIBUTE_INSTANCETYPE);
}
return null;
}
/**
* Returns the property name specified by <code>[PercentProxy("...")]</code>
* metadata on a variable/getter/setter. </p> This method is in
* DefinitionBase because it is common to VariableDefinition,
* GetterDefinition, and SetterDefinition.
*/
public String getPercentProxy(ICompilerProject project)
{
IMetaTag metaTag = getPropertyMetaTag(
(RoyaleProject)project, IMetaAttributeConstants.ATTRIBUTE_PERCENT_PROXY);
return metaTag != null ? metaTag.getValue() : null;
}
/**
* Returns <code>true</code> if there is <code>[RichTextContent]</code>
* metadata on a variable/getter/setter.
* <p>
* This method is in DefinitionBase because it is common to
* VariableDefinition, GetterDefinition, and SetterDefinition.
*/
public boolean hasRichTextContent(ICompilerProject project)
{
IMetaTag metaTag = getPropertyMetaTag(
(RoyaleProject)project, IMetaAttributeConstants.ATTRIBUTE_RICHTEXTCONTENT);
return metaTag != null;
}
/**
* Returns <code>true</code> if there is <code>[CollapseWhiteSpace]</code>
* metadata on a variable/getter/setter.
* <p>
* This method is in DefinitionBase because it is common to
* VariableDefinition, GetterDefinition, and SetterDefinition.
*/
public boolean hasCollapseWhiteSpace(ICompilerProject project)
{
IMetaTag metaTag = getPropertyMetaTag(
(RoyaleProject)project, IMetaAttributeConstants.ATTRIBUTE_COLLAPSEWHITESPACE);
return metaTag != null;
}
/**
* Returns <code>true</code> if there is <code>[Inspectable(...)]</code>
* metadata on a vairable/getter/setter that specifies
* <code>format="Color"</code>.
* <p>
* This method is in DefinitionBase because it is common to
* VariableDefinition, GetterDefinition, and SetterDefinition.
*/
public boolean isColor(ICompilerProject project)
{
IMetaTag metaTag = getPropertyMetaTag(
(RoyaleProject)project, IMetaAttributeConstants.ATTRIBUTE_INSPECTABLE);
if (metaTag == null)
return false;
String format = metaTag.getAttributeValue(IMetaAttributeConstants.NAME_INSPECTABLE_FORMAT);
return format.equals(IMetaAttributeConstants.VALUE_INSPECTABLE_FORMAT_COLOR);
}
private IMetaTag getPropertyMetaTag(RoyaleProject project, String metadataName)
{
// Look for metadata with the specified name.
IMetaTag metaTag = getMetaTagByName(metadataName);
// If we don't find it, and we're a getter or setter,
// look on the corresponding setter or getter.
if (metaTag == null && this instanceof IAccessorDefinition)
{
IAccessorDefinition correspondingAccessor =
((IAccessorDefinition)this).resolveCorrespondingAccessor(project);
if (correspondingAccessor != null)
metaTag = correspondingAccessor.getMetaTagByName(metadataName);
}
return metaTag;
}
/**
* This helper method is called by {@code getArrayElementType()} and
* {@code getInstanceType()}.
*/
private String getPropertyMetaTagValue(RoyaleProject project, String metadataName)
{
// Look for metadata with the specified name.
IMetaTag metaTag = getPropertyMetaTag(project, metadataName);
// If we found the metadata, return the type that it specifies.
// There should be one keyless attribute, and we want its value.
return metaTag != null ? metaTag.getValue() : null;
}
/**
* Determines if this definition is in a package namespaceReference. This is here, so
* that the various sub-classes can call it from get*Classification()
* methods.
*
* @return true, if the namespaceReference for this definition is some kind of
* package namespaceReference.
*/
protected boolean inPackageNamespace()
{
INamespaceReference nsRef = getNamespaceReference();
assert nsRef != null : "All definitions should have a namespaceReference reference!";
if (nsRef.isPublicOrInternalNamespace())
return true;
return false;
}
@Override
public boolean isDeprecated()
{
return (flags & FLAG_DEPRECATED) != 0;
}
/**
* Sets a bitflag to indicate that this definition has [Deprecated] metadata.
* Checking this flag is faster than walking through the metadata multiple times.
*/
private void setDeprecated()
{
flags |= FLAG_DEPRECATED;
}
@Override
public IDeprecationInfo getDeprecationInfo()
{
for (IMetaTag metaTag : metaTags)
{
if (metaTag.getTagName().equals(IMetaAttributeConstants.ATTRIBUTE_DEPRECATED))
{
String replacement = metaTag.getAttributeValue(IMetaAttributeConstants.NAME_DEPRECATED_REPLACEMENT);
String since = metaTag.getAttributeValue(IMetaAttributeConstants.NAME_DEPRECATED_SINCE);
String message = metaTag.getAttributeValue(IMetaAttributeConstants.NAME_DEPRECATED_MESSAGE);
return new DeprecationInfo(replacement, since, message);
}
}
return null;
}
/**
* Used only in asserts.
*/
public boolean verify()
{
// Verify the name.
assert getBaseName() != null : "Definition has null name";
// TODO: Verify the source location eventually.
// assert getSourcePath() != null : "Definition has null source path";
// assert getStart() != UNKNOWN : "Definition has unknown start";
// assert getEnd() != UNKNOWN : "Definition has unknown end";
// assert getLine() != UNKNOWN : "Definition has unknown line";
// assert getColumn() != UNKNOWN : "Definition has unknown column";
return true;
}
/**
* Used only for debugging.
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
buildString(sb, false);
return sb.toString();
}
/**
* Used only for debugging, as part of {@link #toString()}.
*/
public void buildString(StringBuilder sb, boolean withLocation)
{
buildInnerString(sb);
if (withLocation)
{
sb.append(' ');
sb.append(getLocationString());
}
}
/**
* Used only for debugging, as part of {@link #toString()}.
*/
protected void buildInnerString(StringBuilder sb)
{
}
/**
* Counts various types of definitions that are created,
* as well as the total number of definitions.
*/
private void countDefinitions()
{
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.COUNTER) == CompilerDiagnosticsConstants.COUNTER)
System.out.println("DefinitionBase incrementing Counter for " + getClass().getSimpleName());
Counter counter = Counter.getInstance();
counter.incrementCount(getClass().getSimpleName());
counter.incrementCount("definitions");
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.COUNTER) == CompilerDiagnosticsConstants.COUNTER)
System.out.println("DefinitionBase done incrementing Counter for " + getClass().getSimpleName());
}
//
// IDefinitionSet methods
//
@Override
public boolean isEmpty()
{
return false;
}
@Override
public int getSize()
{
return 1;
}
@Override
public int getMaxSize()
{
return 1;
}
@Override
public IDefinition getDefinition(int i)
{
assert i == 0;
return this;
}
@Override
public boolean isInProject(ICompilerProject project)
{
final ICompilationUnit compilationUnit = ((ASProjectScope)project.getScope()).getCompilationUnitForDefinition(this);
if (compilationUnit != null)
{
if (compilationUnit.getProject() == project)
return true;
else
return false;
}
if (AppliedVectorDefinition.isVectorScope(getContainingASScope()))
{
IDefinition parentDef = getParent();
return parentDef.isInProject(project);
}
return false;
}
}