/* * * 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.flex.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.flex.abc.ABCConstants; import org.apache.flex.abc.semantics.Name; import org.apache.flex.abc.semantics.Namespace; import org.apache.flex.abc.semantics.Nsset; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.constants.IASKeywordConstants; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.constants.IASLanguageConstants.BuiltinType; import org.apache.flex.compiler.definitions.IAccessorDefinition; import org.apache.flex.compiler.definitions.IClassDefinition; import org.apache.flex.compiler.definitions.IConstantDefinition; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.IFunctionDefinition; import org.apache.flex.compiler.definitions.IFunctionDefinition.FunctionClassification; import org.apache.flex.compiler.definitions.IInterfaceDefinition; import org.apache.flex.compiler.definitions.INamespaceDefinition; import org.apache.flex.compiler.definitions.IParameterDefinition; import org.apache.flex.compiler.definitions.IScopedDefinition; import org.apache.flex.compiler.definitions.ITypeDefinition; import org.apache.flex.compiler.definitions.metadata.IDeprecationInfo; import org.apache.flex.compiler.definitions.references.INamespaceReference; import org.apache.flex.compiler.definitions.references.IReference; import org.apache.flex.compiler.definitions.references.IResolvedQualifiersReference; import org.apache.flex.compiler.definitions.references.ReferenceFactory; import org.apache.flex.compiler.exceptions.MissingBuiltinException; import org.apache.flex.compiler.filespecs.IFileSpecification; import org.apache.flex.compiler.internal.as.codegen.Binding; import org.apache.flex.compiler.internal.as.codegen.LexicalScope; import org.apache.flex.compiler.internal.definitions.AmbiguousDefinition; import org.apache.flex.compiler.internal.definitions.AppliedVectorDefinition; import org.apache.flex.compiler.internal.definitions.ClassDefinition; import org.apache.flex.compiler.internal.definitions.ConstantDefinition; import org.apache.flex.compiler.internal.definitions.DefinitionBase; import org.apache.flex.compiler.internal.definitions.FunctionDefinition; import org.apache.flex.compiler.internal.definitions.GetterDefinition; import org.apache.flex.compiler.internal.definitions.InterfaceDefinition; import org.apache.flex.compiler.internal.definitions.NamespaceDefinition; import org.apache.flex.compiler.internal.definitions.PackageDefinition; import org.apache.flex.compiler.internal.definitions.ParameterDefinition; import org.apache.flex.compiler.internal.definitions.SetterDefinition; import org.apache.flex.compiler.internal.definitions.TypeDefinitionBase; import org.apache.flex.compiler.internal.definitions.VariableDefinition; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.internal.scopes.ASProjectScope; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.internal.scopes.ASScopeBase; import org.apache.flex.compiler.internal.scopes.FunctionScope; import org.apache.flex.compiler.internal.scopes.ScopeView; import org.apache.flex.compiler.internal.scopes.TypeScope; import org.apache.flex.compiler.internal.tree.as.BaseDefinitionNode; import org.apache.flex.compiler.internal.tree.as.BaseTypedDefinitionNode; import org.apache.flex.compiler.internal.tree.as.BinaryOperatorAssignmentNode; import org.apache.flex.compiler.internal.tree.as.BlockNode; import org.apache.flex.compiler.internal.tree.as.ExpressionNodeBase; import org.apache.flex.compiler.internal.tree.as.FunctionCallNode; import org.apache.flex.compiler.internal.tree.as.FunctionNode; import org.apache.flex.compiler.internal.tree.as.IdentifierNode; import org.apache.flex.compiler.internal.tree.as.ImportNode; import org.apache.flex.compiler.internal.tree.as.IterationFlowNode; import org.apache.flex.compiler.internal.tree.as.LiteralNode; import org.apache.flex.compiler.internal.tree.as.MemberAccessExpressionNode; import org.apache.flex.compiler.internal.tree.as.NamespaceNode; import org.apache.flex.compiler.internal.tree.as.NodeBase; import org.apache.flex.compiler.internal.tree.as.TypedExpressionNode; import org.apache.flex.compiler.internal.tree.as.UnaryOperatorAtNode; import org.apache.flex.compiler.internal.tree.as.UnaryOperatorNodeBase; import org.apache.flex.compiler.internal.tree.as.VariableExpressionNode; import org.apache.flex.compiler.internal.tree.as.VariableNode; import org.apache.flex.compiler.internal.tree.as.WithNode; import org.apache.flex.compiler.problems.AmbiguousReferenceProblem; import org.apache.flex.compiler.problems.BaseClassIsFinalProblem; import org.apache.flex.compiler.problems.CannotExtendInterfaceProblem; import org.apache.flex.compiler.problems.CircularTypeReferenceProblem; import org.apache.flex.compiler.problems.DeprecatedAPIProblem; import org.apache.flex.compiler.problems.DeprecatedAPIWithMessageProblem; import org.apache.flex.compiler.problems.DeprecatedAPIWithReplacementProblem; import org.apache.flex.compiler.problems.DeprecatedAPIWithSinceAndReplacementProblem; import org.apache.flex.compiler.problems.DeprecatedAPIWithSinceProblem; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.ReturnValueHasNoTypeDeclarationProblem; import org.apache.flex.compiler.problems.ScopedToDefaultNamespaceProblem; import org.apache.flex.compiler.problems.UnknownSuperclassProblem; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.scopes.IASScope; import org.apache.flex.compiler.tree.ASTNodeID; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IClassNode; import org.apache.flex.compiler.tree.as.ICommonClassNode; import org.apache.flex.compiler.tree.as.IContainerNode; import org.apache.flex.compiler.tree.as.IDefinitionNode; import org.apache.flex.compiler.tree.as.IExpressionNode; import org.apache.flex.compiler.tree.as.IFileNode; import org.apache.flex.compiler.tree.as.IFunctionNode; import org.apache.flex.compiler.tree.as.IIdentifierNode; import org.apache.flex.compiler.tree.as.IImportNode; import org.apache.flex.compiler.tree.as.ILanguageIdentifierNode; import org.apache.flex.compiler.tree.as.ILanguageIdentifierNode.LanguageIdentifierKind; import org.apache.flex.compiler.tree.as.ILiteralNode; import org.apache.flex.compiler.tree.as.INamespaceDecorationNode; import org.apache.flex.compiler.tree.as.INumericLiteralNode; import org.apache.flex.compiler.tree.as.IParameterNode; import org.apache.flex.compiler.tree.as.ITryNode; import org.apache.flex.compiler.tree.as.IVariableNode; import org.apache.flex.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.flex.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.flex.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.flex.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); return base != null && base.isDynamicExpression(project); } /** * 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; } }