blob: 33bcc66efb01d53ea809c186ad8deae36746d2e5 [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.scopes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.royale.compiler.common.ASImportTarget;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.common.IImportTarget;
import org.apache.royale.compiler.common.NodeReference;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.IQualifiers;
import org.apache.royale.compiler.definitions.IScopedDefinition;
import org.apache.royale.compiler.definitions.references.INamespaceReference;
import org.apache.royale.compiler.internal.definitions.AmbiguousDefinition;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.definitions.ClassDefinitionBase;
import org.apache.royale.compiler.internal.definitions.FunctionDefinition;
import org.apache.royale.compiler.internal.definitions.InterfaceDefinition;
import org.apache.royale.compiler.internal.definitions.NamespaceDefinition;
import org.apache.royale.compiler.internal.definitions.ScopedDefinitionBase;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.tree.as.ScopedBlockNode;
import org.apache.royale.compiler.internal.workspaces.Workspace;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IDefinitionSet;
import org.apache.royale.compiler.tree.as.IForLoopNode;
import org.apache.royale.compiler.tree.as.IScopedNode;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.workspaces.IWorkspace;
import org.apache.royale.utils.CheapArray;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
/**
* IASScope implementation for class, interface, function, and 'with' scopes.
*/
public abstract class ASScope extends ASScopeBase
{
private static String[] EMPTY_STRING_ARRAY = new String[0];
protected static final NamespaceDefinition.IUseNamespaceDirective[] EMPTY_USE_ARRAY =
new NamespaceDefinition.IUseNamespaceDirective[0];
private static IForLoopNode[] EMPTY_LOOPCHECK_ARRAY = new IForLoopNode[0];
/**
* Constructor
*
* @param block block node to which this scope belongs
*/
public ASScope(ASScope containingScope, ScopedBlockNode block)
{
super();
setContainingScope(containingScope);
if (block != null)
{
block.setScope(this);
// Node reference constructor only works
// if the setContainingScope has already be called
// above.
scopedNodeRef = new NodeReference(block);
}
}
public ASScope(ASScope containingScope)
{
this(containingScope, null);
}
private ASScope containingScope;
/**
* Weak ref back to the Block node to which this scope belongs TODO: Remove
* once code model clients don't depend on this anymore
*/
protected NodeReference scopedNodeRef = NodeReference.noReference;
/**
* List of all imports in scope
*/
private Object importsInScope = null;
/**
* List of all aliases for imports in scope
*/
private Map<String, String> aliasToImportQualifiedName = null;
private Set<String> packageNames = null;
private Object usedNamespaces = null;
private NamespaceDefinition.INamespaceDirective firstNamespaceDirective;
private NamespaceDefinition.INamespaceDirective lastNamespaceDirective;
private boolean inWith = false;
private Object loopChecks = null;
public boolean getHasLoopCheck(){
return loopChecks != null;
}
public void addLoopCheck(IForLoopNode value){
if (loopChecks == null) loopChecks = CheapArray.create(1);
else {
int len = CheapArray.size(loopChecks);
boolean exitEarly = false;
//sometimes the same for loop exists as more than one instance for the same source locations.
//this seems to happen somtimes in mxml code blocks.
//The following makes sure that only one instance representing the same source code location is tracked
//for ArrayLike inspection
for (int i=0; i<len; i++) {
IForLoopNode existing = (IForLoopNode) CheapArray.get(i, loopChecks);
if (existing.getAbsoluteStart() == value.getAbsoluteStart()
&& existing.getAbsoluteEnd() == value.getAbsoluteEnd()
&& existing.getSourcePath().equals(value.getSourcePath())) {
CheapArray.replace(i, value, loopChecks);
exitEarly = true;
break;
}
}
if (exitEarly) {
return;
}
}
CheapArray.add(value, loopChecks);
}
public IForLoopNode[] getLoopChecks(boolean remove){
IForLoopNode[] returnLoopChecks = (IForLoopNode[]) CheapArray.toArray(loopChecks, EMPTY_LOOPCHECK_ARRAY);
if (remove) loopChecks = null;
return returnLoopChecks;
}
/**
* Sets the scope which lexically contains this scope.
*
* @param containingScope The containing scope.
*/
public void setContainingScope(ASScope containingScope)
{
this.containingScope = containingScope;
// calc this once, as it shouldn't change
this.inWith = getContainingWithScope() != null;
}
/**
* Compact the ArrayLists in this scope (so that they don't take up as much
* space)
*/
@Override
public void compact()
{
super.compact();
if (importsInScope != null)
CheapArray.optimize(importsInScope, EMPTY_STRING_ARRAY);
if (usedNamespaces != null)
CheapArray.optimize(usedNamespaces, EMPTY_USE_ARRAY);
if (loopChecks != null)
CheapArray.optimize(loopChecks, EMPTY_LOOPCHECK_ARRAY);
}
public void addNamespaceDirective(NamespaceDefinition.INamespaceDirective directive)
{
if (lastNamespaceDirective != null)
{
lastNamespaceDirective.setNext(directive);
lastNamespaceDirective = directive;
}
else
{
assert firstNamespaceDirective == null;
firstNamespaceDirective = directive;
lastNamespaceDirective = directive;
}
}
public void addUseDirective(NamespaceDefinition.IUseNamespaceDirective useDirective)
{
addNamespaceDirective(useDirective);
if (usedNamespaces == null)
usedNamespaces = CheapArray.create(1);
CheapArray.add(useDirective, usedNamespaces);
}
public boolean hasImportAlias(String alias)
{
return aliasToImportQualifiedName != null
&& aliasToImportQualifiedName.containsKey(alias);
}
public void addImport(String target, String alias)
{
if (aliasToImportQualifiedName == null)
{
aliasToImportQualifiedName = new HashMap<String, String>();
}
assert !hasImportAlias(alias) : "addImport() should not be called with an existing alias";
addImport(target);
aliasToImportQualifiedName.put(alias, target);
}
public void addImport(String target)
{
if (importsInScope == null)
{
importsInScope = CheapArray.create(20);
packageNames = new HashSet<String>();
}
CheapArray.add(target, importsInScope);
int idx = target.lastIndexOf('.');
if (idx != -1)
{
String packName = target.substring(0, idx);
if (!target.endsWith(".*"))
{
// If this is not a wildcard import, then add the imported name
// to the importedNames table so we can construct the right namespace
// set for that name when we see a reference to it.
String defName = target.substring(idx + 1, target.length());
if (importedNames == null)
importedNames = new HashMap<String, Set<String>>();
Set<String> s = importedNames.get(defName);
if (s == null)
{
s = new LinkedHashSet<String>();
importedNames.put(defName, s);
}
s.add(packName);
}
// Whether the import was a wildcard or not, the packageName contributes to the
// set of known package names.
packageNames.add(packName);
}
}
@Override
public ASScope getContainingScope()
{
return containingScope;
}
/**
* Re-connects this scope to the syntax tree node that corresponds to this
* scope.
*
* @param node {@link IScopedNode} that corresponds to this scope.
*/
public void reconnectScopeNode(IScopedNode node)
{
scopedNodeRef.reconnectNode(node);
}
@Override
public IScopedNode getScopeNode()
{
IWorkspace w = getWorkspace();
return (IScopedNode)scopedNodeRef.getNode(w, this);
}
public String[] getImports()
{
return (String[])CheapArray.toArray(importsInScope, EMPTY_STRING_ARRAY);
}
private ScopedDefinitionBase containingDefinition;
@Override
public ScopedDefinitionBase getDefinition()
{
return containingDefinition;
}
public void setContainingDefinition(ScopedDefinitionBase value)
{
containingDefinition = value;
}
/**
* For debugging only.
*/
@Override
protected String toStringHeader()
{
StringBuilder sb = new StringBuilder();
sb.append(super.toStringHeader());
IDefinition definition = getDefinition();
if (definition != null)
{
sb.append(" for ");
sb.append(definition.toString());
}
return sb.toString();
}
/**
* Determine whether the string passed in is a known package name The scope
* will check if the package was introduced by any of it's imports, and if
* not will delegate to its containing scope.
*
* @param p the string to test
* @return true is p is a package name
*/
public boolean isPackageName(String p)
{
if (packageNames != null)
{
if (packageNames.contains(p))
{
return true;
}
}
if (containingScope != null)
return containingScope.isPackageName(p);
return false;
}
/**
* Return the additional namespaces for a reference, if the name has been
* explicitly imported. If 'a.b.Foo' has been imported, and we see reference
* to Foo, this will return the INamespaceDefinition for 'a.b'. If the name
* has not been explicitly imported then this method will return the empty
* set.
*
* @param project CompilerProject to use to resolve the package INamespaces
* @param name The name of the reference
* @return A Set<INamespaceDefinition> representing the packages from the
* imports if the name was explicitly imported. Returns the empty set if the
* name was not explicitly imported.
*/
public Set<INamespaceDefinition> getExplicitImportQualifiers(CompilerProject project, String name)
{
Set<INamespaceDefinition> nsSet = new LinkedHashSet<INamespaceDefinition>();
Workspace workspace = project.getWorkspace();
getContainingScopeExplicitImports(project, name, nsSet);
if (importedNames != null)
{
// Was it an import in this scope
Set<String> packages = importedNames.get(name);
if (packages != null)
{
for (String s : packages)
{
nsSet.add(workspace.getPackageNamespaceDefinitionCache().get(s, false));
}
}
}
return nsSet.size() > 0 ? nsSet : Collections.<INamespaceDefinition> emptySet();
}
/**
* Get the additional namespaces for a reference, if the name has been explicitly imported in
* a containing scope
* @param project the active project
* @param name the name of the reference
* @param nsSet the namespace set to add the namespaces to
*/
protected void getContainingScopeExplicitImports (CompilerProject project, String name, Set<INamespaceDefinition> nsSet)
{
if (getContainingScope() != null)
{
// check any containing scopes
nsSet.addAll(getContainingScope().getExplicitImportQualifiers(project, name));
}
}
/**
* Maps names to the package name used to look them up - this is used to
* store explicit imports of definitions (import a.b.Foo)
*/
private Map<String, Set<String>> importedNames;
protected INamespaceReference[] getUsedNamespaces()
{
return (INamespaceReference[])CheapArray.toArray(usedNamespaces, EMPTY_USE_ARRAY);
}
/**
* Gets the first namespace definition or use namespace directive in the
* scope.
*
* @return The first namespace definition or use namespace directive in the
* scope.
*/
public NamespaceDefinition.INamespaceDirective getFirstNamespaceDirective()
{
return firstNamespaceDirective;
}
/**
* Adds {@link INamespaceDefinition}'s for each import in this scope to the
* specified namespace set.
*
* @param workspace {@link IWorkspace} used to construct
* {@link INamespaceDefinition}'s for imported packages.
* @param namespaceSet Namespace set to add namespaces to.
*/
public void addLocalImportsToNamespaceSet(IWorkspace workspace, Set<INamespaceDefinition> namespaceSet)
{
String[] imports = getImports();
if (imports != null)
{
for (String importStr : imports)
{
IImportTarget importTarget = ASImportTarget.get(workspace, importStr);
// Only wildcard imports contribute to the namespace set
// e.g. a.b.*, but not a.b.Foo
if (importTarget.isWildcard())
namespaceSet.add(importTarget.getNamespace());
}
}
}
/**
* Calculate the namespace set to use to resolve name. If name is an
* explicitly imported definition, then the namespace set will consist of
* the package name from the import(s) plus the open namespace set. If name
* was not explitly imported then the open namespace set will be calculated
* and returned
*
* @param project The compiler project
* @param name A name.
* @return the namespace set to use to lookup name. This set should not be
* modified
*/
public Set<INamespaceDefinition> getNamespaceSetForName(ICompilerProject project, String name)
{
// if the name is an alias, we want the original name -JT
name = resolveBaseNameFromAlias(name);
if (namespaceSetSameAsContainingScopeNamespaceSet() && getContainingScope() != null)
{
// If this scope doesn't contribute anything to the namespace set, then just ask our containing
// scope for the namespace set. Doing this before we hit the cache has the benefit that the
// namespace set will only get cached in the containing scopes cache, instead of getting cached
// in each individual scope cache (e.g. it will be cached in the class scope, instead of in each function
// scope in the class). This saves a lot of memory, as many functions will not affect the list
// of open namespaces.
return getContainingScope().getNamespaceSetForName(project, name);
}
CompilerProject compilerProject = (CompilerProject)project;
ASScopeCache scopeCache = compilerProject.getCacheForScope(this);
return scopeCache.getNamespaceSetForName(name);
}
protected boolean namespaceSetSameAsContainingScopeNamespaceSet()
{
if ((getImports() != null) || (getUsedNamespaces() != null))
return false;
// function with no namespace set modifications, so reuse
if (containingDefinition instanceof FunctionDefinition)
return true;
// TODO: can with scopes also be optimized here?
return false;
}
/**
* Implementation of getNamespaceSetForName method, above. The scope cache
* will call this method when it does not already have the results cached
*
* @param project
* @param name
* @return the namespace set to use to lookup name. This set should not be
* modified
*/
Set<INamespaceDefinition> getNamespaceSetForNameImpl(ICompilerProject project, String name)
{
if (namespaceSetSameAsContainingScopeNamespaceSet())
{
ASScope containingScope = getContainingScope();
if (containingScope != null)
{
return containingScope.getNamespaceSetForName(project, name);
}
}
Set<INamespaceDefinition> openNamespaces = getNamespaceSet(project);
// If the reference has been explicitly imported, then we are a qualified name lookup
// e.g. 'import a.b.Foo' means that any reference to Foo must have the package namespace of 'a.b' added
// to its set of namespaces
Set<INamespaceDefinition> additionalNamespaces = getExplicitImportQualifiers((CompilerProject)project, name);
if (additionalNamespaces != null)
{
Set<INamespaceDefinition> newSet = new LinkedHashSet<INamespaceDefinition>();
newSet.addAll(openNamespaces);
newSet.addAll(additionalNamespaces);
return newSet;
}
else
{
return openNamespaces;
}
}
/**
* Computes and returns the namespace set for this scope.
* <p>
* The returned set should not be modified.
*
* @param project The compiler project.
* @return The namespace set for this scope. The returned set should not be
* modified
*/
public Set<INamespaceDefinition> getNamespaceSet(ICompilerProject project)
{
CompilerProject compilerProject = (CompilerProject)project;
ASScopeCache scopeCache = compilerProject.getCacheForScope(this);
return scopeCache.getNamespaceSet();
}
/**
* Computes and returns the namespace set for this scope. This is the
* implementation of getNamespaceSet above. The scope cache will call this
* method when it does not have a cached result for the namespace set.
* <p>
* The returned set should not be modified.
*
* @param project
* @return The namespace set for this scope. The returned set should not be
* modified
*/
Set<INamespaceDefinition> getNamespaceSetImpl(ICompilerProject project)
{
if (namespaceSetSameAsContainingScopeNamespaceSet())
{
ASScope containingScope = getContainingScope();
if (containingScope != null)
{
return containingScope.getNamespaceSetImpl(project);
}
}
CompilerProject compilerProject = (CompilerProject)project;
IWorkspace workspace = compilerProject.getWorkspace();
Set<INamespaceDefinition> result = new LinkedHashSet<INamespaceDefinition>();
// First add the imports, use namespaces, etc from this scope
this.addLocalImportsToNamespaceSet(workspace, result);
INamespaceReference[] usedNamespaces = this.getUsedNamespaces();
if (usedNamespaces != null)
{
for (INamespaceReference usedNamespaceReference : usedNamespaces)
{
INamespaceDefinition usedNamespace = usedNamespaceReference.resolveNamespaceReference(compilerProject);
if (usedNamespace != null)
result.add(usedNamespace);
}
}
this.addImplicitOpenNamespaces(compilerProject, result);
// Next add the open namespaces from the containing scope
addNamespacesFromContainingScope(compilerProject, result);
Set<INamespaceDefinition> emptyNamespaceSet = Collections.emptySet();
result = result.size() == 0 ? emptyNamespaceSet : result;
return result;
}
/**
* Add the open namespaces from the containing scope to the namespace set passed in
* @param compilerProject the active project
* @param result the Namespace Set to add namespaces to
*/
protected void addNamespacesFromContainingScope (CompilerProject compilerProject, Set<INamespaceDefinition> result)
{
ASScope containingScope = this.getContainingScope();
if (containingScope != null)
{
result.addAll(containingScope.getNamespaceSet(compilerProject));
}
}
public void addImplicitOpenNamespaces(CompilerProject compilerProject, Set<INamespaceDefinition> result)
{
// By default there is nothing to do here.
// overrides in ASFileScope, PackageScope, and TypeScope.
}
/**
* Adds all definitions ( including definitions from base types ) in the
* current scope to the specified collections of definitions that have a
* namespace qualifier in the specified definition set, when looking for
* definitions in the scope chain.
*
* @param project {@link CompilerProject} used to resolve reference to
* definitions outside of the {@link ICompilationUnit} that contains this
* scope.
* @param defs Collection that found {@link IDefinition}'s are added to.
* @param namespaceSet Namespace set in which the qualifier of any matching
* definition must exist to be considered a match.
*/
public void getAllPropertiesForScopeChain(CompilerProject project, Collection<IDefinition> defs, Set<INamespaceDefinition> namespaceSet)
{
getAllLocalProperties(project, defs, namespaceSet, null);
}
/**
* Adds all definitions ( including definitions from base types ) in the
* current scope to the specified collections of definitions that have a
* namespace qualifier in the specified definition set, when looking for
* definitions through a member access.
*
* @param project {@link CompilerProject} used to resolve reference to
* definitions outside of the {@link ICompilationUnit} that contains this
* scope.
* @param defs Collection that found {@link IDefinition}'s are added to.
* @param namespaceSet Namespace set in which the qualifier of any matching
* definition must exist to be considered a match.
*/
public void getAllPropertiesForMemberAccess(CompilerProject project, Collection<IDefinition> defs, Set<INamespaceDefinition> namespaceSet)
{
getAllLocalProperties(project, defs, namespaceSet, null);
}
/**
* Gets all definitions (including definitions from base types) that have
* the specified name to the specified collections of definitions that have
* a namespace qualifier in the specified definition set, when looking for
* definitions through a member access.
*
* @param project {@link CompilerProject} used to resolve reference to
* definitions outside of the {@link ICompilationUnit} that contains this
* scope.
* @param memberName the name of the desired definition(s).
* @param namespaceSet Namespace set in which the qualifier of any matching
* definition must exist to be considered a match.
* @return the collection of matching definitions.
*/
public List<IDefinition> getPropertiesByNameForMemberAccess(CompilerProject project, String memberName, Set<INamespaceDefinition> namespaceSet)
{
// Get the collection of all properties.
List<IDefinition> result = new ArrayList<IDefinition>();
getPropertyForMemberAccess(project, result, memberName, namespaceSet, true);
return result;
}
/**
* Gets the definition (including definitions from base types) that has
* the specified name and that has a namespace qualifier in the specified
* namespace set, when looking for definitions through a member access.
*
* @param project {@link CompilerProject} used to resolve reference to
* definitions outside of the {@link ICompilationUnit} that contains this
* scope.
* @param memberName the name of the desired definition(s).
* @param namespaceSet Namespace set in which the qualifier of any matching
* definition must exist to be considered a match.
* @return The first definition that matches the name and namespaceSet. May return the AmbiguousDefinition
* if more than one definition in a scope matches.
*/
public IDefinition getPropertyByNameForMemberAccess(CompilerProject project, String memberName, Set<INamespaceDefinition> namespaceSet)
{
List<IDefinition> defs = new ArrayList<IDefinition>();
getPropertyForMemberAccess(project, defs, memberName, namespaceSet, false);
return getSingleResult(project, defs);
}
/**
* Finds all the definitions in this scope that match the specified
* namespace set and base name. This method is intended to implement the
* getproperty operation defined by AS3 and the VM.
* <p>
* If this scope is not for a class or interface definition then only
* definitions in this scope are considered.
* <p>
* If this scope is for a class or interface definition then definitions in
* this scope and the scope for any implemented or extended interfaces and
* classes are also considered. Unless findAll is true, then this function
* returns as soon as one or more definitions has been found that match the
* namespace set and base name.
* <p>
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param defs Collection of {@link IDefinition}'s to add found definitions
* to.
* @param baseName The name of the definition(s) to find.
* @param namespaceSet The namespace set in which a found definition's
* qualifier must be in.
* @param findAll If true find all match definitions that match the baseName
* and namespace set not just those in the first scope that had one or more
* matches.
*/
public void getPropertyForMemberAccess(CompilerProject project, Collection<IDefinition> defs, String baseName, Set<INamespaceDefinition> namespaceSet, boolean findAll)
{
NamespaceSetPredicate nsPred = new NamespaceSetPredicate(project, namespaceSet);
Collection<IDefinition> filteredDefs = new FilteredCollection<IDefinition>(nsPred, defs);
getPropertyForMemberAccess(project, filteredDefs, baseName, nsPred, findAll);
}
/**
* Finds all the definitions in this scope that match the specified
* namespace set and base name. This method is intended to implement the
* getproperty operation defined by AS3 and the VM.
* <p>
* This version of the method expects that the Collection passed in will implement
* whatever filtering is necessary, other than filtering based on the base name.
* For most cases, this means the Collection will be an {@link ASScopeBase.FilteredCollection}
* with a {@link NamespaceSetPredicate}.
* The {@link NamespaceSetPredicate} must also be passed down as some name resolution
* may need it to apply extra namespaces (i.e. deal with protected namespaces)
*
* <p>
* If this scope is not for a class or interface definition then only
* definitions in this scope are considered.
* <p>
* If this scope is for a class or interface definition then definitions in
* this scope and the scope for any implemented or extended interfaces and
* classes are also considered. Unless findAll is true, then this function
* returns as soon as one or more definitions has been found that match the
* namespace set and base name.
* <p>
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param defs Collection of {@link IDefinition}'s to add found definitions
* to. This collection must perform any necessary filtering of results, other than filtering
* based on the baseName.
* @param baseName The name of the definition(s) to find.
* @param namespaceSet The {@link NamespaceSetPredicate} which the name resolution code
* can use to modify the namespace set as necessary.
* @param findAll If true find all match definitions that match the baseName
* and namespace set not just those in the first scope that had one or more
* matches.
*/
protected void getPropertyForMemberAccess(CompilerProject project, Collection<IDefinition> defs, String baseName, NamespaceSetPredicate namespaceSet, boolean findAll)
{
getLocalProperty(project, defs, baseName, true);
}
/**
* Helper method to get a namespace set for a member access
*/
private Set<INamespaceDefinition> getNamespaceSetForMemberAccess(ICompilerProject project, IDefinition def, boolean isSuperRef)
{
Set<INamespaceDefinition> namespaceSet;
if (def instanceof InterfaceDefinition)
// If we are getting a property from an interface, use the special interface namespace set
namespaceSet = ((InterfaceDefinition)def).getInterfaceNamespaceSet(project);
else if (isSuperRef)
namespaceSet = getNamespaceSetForSuper(project, def);
else
{
namespaceSet = getNamespaceSet(project);
// If the expression a.b occurs inside the class definition for a's type A,
// then add A's protected namespace so that we can see a protected b.
if (def == getContainingClass())
namespaceSet.add(((IClassDefinition)def).getProtectedNamespaceReference());
}
return namespaceSet;
}
/**
* Find a property in an IDefinition, using the open namespaces & packages
* of this scope.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param def The definition to resolve the property in
* @param name The name of the definition to find
* @param isSuperRef whether this lookup is through a 'super' reference - if
* it is then the namespace set will be adjusted to use the base classes
* protected namespace instead of the containing classes protected namespace
* @return The IDefinition for the property, or null if one is not found
*/
public IDefinition getPropertyFromDef(ICompilerProject project, IDefinition def, String name, boolean isSuperRef)
{
CompilerProject compilerProject = (CompilerProject)project;
Set<INamespaceDefinition> namespaceSet = getNamespaceSetForMemberAccess(project, def, isSuperRef);
return getPropertyFromDef(compilerProject, def, name, namespaceSet, false);
}
/**
* Find a property in an IDefinition, using the open namespaces & packages
* of this scope, and any additional constraints that are passed in as a {@link Predicate}.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param def The definition to resolve the property in
* @param name The name of the definition to find
* @param additional A {@link Predicate} that will perform additional filtering of the results.
* This {@link Predicate} will run before any namespace set checking.
* @param isSuperRef whether this lookup is through a 'super' reference - if
* it is then the namespace set will be adjusted to use the base classes
* protected namespace instead of the containing classes protected namespace
* @return The IDefinition for the property, or null if one is not found
*/
public IDefinition getPropertyFromDef(ICompilerProject project,
IDefinition def,
String name,
Predicate<IDefinition> additional,
boolean isSuperRef)
{
NamespaceSetPredicate nsPred = new NamespaceSetPredicate(project, getNamespaceSetForMemberAccess(project, def, isSuperRef));
Predicate<IDefinition> combinedPred = Predicates.and(additional, nsPred);
return getPropertyFromDef((CompilerProject)project, def, name, combinedPred, nsPred, isSuperRef);
}
/**
* Find a property in an IDefinition, using the namespace passed in as the
* qualifier.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve references.
* @param def The definition to resolve the property in
* @param name The name of the definition to find
* @param qualifier The namespace to us to look up name
* @param isSuperRef whether this lookup is through a 'super' reference - if
* it is then the namespace set will be adjusted to use the base classes
* protected namespace instead of the containing classes protected namespace
* @return The IDefinition for the property, or null if one is not found
*/
public IDefinition getQualifiedPropertyFromDef(ICompilerProject project, IDefinition def, String name,
INamespaceDefinition qualifier, boolean isSuperRef)
{
Set<INamespaceDefinition> namespaceSet = ImmutableSet.of(qualifier);
if (isSuperRef)
namespaceSet = adjustNamespaceSetForSuper(def, namespaceSet);
return getPropertyFromDef((CompilerProject)project, def, name, namespaceSet, false);
}
/**
* Find a property in an IDefinition, using the qualifiers passed in to provide the namespace set.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve references.
* @param def The definition to resolve the property in
* @param name The name of the definition to find
* @param qualifiers The namespace(s) to us to look up name
* @param isSuperRef whether this lookup is through a 'super' reference - if
* it is then the namespace set will be adjusted to use the base classes
* protected namespace instead of the containing classes protected namespace
* @return The IDefinition for the property, or null if one is not found
*/
public IDefinition getQualifiedPropertyFromDef(ICompilerProject project, IDefinition def, String name,
IQualifiers qualifiers, boolean isSuperRef)
{
Set<INamespaceDefinition> namespaceSet = qualifiers.getNamespaceSet();
if (isSuperRef)
namespaceSet = adjustNamespaceSetForSuper(def, namespaceSet);
return getPropertyFromDef((CompilerProject)project, def, name, namespaceSet, false);
}
/**
* Implementation for getPropertyFromDef + getQualifiedPropertyFromDef
*/
private IDefinition getPropertyFromDef(CompilerProject project, IDefinition def, String name, Set<INamespaceDefinition> namespaceSet, boolean lookForStatics)
{
NamespaceSetPredicate nsPred = new NamespaceSetPredicate(project, namespaceSet);
return getPropertyFromDef(project, def, name, nsPred, nsPred, lookForStatics);
}
/**
* Implementation of getPropertyFromDef + getQualifiedPropertyFromDef
*
* @param project project to resolve references in
* @param def The {@link IDefinition} to get the property from
* @param name The name to look for
* @param pred The {@link Predicate} to use to perform the lookup
* @param nsPred The {@link NamespaceSetPredicate} to use if the namespace set needs to be modified
* during lookup
* @param lookForStatics whether to find statics or not
*/
private IDefinition getPropertyFromDef(CompilerProject project,
IDefinition def,
String name,
Predicate<IDefinition> pred,
NamespaceSetPredicate nsPred,
boolean lookForStatics)
{
ASScope defScope = (ASScope)(def instanceof IScopedDefinition ? ((IScopedDefinition)def).getContainedScope() : null);
// TODO: eliminate lookForStatics flag from getPropertyFromDef methods
if (defScope instanceof TypeScope)
{
// Adjust scope if we are looking in a TypeScope
TypeScope ts = (TypeScope)defScope;
if (lookForStatics)
defScope = ts.getStaticScope();
else
defScope = ts.getInstanceScope();
}
if (defScope != null)
{
ArrayList<IDefinition> defs = new ArrayList<IDefinition>(1);
defScope.getPropertyForMemberAccess(project, new FilteredCollection<IDefinition>(pred, defs), name, nsPred, false);
return getSingleResult(project, defs);
}
return null;
}
/**
* Implementation of getPropertyForScopeChain.
*
* This method will filter results based on baseName only - any additional filtering
* should be done by the {@link Collection} passed in.
*
* @param project {@link CompilerProject} to resolve things in
* @param defs The {@link Collection} to add the results to
* @param baseName The name of the definition to find
* @param namespaceSet the {@link NamespaceSetPredicate} to use if the namespace set needs to be adjusted
* during lookup
*/
protected void getPropertyForScopeChain(CompilerProject project, Collection<IDefinition> defs, String baseName, NamespaceSetPredicate namespaceSet, boolean findAll)
{
getLocalProperty(project, defs, baseName, true);
}
protected String resolveBaseNameFromAlias(String possibleAlias)
{
if (aliasToImportQualifiedName != null
&& aliasToImportQualifiedName.containsKey(possibleAlias))
{
String qualifiedName = aliasToImportQualifiedName.get(possibleAlias);
int index = qualifiedName.lastIndexOf(".");
if (index != -1)
{
return qualifiedName.substring(index + 1);
}
return qualifiedName;
}
ASScope containingScope = getContainingScope();
if (containingScope != null)
{
return containingScope.resolveBaseNameFromAlias(possibleAlias);
}
return possibleAlias;
}
protected String resolveAliasFromQualifiedImport(String qualifiedName)
{
if (aliasToImportQualifiedName != null
&& aliasToImportQualifiedName.containsValue(qualifiedName))
{
for (String key : aliasToImportQualifiedName.keySet())
{
if (aliasToImportQualifiedName.get(key).equals(qualifiedName))
{
return key;
}
}
}
ASScope containingScope = getContainingScope();
if (containingScope != null)
{
return containingScope.resolveAliasFromQualifiedImport(qualifiedName);
}
return null;
}
protected String resolveQualifiedNameFromAlias(String possibleAlias)
{
if (aliasToImportQualifiedName != null
&& aliasToImportQualifiedName.containsKey(possibleAlias))
{
return aliasToImportQualifiedName.get(possibleAlias);
}
ASScope containingScope = getContainingScope();
if (containingScope != null)
{
return containingScope.resolveQualifiedNameFromAlias(possibleAlias);
}
return possibleAlias;
}
/**
* This is called by {@link ASScopeCache} when there was a cache miss.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param baseName base name of the property we are looking for.
* @param namespaceSet Namespace set in which the qualifier of any found
* definition must found.
* @param dt The type of dependency that should be added to the dependency
* graph when resolving this reference across a compilation unit boundary.
* @return One or more {@link IDefinition}'s matched by the namespace set
* and base name.
*/
public List<IDefinition> findProperty(CompilerProject project, String baseName, Set<INamespaceDefinition> namespaceSet, DependencyType dt)
{
return findProperty(project, baseName, namespaceSet, dt, false);
}
/**
* Version of findProperty that determine the results based on the namespace set passed in,
* along with any additional constraints passed in via the {@link Predicate}.
*
*
* @param project The {@link CompilerProject} to resolve things in
* @param baseName The name to find
* @param additional Any additional constraints on the lookup. This predicate will
* run before any namespace checking occurs.
* @param namespaceSet The Namespace set to use for the lookup
* @param dt The dependency type to introduce if this resolves to something from
* another compilation unit
* @return a List of IDefinition that matched the name, namespace set, and any
* additional constraints specified by the predicate.
*/
public List<IDefinition> findProperty(CompilerProject project,
String baseName,
Predicate<IDefinition> additional,
Set<INamespaceDefinition> namespaceSet,
DependencyType dt)
{
return findProperty(project, baseName, additional, namespaceSet, dt, false);
}
/**
* This is the core <code>findproperty()</code> method. It implements the
* equivalent of the <code>findprop</code> AVM instruction in Royale.
* <p>
* The algorithm searches up the scope chain, starting with this scope, for
* definitions with the specified base name and namespace set.
* <p>
* After the file scope, the project scope is searched if necessary. If
* definitions are found in the project scope, a dependency is created on
* the compilation unit that produced them.
* <p>
* If the <code>findAll</code> parameter is <code>false</code>, the search
* stops with the first scope that has one or more matching definition; if
* it is <code>true</code>, the search continues to find all matching
* definitions in the entire chain, including the project scope.
*
* @param accumulator Collection to which definitions that match the
* namespace set and base name are added.
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param baseName base name of the property we are looking for.
* @param namespaceSet Namespace set in which the qualifier of any found
* definition must found.
* @param dt The type of dependency that should be added to the dependency
* graph when resolving this reference across a compilation unit boundary.
* @param findAll If true, then find all definitions that match the
* namespace set and base name, not just those from the first scope with a
* single match.
*/
public void findProperty(Collection<IDefinition> accumulator, CompilerProject project,
String baseName, Set<INamespaceDefinition> namespaceSet,
DependencyType dt, boolean findAll)
{
NamespaceSetPredicate nsPred = new NamespaceSetPredicate(project, namespaceSet);
FilteredCollection<IDefinition> filteredCollection = new FilteredCollection<IDefinition>(nsPred, accumulator);
findProperty(filteredCollection, project, baseName, nsPred, dt, findAll);
}
/**
* This is the implementation of the various <code>findproperty()</code> methods. It implements the
* equivalent of the <code>findprop</code> AVM instruction in Royale.
* <p>
* The algorithm searches up the scope chain, starting with this scope, for
* definitions with the specified base name.
* <p>
* If any additional constraints are required (e.g. filtering based on the namespace set), then
* callers should pass in an {@link ASScopeBase.FilteredCollection} as the accumulator that will implement those
* constraints. For the common case, the accumulator will be an {@link ASScopeBase.FilteredCollection}
* with a {@link NamespaceSetPredicate}.
* <p>
* After the file scope, the project scope is searched if necessary. If
* definitions are found in the project scope, a dependency is created on
* the compilation unit that produced them.
* <p>
* If the <code>findAll</code> parameter is <code>false</code>, the search
* stops with the first scope that has one or more matching definition; if
* it is <code>true</code>, the search continues to find all matching
* definitions in the entire chain, including the project scope.
*
* @param accumulator Collection to which definitions that match the
* base name are added.
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param baseName base name of the property we are looking for.
* @param nsPred The {@link NamespaceSetPredicate}, if one is being used, that the lookup
* can modify as it walks up the scope chain (necessary to handle protected correctly).
* @param dt The type of dependency that should be added to the dependency
* graph when resolving this reference across a compilation unit boundary.
* @param findAll If true, then find all definitions that match the
* namespace set and base name, not just those from the first scope with a
* single match.
*/
protected void findProperty(Collection<IDefinition> accumulator, CompilerProject project,
String baseName, NamespaceSetPredicate nsPred,
DependencyType dt, boolean findAll)
{
assert accumulator.isEmpty() : "findProperty() should not be called with a non-empty collection";
assert baseName.indexOf('.') == -1 : "baseName must not be any sort of qname " + baseName;
// Walk the scope chain starting with this scope.
// This loop may go as far as the file scope, whose containing scope is null.
// But it may break out early; lastSearchScope will keep track of how far it went.
ASScope lastSearchedScope = null;
String baseNameForAlias = this.resolveBaseNameFromAlias(baseName);
for (ASScope currentScope = this; currentScope != null; currentScope = currentScope.getContainingScope())
{
// If we're not looking for all matching definitions,
// and we've already got some, stop walking.
if (!findAll && accumulator.size() != 0)
break;
// Search one scope for any definitions matching baseName and naamespaceSet.
currentScope.getPropertyForScopeChain(project, accumulator, baseNameForAlias, nsPred, findAll);
// Keep track of the last scope that was searched.
lastSearchedScope = currentScope;
}
assert lastSearchedScope != null || accumulator.size() == 0 : "If accumulator is not empty, which searched scope added to it?";
// Determine whether we need to search the project scope.
boolean searchProjectScope = false;
// If we haven't found any matching definitions yet,
// we need to search the project scope.
if (accumulator.size() == 0)
searchProjectScope = true;
// If we're looking for all matching definitions,
// we need to search the project scope.
else if (findAll)
searchProjectScope = true;
// If the last scope we searched was a package scope or a file scope,
// we need to search the project scope because the project scope
// might have other definitions with the same name which should
// cause an ambiguity.
else if (lastSearchedScope instanceof PackageScope ||
lastSearchedScope instanceof ASFileScope)
{
searchProjectScope = true;
}
// Search the project scope if necessary.
if (searchProjectScope)
{
ASProjectScope projectScope = project.getScope();
projectScope.getPropertyForScopeChain(this, accumulator, baseNameForAlias, nsPred.getNamespaceSet(), dt);
}
if(!baseName.equals(baseNameForAlias)) // has alias
{
// remove anything with the same base name that doesn't have an
// alias, unless its base name is equal to the alias. that is the
// only time where there will be ambiguity. -JT
String alias = baseName; //for clarity
ArrayList<IDefinition> toRemove = new ArrayList<IDefinition>();
String qualifiedNameForAlias = resolveQualifiedNameFromAlias(alias);
for(IDefinition definition : accumulator)
{
if(!definition.getBaseName().equals(alias)
&& !definition.getQualifiedName().equals(qualifiedNameForAlias))
{
toRemove.add(definition);
}
}
// some collections can't remove while iterating, so do it after
// collecting all of the definitions to remove -JT
accumulator.removeAll(toRemove);
}
else // no alias
{
// remove anything that has an alias, unless its alias is equal to
// the original base name.
ArrayList<IDefinition> toRemove = new ArrayList<IDefinition>();
for (IDefinition definition : accumulator)
{
String otherAlias = resolveAliasFromQualifiedImport(definition.getQualifiedName());
if (otherAlias != null && !otherAlias.equals(baseName))
{
toRemove.add(definition);
}
}
accumulator.removeAll(toRemove);
}
}
/**
* For each {@link IProtectedNamespaceDefinition} in the given
* {@code namespaceSet}, if it does not have a corresponding
* {@link IStaticProtectedNamespaceDefinition}, create one and add it to the
* {@code namespaceSet}.
*
* @param namespaceSet Namespace definitions. New items might be added.
* @return Updated namespace definitions.
*/
@SuppressWarnings("unused")
private static Set<INamespaceDefinition> addStaticProtectedNS(Set<INamespaceDefinition> namespaceSet)
{
if (namespaceSet == null)
return null;
// The keys are URI strings. The values are "protected" namespace definitions.
final Map<String, INamespaceDefinition.IStaticProtectedNamespaceDefinition> staticProtectedNamespaces =
new HashMap<String, INamespaceDefinition.IStaticProtectedNamespaceDefinition>();
final Set<INamespaceDefinition.IProtectedNamespaceDefinition> protectedNamespaces =
new HashSet<NamespaceDefinition.IProtectedNamespaceDefinition>();
for (final INamespaceDefinition namespace : namespaceSet)
{
if (namespace instanceof INamespaceDefinition.IStaticProtectedNamespaceDefinition)
{
final INamespaceDefinition.IStaticProtectedNamespaceDefinition staticProtectedNamespace =
(INamespaceDefinition.IStaticProtectedNamespaceDefinition)namespace;
staticProtectedNamespaces.put(
staticProtectedNamespace.getURI(),
staticProtectedNamespace);
}
else if (namespace instanceof INamespaceDefinition.IProtectedNamespaceDefinition)
{
protectedNamespaces.add((INamespaceDefinition.IProtectedNamespaceDefinition)namespace);
}
}
// Find all "protected" namespace definitions that don't have their
// corresponding "static protected" namespace definitions.
final Set<INamespaceDefinition.IStaticProtectedNamespaceDefinition> addedStaticProtectedNamespaces =
new HashSet<NamespaceDefinition.IStaticProtectedNamespaceDefinition>();
for (final INamespaceDefinition.IProtectedNamespaceDefinition protectedNamespace : protectedNamespaces)
{
if (!staticProtectedNamespaces.containsKey(protectedNamespace.getURI()))
{
addedStaticProtectedNamespaces.add(
NamespaceDefinition.createStaticProtectedNamespaceDefinition(
protectedNamespace.getURI()));
}
}
final Set<INamespaceDefinition> result;
if (addedStaticProtectedNamespaces.isEmpty())
{
result = namespaceSet;
}
else
{
result = new HashSet<INamespaceDefinition>();
result.addAll(addedStaticProtectedNamespaces);
result.addAll(namespaceSet);
}
return result;
}
/**
* This is the core findproperty method. This method implements the
* equivalent of the findprop AVM instruction in Royale.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param baseName base name of the property we are looking for.
* @param namespaceSet Namespace set in which the qualifier of any found
* definition must found.
* @param dt The type of dependency that should be added to the dependency
* graph when resolving this reference across a compilation unit boundary.
* @param findAll If true, then find all definitons that match the namespace
* set and base name, not just those from the first scope with a single
* match.
* @return One or more {@link IDefinition}'s matched by the namespace set
* and base name.
*/
private List<IDefinition> findProperty(CompilerProject project, String baseName, Set<INamespaceDefinition> namespaceSet, DependencyType dt, boolean findAll)
{
ArrayList<IDefinition> defs = new ArrayList<IDefinition>(1);
findProperty(defs, project, baseName, namespaceSet, dt, findAll);
return defs;
}
/**
* Version of findProperty that determine the results based on the namespace set passed in,
* along with any additional constraints passed in via the {@link Predicate}.
*
*
* @param project The {@link CompilerProject} to resolve things in
* @param baseName The name to find
* @param additional Any additional constraints on the lookup. This predicate will
* run before any namespace checking occurs.
* @param namespaceSet The Namespace set to use for the lookup
* @param dt The dependency type to introduce if this resolves to something from
* another compilation unit
* @return a List of IDefinition that matched the name, namespace set, and any
* additional constraints specified by the predicate.
*/
private List<IDefinition> findProperty(CompilerProject project,
String baseName,
Predicate<IDefinition> additional,
Set<INamespaceDefinition> namespaceSet,
DependencyType dt,
boolean findAll)
{
ArrayList<IDefinition> defs = new ArrayList<IDefinition>(1);
NamespaceSetPredicate nsPred = new NamespaceSetPredicate(project, namespaceSet);
Predicate<IDefinition> pred = Predicates.and(additional, nsPred);
FilteredCollection<IDefinition> filteredCollection = new FilteredCollection<IDefinition>(pred, defs);
findProperty(filteredCollection, project, baseName, nsPred, dt, findAll);
return defs;
}
/**
* Is this scope inside a with scope.
*
* @return true if this scope is nested in a with scope, or this scope is a
* with scope
*/
public boolean isInWith()
{
return inWith;
}
/**
* Get any containing with scope
*
* @return the ASScope that is the containing with scope, or null if there
* is no containing with scope
*/
ASScope getContainingWithScope()
{
ASScope scope = this;
while (scope != null)
{
if (scope instanceof WithScope)
return scope;
scope = scope.getContainingScope();
}
return scope;
}
/**
* IFilter the result of a findDefinition based on any containing with scopes
* and if the lookup is allowed to escape a with scope This method is used
* by findProperty, and findPropertyQualified to filter the results. By
* default, the lookup will look past the with scopes (this is what code
* model expects), so we will filter the results here, but only if we are in
* a with scope. With scopes are rare enough that the performance hit
* shouldn't be too bad, since we only do the filtering when we know we are
* in a with scope. If we are not in a with scope, or the lookup is allowed
* to escape the with block, then we immediately return the passed in
* definition.
*
* @param d the definition the lookup resolved to
* @param canEscapeWith true if the lookup can esape a with, false if it
* can't
* @return the definition to use as the result of the lookup
*/
IDefinition filterWith(IDefinition d, boolean canEscapeWith)
{
if (!inWith || canEscapeWith || d == null)
return d;
ASScope withScope = getContainingWithScope();
if (withScope != null)
{
ASScope scope = this;
while (scope != null)
{
// Didn't find the defns containing scope before we hit
// the with, so act as if we couldn't resolve it.
if (scope == withScope)
return null;
// We found the declaring scope, and we haven't seen a with
// scope yet, so we can return the definition
if (scope == d.getContainingScope())
return d;
scope = scope.getContainingScope();
}
return null;
}
return d;
}
/**
* The main public entry point for the findprop operation in the compiler.
* This method uses the {@link ASScopeCache} to improve performance.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param baseName base name of the property we are looking for.
* @param dt The type of dependency that should be added to the dependency
* graph when resolving this reference across a compilation unit boundary.
* @return A single {@link IDefinition} to which the specified base name
* resolves to in this scope, or null. Null is returned when no definition
* is found <b>and</b> when more than one definition is found.
*/
public IDefinition findProperty(ICompilerProject project, String baseName, DependencyType dt)
{
return findProperty(project, baseName, dt, false);
}
/**
* The main public entry point for the findprop operation in the compiler.
* This method uses the {@link ASScopeCache} to improve performance.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param baseName base name of the property we are looking for.
* @param dt The type of dependency that should be added to the dependency
* graph when resolving this reference across a compilation unit boundary.
* @param canEscapeWith should this lookup find definitions that occur
* outside of a containing with scope
* @return A single {@link IDefinition} to which the specified base name
* resolves to in this scope, or null. Null is returned when no definition
* is found <b>and</b> when more than one definition is found.
*/
public IDefinition findProperty(ICompilerProject project, String baseName, DependencyType dt, boolean canEscapeWith)
{
if (canDelegateLookupToContainingScope(baseName))
{
// If we know that we can't possibly find the property in this scope, just ask the containing scope
// which may have already computed and cached the result.
return getContainingScope().findProperty(project, baseName, dt, canEscapeWith);
}
assert baseName.indexOf('.') == -1 : "baseName must not be any sort of qname " + baseName;
CompilerProject compilerProject = (CompilerProject)project;
ASScopeCache scopeCache = compilerProject.getCacheForScope(this);
return filterWith(scopeCache.findProperty(baseName, dt, canEscapeWith), canEscapeWith);
}
/**
* An alternate entry point for findprop operations.
*
* This method takes an addition Predicate that allows custom filtering of the results, instead
* of just using the namespace set. This method will still use the namespace set, but the predicate passed
* in will be called first to filter the results.
*
* @param project the active project
* @param baseName base name of the property we're looking for
* @param additional A Predicate that performs custom filtering on the results
* @param dt The dependency type that should be added to the dependency graph
* when resolving this reference across a compilation boundary
* @param canEscapeWith should this lookup find definitions that occur outside of a containing with scope
* @return A single {@link IDefinition} to which the specified base name resolves to in this
* scope, given the additional constraints supplied by the additional Predicate.
*/
public IDefinition findProperty(ICompilerProject project, String baseName, Predicate<IDefinition> additional, DependencyType dt, boolean canEscapeWith)
{
Set<INamespaceDefinition> nsSet = getNamespaceSetForName(project, baseName);
return findProperty(project, baseName, additional, nsSet, dt, canEscapeWith);
}
public IDefinition findProperty (ICompilerProject project, String baseName, Predicate<IDefinition> additional, Set<INamespaceDefinition> nsSet, DependencyType dt, boolean canEscapeWith)
{
NamespaceSetPredicate nsPred = new NamespaceSetPredicate(project, nsSet);
List<IDefinition> storage = new ArrayList<IDefinition>();
Predicate<IDefinition> pred = Predicates.and(additional, nsPred);
FilteredCollection<IDefinition> defs = new FilteredCollection<IDefinition>(pred, storage);
findProperty(defs, (CompilerProject)project, baseName, nsPred, dt, false);
IDefinition def = null;
def = getSingleResult(project, storage);
return filterWith(def, canEscapeWith);
}
/**
* Helper method to narrow a List of results down to one result for the methods
* that return only 1 result.
* @return null if there are no results,
* the first definition if there is 1 result
* one of the definitions if there are multiple results, and the ambiguities can be resolved
* an {@link AmbiguousDefinition} if there are multiple results and the ambiguities could
* not be resolved
*/
static IDefinition getSingleResult (ICompilerProject project, List<IDefinition> defs)
{
IDefinition def;
switch (defs.size())
{
case 0:
// No definition found!
def = null;
break;
case 1:
// found single definition!
def = defs.get(0);
assert def.isInProject(project);
break;
default:
IDefinition d = AmbiguousDefinition.resolveAmbiguities(project, defs, false);
if (d != null)
def = d;
else
def = AmbiguousDefinition.get();
}
return def;
}
/**
* Is it ok to skip this scope, and just ask the containing scope to perform
* the lookup. This is possible if this scope does not contribute to the set
* of open namespaces, and if we have no definitions with the simple name we
* are looking for.
*
* @return true if we can just ask the containing scope to perform the
* lookup
*/
protected boolean canDelegateLookupToContainingScope(String name)
{
return namespaceSetSameAsContainingScopeNamespaceSet()
&& getContainingScope() != null
&& getLocalDefinitionSetByName(name) == null;
}
/**
* The main public entry point for the findprop operation in the compiler
* with an explicit qualifier namespace. This method uses the
* {@link ASScopeCache} to improve performance.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param qual {@link INamespaceDefinition} which must match the qualifier
* namespace of the found {@link IDefinition}.
* @param baseName base name of the property we are looking for.
* @param dt The type of dependency that should be added to the dependency
* graph when resolving this reference across a compilation unit boundary.
* @return A single {@link IDefinition} to which the specified qualifier and
* base name resolves to in this scope, or null. Null is returned when no
* definition is found <b>and</b> when more than one definition is found.
*/
public IDefinition findPropertyQualified(ICompilerProject project, INamespaceDefinition qual, String baseName, DependencyType dt)
{
return findPropertyQualified(project, qual, baseName, dt, false);
}
/**
* The main public entry point for the findprop operation in the compiler
* with an explicit qualifier namespace. This method uses the
* {@link ASScopeCache} to improve performance.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param qual {@link INamespaceDefinition} which must match the qualifier
* namespace of the found {@link IDefinition}.
* @param baseName base name of the property we are looking for.
* @param dt The type of dependency that should be added to the dependency
* graph when resolving this reference across a compilation unit boundary.
* @return A single {@link IDefinition} to which the specified qualifier and
* base name resolves to in this scope, or null. Null is returned when no
* definition is found <b>and</b> when more than one definition is found.
*/
public IDefinition findPropertyQualified(ICompilerProject project, Predicate<IDefinition> additional,
INamespaceDefinition qual, String baseName, DependencyType dt)
{
if( qual == null )
return null;
NamespaceSetPredicate nsPred = new NamespaceSetPredicate(project, ImmutableSet.of(qual));
Predicate<IDefinition> pred = Predicates.and(additional, nsPred);
List<IDefinition> defs = new ArrayList<IDefinition>();
FilteredCollection<IDefinition> filteredCollection = new FilteredCollection<IDefinition>(pred, defs);
findProperty(filteredCollection, (CompilerProject)project, baseName, nsPred, dt, false);
return getSingleResult(project, defs);
}
/**
* The main public entry point for the findprop operation in the compiler
* with an explicit qualifier namespace. This method uses the
* {@link ASScopeCache} to improve performance.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param qual The qualifier(s) to use to lookup the property.
* @param baseName base name of the property we are looking for.
* @param canEscapeWith should this lookup find definitions that occur
* outside of a containing with scope
* @param dt The type of dependency that should be added to the dependency
* graph when resolving this reference across a compilation unit boundary.
* @return A single {@link IDefinition} to which the specified qualifier and
* base name resolves to in this scope, or null. Null is returned when no
* definition is found <b>and</b> when more than one definition is found.
*/
public IDefinition findPropertyQualified(ICompilerProject project, INamespaceDefinition qual, String baseName, DependencyType dt, boolean canEscapeWith)
{
assert baseName.indexOf('.') == -1 : "baseName must not be any sort of qname " + baseName;
// Can't find a property if we don't know what its qualifier is
if( qual == null )
return null;
CompilerProject compilerProject = (CompilerProject)project;
ASScopeCache scopeCache = compilerProject.getCacheForScope(this);
IDefinition definition = scopeCache.findPropertyQualified(qual, baseName, dt);
return filterWith(definition, canEscapeWith);
}
/**
* The main public entry point for the findprop operation in the compiler
* with an explicit set of qualifier namespaces. This method uses the
* {@link ASScopeCache} to improve performance.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set this scope.
* @param baseName base name of the property we are looking for.
* @param qual The qualifier(s) to use to lookup the property.
* @param canEscapeWith should this lookup find definitions that occur
* outside of a containing with scope
* @param dt The type of dependency that should be added to the dependency
* graph when resolving this reference across a compilation unit boundary.
* @return A single {@link IDefinition} to which the specified qualifier and
* base name resolves to in this scope, or null. Null is returned when no
* definition is found <b>and</b> when more than one definition is found.
*/
public IDefinition findPropertyQualified(ICompilerProject project, IQualifiers qual, String baseName, DependencyType dt, boolean canEscapeWith)
{
if( qual == null || qual.getNamespaceCount() == 0 )
return null ;
if( qual.getNamespaceCount() == 1 )
{
return findPropertyQualified(project, qual.getFirst(), baseName, dt, canEscapeWith);
}
else
{
List<IDefinition> defs = findProperty((CompilerProject)project, baseName, qual.getNamespaceSet(), dt);
return filterWith(getSingleResult(project, defs), canEscapeWith);
}
}
/**
* Helper method to get the namespace set to use for a super reference. This
* will replace the protected namespace for this class with the protected
* namespace for the super class in the returned namespace set.
*
* @param project project used to resolve namespaces
* @param superDef the IDefinition representing the base class
* @return The correct namespace set to use for a super reference
*/
public Set<INamespaceDefinition> getNamespaceSetForSuper(ICompilerProject project, IDefinition superDef)
{
Set<INamespaceDefinition> nsSet = getNamespaceSet(project);
return adjustNamespaceSetForSuper(superDef, nsSet);
}
/**
* Adjust the namespace set passed in so it's the right set for a super
* access. This will replace the protected namespace for this class with the
* protected namespace for the super class in the returned namespace set.
*
* @param superDef the IDefinition representing the base class
* @param nsSet the namespace set to adjust
* @return The correct namespace set to use for a super reference
*/
public Set<INamespaceDefinition> adjustNamespaceSetForSuper(IDefinition superDef, Set<INamespaceDefinition> nsSet)
{
ClassDefinitionBase containingClass = getContainingClass();
if (superDef instanceof ClassDefinition &&
nsSet.contains(containingClass.getProtectedNamespaceReference()))
{
Set<INamespaceDefinition> adjustedSet = new LinkedHashSet<INamespaceDefinition>();
adjustedSet.addAll(nsSet);
adjustedSet.remove(containingClass.getProtectedNamespaceReference());
adjustedSet.add(((ClassDefinition)superDef).getProtectedNamespaceReference());
return adjustedSet;
}
return nsSet;
}
/**
* Helper method to return the ClassDefinition this scope is inside of, if
* there is one.
*
* @return the ClassDefinition that contains this scope, or null if this
* scope is not in a ClassDefinition
*/
public ClassDefinitionBase getContainingClass()
{
ASScope scope = this;
ScopedDefinitionBase sdb = null;
while (scope != null && sdb == null)
{
// Walk up the scope chain until we find the first scope with a definition.
// stuff like catch, or with scopes will have no definition associated with them
sdb = scope.getDefinition();
scope = scope.getContainingScope();
}
if (sdb instanceof ClassDefinitionBase)
return (ClassDefinitionBase)sdb;
else if (sdb != null)
return (ClassDefinitionBase)sdb.getAncestorOfType(ClassDefinitionBase.class);
return null;
}
public ASFileScope getFileScope()
{
ASScope scope = this;
while (!(scope instanceof ASFileScope))
{
scope = scope.getContainingScope();
// instantiated Vectors may not have containing scopes
if (scope == null)
break;
}
return (ASFileScope)scope;
}
/**
* Get's the {@link IWorkspace} in which this {@link ASScope} lives.
*
* @return The {@link IWorkspace} in which this {@link ASScope} lives.
*/
public IWorkspace getWorkspace()
{
return getFileScope().getWorkspace();
}
public String getContainingSourcePath(String qName, ICompilerProject project)
{
ASScope containingScope = getContainingScope();
if (containingScope != null)
return containingScope.getContainingSourcePath(qName, project);
return null;
}
/**
* Determine if any of the definitions in this scope are Bindable
*
* @return true, if any non-static definitions in this scope are explicitly marked
* bindable, false if there are none.
*/
public boolean hasAnyBindableDefinitions()
{
for (IDefinitionSet set : getAllLocalDefinitionSets())
{
int n = set.getSize();
for (int i = 0; i < n; i++)
{
IDefinition d = set.getDefinition(i);
if (d.isBindable() && !d.isStatic())
return true;
}
}
return false;
}
/**
* Get's the {@link ScopedDefinitionBase} that contains this scope. This
* method differs from {@link #getDefinition()} in that this method will
* walk up the scope chain.
*
* @return The {@link ScopedDefinitionBase} that contains this scope
*/
public IScopedDefinition getContainingDefinition()
{
// sub-classes override this method.
// This class just returns the definition attached this scope.
return containingDefinition;
}
/**
* Makes this scope be the containing scope of the specified anonymous
* function. Anonymous functions do not get added to scopes, but they do
* need to know which scope they are inside of.
*/
public void setAsContainingScopeOfAnonymousFunction(FunctionDefinition anonymousFunction)
{
anonymousFunction.setContainingScope(this);
}
/**
* Add a dependency to the given builtintype, from the compilation unit which contains this scope
* @param project the active project
* @param builtinType the builtin type to depend on
* @param dependencyType the type of dependency to add
*/
public void addDependencyOnBuiltinType(ICompilerProject project, IASLanguageConstants.BuiltinType builtinType,
DependencyType dependencyType)
{
// Just proxy up to the file scope, since dependencies are from CompilationUnit to CompilationUnit
if( containingScope != null )
containingScope.addDependencyOnBuiltinType(project, builtinType, dependencyType);
}
/**
* Implementation of addDependencyOnBuiltinType that will actually add the dependency.
* This will only be called if there is a cache miss.
* @param project the active project
* @param builtinType the builtin type to depend on
* @param dependencyType type of dependency to add
*/
void addDependencyOnBuiltinTypeImpl(CompilerProject project, IASLanguageConstants.BuiltinType builtinType,
DependencyType dependencyType)
{
IDefinition definition = project.getBuiltinType(builtinType);
if( definition != null &&
builtinType != IASLanguageConstants.BuiltinType.ANY_TYPE &&
builtinType != IASLanguageConstants.BuiltinType.VOID)
{
ASProjectScope projectScope = project.getScope();
ICompilationUnit from = projectScope.getCompilationUnitForScope(this);
ICompilationUnit to = projectScope.getCompilationUnitForDefinition(definition);
String qname = definition.getQualifiedName();
project.addDependency(from, to, dependencyType, qname);
}
}
}