blob: ccad9d69506bbe951f129918f041b820f06b85d2 [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.tree.as;
import static org.apache.royale.abc.ABCConstants.CONSTANT_Multiname;
import static org.apache.royale.abc.ABCConstants.CONSTANT_MultinameA;
import static org.apache.royale.abc.ABCConstants.CONSTANT_Qname;
import static org.apache.royale.abc.ABCConstants.CONSTANT_QnameA;
import static org.apache.royale.abc.ABCConstants.CONSTANT_RTQname;
import static org.apache.royale.abc.ABCConstants.CONSTANT_RTQnameA;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Set;
import antlr.Token;
import com.google.common.collect.ImmutableSet;
import org.apache.royale.abc.semantics.Name;
import org.apache.royale.abc.semantics.Nsset;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.config.Configuration;
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.ITypeDefinition;
import org.apache.royale.compiler.definitions.IQualifiers;
import org.apache.royale.compiler.definitions.IVariableDefinition.VariableClassification;
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.definitions.references.ReferenceFactory;
import org.apache.royale.compiler.internal.definitions.AmbiguousDefinition;
import org.apache.royale.compiler.internal.definitions.ClassDefinitionBase;
import org.apache.royale.compiler.internal.definitions.DefinitionBase;
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.VariableDefinition;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.semantics.PostProcessStep;
import org.apache.royale.compiler.internal.semantics.SemanticUtils;
import org.apache.royale.compiler.internal.tree.as.metadata.DefaultPropertyTagNode;
import org.apache.royale.compiler.internal.tree.as.metadata.EffectTagNode;
import org.apache.royale.compiler.internal.tree.as.metadata.EventTagNode;
import org.apache.royale.compiler.internal.tree.as.metadata.MetaTagsNode;
import org.apache.royale.compiler.internal.tree.as.metadata.StyleTagNode;
import org.apache.royale.compiler.internal.workspaces.Workspace;
import org.apache.royale.compiler.parsing.IASToken;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IDefinitionNode;
import org.apache.royale.compiler.tree.as.IFileNode;
import org.apache.royale.compiler.tree.as.IIdentifierNode;
import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode;
import org.apache.royale.compiler.tree.as.IScopedDefinitionNode;
import org.apache.royale.compiler.tree.as.IScopedNode;
import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode.LanguageIdentifierKind;
import org.apache.royale.compiler.tree.metadata.IMetaTagNode;
import org.apache.royale.compiler.tree.mxml.IMXMLMetadataNode;
import org.apache.royale.compiler.workspaces.IWorkspace;
/**
* ActionScript parse tree node representing an identifier
*/
public class IdentifierNode extends ExpressionNodeBase implements IIdentifierNode
{
/**
* Create a dummy {@code IdentifierNode} after the given token. This is used
* to repair the AST where an identifier node is expected.
*
* @param token The empty ID node will have start offset following this
* token.
* @return Dummy identifier node.
*/
public static IdentifierNode createEmptyIdentifierNodeAfterToken(final Token token)
{
final IdentifierNode result = new IdentifierNode("");
result.startAfter(token);
result.endAfter(token);
return result;
}
/**
* Determine if the definition passed in is one of the XML types (XML or
* XMLList) These classes are unrelated, but behave in similar manners.
*
* @param def the {@link IDefinition} to check
* @param project the {@link ICompilerProject} in which to look up types
* @return true if definition is the built-in XML or XMLList type.
*/
public static boolean isXMLish(IDefinition def, ICompilerProject project)
{
IDefinition xmlDef = project.getBuiltinType(IASLanguageConstants.BuiltinType.XML);
IDefinition xmlListDef = project.getBuiltinType(IASLanguageConstants.BuiltinType.XMLLIST);
return (xmlDef != null && def == xmlDef) ||
(xmlListDef != null && def == xmlListDef);
}
/**
* Constructor.
*/
public IdentifierNode(String name)
{
this.name = name;
}
/**
* Constructor.
*/
public IdentifierNode(String name, Token token)
{
this.name = name;
span(token);
}
/**
* Constructor.
*/
public IdentifierNode(String name, IASToken token)
{
this.name = name;
span(token);
}
/**
* Constructor.
*/
public IdentifierNode(IASToken token)
{
name = token.getText();
span(token);
}
/**
* Copy constructor.
*
* @param other The node to copy.
*/
protected IdentifierNode(IdentifierNode other)
{
super(other);
this.name = other.name;
}
/**
* The name of this identifier.
*/
private String name;
//
// NodeBase overrides
//
@Override
public ASTNodeID getNodeID()
{
return ASTNodeID.IdentifierID;
}
@Override
public boolean isTerminal()
{
return true;
}
@Override
protected void analyze(EnumSet<PostProcessStep> set, ASScope scope, Collection<ICompilerProblem> problems)
{
if (IASLanguageConstants.arguments.equals(name))
{
if (!this.isMemberRef() && !this.isQualifiedRef() && set.contains(PostProcessStep.POPULATE_SCOPE))
{
FunctionNode fn = (FunctionNode)getAncestorOfType(FunctionNode.class);
if (fn != null)
fn.needsArguments = true;
}
}
}
@Override
public void normalize(boolean fillInOffsets)
{
// do nothing
}
@Override
protected void fillInOffsets()
{
//we gain our offsets externally, no reason to calculate this
}
@Override
public IScopedNode getScopeNode()
{
IASNode parent = getParent();
if (parent instanceof IterationFlowNode)
{
// we're a label for a break statement
return null;
}
// If our parent is a DefaultPropertTag [DefaultProperty("property")]
// then it is referencing a property that is not in the same scope;
// so find the MemberedNode it is above and use that scope instead.
if (parent instanceof DefaultPropertyTagNode)
{
// Get containing block.
IASNode node = parent.getAncestorOfType(BlockNode.class);
if (node instanceof BlockNode)
{
int childCount = node.getChildCount();
boolean descend = false;
for (int i = 0; i < childCount; i++)
{
IASNode child = node.getChild(i);
if (!descend && child instanceof MetaTagsNode)
{
IMetaTagNode[] allTags = ((MetaTagsNode)child).getAllTags();
for (int t = 0; t < allTags.length; t++)
{
if (allTags[t] == parent)
{
descend = true;
break;
}
}
}
if (descend && child instanceof MemberedNode)
{
return ((MemberedNode)child).getScopedNode().getScopeNode();
}
}
}
}
else if (parent instanceof EventTagNode ||
parent instanceof EffectTagNode ||
parent instanceof StyleTagNode)
{
IASNode parentNode = parent.getParent();
if (parentNode instanceof MetaTagsNode)
{
IASNode decoratedNode = ((MetaTagsNode)parentNode).getDecoratedDefinition();
if (decoratedNode instanceof MemberedNode)
return ((MemberedNode)decoratedNode).getScopedNode().getScopeNode();
}
else if (parentNode instanceof IMXMLMetadataNode)
{
return ((IScopedDefinitionNode)((IMXMLMetadataNode)parentNode).getParent()).getScopedNode();
}
// for these case, we don't need to scan locals so skip up to the membered node here
}
else if ((parent instanceof ParameterNode &&
((ParameterNode)parent).getTypeNode() != null &&
((ParameterNode)parent).getTypeNode().equals(this)) ||
(parent instanceof VariableNode && ((VariableNode)parent).getTypeNode() != null &&
((VariableNode)parent).getTypeNode().equals(this)))
{
while (!(parent instanceof MemberedNode) && !(parent instanceof IFileNode))
{
parent = parent.getParent();
}
return ((NodeBase)parent).getScopeNode();
}
else if (parent instanceof ParameterNode &&
parent.getParent() instanceof CatchNode)
{
return ((CatchNode)parent.getParent());
}
else if (parent instanceof ClassNode &&
((ClassNode)parent).getBaseClassNode() != null &&
((ClassNode)parent).getBaseClassNode().equals(this))
{
while (!(parent instanceof PackageNode) && !(parent instanceof IFileNode))
{
parent = parent.getParent();
}
return ((NodeBase)parent).getScopeNode();
}
return super.getScopeNode();
}
/*
* For debugging only. Builds a string such as <code>"i"</code> from the
* name of the identifier.
*/
@Override
protected boolean buildInnerString(StringBuilder sb)
{
sb.append('"');
sb.append(getName());
sb.append('"');
return true;
}
//
// ExpressionNodeBase overrides
//
@Override
public IDefinition resolve(ICompilerProject project)
{
ASScope asScope = getASScope();
if (asScope == null)
return null;
// attributes are not statically knowable
if (this.isAttributeIdentifier())
return null;
// If this is a reference to a known package, then we can't resolve to anything
if (isPartOfPackageReference())
return null;
IDefinition result = null;
final String name = getName();
IQualifiers qualifier = null;
if (isQualifiedRef())
{
qualifier = resolveQualifier(project);
// If we can't resolve the qualifier, then we can't resolve the entire expression
if (qualifier == null)
return null;
}
boolean isMemberRef = isMemberRef();
boolean wasMemberRef = isMemberRef;
if (isMemberRef && baseIsPackage())
{
// If our base refers to a package, then we're really a qualified name
// and not a regular member acces (a.b.C means find C in package a.b, if a.b is a package)
// convert the name to a fully qualified name, and look that up via findProperty
ExpressionNodeBase base = getBaseExpression();
String packageName = base.computeSimpleReference();
packageName = project.getActualPackageName(packageName);
Workspace workspace = (Workspace)project.getWorkspace();
qualifier = workspace.getPackageNamespaceDefinitionCache().get(packageName, false);
// Set this to false, so we'll fall through to the findProperty case
// below, instead of trying to call resolveMemberRef
isMemberRef = false;
}
if (isNameNode())
{
// If we are the name node for a declaration, just grab the definition
// don't have to look anywhere else
IDefinitionNode defNode = getParentAsDefinition();
if (defNode != null)
result = defNode.getDefinition();
}
else if (isMemberRef)
{
result = resolveMemberRef(project, asScope, name, qualifier);
}
else
{
if (qualifier == null)
{
result = asScope.findProperty(project, name, getDependencyType(), isTypeRef());
// ASVariableTests_localVarSameNameAsPrivateMethod
if (isLegacyCodegen(project) && result != null && getParent().getNodeID() == ASTNodeID.FunctionCallID && result instanceof VariableDefinition)
{
VariableDefinition varDef = (VariableDefinition)result;
if (varDef.getVariableClassification() == VariableClassification.LOCAL)
{
ClassDefinitionBase cdef = asScope.getContainingClass();
IDefinition memberResult = asScope.getPropertyFromDef(project, cdef, name, false);
if (memberResult instanceof FunctionDefinition)
result = memberResult;
}
}
}
else {
result = asScope.findPropertyQualified(project, qualifier, name, getDependencyType(), isTypeRef());
if (result == null && wasMemberRef && baseIsPackage())
{
// if we get here it was because there is a memberaccessexpression like "a.b.foo"
// that did not resolve because a.b is a package but foo isn't a class. There is a chance that
// "a" by itself is a package and there is a class "b" with a member called "foo" so
// look for that
result = resolveMemberRef(project, asScope, name, null);
}
}
}
if (result != null && project instanceof RoyaleProject && ((RoyaleProject)project).apiReportFile != null)
{
if (isMemberRef())
{
// if member ref, try to resolve the left side, because otherwise the parent definition
// will be the class that defined this property like ListCollectionView.sort instead
// of the subclass the developer was using on, like ArrayCollection. We want to log
// ArrayCollection.sort in case we don't need to implement full class hierarchy.
// A Royale ArrayCollection might not have to subclass ListCollectionView
if (parent.getNodeID() == ASTNodeID.MemberAccessExpressionID)
{
MemberAccessExpressionNode mae = (MemberAccessExpressionNode)parent;
if (mae.getRightOperandNode() == this && mae.getLeftOperandNode().getNodeID() == ASTNodeID.IdentifierID)
{
// if the member access expression left side is not an identifier
IDefinition parentDef = mae.getLeftOperandNode().resolveType(project);
if (parentDef instanceof IClassDefinition)
{
((RoyaleProject)project).addToAPIReport((IClassDefinition)parentDef, result);
}
else
{
((RoyaleProject)project).addToAPIReport(result);
}
}
else
{
((RoyaleProject)project).addToAPIReport(result);
}
}
else
{
((RoyaleProject)project).addToAPIReport(result);
}
}
else
((RoyaleProject)project).addToAPIReport(result);
}
return result;
}
@Override
public ITypeDefinition resolveType(ICompilerProject project)
{
IDefinition def = resolve(project);
if (def != null)
{
if (isXMLish(def.getParent(), project))
{
// XML and XMLList members should be treated as '*' because any access could
// resolve to some content inside the XML (i.e. it has a child tag named 'name').
// return '*' since we can't statically know what the type will be.
// Compat with ASC behavior.
return project.getBuiltinType(IASLanguageConstants.BuiltinType.ANY_TYPE);
}
return def.resolveType(project);
}
return null;
}
@Override
protected IdentifierNode copy()
{
return new IdentifierNode(this);
}
@Override
public boolean isDynamicExpression(ICompilerProject project)
{
// If this is a package reference, we're not dynamic.
if (isPackageReference())
return false;
return super.isDynamicExpression(project);
}
@Override
public Name getMName(ICompilerProject project)
{
// If we can resolve the reference, then just use the QName
// of whatever we resolved to
IDefinition def = resolve(project);
if (canEarlyBind(project, def))
return ((DefinitionBase)def).getMName(project);
if (def == null)
{
// no def? see if this is dynamic
// or a missing member on a known and non-dynamic def
if (getParent().getNodeID() == ASTNodeID.MemberAccessExpressionID)
{
// ok it is a member expression, are we on the right?
MemberAccessExpressionNode mae = (MemberAccessExpressionNode) getParent();
if (mae.getRightOperandNode() == this)
{
// we're on the right, what is the left?
ITypeDefinition leftDef = mae.getLeftOperandNode().resolveType(project);
if (leftDef != null && leftDef.isDynamic() == false)
{
INamespaceReference nr = leftDef.getNamespaceReference();
INamespaceDefinition nd = nr.resolveNamespaceReference(project);
Set<INamespaceDefinition> nsset = ImmutableSet.of((INamespaceDefinition)nd);
return makeName(nsset, getName(), isAttributeIdentifier());
}
}
}
}
ASScope scope = getASScope();
if (isQualifiedRef())
{
// If we're a qualified name, and the qualifier resolves to a compile
// time constant, return a QName
// otherwise emit a RTQname - the CG will have to take care of generating the code to
// evaluate the qualifier and place it on the stack.
IQualifiers qual = resolveQualifier(project);
Nsset namespaceSet;
int nameKind;
if (qual != null )
{
if( qual.getNamespaceCount() == 1 )
{
// Qualifier resolved to 1 namespace, so we can emit a QName
NamespaceDefinition ns = (NamespaceDefinition)qual.getFirst();
nameKind = isAttributeIdentifier() ? CONSTANT_QnameA : CONSTANT_Qname;
if (isMemberRef())
{
ExpressionNodeBase baseExpr = getBaseExpression();
if (baseExpr instanceof LanguageIdentifierNode &&
((LanguageIdentifierNode)baseExpr).getKind() == LanguageIdentifierKind.SUPER)
{
// If we're a super expression, adjust the namespace in case it's the protected namespace
IDefinition baseType = baseExpr.resolveType(project);
Set<INamespaceDefinition> nsset = ImmutableSet.of((INamespaceDefinition)ns);
nsset = scope.adjustNamespaceSetForSuper(baseType, nsset);
// We only started with 1 namespace, so we know that's how many we have
ns = (NamespaceDefinition)nsset.iterator().next();
}
}
// If the qualifier is the any namespace, then we want a null nsset
// instead of a nsset of length 1, with a null namespace in it.
if( ns == NamespaceDefinition.getAnyNamespaceReference() )
namespaceSet = null;
else
namespaceSet = new Nsset(ns.getAETNamespace());
}
else
{
// qualifier resolve to 1+ namespaces, so emit a multiname
Set<INamespaceDefinition> nsset = qual.getNamespaceSet();
nameKind = isAttributeIdentifier() ? CONSTANT_MultinameA : CONSTANT_Multiname;
namespaceSet = SemanticUtils.convertSetINamespaceToNsset(nsset);
}
}
else
{
namespaceSet = null;
nameKind = isAttributeIdentifier() ? CONSTANT_RTQnameA : CONSTANT_RTQname;
}
return new Name(nameKind, namespaceSet, getName());
}
Name name = null;
if (isMemberRef())
{
ExpressionNodeBase baseExpr = getBaseExpression();
if (baseExpr != null)
{
// Handle the case where we look like a member expression, but the base expression is really
// a reference to a package. For example 'a.b.c.Foo' where a.b.c is a known package name.
// This needs to generate a QName with a.b.c as the qualifier.
if (baseIsPackage())
{
String packageName = baseExpr.computeSimpleReference();
Workspace workspace = (Workspace)project.getWorkspace();
INamespaceReference qualifier = workspace.getPackageNamespaceDefinitionCache().get(packageName, false);
return new Name(isAttributeIdentifier() ? CONSTANT_QnameA : CONSTANT_Qname,
new Nsset(((INamespaceResolvedReference)qualifier).resolveAETNamespace(project)), getName());
}
Set<INamespaceDefinition> namespaceSet = null;
IDefinition baseType = baseExpr.resolveType(project);
// If our base type is an interface, then we need to use the special
// interface namespace set (the namespace of the interface, plus the namespaces for all the interfaces
// it extends)
if (baseType instanceof InterfaceDefinition)
{
namespaceSet = ((InterfaceDefinition)baseType).getInterfaceNamespaceSet(project);
}
else if (baseExpr instanceof LanguageIdentifierNode &&
((LanguageIdentifierNode)baseExpr).getKind() == LanguageIdentifierKind.SUPER)
{
namespaceSet = scope.getNamespaceSetForSuper(project, baseType);
}
if (namespaceSet != null)
return makeName(namespaceSet, getName(), isAttributeIdentifier());
}
}
if (isNameNode())
{
BaseDefinitionNode defNode = getParentAsDefinition();
name = defNode.getDefinition().getMName(project);
}
else if (scope != null)
{
Set<INamespaceDefinition> namespaceSet = null;
if (isMemberRef())
{
// Member refs just use the open namespace set
namespaceSet = scope.getNamespaceSet(project);
}
else
{
// lexical refs may be influenced by the imports
namespaceSet = scope.getNamespaceSetForName(project, getName());
}
name = makeName(namespaceSet, getName(), isAttributeIdentifier());
}
return name;
}
@Override
String computeSimpleReference()
{
return getName();
}
@Override
IReference computeTypeReference()
{
// Parser creates IdentifierNodes for "" for some error cases of improperly written code,
// like class C extends {} - it gets an IDNode of "" for the extends clause
if (name == "")
return null;
IReference typeRef = null;
IWorkspace w = getWorkspace();
switch (getRefType())
{
case PACKAGE_QUALIFIED:
{
ExpressionNodeBase baseExpr = getBaseExpression();
typeRef = ReferenceFactory.packageQualifiedReference(w, baseExpr.computeSimpleReference(), computeSimpleReference(), false);
break;
}
case LEXICAL:
{
typeRef = ReferenceFactory.lexicalReference(w, computeSimpleReference());
break;
}
default:
{
typeRef = ReferenceFactory.notATypeReference(w, computeSimpleReference());
break;
}
}
return typeRef;
}
@Override
public INamespaceReference computeNamespaceReference()
{
// Parser creates IdentifierNodes for "" for some error cases
// of improperly written code,
if (name == "")
return null;
INamespaceReference nsRef = null;
Workspace w = (Workspace)getWorkspace();
switch (getRefType())
{
case PACKAGE_QUALIFIED:
{
// Create a reference with the package name as the qualifier
// a.b.ns1
// where a.b is a package name
ExpressionNodeBase baseExpr = getBaseExpression();
nsRef = NamespaceDefinition.createNamespaceReference(
getASScope(), getName(),
w.getPackageNamespaceDefinitionCache().get(baseExpr.computeSimpleReference(), false));
break;
}
case LEXICAL:
{
// no qualifer, just a lexical ref
nsRef = NamespaceDefinition.createNamespaceReference(getASScope(), getName(), null);
break;
}
case NAMESPACE_QUALIFIED:
{
// Create a reference with the namespace from the qualifier expression as the qualifier
// handles code like:
// ns1::ns2
ExpressionNodeBase qualExpr = getQualifierExpression();
nsRef = NamespaceDefinition.createNamespaceReference(getASScope(), getName(), qualExpr.computeNamespaceReference());
break;
}
case MEMBER:
{
ExpressionNodeBase baseExpr = getBaseExpression();
IReference base = baseExpr.computeTypeReference();
nsRef = NamespaceDefinition.createNamespaceReference(getASScope(), getName(), null, base);
break;
}
default:
{
break;
}
}
return nsRef;
}
// IIdentifierNode implementations
@Override
public String getName()
{
return name;
}
@Override
public IdentifierType getIdentifierType()
{
return IdentifierType.NAME;
}
// Other methods
public void setReferenceValue(IDefinition definition)
{
}
/**
* Determines if this identifier is actually implicit and does not exist in source.
*/
public boolean isImplicit()
{
return getAbsoluteStart() == getAbsoluteEnd();
}
private REF_TYPE getRefType()
{
if (isMemberRef())
{
// If this is a member expr, then the stem better be a package;
// otherwise we can't possibly be a type ref.
if (baseIsPackage())
return REF_TYPE.PACKAGE_QUALIFIED;
else
return REF_TYPE.MEMBER;
}
else if (isQualifiedRef())
{
return REF_TYPE.NAMESPACE_QUALIFIED;
}
else
{
return REF_TYPE.LEXICAL;
}
}
/**
* Determine if this identifier is an attribute reference (e.g. @name).
*
* @return True if this node is an attribute
*/
protected boolean isAttributeIdentifier()
{
ExpressionNodeBase p = getParentExpression();
if (p != null)
return p.isAttributeExpr(this);
return false;
}
/**
* Is this a reference that has an explicit qualifier. e.g. the node for 'b'
* in a::b woul return ture.
*
* @return true if this node should be resolved with an explicit qualifier
*/
private boolean isQualifiedRef()
{
ExpressionNodeBase expr = getParentExpression();
if (expr != null)
return expr.isQualifiedExpr(this);
return false;
}
/**
* Determine if this identifier node is really part of a package reference.
* this would be the node for b in 'a.b.c' if a.b.c was a known package name.
* @return true if this identifier is used as part of a package reference.
*/
private boolean isPartOfPackageReference()
{
ExpressionNodeBase expr = this.getParentExpression();
MemberAccessExpressionNode memExpr = null;
while (expr instanceof MemberAccessExpressionNode)
{
memExpr = (MemberAccessExpressionNode)expr;
expr = expr.getParentExpression();
}
if (memExpr != null)
return memExpr.isPackageReference();
return false;
}
/**
* Is this a reference that should be resolve in another object e.g. the
* node for 'b' in a.b would return true
*
* @return true if this node should be resolved in another object
*/
public boolean isMemberRef()
{
ExpressionNodeBase expr = getParentExpression();
boolean memberRef = false;
if (expr != null)
return expr.isPartOfMemberRef(this);
return memberRef;
}
/**
* This method resolves an identifier that is the right-hand-side of a member access expression
* (i.e., <code>b</code> in <code>a.b</code> or <code>a.ns::b</code>).
*/
private IDefinition resolveMemberRef(ICompilerProject project, ASScope asScope, String name, IQualifiers qualifier)
{
IDefinition result = null;
// Determine baseType, the type of 'a' (the left-hand-side of the member access operator).
IDefinition baseType = null;
ExpressionNodeBase baseExpr = getBaseExpression();
if (baseExpr != null)
{
baseType = baseExpr.resolveType(project);
if (baseType != null)
{
boolean isSuper = false;
if (baseType instanceof IClassDefinition)
{
// If the base type is XML or XMLList,
// don't resolve a member reference to any definition.
// As in the old compiler, this avoids possibly bogus
// -strict type-coercion errors.
// For example, we don't want a declared property like .name
// to resolve to the method slot, because it might actually
// be referring to a dynamically-defined XML attribute or child tag.
// And if we did resolve it to the name() method, which returns Object,
// then when doing s = x.name() where s is type String
// and x is type XML you would get a can't-convert-Object-to-String
// problem, but there is lots of existing source code that expects
// this to compile with no cast.
if (!((RoyaleProject)project).useStrictXML() && isXMLish(baseType, project))
return null;
if (baseExpr instanceof IdentifierNode)
{
IdentifierNode idBase = (IdentifierNode)baseExpr;
if (idBase instanceof ILanguageIdentifierNode &&
(((ILanguageIdentifierNode)idBase).getKind() == LanguageIdentifierKind.THIS
|| ((ILanguageIdentifierNode)idBase).getKind() == LanguageIdentifierKind.SUPER))
{
if (((ILanguageIdentifierNode)idBase).getKind() == LanguageIdentifierKind.SUPER)
{
isSuper = true;
}
}
}
}
if (qualifier != null)
result = asScope.getQualifiedPropertyFromDef(project, baseType, name, qualifier, isSuper);
else
result = asScope.getPropertyFromDef(project, baseType, name, isSuper);
}
}
return result;
}
/**
* Resolve the qualifier of this IdentifierNode, if the IDentifierNode is
* part of a QualifiedExpression
*
* @param project The {@link ICompilerProject} to use to do lookups.
* @return The IQualifiers the qualifier resolved to, or null if it
* was unresolved. A single qualifier may resolve to multiple namespaces
*/
private IQualifiers resolveQualifier(ICompilerProject project)
{
IQualifiers qual = null;
ExpressionNodeBase qualExpr = getQualifierExpression();
if (qualExpr != null)
{
if( qualExpr instanceof NamespaceIdentifierNode )
{
// namespace reference, have the namespace
// node resolve as a qualifier
qual = ((NamespaceIdentifierNode) qualExpr).resolveQualifier(project);
}
else
{
// Some random expression, just try and resolve it
IDefinition def = qualExpr.resolve(project);
if (def instanceof NamespaceDefinition)
qual = (NamespaceDefinition)def;
}
}
return qual;
}
/**
* Get the ExpressionNodeBase of the qualifier if this Node is part of a
* qualified expression
*/
private ExpressionNodeBase getQualifierExpression()
{
ExpressionNodeBase expr = getParentExpression();
if (expr != null)
return expr.getQualifier(this);
return null;
}
/**
* For member refs, determine if the base expression is really a reference
* to a known package name.
*
* @return true, if the base expression should be treated as a package name
*/
private boolean baseIsPackage()
{
ExpressionNodeBase expr = getParentExpression();
if (expr instanceof MemberAccessExpressionNode)
return ((MemberAccessExpressionNode)expr).stemIsPackage();
return false;
}
/**
* Helper method to get the parent node as an IDefinitionNode
*
* @return the parent IDefinitionNode, or null if the parent is not an
* IDefinitionNode
*/
private BaseDefinitionNode getParentAsDefinition()
{
IASNode p = getParent();
BaseDefinitionNode def = p instanceof BaseDefinitionNode ? (BaseDefinitionNode)p : null;
return def;
}
/**
* Helper method to determine if this node is the name node of a definition.
*
* @return true if this Node is the Name node of a definition
*/
private boolean isNameNode()
{
IDefinitionNode parent = getParentAsDefinition();
if (parent != null)
{
if (parent.getNameExpressionNode() == this)
return true;
}
return false;
}
/**
* Can this Node be early bound to the Definition it refers to.
* We can early bind if we resolve to a definition, the definition is not ambiguous, and
* the definition is not something defined in one of the XML classes (XML or XMLList).
* @param project project to resolve things in
* @param def the definition this node resolved to
* @return true if we can early bind, otherwise false
*/
protected boolean canEarlyBind(ICompilerProject project, IDefinition def)
{
if (def instanceof DefinitionBase && !AmbiguousDefinition.isAmbiguous(def))
{
// Can't early bind to XML/XMLList properties as they may be hidden by the unknown contents
// of the XML itself, i.e. a child tag named 'parent'
// Matches ASC behavior.
if (!isXMLish(def.getParent(), project))
return true;
}
return false;
}
/**
* Helper method to make an AET name from a Set<INamespaceDefinition> and a
* name.
*
* @param namespaceSet the set of namespaces to use for the name
* @param name the name to use for the AET Name.
* @return the AET Name
*/
private static Name makeName(Set<INamespaceDefinition> namespaceSet, String name, boolean isAttr)
{
int nameKind = isAttr ? CONSTANT_MultinameA : CONSTANT_Multiname;
Nsset nsSet = SemanticUtils.convertSetINamespaceToNsset(namespaceSet);
Name n = new Name(nameKind, nsSet, name);
return n;
}
/**
* Is this a type reference - e.g. a base class ref, type anno, etc
*
* @return true if this is a type reference
*/
protected boolean isTypeRef()
{
DependencyType dt = getDependencyType();
if (dt == DependencyType.SIGNATURE || dt == DependencyType.INHERITANCE)
return true;
if (dt == DependencyType.EXPRESSION)
{
final IASNode parent = getParent();
if (parent instanceof BaseTypedDefinitionNode && this == ((BaseTypedDefinitionNode)parent).getTypeNode())
return true;
}
return false;
}
public boolean isLegacyCodegen(ICompilerProject project)
{
if (!(project instanceof RoyaleProject))
return false;
final Integer compatibilityVersion = ((RoyaleProject)project).getCompatibilityVersion();
if (compatibilityVersion == null)
return false;
else if (compatibilityVersion <= Configuration.MXML_VERSION_4_6)
return true;
else
return false;
}
//
// Inner types
//
private static enum REF_TYPE
{
/**
* A Member expression where the stem is a package name
*/
PACKAGE_QUALIFIED,
/**
* A lexical ref
*/
LEXICAL,
/**
* A name where the base/qualifier is a runtime value
*/
RUNTIME,
/**
* A reference qualified by a namespace expression, such as ns1::x
*/
NAMESPACE_QUALIFIED,
/**
* A member reference, such as a.b.Foo
*/
MEMBER
}
}