blob: 344f14fc4cdbe9b2778eb61588bad0999a0e5075 [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.semantics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.apache.royale.abc.ABCConstants;
import org.apache.royale.abc.semantics.Name;
import org.apache.royale.abc.semantics.Namespace;
import org.apache.royale.abc.semantics.Nsset;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.constants.IASKeywordConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants.BuiltinType;
import org.apache.royale.compiler.definitions.IAccessorDefinition;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IConstantDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition.FunctionClassification;
import org.apache.royale.compiler.definitions.IInterfaceDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.IParameterDefinition;
import org.apache.royale.compiler.definitions.IScopedDefinition;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.definitions.metadata.IDeprecationInfo;
import org.apache.royale.compiler.definitions.references.INamespaceReference;
import org.apache.royale.compiler.definitions.references.IReference;
import org.apache.royale.compiler.definitions.references.IResolvedQualifiersReference;
import org.apache.royale.compiler.definitions.references.ReferenceFactory;
import org.apache.royale.compiler.exceptions.MissingBuiltinException;
import org.apache.royale.compiler.filespecs.IFileSpecification;
import org.apache.royale.compiler.internal.as.codegen.Binding;
import org.apache.royale.compiler.internal.as.codegen.LexicalScope;
import org.apache.royale.compiler.internal.definitions.AmbiguousDefinition;
import org.apache.royale.compiler.internal.definitions.AppliedVectorDefinition;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.definitions.ConstantDefinition;
import org.apache.royale.compiler.internal.definitions.DefinitionBase;
import org.apache.royale.compiler.internal.definitions.FunctionDefinition;
import org.apache.royale.compiler.internal.definitions.GetterDefinition;
import org.apache.royale.compiler.internal.definitions.InterfaceDefinition;
import org.apache.royale.compiler.internal.definitions.NamespaceDefinition;
import org.apache.royale.compiler.internal.definitions.PackageDefinition;
import org.apache.royale.compiler.internal.definitions.ParameterDefinition;
import org.apache.royale.compiler.internal.definitions.SetterDefinition;
import org.apache.royale.compiler.internal.definitions.TypeDefinitionBase;
import org.apache.royale.compiler.internal.definitions.VariableDefinition;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.scopes.ASProjectScope;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.scopes.ASScopeBase;
import org.apache.royale.compiler.internal.scopes.FunctionScope;
import org.apache.royale.compiler.internal.scopes.ScopeView;
import org.apache.royale.compiler.internal.scopes.TypeScope;
import org.apache.royale.compiler.internal.tree.as.BaseDefinitionNode;
import org.apache.royale.compiler.internal.tree.as.BaseTypedDefinitionNode;
import org.apache.royale.compiler.internal.tree.as.BinaryOperatorAssignmentNode;
import org.apache.royale.compiler.internal.tree.as.BlockNode;
import org.apache.royale.compiler.internal.tree.as.ExpressionNodeBase;
import org.apache.royale.compiler.internal.tree.as.FunctionCallNode;
import org.apache.royale.compiler.internal.tree.as.FunctionNode;
import org.apache.royale.compiler.internal.tree.as.IdentifierNode;
import org.apache.royale.compiler.internal.tree.as.ImportNode;
import org.apache.royale.compiler.internal.tree.as.IterationFlowNode;
import org.apache.royale.compiler.internal.tree.as.LiteralNode;
import org.apache.royale.compiler.internal.tree.as.MemberAccessExpressionNode;
import org.apache.royale.compiler.internal.tree.as.NamespaceNode;
import org.apache.royale.compiler.internal.tree.as.NodeBase;
import org.apache.royale.compiler.internal.tree.as.TypedExpressionNode;
import org.apache.royale.compiler.internal.tree.as.UnaryOperatorAtNode;
import org.apache.royale.compiler.internal.tree.as.UnaryOperatorNodeBase;
import org.apache.royale.compiler.internal.tree.as.VariableExpressionNode;
import org.apache.royale.compiler.internal.tree.as.VariableNode;
import org.apache.royale.compiler.internal.tree.as.WithNode;
import org.apache.royale.compiler.problems.AmbiguousReferenceProblem;
import org.apache.royale.compiler.problems.BaseClassIsFinalProblem;
import org.apache.royale.compiler.problems.CannotExtendInterfaceProblem;
import org.apache.royale.compiler.problems.CircularTypeReferenceProblem;
import org.apache.royale.compiler.problems.DeprecatedAPIProblem;
import org.apache.royale.compiler.problems.DeprecatedAPIWithMessageProblem;
import org.apache.royale.compiler.problems.DeprecatedAPIWithReplacementProblem;
import org.apache.royale.compiler.problems.DeprecatedAPIWithSinceAndReplacementProblem;
import org.apache.royale.compiler.problems.DeprecatedAPIWithSinceProblem;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.ReturnValueHasNoTypeDeclarationProblem;
import org.apache.royale.compiler.problems.ScopedToDefaultNamespaceProblem;
import org.apache.royale.compiler.problems.UnknownSuperclassProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IClassNode;
import org.apache.royale.compiler.tree.as.ICommonClassNode;
import org.apache.royale.compiler.tree.as.IContainerNode;
import org.apache.royale.compiler.tree.as.IDefinitionNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.tree.as.IFileNode;
import org.apache.royale.compiler.tree.as.IFunctionNode;
import org.apache.royale.compiler.tree.as.IIdentifierNode;
import org.apache.royale.compiler.tree.as.IImportNode;
import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode;
import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode.LanguageIdentifierKind;
import org.apache.royale.compiler.tree.as.ILiteralNode;
import org.apache.royale.compiler.tree.as.INamespaceDecorationNode;
import org.apache.royale.compiler.tree.as.INumericLiteralNode;
import org.apache.royale.compiler.tree.as.IParameterNode;
import org.apache.royale.compiler.tree.as.ITryNode;
import org.apache.royale.compiler.tree.as.IVariableNode;
import org.apache.royale.compiler.tree.mxml.IMXMLEventSpecifierNode;
/**
* SemanticUtils is a set of utility routines to provide information
* about AS3 constructs' semantics.
*
* Many of these routines come in pairs; a static form that accepts
* an ICompilerProject parameter for name resolution context, and
* an instance form that uses the project provided at construction.
*/
public class SemanticUtils
{
/**
* Enum that describes different kinds of multiple definitions
*/
public enum MultiDefinitionType {
/** The definition is not multiply defined at all */
NONE,
/** The definition is ambiguous in a way that violates the language spec */
AMBIGUOUS,
/** The definition is multiply defined, but in a way that is legal and unambiguous ECMAScript */
MULTIPLE,
/** The definition is also a parameter definition, which is legal
* The only reason SHADOWS_PARAM and MULTIPLE are different is that the old compiler did not detect shadows
*/
SHADOWS_PARAM
}
/**
* ICompilerProject for the current project,
* provided by the constructor.
*/
private final ICompilerProject project;
/**
* Construct a new SemanticUtils object.
* @param project - an ICompilerProject for the current project.
*/
public SemanticUtils(ICompilerProject project)
{
this.project = project;
}
/**
* Check if the node is for {@code super(this)} but not
* {@code super(this).call()}. The incoming tree shape is:
*
* <pre>
* MemberAccessExpressionID(FunctionCallID(SuperID(void), ContainerID(IdentifierID(void))), qname)
* </pre>
*
* @return "0" if the node represents {@code super(this)};
* {@link Integer#MAX_VALUE} otherwise.
*/
public static int isSuperThisForFieldAccess(IASNode n)
{
// super(this).callSomething() is handled by another pattern that emits
// "callsuper" instruction.
if (n.getParent() instanceof FunctionCallNode)
return Integer.MAX_VALUE;
// The following unguarded casts are safe, because this is a cost
// function and the pattern matcher has checked all the node IDs.
final MemberAccessExpressionNode memberAccessNode = (MemberAccessExpressionNode)n;
final FunctionCallNode callNode = (FunctionCallNode)memberAccessNode.getLeftOperandNode();
final IdentifierNode idNode = (IdentifierNode)callNode.getArgumentsNode().getChild(0);
if (idNode.getName().equals(IASKeywordConstants.THIS))
return 1;
else
return Integer.MAX_VALUE;
}
/**
* Check that a implicit conversion from actual to expected type is valid.
*
* @param expected the expected type.
* @param actual the actual type.
* @param project an ICompilerProject for the current project.
* @return true if there is a valid implicit type conversion from actual to
* expected, false otherwise.
*/
private static boolean isValidTypeConversion(IDefinition expected, IDefinition actual, ICompilerProject project)
{
if (
actual == null ||
expected == null ||
actual.equals(getBuiltinType(BuiltinType.ANY_TYPE, project)) ||
expected.equals(getBuiltinType(BuiltinType.ANY_TYPE, project)) ||
isInstanceOf(actual, expected, project) ||
isBuiltin(actual, BuiltinType.NULL, project) ||
isBuiltin(expected, BuiltinType.BOOLEAN, project) ||
isBuiltin(expected, BuiltinType.VOID, project)
)
{
return true;
}
// else if ( isVectorSplat(expected) && isVector(actual) ) // This is allowed by the AVM.
else if (
isBuiltin(expected, BuiltinType.STRING, project) &&
(isBuiltin(actual, BuiltinType.XML, project) || isBuiltin(actual, BuiltinType.XMLLIST, project))
)
{
// allow this, e4x values are string values transparently (i.e. without having to call toString()/toXMLString()).
return true;
}
// Types are unrelated, unless they're both numeric.
return isNumericType(expected, project) && isNumericType(actual, project);
}
/**
* Check that implicit conversion done as part of an op= style assignment is valid.
* @param lhsType - the type of the lvalue (and one operand of the implicit binary operator).
* @param rhsType - the type of the result of the implicit binary operation.
* @param opcode - the operator of the implicit binary operator.
* @param project an ICompilerProject for the current project.
* @return true if there is a valid implicit binary operation that can assign
* its result to lhsType.
*/
public static boolean isValidImplicitOpAssignment(IDefinition lhsType, IDefinition rhsType, final int opcode, ICompilerProject project, final boolean compareNormalized)
{
boolean result = isValidTypeConversion(lhsType, rhsType, project, compareNormalized);
if ( ! result )
{
switch (opcode)
{
case ABCConstants.OP_add:
// Special cases for +=:
// anything can be added to String
// XML can be added to XMLList
result =
isBuiltin(lhsType, BuiltinType.STRING, project ) ||
(isBuiltin(lhsType, BuiltinType.XMLLIST, project) && isBuiltin(rhsType, BuiltinType.XML, project));
break;
}
}
return result;
}
/**
* If the specified {@link IDefinition} is a public or internal definition,
* then construct a qualified name to lookup a definition with the same name
* in the {@link ICompilerProject}. The {@link IDefinition} that can be
* found within the {@link ICompilerProject} is the "normalized"
* {@link IDefinition}.
* <p>
* This method asserts that is only called with definitions that are not
* local variables, parameters, or class members.
* <p>
* This method is necessary because
* {@link org.apache.royale.compiler.units.IInvisibleCompilationUnit}s create the
* possibility that we will be processing a file whose definitions are not
* in the {@link ASProjectScope} in the {@link ICompilerProject}. This in
* turn creates the possibility there are two or more {@link IDefinition}s
* floating around for the same class, variable, or function. One the
* {@link IDefinition}, the "normalized" one, should be registered with the
* {@link ASProjectScope} in the {@link ICompilerProject}. The other
* {@link IDefinition}s are from
* {@link org.apache.royale.compiler.units.IInvisibleCompilationUnit}s. When doing
* semantic analysis if we don't "normalize" {@link IDefinition}s in some
* cases, we'll get spurious errors because the semantic analysis code
* generally compares {@link IDefinition}s by identity ( which is faster )
* rather than by name. This method should only be called when doing
* semantic analysis of an
* {@link org.apache.royale.compiler.units.IInvisibleCompilationUnit}.
*
* @param def The {@link IDefinition} whose corresponding normalized
* definition should be returned.
* @param project The {@link ICompilerProject} in which to find normalized
* {@link IDefinition}s.
* @return The normalized {@link IDefinition} ( which may be the same as the
* specified {@link IDefinition} ) or null.
*/
private static IDefinition normalizeDefinition(IDefinition def, ICompilerProject project)
{
assert !(def.getContainingScope() instanceof FunctionScope);
assert !(def.getContainingScope() instanceof ScopeView);
assert !(def.getContainingScope() instanceof TypeScope);
assert !(def instanceof ParameterDefinition);
INamespaceReference qualifier = def.getNamespaceReference();
if (!qualifier.isPublicOrInternalNamespace())
return def;
INamespaceDefinition qualifierDef = (INamespaceDefinition)qualifier;
IResolvedQualifiersReference refToDefinition =
ReferenceFactory.resolvedQualifierQualifiedReference(project.getWorkspace(), qualifierDef, def.getBaseName());
return refToDefinition.resolve(project);
}
/**
* Check that a implicit conversion from actual to expected type is valid.
*
* @param expected the expected type.
* @param actual the actual type.
* @param project an ICompilerProject for the current project.
* @param normalizeDefinitions If true, we'll try normalizing both
* {@link IDefinition}s before returning false. See the java doc for
* {@code normalizeDefinition(IDefinition, ICompilerProject)} for more info.
* @return true if there is a valid implicit type conversion from actual to
* expected, false otherwise.
*/
public static boolean isValidTypeConversion(IDefinition expected, IDefinition actual, ICompilerProject project, boolean normalizeDefinitions)
{
if (isValidTypeConversion(expected, actual, project))
return true;
if (!normalizeDefinitions)
return false;
final IDefinition normalizedExpected = normalizeDefinition(expected, project);
if (normalizedExpected == null)
return false;
final IDefinition normalizedActual = normalizeDefinition(actual, project);
if (normalizedActual == null)
return false;
if ((normalizedExpected == expected) && (normalizedActual == actual))
return false;
return isValidTypeConversion(normalizedExpected, normalizedActual, project);
}
/**
* Helper method to get the node to report a problem with a Function. This will get the name node,
* if it exists, otherwise it will return the function node.
*/
public static IASNode getFunctionProblemNode(IFunctionNode iNode)
{
IASNode result = iNode;
IExpressionNode nameExpression = iNode.getNameExpressionNode();
if( nameExpression != null )
result = nameExpression;
return result;
}
/**
* Helper method to determine if an IdentifierNode refers to the Class it is contained
* in from a context where we are generating code that may be run during the cinit method.
*
* This is important because cinit code must refer to the class via getlocal0 instead of a
* named lookup like findprop, getprop. This is because the Class property is initialized with the
* result of the cinit, but since we are potentially in the cinit, the property will not have been initialized.
*
* class C {
* static var a = C; // C won't have been initialized yet, so we need to use getlocal0
* }
*
* The above class generates code like:
*
* findprop C
* newclass C // this runs the cinit, and leaves the result on the stack, at this point 'C' is undefined
* setprop C // this will init the property
*
* @param id the identifier node that we are resolving
* @param def the definition the identifier node resolved to
* @return true if this is a reference to the Class that is being initialized, and needs special treatment.
*/
public static boolean isRefToClassBeingInited (IdentifierNode id, IDefinition def)
{
boolean refToClassBeingInited = false;
if( def instanceof IClassDefinition)
{
ASScope scope = id.getASScope();
IDefinition containingDef = null;
while( scope != null )
{
if( containingDef == null )
containingDef = scope.getDefinition();
if( scope instanceof ScopeView)
{
if (((ScopeView)scope).isInstanceScope() )
{
// stop looking, we're in an instance method, so we're ok
break;
}
else if( ((ScopeView)scope).isStaticScope() && def == scope.getDefinition() )
{
// we're in static code, and are referencing the class we are in
refToClassBeingInited = true;
break;
}
}
scope = scope.getContainingScope();
}
}
return refToClassBeingInited;
}
/**
* Determine if an IDefinition is a specific builtin type.
* @param def - the IDefinition of interest.
* @param builtin - the builtin type.
* @param project - an ICompilerProject for the current project.
* @return true if def is the desired builtin type.
*/
public static boolean isBuiltin(IDefinition def, BuiltinType builtin, ICompilerProject project)
{
return def != null && def.equals(project.getBuiltinType(builtin));
}
/**
* Find potential conflict for a FunctionDefinition. In some contexts it is ok to redeclare a Function (global, or
* inside another function body). For those cases the function will not be reported as ambiguous. However, in strict
* mode we do want to issue a diagnostic that the function has been redeclared - this method is to find the redlarations
* even though those redecls don't make references ambiguous.
* @param project The project to resolve things in
* @param funcDef The function definition to find potential conlicts for
* @return A List of Definitions from the same scope that have the same name as the function definition, including
* the function definition (so if there are no conflicts, the list should have 1 item).
*/
public static List<IDefinition> findPotentialFunctionConflicts (ICompilerProject project, IFunctionDefinition funcDef)
{
ASScope scope = (ASScope)funcDef.getContainingScope();
INamespaceDefinition qualifier = funcDef.getNamespaceReference().resolveNamespaceReference(project);
Set<INamespaceDefinition> namespaceSet = Collections.singleton(qualifier);
return scope.getPropertiesByNameForMemberAccess((CompilerProject) project, funcDef.getBaseName(), namespaceSet);
}
/**
* Is the binding a potential reference to
* the 'arguments' object
* @param b the Binding to check
* @return true if the reference could be a reference to 'arguments'
*/
public static boolean isArgumentsReference (Binding b)
{
Name name = b.getName();
if (name != null && IASLanguageConstants.arguments.equals(name.getBaseName()))
{
// the name is "arguments", but make sure it's not a user defined definition
// by checking whether the def is implicit
IDefinition definition = b.getDefinition();
if (definition != null)
{
return definition.isImplicit();
}
else
{
// if there's no definition, as can happen when referencing "arguments" from
// within a 'with', then fall back to checking on the node.
IASNode node = b.getNode();
return node instanceof IdentifierNode && !((IdentifierNode) node).isMemberRef();
}
}
return false;
}
/**
* Determine if an IDefinition is a specific builtin type.
* @param def - the IDefinition of interest.
* @param builtin - the builtin type.
* @return true if def is the desired builtin type.
*/
public boolean isBuiltin(IDefinition def, BuiltinType builtin)
{
return isBuiltin(def, builtin, this.project);
}
/**
* @return true if the given definition is a numeric type.
* @param def - the IDefinition of interest.
* @param project - an ICompilerProject for the current project.
*/
public static boolean isNumericType(IDefinition def, ICompilerProject project)
{
return isBuiltin(def, BuiltinType.INT, project) ||
isBuiltin(def, BuiltinType.UINT, project) ||
isBuiltin(def, BuiltinType.NUMBER, project);
}
/**
* @return true if the given definition is a numeric or boolean type.
* @param def - the IDefinition of interest.
* @param project - an ICompilerProject for the current project.
*/
public static boolean isNumericTypeOrBoolean(IDefinition def, ICompilerProject project)
{
return isNumericType(def, project) ||
isBuiltin(def, BuiltinType.BOOLEAN, project);
}
/**
* @return true if the given definition is a numeric type.
* @param def - the IDefinition of interest.
* @see #isNumericType(IDefinition, ICompilerProject)
*/
public boolean isNumericType(IDefinition def)
{
return isNumericType(def, this.project);
}
/**
* Determine if one IDefinition is a subclass of another.
* @param maybe_derived The first definition.
* @param maybe_base The second definition.
* @param project - an ICompilerProject for the current project.
* @return true if maybe_derived is an instance of maybe_base.
*/
public static boolean isInstanceOf(IDefinition maybe_derived, IDefinition maybe_base, ICompilerProject project)
{
if (!(maybe_base instanceof ITypeDefinition))
return false;
if (!(maybe_derived instanceof ITypeDefinition))
return false;
ITypeDefinition base = (ITypeDefinition) maybe_base;
ITypeDefinition derived = (ITypeDefinition) maybe_derived;
if (derived.isInstanceOf(base, project))
return true;
// Consider null to be an "instanceof" any Object subtype.
boolean base_is_object = isInstanceOf(base, getBuiltinType(BuiltinType.OBJECT, project), project);
if (base_is_object && derived.equals(getBuiltinType(BuiltinType.NULL, project)))
return true;
if (AppliedVectorDefinition.vectorInstanceOfCheck(base, derived))
return true;
return false;
}
/**
* Determine if one IDefinition is a subclass of another.
* @param maybe_derived The first definition.
* @param maybe_base The second definition.
* @return true if maybe_derived is an instance of maybe_base.
* @see #isInstanceOf(IDefinition, IDefinition, ICompilerProject)
*/
public boolean isInstanceOf(IDefinition maybe_derived, IDefinition maybe_base)
{
return isInstanceOf(maybe_derived, maybe_base, this.project);
}
/**
* Fetch a builtin type.
* @param builtin - the BuiltinType identifier of the type.
* @param project - an ICompilerProject for the current project.
* @return the specified builtin type.
*/
public static IDefinition getBuiltinType(BuiltinType builtin, ICompilerProject project)
{
return project.getBuiltinType(builtin);
}
/**
* Fetch a builtin type.
* @param builtin - the BuiltinType identifier of the type.
* @return the specified builtin type.
*/
public IDefinition getBuiltinType(BuiltinType builtin)
{
return getBuiltinType(builtin, this.project);
}
/**
* Get the current number type.
* @param project - an ICompilerProject for the current project.
* @return the current number type; currently hardcoded
* to BuiltinTypes.NUMBER.
*/
public static IDefinition numberType(ICompilerProject project)
{
return getBuiltinType(BuiltinType.NUMBER, project);
}
/**
* Get the current number type.
* @return the current number type; currently hardcoded
* to BuiltinTypes.NUMBER.
* @see #numberType(ICompilerProject)
*/
public IDefinition numberType()
{
return numberType(this.project);
}
/**
* Given the definition of a getter or setter, resolve its
* corresponding accessor (e.g., the setter for a getter).
* @param def - the definition of interest.
* @param project - an ICompilerProject for the current project.
* @return the corresponding accessor, or null if
* there is no corresponding accessor.
* @throws IllegalArgumentException if the definition is
* not for a getter or setter.
*/
public static IDefinition resolveCorrespondingAccessor(IDefinition def, ICompilerProject project)
{
if ( def instanceof GetterDefinition )
return ((GetterDefinition)def).resolveCorrespondingAccessor(project);
else if ( def instanceof SetterDefinition )
return ((SetterDefinition)def).resolveCorrespondingAccessor(project);
else
throw new IllegalArgumentException("Definition " + def + " was not a getter or setter.");
}
/**
* Given the definition of a getter or setter, resolve its
* corresponding accessor (e.g., the setter for a getter).
* @param def - the definition of interest.
* @return the corresponding accessor, or null if
* there is no corresponding accessor.
* @throws IllegalArgumentException if the definition is
* not for a getter or setter.
* @see #resolveCorrespondingAccessor(IDefinition, ICompilerProject)
*/
public IDefinition resolveCorrespondingAccessor(IDefinition def)
{
return resolveCorrespondingAccessor(def, this.project);
}
/**
* Is the given node in a with scope?
* @param iNode - the node of interest.
* @return the node's own answer if it's an
* ExpressionNodeBase; otherwise return true
* if the node has a WithNode ancestor.
*/
public static boolean isInWith(IASNode iNode)
{
if ( iNode instanceof ExpressionNodeBase )
{
return ((ExpressionNodeBase)iNode).inWith();
}
else
{
return iNode.getAncestorOfType(WithNode.class) != null;
}
}
/**
* Is the given node in a filter expression?
* @param iNode - the node of interest.
* @return the node's own answer if it's an
* ExpressionNodeBase; otherwise return false.
*/
public static boolean isInFilter(IASNode iNode)
{
if ( iNode instanceof ExpressionNodeBase )
{
return ((ExpressionNodeBase)iNode).inFilter();
}
else
{
return false;
}
}
/**
* Check if a nested function can have it's init instructions hoisted to the top of the
* function it is contained in. This is not possible when the function is inside some control-flow,
* as the OP_newfunction must be executed only when control flows there (and with whatever scope stack is in effect
* if we're in a with/catch scope).
* @param iNode The node to check
* @return true if it is ok to hoist the init instructions for the node to the top of the function
*/
public static boolean canNestedFunctionBeHoisted(IASNode iNode)
{
IASNode n = iNode.getParent();
// Walk up the parent chain looking for anything other than block nodes,
// until we get to the containing function - we don't have to look past the containing
// function because any control flow around it should have been handled when setting up the outer function
while( n != null && !(n instanceof FunctionNode) )
{
if( !(n instanceof BlockNode) )
return false;
n = n.getParent();
}
return true;
}
/**
* Is this definition a member of a class?
* @param def - the definition of interest.
* @return true if the definition's parent is a class definition.
*/
public static boolean isMemberDefinition(IDefinition def)
{
return def != null && def.getParent() instanceof ClassDefinition;
}
/**
* Is this definition a member of a package?
* @param def - the definition of interest.
* @return true if the definition's parent is a package definition.
*/
public static boolean isPackageDefinition(IDefinition def)
{
return def != null && def.getParent() instanceof PackageDefinition;
}
/**
* Is the definition going to be a method binding in the ABC?
* method bindings can be called with findprop/callprop instead of having to
* use op_call.
*
* A method biniding is a member function, or a global function defined in a package
*
* @param def - the definition of interest.
* @return true if the definition is a method binding.
*/
public static boolean isMethodBinding(IDefinition def)
{
return def instanceof IFunctionDefinition &&
(isMemberDefinition(def) || isPackageDefinition(def));
}
/**
* Is this definition read-only?
* @param def - the definition of interest.
*/
public boolean isReadOnlyDefinition(IDefinition def)
{
boolean result = false;
if ( def instanceof GetterDefinition )
{
IDefinition otherDef = resolveCorrespondingAccessor(def);
if (otherDef == null) return true;
if (otherDef.getNamespaceReference().getBaseName() != def.getNamespaceReference().getBaseName())
return true;
}
else if ( def instanceof ConstantDefinition )
{
result = true;
}
return result;
}
/**
* Is this Binding's definition statically analyzable?
* @param binding - the Binding of interest.
* @param project - an ICompilerProject for the current project.
* @return true if the Binding has no definition and
* it is in a context that implies its definition
* is statically resolvable.
*/
public static boolean definitionCanBeAnalyzed(Binding binding, ICompilerProject project)
{
assert(binding.getNode()) != null;
IASNode node = binding.getNode();
if ( node instanceof ExpressionNodeBase )
{
// Ensure we're not in a with scope or part of a filter expression.
final ExpressionNodeBase expressionNode = (ExpressionNodeBase)node;
if (expressionNode.inWith() || expressionNode.inFilter())
return false;
}
// Attribute names aren't statically knowable.
if ( binding.getName() != null && binding.getName().isAttributeName() )
return false;
return !hasDynamicBase(binding, project);
}
/**
* Is this Binding's definition statically analyzable?
* @param binding - the Binding of interest.
* @return true if the Binding has no definition and
* it is in a context that implies its definition
* is statically resolvable.
* @see #definitionCanBeAnalyzed(Binding, ICompilerProject)
*/
public boolean definitionCanBeAnalyzed(Binding binding)
{
return definitionCanBeAnalyzed(binding, this.project);
}
/**
* Is this definition write-only?
* @param def - the definition of interest.
* @return true if the definition is a set
* that does not have a corresponding get.
*/
public boolean isWriteOnlyDefinition(IDefinition def)
{
return def instanceof SetterDefinition && resolveCorrespondingAccessor(def) == null;
}
/**
* Check an ExpressionNodeBase's base expression (if any)
* to determine if it's a dynamic expression, which
* means we don't know much about its definition.
* @param binding - the binding whose base node is to be checked.
* @param project - an ICompilerProject for the current project.
* @return true if node's base expression is dynamic.
*/
public static boolean hasDynamicBase(Binding binding, ICompilerProject project)
{
ExpressionNodeBase base = getBaseNode(binding);
if (base != null && base.isDynamicExpression(project))
return true;
// the JS version of XML is not currently dynamic so special case it here.
if (base != null && base.getNodeID() == ASTNodeID.IdentifierID && IdentifierNode.isXMLish(base.resolveType(project), project))
return true;
return false;
}
/**
* Check an ExpressionNodeBase's base expression (if any)
* to determine if it's a dynamic expression, which
* means we don't know much about its definition.
* @param binding - the binding whose base node is to be checked.
* @return true if node's base expression is dynamic.
* @see #hasDynamicBase(Binding, ICompilerProject)
*/
public boolean hasDynamicBase(Binding binding)
{
return hasDynamicBase(binding, this.project);
}
/**
* Get an ExpressionNodeBase's base node.
* @return the node's base expression, or null
* if the Binding does not wrap an ExpressionNodeBase.
*/
public static ExpressionNodeBase getBaseNode(Binding binding)
{
if ( binding.getNode() instanceof ExpressionNodeBase )
return ((ExpressionNodeBase)binding.getNode()).getBaseExpression();
else
return null;
}
/**
* Get the definition associated with a IASNode.
* @param iNode - the node whose definition is desired.
* @return the node's definition, or null if the node
* is of an unknown type or has no definition.
*/
public IDefinition getDefinition(IASNode iNode)
{
return getDefinition(iNode, project);
}
/**
* Get the definition associated with a IASNode.
* @param iNode - the node whose definition is desired.
* @param project - an ICompilerProject for the current project.
* @return the node's definition, or null if the node
* is of an unknown type or has no definition.
*
* TODO: don't require a ASDefinition cache, base on ICompilerProject
*/
public static IDefinition getDefinition(IASNode iNode, ICompilerProject project)
{
if ( iNode instanceof ExpressionNodeBase )
{
return ((ExpressionNodeBase)iNode).resolve(project);
}
else if ( iNode instanceof BaseTypedDefinitionNode )
{
return ((BaseTypedDefinitionNode)iNode).getDefinition();
}
else if ( iNode instanceof NamespaceNode )
{
return ((NamespaceNode)iNode).getDefinition();
}
return null;
}
public static IDefinition resolveType(IASNode iNode, ICompilerProject project)
{
if ( iNode instanceof ExpressionNodeBase )
{
return ((ExpressionNodeBase)iNode).resolveType(project);
}
else if ( iNode instanceof BaseTypedDefinitionNode )
{
return ((BaseTypedDefinitionNode)iNode).getDefinition().resolveType(project);
}
return null;
}
public IDefinition resolveType(IASNode iNode)
{
return resolveType(iNode, this.project);
}
/**
* Resolve the type of the underlying expression in a UnaryOperatorNode.
*
* for '++i', this would return the type of 'i' as opposed to the result of the ++
*
* @param iNode The UnaryOperatorNode you want the underlying type for
* @param project - an ICompilerProject for the current project.
* @return The type of the underlying expression, or null if the node passed in isn't
* a UnaryOperatorNode
*/
public static IDefinition resolveUnaryExprType(IASNode iNode, ICompilerProject project)
{
if ( iNode instanceof UnaryOperatorNodeBase )
{
return ((UnaryOperatorNodeBase)iNode).getOperandNode().resolveType(project);
}
return null;
}
/**
* Get the type of a node's base expression as a string.
* @param node the node whose base's type is desired.
* @return the type string, or null if none found.
*/
public String getTypeOfBase(IASNode node)
{
return getTypeOfBase(node, project);
}
/**
* Get the type of a node's base expression as a string.
* @param node the node whose base's type is desired.
* @param project an ICompilerProject for the current project.
* @return the type string, or null if none found.
*/
public static String getTypeOfBase(IASNode node, ICompilerProject project)
{
if (!(node instanceof ExpressionNodeBase))
return null;
ExpressionNodeBase base = ((ExpressionNodeBase)node).getBaseExpression();
if (base == null)
return null;
IDefinition def = base.resolve(project);
if (def == null)
return null;
return def.getTypeAsDisplayString();
}
/**
* Get the type of an expression's stem.
* @param iNode - the node of interest.
* @param project - an ICompilerProject for the current project.
* @return the type of the node's underlying reference base as a string.
* @pre hasUnderlyingType(iNode, cache) must be true.
*/
public static String getTypeOfStem(IASNode iNode, ICompilerProject project)
{
IDefinition type_def = getDefinitionOfUnderlyingType(iNode, ASSERT_ON_UNKNOWN_INODE, project);
return ( type_def != null )? type_def.getBaseName(): "?";
}
/**
* Get the type of an expression's stem.
* @param node - the node of interest.
* @return the type of the node's underlying reference base as a string.
* @see #getTypeOfStem(IASNode, ICompilerProject)
*/
public String getTypeOfStem(IASNode node)
{
return getTypeOfStem(node, this.project);
}
/**
* Get the definition of the explicit or implicit type underlying
* an iNode's reference(s).
* @param iNode - the inode of interest.
* @param allow_unknown_iNode - tolerate a node type that doesn't
* have a procedure for extracting its underlying scope.
* @param project - an ICompilerProject for the current project.
* @return the definition of the type that underlies the node's references,
* or null if no underlying scope is available.
*/
public static IDefinition getDefinitionOfUnderlyingType(IASNode iNode, final boolean allow_unknown_iNode, ICompilerProject project)
{
IDefinition result;
if ( iNode instanceof MemberAccessExpressionNode )
{
MemberAccessExpressionNode member_node = (MemberAccessExpressionNode) iNode;
result = member_node.getLeftOperandNode().resolveType(project);
}
else if ( iNode instanceof FunctionCallNode && ((FunctionCallNode)iNode).getNameNode() instanceof MemberAccessExpressionNode )
{
MemberAccessExpressionNode member_node = (MemberAccessExpressionNode) ((FunctionCallNode)iNode).getNameNode();
result = member_node.getLeftOperandNode().resolveType(project);
}
else if ( isInInstanceFunction(iNode, project) )
{
result = getEnclosingFunctionDefinition(iNode, project).getAncestorOfType(ClassDefinition.class);
}
else if ( isInStaticClassFunction(iNode, project) )
{
result = getEnclosingFunctionDefinition(iNode, project).getAncestorOfType(ClassDefinition.class);
}
else
{
assert(allow_unknown_iNode): "Unknown iNode:" + iNode;
result = null;
}
return result;
}
/**
* Get the definition of the explicit or implicit type underlying
* an iNode's reference(s).
* @param iNode - the inode of interest.
* @param allow_unknown_iNode - tolerate a node type that doesn't
* have a procedure for extracting its underlying scope.
* @return the definition of the type that underlies the node's references,
* or null if no underlying scope is available.
* @see #getDefinitionOfUnderlyingType(IASNode, boolean, ICompilerProject)
*/
public IDefinition getDefinitionOfUnderlyingType(IASNode iNode, final boolean allow_unknown_iNode)
{
return getDefinitionOfUnderlyingType(iNode, allow_unknown_iNode, this.project);
}
/** Manifest constant, getDefinitionOfUnderlyingType asserts if this is passed */
public static final boolean ASSERT_ON_UNKNOWN_INODE = false;
/** Manifest constant, getDefinitionOfUnderlyingType tolerates unknown tree shapes if this is passed */
public static final boolean ALLOW_UNKNOWN_INODE = true;
/**
* Does the given inode have an underlying type that could
* resolve its references?
* @param iNode - the inode of interest.
* @param project - an ICompilerProject for the current project.
*/
public static boolean hasUnderlyingType(IASNode iNode, ICompilerProject project)
{
return getDefinitionOfUnderlyingType(iNode, ALLOW_UNKNOWN_INODE, project) != null;
}
/**
* Does the given inode have an underlying type that could
* resolve its references?
* @param iNode - the inode of interest.
* @see #hasUnderlyingType(IASNode, ICompilerProject)
*/
public boolean hasUnderlyingType(IASNode iNode)
{
return hasUnderlyingType(iNode, this.project);
}
/**
* Does the given node have an explicit stem reference, e.g., foo.bar?
* @param iNode - the node of interest.
* @return true if the node has an explicit stem (the foo in foo.bar).
* @see #hasUnderlyingType which checks for explicit or implicit contexts for resolution.
*/
public static boolean hasExplicitStem(IASNode iNode)
{
return ( iNode instanceof MemberAccessExpressionNode ) ||
( iNode instanceof FunctionCallNode && ((FunctionCallNode)iNode).getNameNode() instanceof MemberAccessExpressionNode );
}
/**
* Does this binding have a base node?
* @param binding - the Binding of interest.
* @return true if the Binding's i-node has a base node.
*/
public static boolean hasBaseNode(Binding binding)
{
return getBaseNode(binding) != null;
}
/**
* Is this node enclosed in a function definition?
* @param iNode - the node of interest.
* @return true if the node is in a function definition.
*/
public static boolean isInFunction(IASNode iNode)
{
return iNode.getAncestorOfType(FunctionNode.class) != null;
}
/**
* Is this node declaring a property in a class, but also nested inside
* another statement.
* @param iNode the node
* @param def the
*/
public static boolean isNestedClassProperty(IASNode iNode, VariableDefinition def)
{
if(def != null && def.getParent() instanceof IClassDefinition)
{
return def.declaredInControlFlow();
}
return false;
}
/**
* Is this container an implicit container?
* @param iNode - the container of interest.
* @return true if the container is implicit.
*/
public static boolean isImplicitContainer(IASNode iNode)
{
// TODO: The result of CMP-1004 goes here.
return
( iNode instanceof IContainerNode ) &&
false;
}
/**
* Is the given multiply defined, or ambiguous?
* @param def - the definition of interest.
* @param project - the active compiler project.
* @return An enum telling the nature of any discovered multple definitions
*/
public static MultiDefinitionType getMultiDefinitionType(IDefinition def, ICompilerProject project)
{
// A null definition cannot be ambiguous.
if (def == null)
return MultiDefinitionType.NONE;
// Look for other definitions with the same base name
// and the same namespace as the specified definition.
IDefinition found = findPropertyQualified(def, project);
if (AmbiguousDefinition.isAmbiguous(found))
return MultiDefinitionType.AMBIGUOUS;
// if we found something, andit's not us, and it's not null, then there are multi deinitions.
if (def != found && found != null && !isGetterSetterPair(def, found, project))
{
return found instanceof IParameterDefinition ? MultiDefinitionType.SHADOWS_PARAM :
MultiDefinitionType.MULTIPLE;
}
return MultiDefinitionType.NONE ;
}
/**
* Determine if a given binding is ambiguous
* @param b the binding to check
* @return true if the binding refers to more than 1 definiton
*/
public static boolean isAmbiguousReference(Binding b)
{
return AmbiguousDefinition.isAmbiguous(b.getDefinition());
}
/**
* Does this node represent the "this" keyword?
* @param iNode - the node of interest.
* @return true if the node represents "this"
*/
public static boolean isThisKeyword(IASNode iNode)
{
return
(iNode instanceof ILanguageIdentifierNode) &&
(((ILanguageIdentifierNode)iNode).getKind() == LanguageIdentifierKind.THIS);
}
/**
* Is the given node in a class with a base class definition?
* @param iNode - the node of interest.
* @return true if the node is in a class with a base class.
* @see #hasBaseClassDefinition(IASNode, ICompilerProject)
*/
public boolean hasBaseClassDefinition(IASNode iNode)
{
return hasBaseClassDefinition(iNode, this.project);
}
/**
* Is the given node in a class with a base class definition?
* @param iNode - the node of interest.
* @param project - the active compiler project.
* @return true if the node is in a class with a base class.
*/
public static boolean hasBaseClassDefinition(IASNode iNode, ICompilerProject project)
{
return getBaseClassDefinition(iNode, project) != null;
}
/**
* Fetch the definition of the given node's
* class' base class.
* @param iNode - the node of interest.
* @param project - an ICompilerProject for the current project.
* @return the definition of the node's class' base class,
* or null if it not in a class or its class
* has no base class.
*/
public static IDefinition getBaseClassDefinition(IASNode iNode, ICompilerProject project)
{
IDefinition result = null;
IDefinition member_def = getDefinition(iNode, project);
if ( member_def != null )
{
ASScope super_scope = getSuperClassScope(project, member_def);
if( super_scope != null )
{
result = getPropertyQualified(
super_scope,
getNamespaceInClassContext(member_def, project),
member_def.getBaseName(),
project
);
}
}
return result;
}
/**
* Resolve a definition's namespace, and if its parent is a class, normalize
* that namespace against the parent class' superclass' protected namespace.
* @param def - the definition of interest.
* @param project - an ICompilerProject for the current project.
* @return the definition's class' superclass' namespace if the definition is
* in a class and its namespace is the same as the enclosing class' superclass'
* protected namespace; otherwise, return the definition's namespace.
*/
public static INamespaceDefinition getNamespaceInClassContext(IDefinition def, ICompilerProject project)
{
INamespaceDefinition namespace = def.resolveNamespace(project);
IDefinition parent = def.getParent();
if ( namespace != null && parent instanceof ClassDefinition )
{
// Iterate over the superclasses of this method's class.
ClassDefinition cls = (ClassDefinition)parent;
ClassDefinition base = (ClassDefinition)cls.resolveBaseClass(project);
if (base != null)
{
// Adjust the namespace if this is the protected namespace
INamespaceDefinition protectedNS = cls.getProtectedNamespaceReference().resolveNamespaceReference(project);
if (namespace.equals(protectedNS))
namespace = base.getProtectedNamespaceReference().resolveNamespaceReference(project);
}
}
return namespace;
}
/**
* Get the super class scope for the definition passed in. This will return an ASScope
* for the super class of whatever class declared the member passed in.
* @param project The project to resolve references in
* @param member_def The member to find a super class for
* @return The scope of the base class of the class the member_def is declared in.
* This will return null if the member_def was not declared in a class.
*/
public static ASScope getSuperClassScope(ICompilerProject project, IDefinition member_def)
{
IASScope containingScope = member_def.getContainingScope();
if( containingScope instanceof ScopeView )
{
return ((ScopeView) containingScope).resolveSuperScope(project);
}
return null;
}
/**
* Find a member definition, given its name.
* @param iNode - the node that forms the reference.
* Should be a MemberAccessExpressionNode to get any useful result.
* @param member_name - the name of the member of interest.
* @param match_static_to_static - when true, only accept definitions
*/
public static IDefinition findMemberByName(IASNode iNode, String member_name, boolean match_static_to_static, ICompilerProject project)
{
// Find a scope to work with.
IASScope starting_scope = null;
// Is this a static reference, e.g., ClassName.staticField?
boolean is_static_reference = false;
if ( iNode instanceof MemberAccessExpressionNode )
{
TypeDefinitionBase type = (TypeDefinitionBase)((MemberAccessExpressionNode)iNode).getLeftOperandNode().resolveType(project);
if ( type != null )
{
starting_scope = ((TypeDefinitionBase)((MemberAccessExpressionNode)iNode).getLeftOperandNode().resolveType(project)).getContainedScope();
is_static_reference = ((MemberAccessExpressionNode)iNode).getLeftOperandNode().resolve(project) instanceof ClassDefinition;
}
}
else if ( iNode instanceof FunctionCallNode && ((FunctionCallNode)iNode).getNameNode() instanceof MemberAccessExpressionNode )
{
MemberAccessExpressionNode member_node = (MemberAccessExpressionNode) ((FunctionCallNode)iNode).getNameNode();
TypeDefinitionBase type = (TypeDefinitionBase)member_node.getLeftOperandNode().resolveType(project);
if ( type != null )
{
starting_scope = type.getContainedScope();
is_static_reference = member_node.getLeftOperandNode().resolve(project) instanceof ClassDefinition;
}
}
else if ( isInInstanceFunction(iNode, project) )
{
starting_scope = getClassScope(iNode, project);
is_static_reference = false;
}
else if ( isInStaticClassFunction(iNode, project) )
{
starting_scope = getClassScope(iNode, project);
is_static_reference = true;
}
// Did we find a scope to work with?
if ( starting_scope != null )
return findMemberByName(starting_scope, member_name, match_static_to_static, is_static_reference, project);
else
return null;
}
/**
* Get the ASScope of the class that contains an inode.
* @param iNode - the inode of interest.
* @param project - an ICompilerProject for the current project.
* @return the ASScope (holding both static and instance definitions)
* of the class enclosing the inode, or null if no such scope is found.
*/
public static ASScope getClassScope(IASNode iNode, ICompilerProject project)
{
if ( isInInstanceFunction(iNode, project) || isInStaticClassFunction(iNode, project) )
{
ClassDefinition class_def = (ClassDefinition) getEnclosingFunctionDefinition(iNode, project).getAncestorOfType(ClassDefinition.class);
if ( class_def != null )
return class_def.getContainedScope();
}
return null;
}
/**
* Get the ASScope of the class that contains an inode.
* @param iNode - the inode of interest.
* @return the ASScope (holding both static and instance definitions)
* of the class enclosing the inode, or null if no such scope is found.
* @see #getClassScope(IASNode, ICompilerProject)
*/
public ASScope getClassScope(IASNode iNode)
{
return getClassScope(iNode, this.project);
}
/**
* Search a scope and its enclosing scopes for a member whose name is known.
* @param scope - the innermost scope to search.
* @param member_name - the (string) name of interest.
* @param match_static_to_static - if set, then only return static definitions if is_static_reference is set.
* @param is_static_reference - caller sets this if the reference occurs in a context that is known to be static.
* @param project - an ICompilerProject for the current project.
*/
public static IDefinition findMemberByName(IASScope scope, String member_name, boolean match_static_to_static, boolean is_static_reference, ICompilerProject project)
{
if ( scope == null )
return null;
IDefinition result = null;
// A definition by the right name but in the wrong scope
// (e.g., an instance def found while looking for a static def).
IDefinition possible_result = null;
for ( IDefinition def: getPropertiesByNameForMemberAccess(scope, member_name, project) )
{
// Check for matching scope: static references should only
// match static definitions, and vice versa.
// Note: the diagnostic could be improved by doing a subsequent
// check for definitions with matching names and conflicting scopes
// (i.e., a static reference to an instance field).
if ( is_static_reference )
{
if ( def.isStatic() )
{
result = def;
break;
}
else if ( match_static_to_static && possible_result != null )
{
possible_result = def;
// Keep looking for a static definition as a better match.
continue;
}
}
else
{
if ( !def.isStatic() )
{
result = def;
break;
}
}
}
return result != null? result: possible_result;
}
/**
* Is the given binding an inaccessible reference?
* @param iNode - the iNode that anchors the reference.
* @param member - the Binding.
* @param project - an ICompilerProject for the current project.
* @return true if the binding has no definition, but
* a search for the member by name in the iNode's
* underlying type scopes finds something.
*/
public static boolean isInaccessible(IASNode iNode, Binding member, ICompilerProject project)
{
assert(member.getName() != null);
return member.getDefinition() == null && findMemberByName(iNode, member.getName().getBaseName(), true, project) != null;
}
/**
* Is the given binding an inaccessible reference?
* @param iNode - the iNode that anchors the reference.
* @param member - the Binding.
* @return true if the binding has no definition, but
* a search for the member by name in the iNode's
* underlying type scopes finds something.
* @see #isInaccessible(IASNode, Binding, ICompilerProject)
*/
public boolean isInaccessible(IASNode iNode, Binding member)
{
return isInaccessible(iNode, member, this.project);
}
/**
* Fetch the definiton of the given node's
* class' base class.
* @param iNode - the node of interest.
* @return the definition of the node's class' base class,
* or null if it not in a class or its class
* has no base class.
* @see #getBaseClassDefinition(IASNode, ICompilerProject)
*/
public IDefinition getBaseClassDefinition(IASNode iNode)
{
return getBaseClassDefinition(iNode, this.project);
}
/**
* Find a property in a defintion's containing scope.
* @param member_def - the definition of interest.
* @param project - the active compiler project.
* @return the definition found in the containing scope, or null if not found.
*/
public static IDefinition findPropertyQualified(IDefinition member_def, ICompilerProject project)
{
ASScope containingScope = (ASScope)member_def.getContainingScope();
return findPropertyQualified(containingScope, member_def, project);
}
/**
* Find a property in a defintion's containing scope.
* @param member_def - the definition of interest.
* @return the definition found in the containing scope, or null if not found.
* @see #findPropertyQualified(IDefinition, ICompilerProject)
*/
public IDefinition findPropertyQualified(IDefinition member_def)
{
return findPropertyQualified(member_def, this.project);
}
/**
* Find a property in a (presumably) enclosing defintion's scope.
* @param enclosing_def - the definition to query.
* @param member_def - the prototype definition of the member desired.
* @param project - the active compiler project.
* @return the definition found in the enclosing scope, or null if not found.
*/
public static IDefinition findPropertyQualified(IScopedDefinition enclosing_def, IDefinition member_def, ICompilerProject project)
{
return findPropertyQualified((ASScope)enclosing_def.getContainedScope(), member_def, project);
}
public static IDefinition getPropertyQualified(IScopedDefinition enclosing_def, IDefinition member_def, ICompilerProject project)
{
return getPropertyQualified((ASScope)enclosing_def.getContainedScope(), member_def, project);
}
/**
* Find a property in a (presumably) enclosing defintion's scope.
* @param enclosing_def - the definition to query.
* @param member_def - the prototype definition of the member desired.
* @return the definition found in the enclosing scope, or null if not found.
* @see #findPropertyQualified(IScopedDefinition, IDefinition, ICompilerProject)
*/
public IDefinition findPropertyQualified(IScopedDefinition enclosing_def, IDefinition member_def)
{
return findPropertyQualified(enclosing_def, member_def, this.project);
}
/**
* Find a qualified property in a scope.
* @param scope - the scope to query.
* @param member_def - the prototype definition of the member desired.
* @param project - the active compiler project.
* @return the definition found in the enclosing scope, or null if not found.
*/
public static IDefinition findPropertyQualified(ASScope scope, IDefinition member_def, ICompilerProject project)
{
IDefinition result = null;
if ( scope != null && member_def != null && member_def.getNamespaceReference() != null )
{
INamespaceDefinition namespace =
member_def.getNamespaceReference().resolveNamespaceReference(project);
if (namespace != null)
{
String baseName = member_def.getBaseName();
result = scope.findPropertyQualified(
project, namespace, baseName, DependencyType.EXPRESSION);
}
}
return result;
}
/**
* Get a qualified property in a scope - this will look for properties with the same qualified name as member_def
* in the scope passed in.
* @param scope the scope to look for the property in
* @param member_def the prototype definition of the member desired
* @param project the active compiler project
* @return the definition found in the passed in scope ,or null if not found
*/
public static IDefinition getPropertyQualified(ASScope scope, IDefinition member_def, ICompilerProject project)
{
IDefinition result = null;
if ( scope != null && member_def != null && member_def.getNamespaceReference() != null )
{
INamespaceDefinition namespace =
member_def.getNamespaceReference().resolveNamespaceReference(project);
if (namespace != null)
{
result = getPropertyQualified( scope, namespace, member_def.getBaseName(), project);
}
}
return result;
}
/**
* Get a qualified property in a scope, given the namespace and base name to search for.
* @param scope the scope to look for the property in
* @param namespace the namespace of the member desired
* @param baseName the base name of the member desired
* @param project the active compiler project
* @return the definition found in the passed in scope ,or null if not found
*/
public static IDefinition getPropertyQualified(ASScope scope, INamespaceDefinition namespace, String baseName, ICompilerProject project)
{
IDefinition result = null;
if ( scope != null && namespace != null && baseName != null )
{
result = scope.getPropertyByNameForMemberAccess(
(CompilerProject)project,
baseName,
Collections.singleton(namespace)
);
}
return result;
}
/**
* Find a qualified property by name in a scope.
* @param scope - the scope to query.
* @param member_name - the name of the desired member.
* @param project - the active compiler project.
* @return the definition found in the scope, or null if not found.
*/
public static IDefinition findProperty(ASScope scope, String member_name, ICompilerProject project, boolean lookForStatics)
{
IDefinition result = null;
if ( scope != null )
{
result = scope.findProperty(
project,
member_name,
DependencyType.EXPRESSION,
false
);
}
return result;
}
/**
* Find the definition of the function that encloses the given node, if any.
* @param iNode - the inode of interest.
* @param project - an ICompilerProject for the current project.
* @return the definition of any function that encloses the inode, or null
* if not found (or if the function is undefined for some reason).
*/
public static FunctionDefinition getEnclosingFunctionDefinition(IASNode iNode, ICompilerProject project)
{
IASNode func_node = iNode.getAncestorOfType(FunctionNode.class);
if ( func_node != null )
{
return (FunctionDefinition) getDefinition(func_node, project);
}
return null;
}
/**
* Find the definition of the function that encloses the given node, if any.
* @param iNode - the inode of interest.
* @return the definition of any function that encloses the inode, or null
* if not found (or if the function is undefined for some reason).
* @see #getEnclosingFunctionDefinition(IASNode, ICompilerProject)
*/
public FunctionDefinition getEnclosingFunctionDefinition(IASNode iNode)
{
return getEnclosingFunctionDefinition(iNode, this.project);
}
/**
* Is the given inode in an instance function?
* @param iNode - the inode of interest.
* @param project - an ICompilerProject for the current project.
* @return true if the inode has an enclosing function
* and that function is a non-static class member.
*/
public static boolean isInInstanceFunction(IASNode iNode, ICompilerProject project)
{
FunctionDefinition func_def = getEnclosingFunctionDefinition(iNode, project);
return func_def != null && func_def.getFunctionClassification() == FunctionClassification.CLASS_MEMBER && !func_def.isStatic();
}
/**
* Is the given inode in an instance function?
* @param iNode - the inode of interest.
* @return true if the inode has an enclosing function
* and that function is a non-static class member.
* @see #isInInstanceFunction(IASNode, ICompilerProject)
*/
public boolean isInInstanceFunction(IASNode iNode)
{
return isInInstanceFunction(iNode, this.project);
}
/**
* Is the given inode in a static function?
* @param iNode - the inode of interest.
* @param project - an ICompilerProject for the current project.
* @return true if the inode has an enclosing function
* and that function is a static class member.
*/
public static boolean isInStaticClassFunction(IASNode iNode, ICompilerProject project)
{
FunctionDefinition func_def = getEnclosingFunctionDefinition(iNode, project);
return func_def != null && func_def.getFunctionClassification() == FunctionClassification.CLASS_MEMBER && func_def.isStatic();
}
/**
* Is the given Binding inaccessible, i.e., known to
* an underlying type scope but not defined here
* due to access restrictions?
* @param scope - the innermost underlying type scope.
* @param b - the Binding of interest
* @param project - an ICompilerProject for the current project.
*/
private static boolean isInaccessible(IASScope scope, Binding b, ICompilerProject project)
{
return b.getDefinition() == null && findMemberByName(scope, b.getName().getBaseName(), true, false, project) != null;
}
/**
* Is the given Binding inaccessible, i.e., known to
* an underlying type scope but not defined here
* due to access restrictions?
* @param containingScope - the innermost underlying type scope.
* @param b - the Binding of interest
= */
public boolean isInaccessible(ASScope containingScope, Binding b)
{
return isInaccessible(containingScope, b, this.project);
}
/**
* Get the name of the class enclosing an instance function in which
* some problematic construct resides.
* @param iNode - the problematic construct.
* @param project - an ICompilerProject for the current project.
* @return the name of the enclosing class.
*/
public static String getEnclosingClassName(IASNode iNode, ICompilerProject project)
{
assert(isInInstanceFunction(iNode, project)); // assert precondition.
IDefinition class_def = getEnclosingFunctionDefinition(iNode, project).getAncestorOfType(ClassDefinition.class);
return class_def.getBaseName();
}
/**
* Get the name of the class enclosing an instance function in which
* some problematic construct resides.
* @param iNode - the problematic construct.
* @return the name of the enclosing class.
* @see #getEnclosingClassName(IASNode, ICompilerProject)
*/
public String getEnclosingClassName(IASNode iNode)
{
return getEnclosingClassName(iNode, this.project);
}
/**
* Fetch the collection of definitions in the given scope or its underlying scopes
* that match the given name.
* @param scope - the innermost scope to search.
* @param member_name - the (string) name of interest.
* @param project - an ICompilerProject for the current project.
*/
public static Collection<IDefinition> getPropertiesByNameForMemberAccess(IASScope scope, String member_name, ICompilerProject project)
{
assert(scope != null); // check precondition.
return ((ASScope)scope).getPropertiesByNameForMemberAccess(
(CompilerProject)project,
member_name,
ASScopeBase.allNamespacesSet
);
}
/**
* Get an abstract string description of a node, suitable for a diagnostic.
* @return the best guess we can come up with for a node's user-friendly string representation.
*/
public static String getDiagnosticString(IASNode iNode)
{
String content = null;
switch ( iNode.getNodeID() )
{
case IdentifierID:
case NamespaceIdentifierID:
case NonResolvingIdentifierID:
case QualifiedNameExpressionID:
content = ((IdentifierNode)iNode).getName();
break;
case FunctionCallID:
{
FunctionCallNode func = (FunctionCallNode)iNode;
if ( func.getNameNode() != null )
{
// return to avoid quoting the result twice.
return getDiagnosticString(func.getNameNode());
}
else
{
content = iNode.getNodeID().getParaphrase();
}
break;
}
case LiteralArrayID:
case LiteralBooleanID:
case LiteralNumberID:
case LiteralObjectID:
case LiteralRegexID:
case LiteralStringID:
case LiteralXMLID:
case LiteralID:
content = ((LiteralNode)iNode).getValue();
break;
case LiteralNullID:
content = "null";
break;
case LiteralVoidID:
content = "void";
break;
case MemberAccessExpressionID:
content = ((MemberAccessExpressionNode)iNode).getDisplayString();
break;
default:
content = iNode.getNodeID().getParaphrase();
}
return "'" + content + "'";
}
/**
* Get the set of open namespaces for the given node.
* @param iNode - the node of interest.
* @param project - an ICompilerProject for the current project.
* @return the open namespaces at this node as a Nsset,
* or null if the node doesn't have a containing scope.
*/
public static Nsset getOpenNamespaces(IASNode iNode, ICompilerProject project)
{
ASScope scope = getASScope(iNode);
if (scope == null)
return null;
Set<INamespaceDefinition> namespaceSet = scope.getNamespaceSet(project);
Nsset nsSet = convertSetINamespaceToNsset(namespaceSet);
return nsSet;
}
/**
* Get the set of open namespaces for the given node,
* as appropriate for <code>super[...]</code> access.
* @param iNode - the node of interest.
* @param project - an ICompilerProject for the current project.
* @return the open namespaces at this node as a Nsset,
* or null if the node doesn't have a containing scope.
*/
public static Nsset getOpenNamespacesForSuper(IASNode iNode, ICompilerProject project,
IDefinition superDef)
{
ASScope scope = getASScope(iNode);
if (scope == null)
return null;
Set<INamespaceDefinition> namespaceSet = scope.getNamespaceSetForSuper(project, superDef);
Nsset nsSet = convertSetINamespaceToNsset(namespaceSet);
return nsSet;
}
/**
* Convert a set of INamespaceDefinition objects into a Nsset.
* @param namespaceSet - the set of INamespaceDefinition objects.
* @return the namespaces as a Nsset.
*/
public static Nsset convertSetINamespaceToNsset(Set<INamespaceDefinition> namespaceSet)
{
ArrayList<Namespace> ns_set = new ArrayList<Namespace>(namespaceSet.size());
for (INamespaceDefinition namespace : namespaceSet)
{
Namespace aetNamespace = ((NamespaceDefinition)namespace).getAETNamespace();
ns_set.add(aetNamespace);
}
return new Nsset(ns_set);
}
/**
* Get the given node's containing scope.
* (Convenience method that hides a downcast.)
* @param iNode - the node of interest.
* @return the node's scope, or null if not available.
*/
public static ASScope getASScope(IASNode iNode)
{
return ((NodeBase)iNode).getASScope();
}
/**
* Is the given definition const?
* @param iNode - the root of the definition.
* @param project - an ICompilerProject for the current project.
* @return true if iNode can be resolved to a const definition.
*/
public static boolean isConst(IASNode iNode, ICompilerProject project)
{
return isConstDefinition(getDefinition(iNode, project));
}
/**
* Is the given definition const?
* @param iNode - the root of the definition.
* @return true if iNode can be resolved to a const definition.
* @see #isConst(IASNode iNode, ICompilerProject cache)
*/
public boolean isConst(IASNode iNode)
{
return isConst(iNode, this.project);
}
/**
* Is the given definition a constant?
* @param def - the definition of interest.
* @return true if the definition is of a const.
*/
public static boolean isConstDefinition(IDefinition def)
{
return def instanceof IConstantDefinition;
}
/**
* Is the given node in a constructor?
* @param iNode - the i-node of interest.
* @return true if the node is in a constructor function.
*/
@SuppressWarnings("unchecked")
public static boolean isInConstructor(IASNode iNode)
{
FunctionNode fnode = (FunctionNode) searchUpForType(iNode, FunctionNode.class);
return fnode != null && fnode.isConstructor();
}
/**
* Is the given i-node inside a variable declaration?
* @param iNode - the i-node of interest.
* @return true if the node has a VariableNode or VariableExpressionNode parent.
*/
@SuppressWarnings("unchecked")
public static boolean isInVariableDeclaration(IASNode iNode)
{
return searchUpForType(iNode, VariableNode.class, VariableExpressionNode.class) != null;
}
/**
* Search a node's parent chain for an ancestor of any of a group of classes.
* @param iNode - the node of interest.
* @param desired - the group of classes desired.
* @return the first node in the node and its ancestors that matches a desired class.
*/
private static IASNode searchUpForType(IASNode iNode, Class<? extends IASNode> ... desired)
{
if ( iNode == null )
return null;
IASNode found = null;
for ( Class<? extends IASNode> candidate: desired)
{
if ( candidate.isInstance(iNode) )
found = iNode;
else
found = iNode.getAncestorOfType(candidate);
if ( found != null )
break;
}
return found;
}
/**
* Fetch the most appropriate part of a type name for diagnostics.
* @return the type part of a parameterized type, or the input node.
*/
public IASNode getPotentiallyParameterizedType(IASNode iNode)
{
IASNode result = iNode;
if ( iNode instanceof TypedExpressionNode )
{
result = ((TypedExpressionNode)iNode).getTypeNode();
}
return result;
}
/**
* Get the Nth child of a node per the BURM's semantics.
*/
public static IASNode getNthChild( IASNode node, int index)
{
IASNode result = null;
switch( node.getNodeID() )
{
case BindableVariableID:
case VariableID:
{
IVariableNode var = (IVariableNode) node;
switch( index )
{
case 0:
{
result = var.getNameExpressionNode();
break;
}
default:
{
// We have to go hunt among the children
// for the nodes that are relevant to the BURM.
int lastFoundChildPos = 0;
result = var.getVariableTypeNode();
if ( result != null )
{
lastFoundChildPos++;
if ( index == lastFoundChildPos)
break;
}
result = var.getAssignedValueNode();
if ( result != null )
{
lastFoundChildPos++;
if ( index == lastFoundChildPos)
break;
}
// Look for chained variable declarations.
int needle = 0;
while ( needle < node.getChildCount() && lastFoundChildPos < index )
{
if ( node.getChild(needle) instanceof IVariableNode )
lastFoundChildPos++;
if ( lastFoundChildPos < index )
needle++;
}
assert(lastFoundChildPos == index): "getNthChild() failed, should have been constrained by getChildCount(node)";
return ( node.getChild(needle));
}
}
break;
}
case FunctionID:
case GetterID:
case SetterID:
{
FunctionNode func = (FunctionNode) node;
switch( index )
{
case 0:
result = func.getNameExpressionNode();
break;
case 1:
result = func.getParametersContainerNode();
break;
case 2:
result = func.getReturnTypeNode();
if ( result != null )
break;
case 3:
assert (func.hasBeenParsed()) : "getScopedNode() called on a function before the body has been parsed";
result = func.getScopedNode();
break;
}
break;
}
case Op_CommaID:
{
switch( index )
{
case 0:
{
result = node.getChild(node.getChildCount()-1);
break;
}
default:
result = node.getChild(index - 1);
}
break;
}
case TryID:
{
ITryNode tryNode = (ITryNode) node;
switch( index )
{
case 0:
{
result = tryNode.getStatementContentsNode();
break;
}
case 1:
{
if ( tryNode.getFinallyNode() != null )
{
result = tryNode.getFinallyNode();
}
else
{
assert ( tryNode.getCatchNodeCount() > 0 );
result = tryNode.getCatchNode(0);
}
break;
}
default:
{
// Note: If the try has a contents and finally nodes,
// they are presented to the CG by getNthChild() as child
// nodes 0 and 1 before the n-ary tail of catch nodes.
if (tryNode.getStatementContentsNode() != null)
index--;
if (tryNode.getFinallyNode() != null)
index--;
result = tryNode.getCatchNode(index);
}
}
break;
}
case NamespaceID:
{
// Skip over MetaTagsNode and NamespaceIdentifierNode
final NamespaceNode nsNode = (NamespaceNode)node;
final IASNode nsName = nsNode.getNameExpressionNode();
final IASNode nsURI = nsNode.getNamespaceURINode();
switch(index)
{
case 0:
if(nsName != null)
result = nsName;
else
result = nsURI;
break;
case 1:
if(nsName != null && nsURI != null)
result = nsURI;
else
throw new ArrayIndexOutOfBoundsException(index);
break;
default:
throw new ArrayIndexOutOfBoundsException(index);
}
break;
}
default:
{
result = node.getChild(index);
break;
}
}
assert(result != null): String.format("getNthChild(%s,%d) null result", node.getNodeID(), index);
return result;
}
/**
* Get the child count for nodes whose
* CodeModel-centric child count isn't
* reliable.
*/
public static int getChildCount(IASNode node)
{
int result = 0;
switch( node.getNodeID() )
{
case BindableVariableID:
case VariableID:
{
IVariableNode var = (IVariableNode) node;
assert ( var.getNameExpressionNode() != null ): "Variable has null name expression.";
result = 1;
if ( var.getVariableTypeNode() != null )
result++;
if ( var.getAssignedValueNode() != null )
result++;
// Scan for chained declarations.
for ( int i = 0; i < node.getChildCount(); i++ )
{
if ( node.getChild(i) instanceof IVariableNode )
result++;
}
break;
}
case FunctionID:
case GetterID:
case SetterID:
{
FunctionNode func = (FunctionNode)node;
assert ( func.getNameExpressionNode() != null ): "Function has null name expression";
assert ( func.getParametersContainerNode() != null ): "Function has null parameters";
result = 2;
if ( func.getReturnTypeNode() != null )
result++;
if ( func.getScopedNode() != null )
result++;
}
break;
case NamespaceID:
{
final NamespaceNode nsNode = (NamespaceNode)node;
assert nsNode.getNameExpressionNode() != null : "'name' node is required";
result = 1;
if (nsNode.getNamespaceURINode() != null)
result = 2;
break;
}
default:
result = node.getChildCount();
}
return( result);
}
/**
* @return a parameter's definition.
* @param iNode - the parameter node.
*/
public static IDefinition getParameterContent(IASNode iNode)
{
return ((( IParameterNode )iNode).getDefinition());
}
/**
* @return the string content of a literal.
* @param iNode - the literal node.
*/
public static String getStringLiteralContent(IASNode iNode)
{
return (( ILiteralNode )iNode).getValue();
}
/**
* @return the uint content of a numeric literal.
* @param iNode - the literal node.
*/
public static Long getUintContent(IASNode iNode)
{
return Long.valueOf((( INumericLiteralNode )iNode).getNumericValue().toUint32());
}
/**
* @return the double content of a numeric literal.
* @param iNode - the literal node.
*/
public static Double getDoubleContent(IASNode iNode)
{
return Double.valueOf((( INumericLiteralNode )iNode).getNumericValue().toNumber());
}
/**
* @return the boolean content of a literal.
* @param iNode - the literal node.
*/
public static Boolean getBooleanContent(IASNode iNode)
{
if ( "true".equalsIgnoreCase(getStringLiteralContent(iNode)) )
{
return Boolean.TRUE;
}
else
return Boolean.FALSE;
}
/**
* @return the name of an identifier.
* @param iNode - the IIdentifier node.
*/
public static String getIdentifierContent(IASNode iNode)
{
return (( IIdentifierNode )iNode).getName();
}
/**
* @return the int content of a numeric literal.
* @param iNode - the literal node.
*/
public static Integer getIntegerContent(IASNode iNode)
{
return Integer.valueOf((( INumericLiteralNode )iNode).getNumericValue().toInt32());
}
/**
* @return a node cast to MXMLEventSpecifierNode type.
* @param iNode - the MXMLEventSpecifierNode node.
*/
public static IMXMLEventSpecifierNode getMXMLEventSpecifierContent(IASNode iNode)
{
return (IMXMLEventSpecifierNode)iNode;
}
/**
* Explore a MemberAccessNode and decide if
* its stem is a referenceto a package.
* @return a "feasible" cost of this node
* is of the form foo.bar, where foo is a package.
*/
public static int isDottedName(IASNode n)
{
if ( n instanceof MemberAccessExpressionNode && ((MemberAccessExpressionNode)n).stemIsPackage() )
return 1;
// Return "unfeasible" cost.
return Integer.MAX_VALUE;
}
/**
* Get the definition associated with a node's qualifier
* and decide if the qualifier is a compile-time constant.
* @param iNode - the node to check.
* @pre - the node has an IdentifierNode 0th child.
* @return an attractive cost if the child has a known namespace, i.e.,
* it's a compile-time constant qualifier.
*/
public static int qualifierIsCompileTimeConstant(IASNode iNode, ICompilerProject project)
{
IdentifierNode qualifier = (IdentifierNode) SemanticUtils.getNthChild(iNode, 0);
IDefinition def = qualifier.resolve(project);
if ( def instanceof NamespaceDefinition )
return 1;
else
return Integer.MAX_VALUE;
}
/**
* Get the definition associated with a node's qualifier
* and decide if the qualifier is a compile-time constant.
* @param iNode - the node to check.
* @pre - the node has an IdentifierNode 0th child.
* @return an attractive cost if the child has a known namespace, i.e.,
* it's a compile-time constant qualifier.
* @see #qualifierIsCompileTimeConstant(IASNode, ICompilerProject)
*/
public int qualifierIsCompileTimeConstant(IASNode iNode)
{
return qualifierIsCompileTimeConstant(iNode, this.project);
}
/**
* Get the definition associated with a node's qualifier
* and decide if the qualifier is an interface name.
* @param iNode - the node to check.
* @pre - the node has an IdentifierNode 0th child.
* @return an attractive cost if the child is an interface.
*/
public static int qualifierIsInterface(IASNode iNode, ICompilerProject project)
{
IdentifierNode qualifier = (IdentifierNode) SemanticUtils.getNthChild(iNode, 0);
IDefinition def = qualifier.resolve(project);
if ( def instanceof InterfaceDefinition )
return 1;
else
return Integer.MAX_VALUE;
}
/**
* Get the definition associated with a node's qualifier
* and decide if the qualifier is an interface name.
* @param iNode - the node to check.
* @pre - the node has an IdentifierNode 0th child.
* @return an attractive cost if the child is an interface.
* @see #qualifierIsInterface(IASNode, ICompilerProject)
*/
public int qualifierIsInterface(IASNode iNode)
{
return qualifierIsInterface(iNode, this.project);
}
/**
* Transform a leaf AST into a compile-time constant;
* note that more complex constant folding is done by
* a series of rules in the BURM. This routine looks
* at "terminal" constants, e.g., 1 or foo where foo
* is a namespace.
* @param iNode - the i-node of interest.
* @param project - an ICompilerProject for the current project.
* @return the constant value, if there is one.
*/
public static Object transformNameToConstantValue(IASNode iNode, ICompilerProject project)
{
Object result = null;
if ( iNode instanceof ExpressionNodeBase )
{
IASNode parentNode = iNode.getParent();
if (parentNode instanceof BaseTypedDefinitionNode)
{
BaseTypedDefinitionNode parentDefinitionNode = (BaseTypedDefinitionNode)parentNode;
if (parentDefinitionNode.getNameExpressionNode() == iNode)
return null;
if (parentDefinitionNode.getTypeNode() == iNode)
return null;
}
else if (parentNode instanceof IterationFlowNode)
{
IterationFlowNode parentIterationFlowNode = (IterationFlowNode)parentNode;
if (parentIterationFlowNode.getLabelNode() == iNode)
return null;
}
// Try for a compile-time constant value.
IDefinition val = ((ExpressionNodeBase)iNode).resolve(project);
if ( val instanceof NamespaceDefinition )
{
NamespaceDefinition ns = (NamespaceDefinition)val;
result = ns.resolveAETNamespace(project);
}
else if( val instanceof ConstantDefinition )
{
ConstantDefinition cd = (ConstantDefinition)val;
result = cd.resolveValueFrom(project, (NodeBase)iNode);
}
}
return result;
}
/**
* Transform a leaf AST into a compile-time constant;
* note that more complex constant folding is done by
* a series of rules in the BURM. This routine looks
* at "terminal" constants, e.g., 1 or foo where foo
* is a namespace.
* @param iNode - the i-node of interest.
* @return the constant value, if there is one.
* @see #transformNameToConstantValue(IASNode, ICompilerProject)
*/
public Object transformNameToConstantValue(IASNode iNode)
{
return transformNameToConstantValue(iNode, this.project);
}
/**
* Check for identifiers that don't have a namespace quailifier. Generates problem if found.
*
* @param scope is the scope where problems will be added
* @param node is the definition node for the defintion we are checking
* @param definition is the dfintion we are checking
* @param className is the class in which the identifier lives, or null if not in a class. Used for error reporting
*/
public static void checkScopedToDefaultNamespaceProblem(LexicalScope scope, BaseDefinitionNode node, IDefinition definition, String className)
{
if (node == null) // if we don't have a node, it means we didn't come from source code, so there is no point
// in doing a semantic check
return;
INamespaceReference nsref = definition.getNamespaceReference();
INamespaceDecorationNode dec = node.getNamespaceNode();
if ((dec ==null) &&(nsref instanceof INamespaceDefinition.IInternalNamespaceDefinition))
{
String type = "declaration"; // should never see this string...
if (definition instanceof IFunctionDefinition)
type = "function";
else if (definition instanceof IClassDefinition)
type = "class";
else if (definition instanceof IInterfaceDefinition)
type = "interface";
String identifierName = type + " '" + definition.getBaseName() + "'";
scope.addProblem( new ScopedToDefaultNamespaceProblem( node, identifierName, className));
}
}
/**
* Checks that a given function definition has a return type, and logs a problem if not
*
* @param scope is the scope where problems are to be logged
* @param node is the function node that is being checked (used for location reporting(
* @param func is the definition of the function to be checked.
*/
public static void checkReturnValueHasNoTypeDeclaration(LexicalScope scope, IFunctionNode node, IFunctionDefinition func)
{
// constructors aren't supposed to have return types, so skip this check for them
if (node.isConstructor())
return;
// Compiler synthesized nodes may not report return types as we expect. For example
// the class generated for embedding has an implicit return type node, which causes our
// check to think the "user code" has no return type. So skip these kinds of nodes....
if (node.getStart() == node.getEnd())
return;
IExpressionNode returnType = node.getReturnTypeNode();
// check for return type declaration
if (returnType == null)
{
// if none found, derive best source location for problem report
IASNode sourceLoc = node;
IExpressionNode nameNode = node.getNameExpressionNode();
if (nameNode != null)
sourceLoc = nameNode;
scope.addProblem(new ReturnValueHasNoTypeDeclarationProblem(sourceLoc, func.getBaseName()));
}
}
/**
* Determines if a list of definitions consists of a matched getter/setter pair.
*
* @param defs The list of definitions to analyze.
* @param project The context in which they are analyzed.
* @return <code>true</code> if the definitions are a getter/setter pair.
*/
public static boolean isGetterSetterPair(List<IDefinition> defs, ICompilerProject project)
{
// Must be a pair to be a getter setter pair. A pair plus others doesn't count.
if (defs.size() != 2)
return false;
IDefinition def0 = defs.get(0);
IDefinition def1 = defs.get(1);
return isGetterSetterPair(def0, def1, project);
}
/**
* Determines if a pair of definitions consists of a matched getter/setter pair.
*
* @param def0 The first definitions to analyze.
* @param def1 The first definitions to analyze.
* @param project The context in which they are analyzed.
* @return <code>true</code> if the definitions are a getter/setter pair.
*/
public static boolean isGetterSetterPair(IDefinition def0, IDefinition def1, ICompilerProject project)
{
if (!(def0 instanceof IAccessorDefinition))
return false;
if (!(def1 instanceof IAccessorDefinition))
return false;
return ((IAccessorDefinition)def0).resolveCorrespondingAccessor(project) == def1;
}
/**
* This is similar to the <code>resolveBaseClass()</code> method of <code>IClassDefinition</code>,
* except it reports problems if the base class doesn't exist,
* if it isn't actually a class, or if it is final;
* it also returns the class definition for <code>Object</code> in these error cases.
*/
public static ClassDefinition resolveBaseClass(ICommonClassNode classNode,
ClassDefinition classDefinition,
ICompilerProject project,
Collection<ICompilerProblem> problems)
{
IReference baseClassReference = classDefinition.getBaseClassReference();
// Object has no base class.
if (baseClassReference == null)
return null;
ITypeDefinition superclassDefinition = classDefinition.resolveType(
baseClassReference, project, DependencyType.INHERITANCE);
if (superclassDefinition == null)
{
IASNode problemNode = getBaseClassProblemNode(classNode, classDefinition);
String baseClassReferenceName = baseClassReference.getName();
// The base class reference might be ambiguous.
IDefinition foundDefinition = baseClassReference.resolve(project, (ASScope)classDefinition.getContainingScope(), DependencyType.INHERITANCE, true);
if ( AmbiguousDefinition.isAmbiguous(foundDefinition))
problems.add(new AmbiguousReferenceProblem(problemNode, baseClassReferenceName));
else
problems.add(new UnknownSuperclassProblem(problemNode, baseClassReferenceName));
// Repair by making the class extend Object.
superclassDefinition = getObjectDefinition(project);
}
else if (superclassDefinition instanceof IInterfaceDefinition)
{
IASNode problemNode = getBaseClassProblemNode(classNode, classDefinition);
problems.add(new CannotExtendInterfaceProblem(problemNode));
// Repair by making the class extend Object.
superclassDefinition = getObjectDefinition(project);
}
else if (superclassDefinition.isFinal())
{
IASNode problemNode = getBaseClassProblemNode(classNode, classDefinition);
problems.add(new BaseClassIsFinalProblem(problemNode));
// Repair by making the class extend Object.
superclassDefinition = getObjectDefinition(project);
}
else if (superclassDefinition == classDefinition)
{
problems.add(new CircularTypeReferenceProblem(classDefinition, classDefinition.getQualifiedName()));
// Repair by making the class extend Object.
superclassDefinition = getObjectDefinition(project);
}
// Report a problem if the superclass is deprecated
// and the reference to it is not within a deprecated API.
if (superclassDefinition != null && superclassDefinition.isDeprecated())
{
IASNode problemNode = getBaseClassProblemNode(classNode, classDefinition);
if (!SemanticUtils.hasDeprecatedAncestor(problemNode))
{
ICompilerProblem problem = SemanticUtils.createDeprecationProblem(superclassDefinition, problemNode);
problems.add(problem);
}
}
return (ClassDefinition)superclassDefinition;
}
private static IASNode getBaseClassProblemNode(ICommonClassNode classNode, ClassDefinition classDefinition)
{
// If we have a source node for 'class A extends B', return the node for B.
if (classNode instanceof IClassNode)
return ((IClassNode)classNode).getBaseClassExpressionNode();
// Otherwise, try to get the class node from the definition.
return classDefinition.getNode();
}
private static IClassDefinition getObjectDefinition(ICompilerProject project)
{
IClassDefinition objectDefinition = (IClassDefinition)project.getBuiltinType(
IASLanguageConstants.BuiltinType.OBJECT);
if (objectDefinition == null)
{
// Abort codegen with a more comprehensible diagnostic.
throw new MissingBuiltinException(IASLanguageConstants.Object);
}
return objectDefinition;
}
public static boolean isValidImport(IImportNode importNode, ICompilerProject project,
boolean inInvisibleCompilationUnit)
{
String importName = importNode.getImportName();
importName = project.getActualPackageName(importName);
// First check whether the import name matches
// a definition in the project scope.
ASProjectScope projectScope = (ASProjectScope)project.getScope();
if (projectScope.isValidImport(importName))
return true;
// For an invisible compilation unit, which doesn't contribute
// definitions to the project scope, we must also check
// whether it matches an externally visible definition
// in the file.
if (inInvisibleCompilationUnit)
{
IFileNode fileNode = (IFileNode)importNode.getAncestorOfType(IFileNode.class);
IDefinition[] externallyVisibleDefinitionsInThisFile =
fileNode.getTopLevelDefinitions(false, true);
for (IDefinition d : externallyVisibleDefinitionsInThisFile)
{
String qName = d.getQualifiedName();
if (qName.equals(importName))
return true;
String wildcardQName = ImportNode.makeWildcardName(qName);
if (wildcardQName.equals(importName))
return true;
}
}
return false;
}
/**
* Determine if a given IDefinition is the definition of a type (class or interface)
* @param d the definition to check
* @return true if the Definition represents a Type
*/
public static boolean isType(IDefinition d)
{
return d instanceof ITypeDefinition;
}
/**
* Check if {@code node} is in an E4X filter expression.
*
* @param node AST node to be checked.
* @return True if the node is in an E4X filter expression without crossing
* a block.
*/
public static boolean isE4XWildcardProperty(final IdentifierNode node)
{
assert node != null : "node can't be null";
IASNode blockOrFilterNode = node.getParent();
// "*::bar", "foo::*", "foo.*", "foo.(@* == 10) are not E4X wildcard properties
if (blockOrFilterNode instanceof MemberAccessExpressionNode ||
blockOrFilterNode instanceof UnaryOperatorAtNode)
return false;
while (blockOrFilterNode != null &&
blockOrFilterNode.getNodeID() != ASTNodeID.BlockID &&
blockOrFilterNode.getNodeID() != ASTNodeID.E4XFilterID)
{
blockOrFilterNode = blockOrFilterNode.getParent();
}
if (blockOrFilterNode != null && blockOrFilterNode.getNodeID() == ASTNodeID.E4XFilterID)
return true;
else
return false;
}
/**
* Decide if an assignment node is in a context that merits a warning.
* @return true if an assignment has a path to a parent's conditional
* expression that contains only Boolean operators.
*/
public static boolean isUnprotectedAssignmentInConditional(final IASNode node)
{
IASNode current = node;
IASNode parent = current.getParent();
while ( parent != null )
{
switch ( parent.getNodeID() )
{
case Op_LogicalAndID:
case Op_LogicalOrID:
case Op_LogicalNotID:
case ContainerID:
case ConditionalID:
current = parent;
parent = current.getParent();
break;
case WhileLoopID:
return current == getNthChild(parent,0);
case DoWhileLoopID:
return current == getNthChild(parent,1);
case IfStatementID:
return current == getNthChild(parent,0);
default:
return false;
}
}
return false;
}
/**
* Downcast an IDefinition and get an AET name from it.
* @param def - the definition.
* @param project - the active project.
* @return the definition's AET name.
*/
public static Name getAETName(IDefinition def, ICompilerProject project)
{
return ((DefinitionBase)def).getMName(project);
}
/**
* Downcast an IDefinition and get an AET name from it.
* @param def - the definition.
* @return the definition's AET name.
* @see #getAETName(IDefinition, ICompilerProject) to which this delegates.
*/
public Name getAETName(IDefinition def)
{
return ((DefinitionBase)def).getMName(this.project);
}
/**
* Determines whether the specified node has an ancestor
* which is a definition node with <code>[Deprecated]</code> metadata.
* <p>
* The node itself is not checked for such metadata.
*
* @param node The node whose ancestors should be checked.
* @return <code>true</code> if an ancestor is deprecated.
*/
public static boolean hasDeprecatedAncestor(IASNode node)
{
for (IASNode ancestor = node.getParent();
ancestor != null;
ancestor = ancestor.getParent())
{
if (ancestor instanceof IDefinitionNode)
{
IDefinition definition = ((IDefinitionNode)ancestor).getDefinition();
if (definition.isDeprecated())
return true;
}
}
return false;
}
/**
* Uses the <code>[Deprecated]</code> metadata of the specified definition
* to create one of five different deprecation problems, depending on
* the presence of <code>message</code>, <code>since</code>, and
* <code>replacement</code> attributes in the metadata.
*
* @param definition A deprecated definition.
* @param site The node referring to the deprecated definition,
* which determines the problem location.
* @return An {@link ICompilerProblem}.
*/
public static ICompilerProblem createDeprecationProblem(IDefinition definition, IASNode site)
{
ICompilerProblem problem = null;
IDeprecationInfo deprecationInfo = definition.getDeprecationInfo();
String message = deprecationInfo.getMessage();
if (message != null)
{
problem = new DeprecatedAPIWithMessageProblem(site, message);
}
else
{
String name = definition.getBaseName();
String since = deprecationInfo.getSince();
String replacement = deprecationInfo.getReplacement();
if (since == null && replacement == null)
problem = new DeprecatedAPIProblem(site, name);
else if (since != null && replacement == null)
problem = new DeprecatedAPIWithSinceProblem(site, name, since);
else if (since == null && replacement != null)
problem = new DeprecatedAPIWithReplacementProblem(site, name, replacement);
else if (since != null && replacement != null)
problem = new DeprecatedAPIWithSinceAndReplacementProblem(site, name, since, replacement);
}
return problem;
}
/**
* Locate a FunctionDefinition that encloses the given i-node.
* @param iNode - the i-node of interest.
* @return the nearest enclosing function definition, or null.
*/
public static FunctionDefinition getFunctionDefinition(IASNode iNode)
{
FunctionNode func_node;
if ( iNode instanceof FunctionNode )
func_node = (FunctionNode) iNode;
else
func_node = (FunctionNode) iNode.getAncestorOfType(FunctionNode.class);
if ( func_node != null )
return func_node.getDefinition();
else
return null;
}
/**
* Does the subject function require a non-void return value?
* @param iNode - an AST node in (or at the border of) the function.
* @return true if the function's definition is known, the definition's
* return type is known (and the function is not a constructor),
* and the return type is not void or *
*/
public static boolean functionMustReturnValue(IASNode iNode, ICompilerProject project)
{
FunctionDefinition func_def = SemanticUtils.getFunctionDefinition(iNode);
if ( func_def != null )
{
IDefinition return_type = func_def.resolveReturnType(project);
// The function must return a value unless its return type is void or *,
// or if it's a constructor (its definition lies).
return !(
return_type == null ||
return_type.equals(ClassDefinition.getVoidClassDefinition()) ||
return_type.equals(ClassDefinition.getAnyTypeClassDefinition()) ||
func_def.isConstructor()
);
}
else
{
// Nothing known about this function.
return false;
}
}
/**
* Calculate the type of the RHS of an assignment. Works with either BinaryOperatorAssignmentNodes
* or VariableNodes
* @param project the active project
* @param iNode the node representing the assignment
* @return the type of the RHS, or null if one couldn't be determined.
*/
public static ITypeDefinition resolveRHSTypeOfAssignment (ICompilerProject project, IASNode iNode)
{
ITypeDefinition srcType = null;
if( iNode instanceof BinaryOperatorAssignmentNode)
{
if ( iNode.getChildCount() > 1 && iNode.getChild(1) instanceof IExpressionNode)
{
IExpressionNode rhs = (IExpressionNode)iNode.getChild(1);
srcType = rhs.resolveType(project);
}
}
else if(iNode instanceof IVariableNode)
{
IExpressionNode rhs = ((IVariableNode)iNode).getAssignedValueNode();
srcType = rhs != null ? rhs.resolveType(project) : null;
}
return srcType;
}
/**
* Helper method to get the file path from an IASNode
* @param iNode the node you want the file path for
* @return A String representing the file path for the file the node came from
*/
public static String getFileName(IASNode iNode)
{
String file_name = iNode.getSourcePath();
if ( file_name == null )
{
// Fall back on the file specification.
// This may also be null (or assert if it's feeling moody),
// in which case the file name remains null and file/line
// processing noops.
try
{
IFileSpecification fs = iNode.getFileSpecification();
if ( fs != null)
file_name = fs.getPath();
}
catch (Exception e)
{
// No file specification available, probably
// because this node is some kind of syntho.
}
}
return file_name;
}
}