/*
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package org.apache.royale.compiler.internal.semantics;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

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

/**
 *  SemanticUtils is a set of utility routines to provide information
 *  about AS3 constructs' semantics.
 *  
 *  Many of these routines come in pairs; a static form that accepts 
 *  an ICompilerProject parameter for name resolution context, and
 *  an instance form that uses the project provided at construction.
 */
public class SemanticUtils
{
    /**
     * Enum that describes different kinds of multiple definitions
     */
    public enum MultiDefinitionType {
        /** The definition is not multiply defined at all */
        NONE,
        
        /** The definition is ambiguous in a way that violates the language spec */
        AMBIGUOUS,
        
        /** The definition is multiply defined, but in a way that is legal and unambiguous  ECMAScript */ 
        MULTIPLE,
        
        /** The definition is also a parameter definition, which is legal
         * The only reason SHADOWS_PARAM and MULTIPLE are different is that the old compiler did not detect shadows
         */
        SHADOWS_PARAM
        }
    /**
     *  ICompilerProject for the current project,
     *  provided by the constructor.
     */
    private final ICompilerProject project;

    /**
     *  Construct a new SemanticUtils object.
     *  @param project - an ICompilerProject for the current project.
     */
    public SemanticUtils(ICompilerProject project)
    {
        this.project = project;
    }

    /**
     * Check if the node is for {@code super(this)} but not
     * {@code super(this).call()}. The incoming tree shape is:
     * 
     * <pre>
     * MemberAccessExpressionID(FunctionCallID(SuperID(void), ContainerID(IdentifierID(void))), qname)
     * </pre>
     * 
     * @return "0" if the node represents {@code super(this)};
     * {@link Integer#MAX_VALUE} otherwise.
     */
    public static int isSuperThisForFieldAccess(IASNode n)
    {
        // super(this).callSomething() is handled by another pattern that emits
        // "callsuper" instruction.
        if (n.getParent() instanceof FunctionCallNode)
            return Integer.MAX_VALUE;

        // The following unguarded casts are safe, because this is a cost 
        // function and the pattern matcher has checked all the node IDs.
        final MemberAccessExpressionNode memberAccessNode = (MemberAccessExpressionNode)n;
        final FunctionCallNode callNode = (FunctionCallNode)memberAccessNode.getLeftOperandNode();
        final IdentifierNode idNode = (IdentifierNode)callNode.getArgumentsNode().getChild(0);

        if (idNode.getName().equals(IASKeywordConstants.THIS))
            return 1;
        else
            return Integer.MAX_VALUE;
    }

    /**
     * Check that a implicit conversion from actual to expected type is valid.
     * 
     * @param expected the expected type.
     * @param actual the actual type.
     * @param project an ICompilerProject for the current project.
     * @return true if there is a valid implicit type conversion from actual to
     * expected, false otherwise.
     */
    private static boolean isValidTypeConversion(IDefinition expected, IDefinition actual, ICompilerProject project)
    {
        if  (
                actual == null ||
                expected == null ||
                actual.equals(getBuiltinType(BuiltinType.ANY_TYPE, project)) ||
                expected.equals(getBuiltinType(BuiltinType.ANY_TYPE, project)) ||
                isInstanceOf(actual, expected, project) ||
                isBuiltin(actual, BuiltinType.NULL, project) ||
                isBuiltin(expected, BuiltinType.BOOLEAN, project) ||
                isBuiltin(expected, BuiltinType.VOID, project)
                )
            {
                return true;
            }
            // else if ( isVectorSplat(expected) && isVector(actual) ) // This is allowed by the AVM.
            else if (
                isBuiltin(expected, BuiltinType.STRING, project) &&
                (isBuiltin(actual, BuiltinType.XML, project) || isBuiltin(actual, BuiltinType.XMLLIST, project))
            )
            {
                // allow this, e4x values are string values transparently (i.e. without having to call toString()/toXMLString()).
                return true;
            }

            //  Types are unrelated, unless they're both numeric.
            return isNumericType(expected, project) && isNumericType(actual, project);
    }

    /**
     *  Check that implicit conversion done as part of an op= style assignment is valid.
     *  @param lhsType - the type of the lvalue (and one operand of the implicit binary operator).
     *  @param rhsType - the type of the result of the implicit binary operation.
     *  @param opcode - the operator of the implicit binary operator.
     *  @param project an ICompilerProject for the current project.
     *  @return true if there is a valid implicit binary operation that can assign
     *    its result to lhsType.
     */
    public static boolean isValidImplicitOpAssignment(IDefinition lhsType, IDefinition rhsType, final int opcode, ICompilerProject project, final boolean compareNormalized)
    {
        boolean result = isValidTypeConversion(lhsType, rhsType, project, compareNormalized);

        if ( ! result )
        {
            switch (opcode)
            {
                case ABCConstants.OP_add:
                    //  Special cases for +=:
                    //  anything can be added to String
                    //  XML can be added to XMLList
                    result = 
                        isBuiltin(lhsType, BuiltinType.STRING, project ) ||
                        (isBuiltin(lhsType, BuiltinType.XMLLIST, project) && isBuiltin(rhsType, BuiltinType.XML, project));
                    break;
            }
        }

        return result;
    }
    
    /**
     * If the specified {@link IDefinition} is a public or internal definition,
     * then construct a qualified name to lookup a definition with the same name
     * in the {@link ICompilerProject}. The {@link IDefinition} that can be
     * found within the {@link ICompilerProject} is the "normalized"
     * {@link IDefinition}.
     * <p>
     * This method asserts that is only called with definitions that are not
     * local variables, parameters, or class members.
     * <p>
     * This method is necessary because
     * {@link org.apache.royale.compiler.units.IInvisibleCompilationUnit}s create the
     * possibility that we will be processing a file whose definitions are not
     * in the {@link ASProjectScope} in the {@link ICompilerProject}. This in
     * turn creates the possibility there are two or more {@link IDefinition}s
     * floating around for the same class, variable, or function. One the
     * {@link IDefinition}, the "normalized" one, should be registered with the
     * {@link ASProjectScope} in the {@link ICompilerProject}. The other
     * {@link IDefinition}s are from
     * {@link org.apache.royale.compiler.units.IInvisibleCompilationUnit}s. When doing
     * semantic analysis if we don't "normalize" {@link IDefinition}s in some
     * cases, we'll get spurious errors because the semantic analysis code
     * generally compares {@link IDefinition}s by identity ( which is faster )
     * rather than by name. This method should only be called when doing
     * semantic analysis of an
     * {@link org.apache.royale.compiler.units.IInvisibleCompilationUnit}.
     * 
     * @param def The {@link IDefinition} whose corresponding normalized
     * definition should be returned.
     * @param project The {@link ICompilerProject} in which to find normalized
     * {@link IDefinition}s.
     * @return The normalized {@link IDefinition} ( which may be the same as the
     * specified {@link IDefinition} ) or null.
     */
    private static IDefinition normalizeDefinition(IDefinition def, ICompilerProject project)
    {
        assert !(def.getContainingScope() instanceof FunctionScope);
        assert !(def.getContainingScope() instanceof ScopeView);
        assert !(def.getContainingScope() instanceof TypeScope);
        assert !(def instanceof ParameterDefinition);

        INamespaceReference qualifier = def.getNamespaceReference();
        if (!qualifier.isPublicOrInternalNamespace())
            return def;
        INamespaceDefinition qualifierDef = (INamespaceDefinition)qualifier;
        IResolvedQualifiersReference refToDefinition =
                ReferenceFactory.resolvedQualifierQualifiedReference(project.getWorkspace(), qualifierDef, def.getBaseName());
        return refToDefinition.resolve(project);
    }
    
    /**
     * Check that a implicit conversion from actual to expected type is valid.
     * 
     * @param expected the expected type.
     * @param actual the actual type.
     * @param project an ICompilerProject for the current project.
     * @param normalizeDefinitions If true, we'll try normalizing both
     * {@link IDefinition}s before returning false.  See the java doc for
     * {@code normalizeDefinition(IDefinition, ICompilerProject)} for more info.
     * @return true if there is a valid implicit type conversion from actual to
     * expected, false otherwise.
     */
    public static boolean isValidTypeConversion(IDefinition expected, IDefinition actual, ICompilerProject project, boolean normalizeDefinitions)
    {
        if (isValidTypeConversion(expected, actual, project))
            return true;
        if (!normalizeDefinitions)
            return false;
        final IDefinition normalizedExpected = normalizeDefinition(expected, project);
        if (normalizedExpected == null)
            return false;
        final IDefinition normalizedActual = normalizeDefinition(actual, project);
        if (normalizedActual == null)
            return false;
        if ((normalizedExpected == expected) && (normalizedActual == actual))
            return false;
        return isValidTypeConversion(normalizedExpected, normalizedActual, project);
    }

    /**
     * Helper method to get the node to report a problem with a Function.  This will get the name node,
     * if it exists, otherwise it will return the function node.
     */
    public static IASNode getFunctionProblemNode(IFunctionNode iNode)
    {
        IASNode result = iNode;
        IExpressionNode nameExpression = iNode.getNameExpressionNode();
        if( nameExpression != null )
            result = nameExpression;
        return result;
    }

    /**
     * Helper method to determine if an IdentifierNode refers to the Class it is contained
     * in from a context where we are generating code that may be run during the cinit method.
     *
     * This is important because cinit code must refer to the class via getlocal0 instead of a
     * named lookup like findprop, getprop.  This is because the Class property is initialized with the
     * result of the cinit, but since we are potentially in the cinit, the property will not have been initialized.
     *
     * class C {
     *     static var a = C;  // C won't have been initialized yet, so we need to use getlocal0
     * }
     *
     * The above class generates code like:
     *
     * findprop C
     * newclass C // this runs the cinit, and leaves the result on the stack, at this point 'C' is undefined
     * setprop  C // this will init the property
     *
     * @param id  the identifier node that we are resolving
     * @param def the definition the identifier node resolved to
     * @return    true if this is a reference to the Class that is being initialized, and needs special treatment.
     */
    public static boolean isRefToClassBeingInited (IdentifierNode id, IDefinition def)
    {
        boolean refToClassBeingInited = false;
        if( def instanceof IClassDefinition)
        {
            ASScope scope = id.getASScope();
            IDefinition containingDef = null;

            while( scope != null )
            {
                if( containingDef == null )
                    containingDef = scope.getDefinition();

                if( scope instanceof ScopeView)
                {
                    if (((ScopeView)scope).isInstanceScope() )
                    {
                        // stop looking, we're in an instance method, so we're ok
                        break;
                    }
                    else if( ((ScopeView)scope).isStaticScope() && def == scope.getDefinition() )
                    {
                        // we're in static code, and are referencing the class we are in
                        refToClassBeingInited = true;
                        break;
                    }
                }

                scope = scope.getContainingScope();
            }
        }
        return refToClassBeingInited;
    }

    /**
     *  Determine if an IDefinition is a specific builtin type.
     *  @param def - the IDefinition of interest.
     *  @param builtin - the builtin type.
     *  @param project - an ICompilerProject for the current project.
     *  @return true if def is the desired builtin type.
     */
    public static boolean isBuiltin(IDefinition def, BuiltinType builtin, ICompilerProject project)
    {
        return def != null && def.equals(project.getBuiltinType(builtin));
    }

    /**
     * Find potential conflict for a FunctionDefinition. In some contexts it is ok to redeclare a Function (global, or
     * inside another function body).  For those cases the function will not be reported as ambiguous.  However, in strict
     * mode we do want to issue a diagnostic that the function has been redeclared - this method is to find the redlarations
     * even though those redecls don't make references ambiguous.
     * @param project   The project to resolve things in
     * @param funcDef   The function definition to find potential conlicts for
     * @return          A List of Definitions from the same scope that have the same name as the function definition, including
     *                  the function definition (so if there are no conflicts, the list should have 1 item).
     */
    public static List<IDefinition> findPotentialFunctionConflicts (ICompilerProject project, IFunctionDefinition funcDef)
    {
        ASScope scope = (ASScope)funcDef.getContainingScope();
        INamespaceDefinition qualifier = funcDef.getNamespaceReference().resolveNamespaceReference(project);
        Set<INamespaceDefinition> namespaceSet = Collections.singleton(qualifier);
        return scope.getPropertiesByNameForMemberAccess((CompilerProject) project, funcDef.getBaseName(), namespaceSet);
    }

    /**
     * Is the binding a potential reference to
     * the 'arguments' object
     * @param b         the Binding to check
     * @return          true if the reference could be a reference to 'arguments'
     */
    public static boolean isArgumentsReference (Binding b)
    {
        Name name = b.getName();
        if (name != null && IASLanguageConstants.arguments.equals(name.getBaseName()))
        {
            // the name is "arguments", but make sure it's not a user defined definition
            // by checking whether the def is implicit
            IDefinition definition = b.getDefinition();
            if (definition != null)
            {
                return definition.isImplicit();
            }
            else
            {
                // if there's no definition, as can happen when referencing "arguments" from
                // within a 'with', then fall back to checking on the node.
                IASNode node = b.getNode();
                return node instanceof IdentifierNode && !((IdentifierNode) node).isMemberRef();
            }
        }

        return false;
    }

    /**
     *  Determine if an IDefinition is a specific builtin type.
     *  @param def - the IDefinition of interest.
     *  @param builtin - the builtin type.
     *  @return true if def is the desired builtin type.
     */
    public boolean isBuiltin(IDefinition def, BuiltinType builtin)
    {
        return isBuiltin(def, builtin, this.project);
    }

    /**
     *  @return true if the given definition is a numeric type.
     *  @param def - the IDefinition of interest.
     *  @param project - an ICompilerProject for the current project.
     */
    public static boolean isNumericType(IDefinition def, ICompilerProject project)
    {
        return isBuiltin(def, BuiltinType.INT, project) ||
               isBuiltin(def, BuiltinType.UINT, project) ||
               isBuiltin(def, BuiltinType.NUMBER, project);
    }

    /**
     *  @return true if the given definition is a numeric or boolean type.
     *  @param def - the IDefinition of interest.
     *  @param project - an ICompilerProject for the current project.
     */
    public static boolean isNumericTypeOrBoolean(IDefinition def, ICompilerProject project)
    {
        return isNumericType(def, project) ||
            isBuiltin(def, BuiltinType.BOOLEAN, project);
    }
    
    /**
     *  @return true if the given definition is a numeric type.
     *  @param def - the IDefinition of interest.
     *  @see #isNumericType(IDefinition, ICompilerProject)
     */
    public boolean isNumericType(IDefinition def)
    {
        return isNumericType(def, this.project);
    }

    /**
     *  Determine if one IDefinition is a subclass of another.
     *  @param maybe_derived The first definition.
     *  @param maybe_base The second definition.
     *  @param project - an ICompilerProject for the current project.
     *  @return true if  maybe_derived is an instance of maybe_base.
     */
    public static boolean isInstanceOf(IDefinition maybe_derived, IDefinition maybe_base, ICompilerProject project)
    {
        if (!(maybe_base instanceof ITypeDefinition))
            return false;
        if (!(maybe_derived instanceof ITypeDefinition))
            return false;
        
        ITypeDefinition base    = (ITypeDefinition) maybe_base;
        ITypeDefinition derived = (ITypeDefinition) maybe_derived;

        if (derived.isInstanceOf(base, project))
            return true;
        
        
        //  Consider null to be an "instanceof" any Object subtype.
        boolean base_is_object = isInstanceOf(base, getBuiltinType(BuiltinType.OBJECT, project), project);
        if (base_is_object && derived.equals(getBuiltinType(BuiltinType.NULL, project)))
            return true;
        
        
        if (AppliedVectorDefinition.vectorInstanceOfCheck(base, derived))
            return true;
        
        return false;
        
    }

    /**
     *  Determine if one IDefinition is a subclass of another.
     *  @param maybe_derived The first definition.
     *  @param maybe_base The second definition.
     *  @return true if  maybe_derived is an instance of maybe_base.
     *  @see #isInstanceOf(IDefinition, IDefinition, ICompilerProject)
     */
    public boolean isInstanceOf(IDefinition maybe_derived, IDefinition maybe_base)
    {
        return isInstanceOf(maybe_derived, maybe_base, this.project);
    }

    /**
     *  Fetch a builtin type.
     *  @param builtin - the BuiltinType identifier of the type.
     *  @param project - an ICompilerProject for the current project.
     *  @return the specified builtin type.
     */
    public static IDefinition getBuiltinType(BuiltinType builtin, ICompilerProject project)
    {
        return project.getBuiltinType(builtin);
    }

    /**
     *  Fetch a builtin type.
     *  @param builtin - the BuiltinType identifier of the type.
     *  @return the specified builtin type.
     */
    public IDefinition getBuiltinType(BuiltinType builtin)
    {
        return getBuiltinType(builtin, this.project);
    }

    /**
     *  Get the current number type.
     *  @param project - an ICompilerProject for the current project.
     *  @return the current number type; currently hardcoded
     *    to BuiltinTypes.NUMBER.
     */
    public static IDefinition numberType(ICompilerProject project)
    {
        return getBuiltinType(BuiltinType.NUMBER, project);
    }

    /**
     *  Get the current number type.
     *  @return the current number type; currently hardcoded
     *    to BuiltinTypes.NUMBER.
     *  @see #numberType(ICompilerProject)
     */
    public IDefinition numberType()
    {
        return numberType(this.project);
    }

    /**
     *  Given the definition of a getter or setter, resolve its 
     *  corresponding accessor (e.g., the setter for a getter).
     *  @param def - the definition of interest.
     *  @param project - an ICompilerProject for the current project.
     *  @return the corresponding accessor, or null if 
     *    there is no corresponding accessor.
     *  @throws IllegalArgumentException if the definition is
     *    not for a getter or setter.
     */
    public static IDefinition resolveCorrespondingAccessor(IDefinition def, ICompilerProject project)
    {
        if ( def instanceof GetterDefinition )
            return ((GetterDefinition)def).resolveCorrespondingAccessor(project);
        else if ( def instanceof SetterDefinition )
            return ((SetterDefinition)def).resolveCorrespondingAccessor(project);
        else
            throw new IllegalArgumentException("Definition " + def + " was not a getter or setter.");
    }

    /**
     *  Given the definition of a getter or setter, resolve its 
     *  corresponding accessor (e.g., the setter for a getter).
     *  @param def - the definition of interest.
     *  @return the corresponding accessor, or null if 
     *    there is no corresponding accessor.
     *  @throws IllegalArgumentException if the definition is
     *    not for a getter or setter.
     *  @see #resolveCorrespondingAccessor(IDefinition, ICompilerProject)
     */
    public IDefinition resolveCorrespondingAccessor(IDefinition def)
    {
        return resolveCorrespondingAccessor(def, this.project);
    }

    /**
     *  Is the given node in a with scope?
     *  @param iNode - the node of interest.
     *  @return the node's own answer if it's an
     *    ExpressionNodeBase; otherwise return true 
     *    if the node has a WithNode ancestor.
     */
    public static boolean isInWith(IASNode iNode)
    {
        if ( iNode instanceof ExpressionNodeBase )
        {
            return ((ExpressionNodeBase)iNode).inWith();
        }
        else
        {
            return iNode.getAncestorOfType(WithNode.class) != null;
        }
    }

    /**
     *  Is the given node in a filter expression?
     *  @param iNode - the node of interest.
     *  @return the node's own answer if it's an
     *    ExpressionNodeBase; otherwise return false.
     */
    public static boolean isInFilter(IASNode iNode)
    {
        if ( iNode instanceof ExpressionNodeBase )
        {
            return ((ExpressionNodeBase)iNode).inFilter();
        }
        else
        {
            return false;
        }
    }

    /**
     * Check if a nested function can have it's init instructions hoisted to the top of the
     * function it is contained in.  This is not possible when the function is inside some control-flow,
     * as the OP_newfunction must be executed only when control flows there (and with whatever scope stack is in effect
     * if we're in a with/catch scope).
     * @param iNode  The node to check
     * @return       true if it is ok to hoist the init instructions for the node to the top of the function
     */
    public static boolean canNestedFunctionBeHoisted(IASNode iNode)
    {
        IASNode n = iNode.getParent();
        // Walk up the parent chain looking for anything other than block nodes,
        // until we get to the containing function - we don't have to look past the containing
        // function because any control flow around it should have been handled when setting up the outer function
        while( n != null && !(n instanceof FunctionNode) )
        {
            if( !(n instanceof BlockNode) )
                return false;

            n = n.getParent();
        }
        return true;
    }

    /**
     *  Is this definition a member of a class?
     *  @param def - the definition of interest.
     *  @return true if the definition's parent is a class definition.
     */
    public static boolean isMemberDefinition(IDefinition def)
    {
        return def != null && def.getParent() instanceof ClassDefinition;
    }

    /**
     *  Is this definition a member of a package?
     *  @param def - the definition of interest.
     *  @return true if the definition's parent is a package definition.
     */
    public static boolean isPackageDefinition(IDefinition def)
    {
        return def != null && def.getParent() instanceof PackageDefinition;
    }

    /**
     *  Is the definition going to be a method binding in the ABC?
     *  method bindings can be called with findprop/callprop instead of having to
     *  use op_call.
     *
     *  A method biniding is a member function, or a global function defined in a package
     *
     *  @param def - the definition of interest.
     *  @return true if the definition is a method binding.
     */
    public static boolean isMethodBinding(IDefinition def)
    {
        return def instanceof IFunctionDefinition &&
                (isMemberDefinition(def) || isPackageDefinition(def));
    }

    /**
     *  Is this definition read-only?
     *  @param def - the definition of interest.
     */
    public boolean isReadOnlyDefinition(IDefinition def)
    {
        boolean result = false;

        if ( def instanceof GetterDefinition )
        {
        	IDefinition otherDef = resolveCorrespondingAccessor(def);
            if (otherDef == null) return true;
            if (otherDef.getNamespaceReference().getBaseName() != def.getNamespaceReference().getBaseName())
            	return true;
        }
        else if ( def instanceof ConstantDefinition )
        {
            result = true;
        }

        return result;
    }

    /**
     *  Is this Binding's definition statically analyzable?
     *  @param binding - the Binding of interest.
     *  @param project - an ICompilerProject for the current project.
     *  @return true if the Binding has no definition and
     *    it is in a context that implies its definition
     *    is statically resolvable.
     */
    public static boolean definitionCanBeAnalyzed(Binding binding, ICompilerProject project)
    {
        assert(binding.getNode()) != null;

        IASNode node = binding.getNode();

        if ( node instanceof ExpressionNodeBase )
        {
            //  Ensure we're not in a with scope or part of a filter expression.
            final ExpressionNodeBase expressionNode = (ExpressionNodeBase)node;
            if (expressionNode.inWith() || expressionNode.inFilter())
                return false;
        }

        //  Attribute names aren't statically knowable.
        if ( binding.getName() != null && binding.getName().isAttributeName() )
            return false;

        return !hasDynamicBase(binding, project);
    }

    /**
     *  Is this Binding's definition statically analyzable?
     *  @param binding - the Binding of interest.
     *  @return true if the Binding has no definition and
     *    it is in a context that implies its definition
     *    is statically resolvable.
     *  @see #definitionCanBeAnalyzed(Binding, ICompilerProject)
     */
    public boolean definitionCanBeAnalyzed(Binding binding)
    {
        return definitionCanBeAnalyzed(binding, this.project);
    }

    /**
     *  Is this definition write-only?
     *  @param def - the definition of interest.
     *  @return true if the definition is a set 
     *    that does not have a corresponding get.
     */
    public boolean isWriteOnlyDefinition(IDefinition def)
    {
        return def instanceof SetterDefinition && resolveCorrespondingAccessor(def) == null;
    }

    /**
     *  Check an ExpressionNodeBase's base expression (if any)
     *  to determine if it's a dynamic expression, which
     *  means we don't know much about its definition.
     *  @param binding - the binding whose base node is to be checked.
     *  @param project - an ICompilerProject for the current project.
     *  @return true if node's base expression is dynamic.
     */
    public static boolean hasDynamicBase(Binding binding, ICompilerProject project)
    {
        ExpressionNodeBase base = getBaseNode(binding);
        if (base != null && base.isDynamicExpression(project))
        	return true;
        // the JS version of XML is not currently dynamic so special case it here.
        if (base != null && base.getNodeID() == ASTNodeID.IdentifierID && IdentifierNode.isXMLish(base.resolveType(project), project))
        	return true;
        return false;
    }

    /**
     *  Check an ExpressionNodeBase's base expression (if any)
     *  to determine if it's a dynamic expression, which
     *  means we don't know much about its definition.
     *  @param binding - the binding whose base node is to be checked.
     *  @return true if node's base expression is dynamic.
     *  @see #hasDynamicBase(Binding, ICompilerProject)
     */
    public boolean hasDynamicBase(Binding binding)
    {
        return hasDynamicBase(binding, this.project);
    }
    
    /**
     *  Get an ExpressionNodeBase's base node.
     *  @return the node's base expression, or null
     *    if the Binding does not wrap an ExpressionNodeBase.
     */
    public static ExpressionNodeBase getBaseNode(Binding binding)
    {
        if ( binding.getNode() instanceof ExpressionNodeBase )
            return ((ExpressionNodeBase)binding.getNode()).getBaseExpression();
        else
            return null;
    }
    

    /**
     *  Get the definition associated with a IASNode.
     *  @param iNode - the node whose definition is desired.
     *  @return the node's definition, or null if the node
     *    is of an unknown type or has no definition.
     */
    public IDefinition getDefinition(IASNode iNode)
    {
        return getDefinition(iNode, project);
    }

    /**
     *  Get the definition associated with a IASNode.
     *  @param iNode - the node whose definition is desired.
     *  @param project - an ICompilerProject for the current project.
     *  @return the node's definition, or null if the node
     *    is of an unknown type or has no definition.
     *    
     *    TODO: don't require a ASDefinition cache, base on ICompilerProject
     */
    public static IDefinition getDefinition(IASNode iNode, ICompilerProject project)
    {
        if ( iNode instanceof ExpressionNodeBase )
        {
            return ((ExpressionNodeBase)iNode).resolve(project);
        }
        else if ( iNode instanceof BaseTypedDefinitionNode )
        {
            return ((BaseTypedDefinitionNode)iNode).getDefinition();
        }
        else if ( iNode instanceof NamespaceNode )
        {
            return ((NamespaceNode)iNode).getDefinition();
        }

        return null;
    }
    
    public static IDefinition resolveType(IASNode iNode, ICompilerProject project)
    {
        if ( iNode instanceof ExpressionNodeBase )
        {
            return ((ExpressionNodeBase)iNode).resolveType(project);
        }
        else if ( iNode instanceof BaseTypedDefinitionNode )
        {
            return ((BaseTypedDefinitionNode)iNode).getDefinition().resolveType(project);
        }

        return null;
    }

    public IDefinition resolveType(IASNode iNode)
    {
        return resolveType(iNode, this.project);
    }

    /**
     * Resolve the type of the underlying expression in a UnaryOperatorNode.
     *
     * for '++i', this would return the type of 'i' as opposed to the result of the ++
     *
     * @param iNode  The UnaryOperatorNode you want the underlying type for
     *  @param project - an ICompilerProject for the current project.
     * @return       The type of the underlying expression, or null if the node passed in isn't
     *               a UnaryOperatorNode
     */
    public static IDefinition resolveUnaryExprType(IASNode iNode, ICompilerProject project)
    {
        if ( iNode instanceof UnaryOperatorNodeBase )
        {
            return ((UnaryOperatorNodeBase)iNode).getOperandNode().resolveType(project);
        }

        return null;
    }

    /**
     *  Get the type of a node's base expression as a string.
     *  @param node the node whose base's type is desired.
     *  @return the type string, or null if none found.
     */
    public String getTypeOfBase(IASNode node)
    {
        return getTypeOfBase(node, project);
    }
    
    /**
     *  Get the type of a node's base expression as a string.
     *  @param node the node whose base's type is desired.
     *  @param project an ICompilerProject for the current project.
     *  @return the type string, or null if none found.
     */
    public static String getTypeOfBase(IASNode node, ICompilerProject project)
    {
        if (!(node instanceof ExpressionNodeBase))
            return null;
        
        ExpressionNodeBase base = ((ExpressionNodeBase)node).getBaseExpression();
        if (base == null)
            return null;
        
        IDefinition def = base.resolve(project);
        if (def == null)
            return null;
        
        return def.getTypeAsDisplayString();
    }
    
    /**
     *  Get the type of an expression's stem.
     *  @param iNode - the node of interest.
     *  @param project - an ICompilerProject for the current project.
     *  @return the type of the node's underlying reference base as a string.
     *  @pre hasUnderlyingType(iNode, cache) must be true.
     */
    public static String getTypeOfStem(IASNode iNode, ICompilerProject project)
    {
        IDefinition type_def = getDefinitionOfUnderlyingType(iNode, ASSERT_ON_UNKNOWN_INODE, project);
        return ( type_def != null )?  type_def.getBaseName(): "?";
    }
    
    /**
     *  Get the type of an expression's stem.
     *  @param node - the node of interest.
     *  @return the type of the node's underlying reference base as a string.
     *  @see #getTypeOfStem(IASNode, ICompilerProject)
     */

    public String getTypeOfStem(IASNode node)
    {
        return getTypeOfStem(node, this.project);
    }

    /**
     *  Get the definition of the explicit or implicit type underlying
     *  an iNode's reference(s).
     *  @param iNode - the inode of interest.
     *  @param allow_unknown_iNode - tolerate a node type that doesn't
     *    have a procedure for extracting its underlying scope.
     *  @param project - an ICompilerProject for the current project.
     *  @return the definition of the type that underlies the node's references,
     *    or null if no underlying scope is available.
     */
    public static IDefinition getDefinitionOfUnderlyingType(IASNode iNode, final boolean allow_unknown_iNode, ICompilerProject project)
    {
        IDefinition result;

        if ( iNode instanceof MemberAccessExpressionNode )
        {
            MemberAccessExpressionNode member_node = (MemberAccessExpressionNode) iNode;
            result = member_node.getLeftOperandNode().resolveType(project);
        }
        else if ( iNode instanceof FunctionCallNode && ((FunctionCallNode)iNode).getNameNode() instanceof MemberAccessExpressionNode )
        {
            MemberAccessExpressionNode member_node = (MemberAccessExpressionNode) ((FunctionCallNode)iNode).getNameNode();
            result = member_node.getLeftOperandNode().resolveType(project);
        }
        else if ( isInInstanceFunction(iNode, project) )
        {
            result = getEnclosingFunctionDefinition(iNode, project).getAncestorOfType(ClassDefinition.class);
        }
        else if ( isInStaticClassFunction(iNode, project) )
        {
            result = getEnclosingFunctionDefinition(iNode, project).getAncestorOfType(ClassDefinition.class);
        }
        else
        {
            assert(allow_unknown_iNode): "Unknown iNode:" + iNode;
            result = null;
        }

        return result;
    }

    /**
     *  Get the definition of the explicit or implicit type underlying
     *  an iNode's reference(s).
     *  @param iNode - the inode of interest.
     *  @param allow_unknown_iNode - tolerate a node type that doesn't
     *    have a procedure for extracting its underlying scope.
     *  @return the definition of the type that underlies the node's references,
     *    or null if no underlying scope is available.
     *  @see #getDefinitionOfUnderlyingType(IASNode, boolean, ICompilerProject)
     */
    public IDefinition getDefinitionOfUnderlyingType(IASNode iNode, final boolean allow_unknown_iNode)
    {
        return getDefinitionOfUnderlyingType(iNode, allow_unknown_iNode, this.project);
    }

    /** Manifest constant, getDefinitionOfUnderlyingType asserts if this is passed */
    public static final boolean ASSERT_ON_UNKNOWN_INODE = false;
    /** Manifest constant, getDefinitionOfUnderlyingType tolerates unknown tree shapes if this is passed */
    public static final boolean ALLOW_UNKNOWN_INODE = true;

    /**
     *  Does the given inode have an underlying type that could
     *  resolve its references?
     *  @param iNode - the inode of interest.
     *  @param project - an ICompilerProject for the current project.
     */
    public static boolean hasUnderlyingType(IASNode iNode, ICompilerProject project)
    {
        return getDefinitionOfUnderlyingType(iNode, ALLOW_UNKNOWN_INODE, project) != null;
    }

    /**
     *  Does the given inode have an underlying type that could
     *  resolve its references?
     *  @param iNode - the inode of interest.
     *  @see #hasUnderlyingType(IASNode, ICompilerProject)
     */
    public boolean hasUnderlyingType(IASNode iNode)
    {
        return hasUnderlyingType(iNode, this.project);
    }

    /**
     *  Does the given node have an explicit stem reference, e.g., foo.bar?
     *  @param iNode - the node of interest.
     *  @return true if the node has an explicit stem (the foo in foo.bar).
     *  @see #hasUnderlyingType which checks for explicit or implicit contexts for resolution.
     */
    public static boolean hasExplicitStem(IASNode iNode)
    {
        return  ( iNode instanceof MemberAccessExpressionNode ) ||
                ( iNode instanceof FunctionCallNode && ((FunctionCallNode)iNode).getNameNode() instanceof MemberAccessExpressionNode );
    }

    /**
     *  Does this binding have a base node?
     *  @param binding - the Binding of interest.
     *  @return true if the Binding's i-node has a base node.
     */
    public static boolean hasBaseNode(Binding binding)
    {
        return getBaseNode(binding) != null;
    }

    /**
     *  Is this node enclosed in a function definition?
     *  @param iNode - the node of interest.
     *  @return true if the node is in a function definition.
     */
    public static boolean isInFunction(IASNode iNode)
    {
        return iNode.getAncestorOfType(FunctionNode.class) != null;
    }

    /**
     * Is this node declaring a property in a class, but also nested inside
     * another statement.
     * @param iNode the node
     * @param def   the
     */
    public static boolean isNestedClassProperty(IASNode iNode, VariableDefinition def)
    {
        if(def != null && def.getParent() instanceof IClassDefinition)
        {
            return def.declaredInControlFlow();
        }
        return false;
    }

    /**
     *  Is this container an implicit container?
     *  @param iNode - the container of interest.
     *  @return true if the container is implicit.
     */
    public static boolean isImplicitContainer(IASNode iNode)
    {
        //  TODO: The result of CMP-1004 goes here.
        return
            ( iNode instanceof IContainerNode ) &&
            false;
          
    }
    
   
    
    /**
     *  Is the given multiply defined, or ambiguous?
     *  @param def - the definition of interest.
     *  @param project - the active compiler project.
     *  @return An enum telling the nature of any discovered multple definitions
     */
    public static MultiDefinitionType getMultiDefinitionType(IDefinition def, ICompilerProject project)
    {
        // A null definition cannot be ambiguous.
        if (def == null)
           return MultiDefinitionType.NONE;
        
        // Look for other definitions with the same base name
        // and the same namespace as the specified definition.
        IDefinition found = findPropertyQualified(def, project);
        
        if (AmbiguousDefinition.isAmbiguous(found))
            return MultiDefinitionType.AMBIGUOUS;
      
        // if we found something, andit's not us, and it's not null, then there are multi deinitions.
        if (def != found && found != null && !isGetterSetterPair(def, found, project))
        {
            return found instanceof IParameterDefinition ? MultiDefinitionType.SHADOWS_PARAM :
                    MultiDefinitionType.MULTIPLE;
        }
        
        return MultiDefinitionType.NONE ;
    }


    /**
     * Determine if a given binding is ambiguous
     * @param b  the binding to check
     * @return   true if the binding refers to more than 1 definiton
     */
    public static boolean isAmbiguousReference(Binding b)
    {
        return AmbiguousDefinition.isAmbiguous(b.getDefinition());
    }

    /**
     *  Does this node represent the "this" keyword?
     *  @param iNode - the node of interest.
     *  @return true if the node represents "this"
     */
    public static boolean isThisKeyword(IASNode iNode)
    {
        return 
            (iNode instanceof ILanguageIdentifierNode) &&
            (((ILanguageIdentifierNode)iNode).getKind() == LanguageIdentifierKind.THIS);
    }

    /**
     *  Is the given node in a class with a base class definition?
     *  @param iNode - the node of interest.
     *  @return true if the node is in a class with a base class.
     *  @see #hasBaseClassDefinition(IASNode, ICompilerProject)
     */
    public boolean hasBaseClassDefinition(IASNode iNode)
    {
        return hasBaseClassDefinition(iNode, this.project);
    }

    /**
     *  Is the given node in a class with a base class definition?
     *  @param iNode - the node of interest.
     *  @param project - the active compiler project.
     *  @return true if the node is in a class with a base class.
     */
    public static boolean hasBaseClassDefinition(IASNode iNode, ICompilerProject project)
    {
        return getBaseClassDefinition(iNode, project) != null;
    }

    /**
     *  Fetch the definition of the given node's
     *    class' base class.
     *  @param iNode - the node of interest.
     *  @param project - an ICompilerProject for the current project.
     *  @return the definition of the node's class' base class,
     *    or null if it not in a class or its class 
     *    has no base class.
     */
    public static IDefinition getBaseClassDefinition(IASNode iNode, ICompilerProject project)
    {
        IDefinition result = null;
        IDefinition member_def = getDefinition(iNode, project);

        if ( member_def != null )
        {
            ASScope super_scope = getSuperClassScope(project, member_def);
            if( super_scope != null )
            {
                result = getPropertyQualified(
                    super_scope,
                    getNamespaceInClassContext(member_def, project),
                    member_def.getBaseName(),
                    project
                );
            }
        }

        return result;
    }

    /**
     *  Resolve a definition's namespace, and if its parent is a class, normalize
     *  that namespace against the parent class' superclass' protected namespace.
     *  @param def - the definition of interest.
     *  @param project - an ICompilerProject for the current project.
     *  @return the definition's class' superclass' namespace if the definition is
     *    in a class and its namespace is the same as the enclosing class' superclass'
     *    protected namespace; otherwise, return the definition's namespace.
     */
    public static INamespaceDefinition getNamespaceInClassContext(IDefinition def, ICompilerProject project)
    {
        INamespaceDefinition namespace = def.resolveNamespace(project);

        IDefinition parent = def.getParent();

        if ( namespace != null && parent instanceof ClassDefinition )
        {
            // Iterate over the superclasses of this method's class.
            ClassDefinition cls  = (ClassDefinition)parent;
            ClassDefinition base = (ClassDefinition)cls.resolveBaseClass(project);

            if (base != null)
            {
                // Adjust the namespace if this is the protected namespace
                INamespaceDefinition protectedNS = cls.getProtectedNamespaceReference().resolveNamespaceReference(project);
                if (namespace.equals(protectedNS))
                    namespace = base.getProtectedNamespaceReference().resolveNamespaceReference(project);
            }
        }

        return namespace;
    }
    
    /**
     * Get the super class scope for the definition passed in.  This will return an ASScope
     * for the super class of whatever class declared the member passed in.
     * @param project       The project to resolve references in
     * @param member_def    The member to find a super class for
     * @return              The scope of the base class of the class the member_def is declared in.
     *                      This will return null if the member_def was not declared in a class.
     */
    public static ASScope getSuperClassScope(ICompilerProject project, IDefinition member_def)
    {
        IASScope containingScope = member_def.getContainingScope();
        if( containingScope instanceof ScopeView )
        {
            return ((ScopeView) containingScope).resolveSuperScope(project);
        }
        return null;
    }

    /**
     *  Find a member definition, given its name.
     *  @param iNode - the node that forms the reference.
     *    Should be a MemberAccessExpressionNode to get any useful result.
     *  @param member_name - the name of the member of interest.
     *  @param match_static_to_static - when true, only accept definitions 
     */
    public static IDefinition findMemberByName(IASNode iNode, String member_name,  boolean match_static_to_static, ICompilerProject project)
    {
        //  Find a scope to work with.
        IASScope starting_scope = null;

        //  Is this a static reference, e.g., ClassName.staticField?
        boolean is_static_reference = false;

        if ( iNode instanceof MemberAccessExpressionNode )
        {
            TypeDefinitionBase type = (TypeDefinitionBase)((MemberAccessExpressionNode)iNode).getLeftOperandNode().resolveType(project);

            if ( type != null )
            {
                starting_scope = ((TypeDefinitionBase)((MemberAccessExpressionNode)iNode).getLeftOperandNode().resolveType(project)).getContainedScope();
                is_static_reference = ((MemberAccessExpressionNode)iNode).getLeftOperandNode().resolve(project) instanceof ClassDefinition;
            }
        }
        else if ( iNode instanceof FunctionCallNode && ((FunctionCallNode)iNode).getNameNode() instanceof MemberAccessExpressionNode )
        {
            MemberAccessExpressionNode member_node = (MemberAccessExpressionNode) ((FunctionCallNode)iNode).getNameNode();
            
            TypeDefinitionBase type = (TypeDefinitionBase)member_node.getLeftOperandNode().resolveType(project);
            if ( type != null )
            {
                starting_scope = type.getContainedScope();
                is_static_reference = member_node.getLeftOperandNode().resolve(project) instanceof ClassDefinition;
            }
        }
        else if ( isInInstanceFunction(iNode, project) )
        {
            starting_scope = getClassScope(iNode, project);
            is_static_reference = false;
        }
        else if ( isInStaticClassFunction(iNode, project) )
        {
            starting_scope = getClassScope(iNode, project);
            is_static_reference = true;
        }

        //  Did we find a scope to work with?
        if ( starting_scope != null )
            return findMemberByName(starting_scope, member_name, match_static_to_static, is_static_reference, project);
        else
            return null;
    }

    /**
     *  Get the ASScope of the class that contains an inode.
     *  @param iNode - the inode of interest.
     *  @param project - an ICompilerProject for the current project.
     *  @return the ASScope (holding both static and instance definitions)
     *    of the class enclosing the inode, or null if no such scope is found.
     */
    public static ASScope getClassScope(IASNode iNode, ICompilerProject project)
    {
        if ( isInInstanceFunction(iNode, project) || isInStaticClassFunction(iNode, project) )
        {
            ClassDefinition class_def = (ClassDefinition) getEnclosingFunctionDefinition(iNode, project).getAncestorOfType(ClassDefinition.class);
            if ( class_def != null )
                return class_def.getContainedScope();
        }

        return null;
    }

    /**
     *  Get the ASScope of the class that contains an inode.
     *  @param iNode - the inode of interest.
     *  @return the ASScope (holding both static and instance definitions)
     *    of the class enclosing the inode, or null if no such scope is found.
     *  @see #getClassScope(IASNode, ICompilerProject)
     */
    public ASScope getClassScope(IASNode iNode)
    {
        return getClassScope(iNode, this.project);
    }

    /**
     *  Search a scope and its enclosing scopes for a member whose name is known.
     *  @param scope - the innermost scope to search.
     *  @param member_name - the (string) name of interest.
     *  @param match_static_to_static - if set, then only return static definitions if is_static_reference is set.
     *  @param is_static_reference - caller sets this if the reference occurs in a context that is known to be static.
     *  @param project - an ICompilerProject for the current project.
     */
    public static IDefinition findMemberByName(IASScope scope, String member_name,  boolean match_static_to_static, boolean is_static_reference, ICompilerProject project)
    {
        if ( scope == null )
            return null;
        
        IDefinition result = null;

        //  A definition by the right name but in the wrong scope
        //  (e.g., an instance def found while looking for a static def).
        IDefinition possible_result = null;

        for ( IDefinition def: getPropertiesByNameForMemberAccess(scope, member_name, project) )
        {
            //  Check for matching scope: static references should only
            //  match static definitions, and vice versa.
            //  Note: the diagnostic could be improved by doing a subsequent
            //  check for definitions with matching names and conflicting scopes
            //  (i.e., a static reference to an instance field).
            if ( is_static_reference )
            {
                if ( def.isStatic() )
                {
                    result = def;
                    break;
                }
                else if ( match_static_to_static && possible_result != null )
                {
                    possible_result = def;
                    //  Keep looking for a static definition as a better match.
                    continue;
                }
            }
            else 
            {
                if ( !def.isStatic() )
                {
                    result = def;
                    break;
                }
            }
        }

        return result != null? result: possible_result;
    }

    /**
     *  Is the given binding an inaccessible reference?
     *  @param iNode - the iNode that anchors the reference.
     *  @param member - the Binding.
     *  @param project - an ICompilerProject for the current project.
     *  @return true if the binding has no definition, but
     *    a search for the member by name in the iNode's 
     *    underlying type scopes finds something.
     */
    public static boolean isInaccessible(IASNode iNode, Binding member, ICompilerProject project)
    {
        assert(member.getName() != null);
        return member.getDefinition() == null && findMemberByName(iNode, member.getName().getBaseName(), true, project) != null;
    }
    
    /**
     *  Is the given binding an inaccessible reference?
     *  @param iNode - the iNode that anchors the reference.
     *  @param member - the Binding.
     *  @return true if the binding has no definition, but
     *    a search for the member by name in the iNode's 
     *    underlying type scopes finds something.
     *  @see #isInaccessible(IASNode, Binding, ICompilerProject)
     */
    public boolean isInaccessible(IASNode iNode, Binding member)
    {
        return isInaccessible(iNode, member, this.project);
    }


    /**
     *  Fetch the definiton of the given node's
     *    class' base class.
     *  @param iNode - the node of interest.
     *  @return the definition of the node's class' base class,
     *    or null if it not in a class or its class 
     *    has no base class.
     *  @see #getBaseClassDefinition(IASNode, ICompilerProject)
     */
    public IDefinition getBaseClassDefinition(IASNode iNode)
    {
        return getBaseClassDefinition(iNode, this.project);
    }

    /**
     *  Find a property in a defintion's containing scope.
     *  @param member_def - the definition of interest.
     *  @param project - the active compiler project.
     *  @return the definition found in the containing scope, or null if not found.
     */
    public static IDefinition findPropertyQualified(IDefinition member_def, ICompilerProject project)
    {
        ASScope containingScope = (ASScope)member_def.getContainingScope();
        return findPropertyQualified(containingScope, member_def, project);
    }

    /**
     *  Find a property in a defintion's containing scope.
     *  @param member_def - the definition of interest.
     *  @return the definition found in the containing scope, or null if not found.
     *  @see #findPropertyQualified(IDefinition, ICompilerProject)
     */
    public IDefinition findPropertyQualified(IDefinition member_def)
    {
        return findPropertyQualified(member_def, this.project);
    }

    /**
     *  Find a property in a (presumably) enclosing defintion's scope.
     *  @param enclosing_def - the definition to query.
     *  @param member_def - the prototype definition of the member desired.
     *  @param project - the active compiler project.
     *  @return the definition found in the enclosing scope, or null if not found.
     */
    public static IDefinition findPropertyQualified(IScopedDefinition enclosing_def, IDefinition member_def, ICompilerProject project)
    {
        return findPropertyQualified((ASScope)enclosing_def.getContainedScope(), member_def, project);
    }

    public static IDefinition getPropertyQualified(IScopedDefinition enclosing_def, IDefinition member_def, ICompilerProject project)
    {
        return getPropertyQualified((ASScope)enclosing_def.getContainedScope(), member_def, project);
    }

    /**
     *  Find a property in a (presumably) enclosing defintion's scope.
     *  @param enclosing_def - the definition to query.
     *  @param member_def - the prototype definition of the member desired.
     *  @return the definition found in the enclosing scope, or null if not found.
     *  @see #findPropertyQualified(IScopedDefinition, IDefinition, ICompilerProject)
     */
    public IDefinition findPropertyQualified(IScopedDefinition enclosing_def, IDefinition member_def)
    {
        return findPropertyQualified(enclosing_def, member_def, this.project);
    }

    /**
     *  Find a qualified property in a scope.
     *  @param scope - the scope to query.
     *  @param member_def - the prototype definition of the member desired.
     *  @param project - the active compiler project.
     *  @return the definition found in the enclosing scope, or null if not found.
     */
    public static IDefinition findPropertyQualified(ASScope scope, IDefinition member_def, ICompilerProject project)
    {
        IDefinition result = null; 

        if ( scope != null && member_def != null && member_def.getNamespaceReference() != null )
        {
            INamespaceDefinition namespace =
                member_def.getNamespaceReference().resolveNamespaceReference(project);
            if (namespace != null)
            {
                String baseName = member_def.getBaseName();

                result = scope.findPropertyQualified(
                        project, namespace, baseName, DependencyType.EXPRESSION);
            }
        }

        return result;
    }

    /**
     * Get a qualified property in a scope - this will look for properties with the same qualified name as member_def
     * in the scope passed in.
     * @param scope         the scope to look for the property in
     * @param member_def    the prototype definition of the member desired
     * @param project       the active compiler project
     * @return              the definition found in the passed in scope ,or null if not found
     */
    public static IDefinition getPropertyQualified(ASScope scope, IDefinition member_def, ICompilerProject project)
    {
        IDefinition result = null;

        if ( scope != null && member_def != null && member_def.getNamespaceReference() != null )
        {
            INamespaceDefinition namespace =
                    member_def.getNamespaceReference().resolveNamespaceReference(project);

            if (namespace != null)
            {
                result = getPropertyQualified( scope, namespace, member_def.getBaseName(), project);
            }
        }

        return result;
    }

    /**
     * Get a qualified property in a scope, given the namespace and base name to search for.
     * @param scope         the scope to look for the property in
     * @param namespace     the namespace of the member desired
     * @param baseName      the base name of the member desired
     * @param project       the active compiler project
     * @return              the definition found in the passed in scope ,or null if not found
     */
    public static IDefinition getPropertyQualified(ASScope scope, INamespaceDefinition namespace, String baseName, ICompilerProject project)
    {
        IDefinition result = null;

        if ( scope != null && namespace != null && baseName != null )
        {
            result = scope.getPropertyByNameForMemberAccess(
                (CompilerProject)project,
                baseName,
                Collections.singleton(namespace)
            );
        }

        return result;
    }

    /**
     *  Find a qualified property by name in a scope.
     *  @param scope - the scope to query.
     *  @param member_name - the name of the desired member.
     *  @param project - the active compiler project.
     *  @return the definition found in the scope, or null if not found.
     */
    public static IDefinition findProperty(ASScope scope, String member_name, ICompilerProject project, boolean lookForStatics)
    {
        IDefinition result = null; 

        if ( scope != null )
        {
            result = scope.findProperty(
                project,
                member_name,
                DependencyType.EXPRESSION,
                false
            );
        }

        return result;
    }

    /**
     *  Find the definition of the function that encloses the given node, if any.
     *  @param iNode - the inode of interest. 
     *  @param project - an ICompilerProject for the current project.
     *  @return the definition of any function that encloses the inode, or null
     *    if not found (or if the function is undefined for some reason).
     */
    public static FunctionDefinition getEnclosingFunctionDefinition(IASNode iNode, ICompilerProject project)
    {
        IASNode func_node = iNode.getAncestorOfType(FunctionNode.class);

        if ( func_node != null )
        {
            return  (FunctionDefinition) getDefinition(func_node, project);
        }

        return null;
    }

    /**
     *  Find the definition of the function that encloses the given node, if any.
     *  @param iNode - the inode of interest. 
     *  @return the definition of any function that encloses the inode, or null
     *    if not found (or if the function is undefined for some reason).
     *  @see #getEnclosingFunctionDefinition(IASNode, ICompilerProject)
     */
    public FunctionDefinition getEnclosingFunctionDefinition(IASNode iNode)
    {
        return getEnclosingFunctionDefinition(iNode, this.project);
    }

    /**
     *  Is the given inode in an instance function?
     *  @param iNode - the inode of interest.
     *  @param project - an ICompilerProject for the current project.
     *  @return true if the inode has an enclosing function
     *    and that function is a non-static class member.
     */
    public static boolean isInInstanceFunction(IASNode iNode, ICompilerProject project)
    {
        FunctionDefinition func_def = getEnclosingFunctionDefinition(iNode, project);
        return func_def != null && func_def.getFunctionClassification() == FunctionClassification.CLASS_MEMBER && !func_def.isStatic();
    }

    
    /**
     *  Is the given inode in an instance function?
     *  @param iNode - the inode of interest.
     *  @return true if the inode has an enclosing function
     *    and that function is a non-static class member.
     *  @see #isInInstanceFunction(IASNode, ICompilerProject)
     */
    public boolean isInInstanceFunction(IASNode iNode)
    {
        return isInInstanceFunction(iNode, this.project);
    }

    /**
     *  Is the given inode in a static function?
     *  @param iNode - the inode of interest.
     *  @param project - an ICompilerProject for the current project.
     *  @return true if the inode has an enclosing function
     *    and that function is a static class member.
     */
    public static boolean isInStaticClassFunction(IASNode iNode, ICompilerProject project)
    {
        FunctionDefinition func_def = getEnclosingFunctionDefinition(iNode, project);
        return func_def != null && func_def.getFunctionClassification() == FunctionClassification.CLASS_MEMBER && func_def.isStatic();
    }


    /**
     *  Is the given Binding inaccessible, i.e., known to 
     *  an underlying type scope but not defined here
     *  due to access restrictions?
     *  @param scope - the innermost underlying type scope.
     *  @param b - the Binding of interest
     *  @param project - an ICompilerProject for the current project.
     */
    private static boolean isInaccessible(IASScope scope, Binding b, ICompilerProject project)
    {
        return b.getDefinition() == null && findMemberByName(scope, b.getName().getBaseName(), true, false, project) != null;
    }

    /**
     *  Is the given Binding inaccessible, i.e., known to 
     *  an underlying type scope but not defined here
     *  due to access restrictions?
     *  @param containingScope - the innermost underlying type scope.
     *  @param b - the Binding of interest
=     */
    public boolean isInaccessible(ASScope containingScope, Binding b)
    {
        return isInaccessible(containingScope,  b, this.project);
    }

    /**
     *  Get the name of the class enclosing an instance function in which 
     *  some problematic construct resides.
     *  @param iNode - the problematic construct.
     *  @param project - an ICompilerProject for the current project.
     *  @return the name of the enclosing class.
     */
    public static String getEnclosingClassName(IASNode iNode, ICompilerProject project)
    {
        assert(isInInstanceFunction(iNode, project));  //  assert precondition.

        IDefinition class_def = getEnclosingFunctionDefinition(iNode, project).getAncestorOfType(ClassDefinition.class);
        return class_def.getBaseName();
    }

    /**
     *  Get the name of the class enclosing an instance function in which 
     *  some problematic construct resides.
     *  @param iNode - the problematic construct.
     *  @return the name of the enclosing class.
     *  @see #getEnclosingClassName(IASNode, ICompilerProject)
     */
    public String getEnclosingClassName(IASNode iNode)
    {
        return getEnclosingClassName(iNode, this.project);
    }

    /**
     *  Fetch the collection of definitions in the given scope or its underlying scopes
     *  that match the given name.
     *  @param scope - the innermost scope to search.
     *  @param member_name - the (string) name of interest.
     *  @param project - an ICompilerProject for the current project.
     */
    public static Collection<IDefinition> getPropertiesByNameForMemberAccess(IASScope scope, String member_name, ICompilerProject project)
    {
        assert(scope != null);  //  check precondition.
        return ((ASScope)scope).getPropertiesByNameForMemberAccess(
            (CompilerProject)project, 
            member_name, 
            ASScopeBase.allNamespacesSet
        );
    }

    /**
     *  Get an abstract string description of a node, suitable for a diagnostic.
     *  @return the best guess we can come up with for a node's user-friendly string representation.
     */
    public static String getDiagnosticString(IASNode iNode)
    {
        String content = null;

        switch ( iNode.getNodeID() )
        {
            case IdentifierID:
            case NamespaceIdentifierID:
            case NonResolvingIdentifierID:
            case QualifiedNameExpressionID:
                content = ((IdentifierNode)iNode).getName();
                break;

            case FunctionCallID:
            {
                FunctionCallNode func = (FunctionCallNode)iNode;
                
                if ( func.getNameNode() != null )
                {
                    // return to avoid quoting the result twice.
                    return getDiagnosticString(func.getNameNode());
                }
                else
                {
                    content = iNode.getNodeID().getParaphrase();
                }
                
                break;
            }

            case LiteralArrayID:
            case LiteralBooleanID:
            case LiteralNumberID:
            case LiteralObjectID:
            case LiteralRegexID:
            case LiteralStringID:
            case LiteralXMLID:
            case LiteralID:
                content = ((LiteralNode)iNode).getValue();
                break;

            case LiteralNullID:
                content = "null";
                break;

            case LiteralVoidID:
                content = "void";
                break;

            case MemberAccessExpressionID:
                content = ((MemberAccessExpressionNode)iNode).getDisplayString();
                break;

            default:
                content = iNode.getNodeID().getParaphrase();
        }

        return  "'" + content + "'";
    }

    /**
     *  Get the set of open namespaces for the given node.  
     *  @param iNode - the node of interest.
     *  @param project - an ICompilerProject for the current project.
     *  @return the open namespaces at this node as a Nsset,
     *    or null if the node doesn't have a containing scope.
     */
    public static Nsset getOpenNamespaces(IASNode iNode, ICompilerProject project)
    {
        ASScope scope = getASScope(iNode);
        if (scope == null)
            return null;

        Set<INamespaceDefinition> namespaceSet = scope.getNamespaceSet(project);
        Nsset nsSet = convertSetINamespaceToNsset(namespaceSet);
        return nsSet;        
    }
    
    /**
     *  Get the set of open namespaces for the given node,
     *  as appropriate for <code>super[...]</code> access.
     *  @param iNode - the node of interest.
     *  @param project - an ICompilerProject for the current project.
     *  @return the open namespaces at this node as a Nsset,
     *    or null if the node doesn't have a containing scope.
     */
    public static Nsset getOpenNamespacesForSuper(IASNode iNode, ICompilerProject project,
                                                  IDefinition superDef)
    {
        ASScope scope = getASScope(iNode);
        if (scope == null)
            return null;

        Set<INamespaceDefinition> namespaceSet = scope.getNamespaceSetForSuper(project, superDef);
        Nsset nsSet = convertSetINamespaceToNsset(namespaceSet);
        return nsSet;        
    }
    

    /**
     *  Convert a set of INamespaceDefinition objects into a Nsset.
     *  @param namespaceSet - the set of INamespaceDefinition objects.
     *  @return the namespaces as a Nsset.
     */
    public static Nsset convertSetINamespaceToNsset(Set<INamespaceDefinition> namespaceSet)
    {
        ArrayList<Namespace> ns_set = new ArrayList<Namespace>(namespaceSet.size());

        for (INamespaceDefinition namespace : namespaceSet)
        {
            Namespace aetNamespace = ((NamespaceDefinition)namespace).getAETNamespace();
            ns_set.add(aetNamespace);
        }

        return new Nsset(ns_set);
    }

    /**
     *  Get the given node's containing scope.
     *  (Convenience method that hides a downcast.)
     *  @param iNode - the node of interest.
     *  @return the node's scope, or null if not available.
     */
    public static ASScope getASScope(IASNode iNode)
    {
        return ((NodeBase)iNode).getASScope();
    }

    /**
     *  Is the given definition const?
     *  @param iNode - the root of the definition.
     *  @param project - an ICompilerProject for the current project.
     *  @return true if iNode can be resolved to a const definition.
     */
    public static boolean isConst(IASNode iNode, ICompilerProject project)
    {
        return isConstDefinition(getDefinition(iNode, project));
    }

    /**
     *  Is the given definition const?
     *  @param iNode - the root of the definition.
     *  @return true if iNode can be resolved to a const definition.
     *  @see #isConst(IASNode iNode,  ICompilerProject cache)
     */
    public boolean isConst(IASNode iNode)
    {
        return isConst(iNode, this.project);
    }

    /**
     *  Is the given definition a constant?
     *  @param def - the definition of interest.
     *  @return true if the definition is of a const.
     */
    public static boolean isConstDefinition(IDefinition def)
    {
        return def instanceof IConstantDefinition;
    }

    /**
     *  Is the given node in a constructor?
     *  @param iNode - the i-node of interest.
     *  @return true if the node is in a constructor function.
     */
    @SuppressWarnings("unchecked")
    public static boolean isInConstructor(IASNode iNode)
    {
        FunctionNode fnode = (FunctionNode) searchUpForType(iNode, FunctionNode.class);
        return fnode != null && fnode.isConstructor();
    }

    /**
     *  Is the given i-node inside a variable declaration?
     *  @param iNode - the i-node of interest.
     *  @return true if the node has a VariableNode or VariableExpressionNode parent.
     */
    @SuppressWarnings("unchecked")
    public static boolean isInVariableDeclaration(IASNode iNode)
    {
        return searchUpForType(iNode, VariableNode.class, VariableExpressionNode.class) != null;
    }

    /**
     *  Search a node's parent chain for an ancestor of any of a group of classes.
     *  @param iNode - the node of interest.
     *  @param desired - the group of classes desired.
     *  @return the first node in the node and its ancestors that matches a desired class.
     */
    private static IASNode searchUpForType(IASNode iNode, Class<? extends IASNode> ... desired)
    {
        if ( iNode == null )
            return null;

        IASNode found = null;
        for ( Class<? extends IASNode> candidate: desired)
        {
            if ( candidate.isInstance(iNode) )
                found = iNode;
            else
                found = iNode.getAncestorOfType(candidate);

            if ( found != null )
                break;
        }

        return found;
    }

    /**
     *  Fetch the most appropriate part of a type name for diagnostics.
     *  @return the type part of a parameterized type, or the input node.
     */
    public IASNode getPotentiallyParameterizedType(IASNode iNode)
    {
        IASNode result = iNode;

        if ( iNode instanceof TypedExpressionNode )
        {
            result = ((TypedExpressionNode)iNode).getTypeNode();
        }

        return result;
        
    }

    /**
     *  Get the Nth child of a node per the BURM's semantics.
     */
	public static IASNode getNthChild( IASNode node, int index)
	{
        IASNode result = null;
        
        switch( node.getNodeID() )
        {
            case BindableVariableID:
            case VariableID:
            {
                IVariableNode var = (IVariableNode) node;
                switch( index )
                {
                    case 0:
                    {
                        result = var.getNameExpressionNode();
                        break;
                    }
                    default:
                    {
                        //  We have to go hunt among the children
                        //  for the nodes that are relevant to the BURM.
                        int lastFoundChildPos = 0;

                        result = var.getVariableTypeNode();
                        if ( result != null )
                        {
                            lastFoundChildPos++;
                            if ( index == lastFoundChildPos)
                                break;
                        }

                        result = var.getAssignedValueNode();
                        if ( result != null )
                        {
                            lastFoundChildPos++;
                            if ( index == lastFoundChildPos)
                                break;
                        }

                        //  Look for chained variable declarations.
                        int needle = 0;
                        while ( needle < node.getChildCount() && lastFoundChildPos < index )
                        {
                            if ( node.getChild(needle) instanceof IVariableNode )
                                lastFoundChildPos++;
                            if ( lastFoundChildPos < index )
                                needle++;
                        }

                        assert(lastFoundChildPos == index): "getNthChild() failed, should have been constrained by getChildCount(node)";
                        return ( node.getChild(needle));
                    }
                    
                }
                break;
            }
            case FunctionID:
            case GetterID:
            case SetterID:
            {
                FunctionNode func = (FunctionNode) node;
                switch( index )
                {
                    case 0:
                        result = func.getNameExpressionNode();
                        break;
                    case 1:
                        result = func.getParametersContainerNode();
                        break;
                    case 2:
                        result = func.getReturnTypeNode();
                        if ( result != null )
                            break;
                    case 3:
                        assert (func.hasBeenParsed()) : "getScopedNode() called on a function before the body has been parsed";
                        result = func.getScopedNode();
                        break;
                }
                break;
            }
            case Op_CommaID:
            {
                switch( index )
                {
                    case 0:
                    {
                        result = node.getChild(node.getChildCount()-1);
                        break;
                    }
                    default:
                        result = node.getChild(index - 1);
                    
                }
                break;
            }
            case TryID:
			{
                ITryNode tryNode = (ITryNode) node;
				switch( index )
				{
					case 0:
					{
						result = tryNode.getStatementContentsNode();
						break;
					}
					case 1:
					{
						if  ( tryNode.getFinallyNode() != null )
                        {
                            result = tryNode.getFinallyNode();
                        }
                        else 
                        {
                            assert ( tryNode.getCatchNodeCount() > 0 );
                            result = tryNode.getCatchNode(0);
                        }
                        break;
					}
					default:
					{
                        //  Note: If the try has a contents and finally nodes,
                        //  they are presented to the CG by getNthChild() as child
                        //  nodes 0 and 1 before the n-ary tail of catch nodes.
                        if (tryNode.getStatementContentsNode() != null)
                            index--;
                        if (tryNode.getFinallyNode() != null)
                            index--;
                        result = tryNode.getCatchNode(index);
                    }
				}
				break;
			}
            case NamespaceID:
            {
                // Skip over MetaTagsNode and NamespaceIdentifierNode
                final NamespaceNode nsNode = (NamespaceNode)node;
                final IASNode nsName = nsNode.getNameExpressionNode();
                final IASNode nsURI = nsNode.getNamespaceURINode();
                switch(index)
                {
                    case 0:
                        if(nsName != null)
                            result = nsName;
                        else 
                            result = nsURI;
                        break;
                    case 1:
                        if(nsName != null && nsURI != null)
                            result = nsURI;
                        else 
                            throw new ArrayIndexOutOfBoundsException(index);
                        break;
			default:
                        throw new ArrayIndexOutOfBoundsException(index);
                }
                break;
            }
            default:
			{
				result = node.getChild(index);
				break;
			}
		}

        assert(result != null): String.format("getNthChild(%s,%d) null result", node.getNodeID(), index);
        return result;
    }

    /**
     *  Get the child count for nodes whose
     *  CodeModel-centric child count isn't 
     *  reliable.
     */
    public static int getChildCount(IASNode node)
    {
        int result = 0;
        
        switch( node.getNodeID() )
        {
            case BindableVariableID:
            case VariableID:
            {
                IVariableNode var = (IVariableNode) node;

                assert ( var.getNameExpressionNode() != null ): "Variable has null name expression.";
                result = 1;

                if ( var.getVariableTypeNode() != null )
                    result++;

                if ( var.getAssignedValueNode() != null )
                    result++;

                //  Scan for chained declarations.
                for ( int i = 0; i < node.getChildCount(); i++ )
                {
                    if ( node.getChild(i) instanceof IVariableNode )
                        result++;
                }
                break;
            }
            case FunctionID:
            case GetterID:
            case SetterID:
            {
                FunctionNode func = (FunctionNode)node;
                assert ( func.getNameExpressionNode() != null ): "Function has null name expression";
                assert ( func.getParametersContainerNode() != null ): "Function has null parameters";
                result = 2;

                if ( func.getReturnTypeNode() != null )
                    result++;

                if ( func.getScopedNode() != null )
                    result++;
            }
            break;
            case NamespaceID:
            {
                final NamespaceNode nsNode = (NamespaceNode)node;
                assert nsNode.getNameExpressionNode() != null : "'name' node is required";
                result = 1;
                if (nsNode.getNamespaceURINode() != null)
                    result = 2;
                break;
            }
            default:
                result = node.getChildCount();
        }
        return( result);
   	}

    /**
     *  @return a parameter's definition.
     *  @param iNode - the parameter node.
     */
    public static IDefinition getParameterContent(IASNode iNode)
    {
        return ((( IParameterNode )iNode).getDefinition());
    }
    
    /**
     *  @return the string content of a literal.
     *  @param iNode - the literal node.
     */
    public static String getStringLiteralContent(IASNode iNode)
    {
        return (( ILiteralNode )iNode).getValue();
    }

    /**
     *  @return the uint content of a numeric literal.
     *  @param iNode - the literal node.
     */
    public static Long getUintContent(IASNode iNode)
    {
        return Long.valueOf((( INumericLiteralNode )iNode).getNumericValue().toUint32());
    }

    /**
     *  @return the double content of a numeric literal.
     *  @param iNode - the literal node.
     */
    public static Double getDoubleContent(IASNode iNode)
    {
        return Double.valueOf((( INumericLiteralNode )iNode).getNumericValue().toNumber());
    }

    /**
     *  @return the boolean content of a literal.
     *  @param iNode - the literal node.
     */
    public static Boolean getBooleanContent(IASNode iNode)
    {
        if ( "true".equalsIgnoreCase(getStringLiteralContent(iNode)) )
        {
            return Boolean.TRUE;
        }
        else
            return Boolean.FALSE;
    }

    /**
     *  @return the name of an identifier.
     *  @param iNode - the IIdentifier node.
     */
    public static String getIdentifierContent(IASNode iNode)
    {
        return (( IIdentifierNode )iNode).getName();
    }

    /**
     *  @return the int content of a numeric literal.
     *  @param iNode - the literal node.
     */
    public static Integer getIntegerContent(IASNode iNode)
    {
        return Integer.valueOf((( INumericLiteralNode )iNode).getNumericValue().toInt32());
    }

    /**
     *  @return a node cast to MXMLEventSpecifierNode type.
     *  @param iNode - the MXMLEventSpecifierNode node.
     */
    public static IMXMLEventSpecifierNode getMXMLEventSpecifierContent(IASNode iNode)
    {
        return (IMXMLEventSpecifierNode)iNode;
    }

    /**
     *  Explore a MemberAccessNode and decide if
     *  its stem is a referenceto a package.
     *  @return a "feasible" cost of this node
     *    is of the form foo.bar, where foo is a package.
     */
    public static int isDottedName(IASNode n)
    {
        if ( n instanceof MemberAccessExpressionNode && ((MemberAccessExpressionNode)n).stemIsPackage() )
            return 1;

        //  Return "unfeasible" cost.
        return Integer.MAX_VALUE;
    }
    
    /**
     *  Get the definition associated with a node's qualifier
     *  and decide if the qualifier is a compile-time constant.
     *  @param iNode - the node to check.
     *  @pre - the node has an IdentifierNode 0th child.
     *  @return an attractive cost if the child has a known namespace, i.e.,
     *    it's a compile-time constant qualifier.
     */
    public static int qualifierIsCompileTimeConstant(IASNode iNode, ICompilerProject project)
    {
        IdentifierNode qualifier = (IdentifierNode) SemanticUtils.getNthChild(iNode, 0);
        IDefinition def = qualifier.resolve(project);

        if ( def instanceof NamespaceDefinition )
            return 1;
        else
            return Integer.MAX_VALUE;
    }

    /**
     *  Get the definition associated with a node's qualifier
     *  and decide if the qualifier is a compile-time constant.
     *  @param iNode - the node to check.
     *  @pre - the node has an IdentifierNode 0th child.
     *  @return an attractive cost if the child has a known namespace, i.e.,
     *    it's a compile-time constant qualifier.
     *  @see #qualifierIsCompileTimeConstant(IASNode, ICompilerProject)
     */
    public int qualifierIsCompileTimeConstant(IASNode iNode)
    {
        return qualifierIsCompileTimeConstant(iNode, this.project);
    }

    /**
     *  Get the definition associated with a node's qualifier
     *  and decide if the qualifier is an interface name.
     *  @param iNode - the node to check.
     *  @pre - the node has an IdentifierNode 0th child.
     *  @return an attractive cost if the child is an interface.
     */
    public static int qualifierIsInterface(IASNode iNode, ICompilerProject project)
    {
        IdentifierNode qualifier = (IdentifierNode) SemanticUtils.getNthChild(iNode, 0);
        IDefinition def = qualifier.resolve(project);

        if ( def instanceof InterfaceDefinition )
            return 1;
        else 
            return Integer.MAX_VALUE;
    }

    /**
     *  Get the definition associated with a node's qualifier
     *  and decide if the qualifier is an interface name.
     *  @param iNode - the node to check.
     *  @pre - the node has an IdentifierNode 0th child.
     *  @return an attractive cost if the child is an interface.
     *  @see #qualifierIsInterface(IASNode, ICompilerProject)
     */
    public  int qualifierIsInterface(IASNode iNode)
    {
        return qualifierIsInterface(iNode, this.project);
    }
    
    /**
     *  Transform a leaf AST into a compile-time constant;
     *  note that more complex constant folding is done by
     *  a series of rules in the BURM.  This routine looks
     *  at "terminal" constants, e.g., 1 or foo where foo
     *  is a namespace.
     *  @param iNode - the i-node of interest.
     *  @param project - an ICompilerProject for the current project.
     *  @return the constant value, if there is one.
     */
    public static Object transformNameToConstantValue(IASNode iNode, ICompilerProject project)
    {
        Object result = null;
        if ( iNode instanceof ExpressionNodeBase )
        {
            IASNode parentNode = iNode.getParent();
            if (parentNode instanceof BaseTypedDefinitionNode)
            {
                BaseTypedDefinitionNode parentDefinitionNode = (BaseTypedDefinitionNode)parentNode;
                if (parentDefinitionNode.getNameExpressionNode() == iNode)
                    return null;
                if (parentDefinitionNode.getTypeNode() == iNode)
                    return null;
            }
            else if (parentNode instanceof IterationFlowNode)
            {
                IterationFlowNode parentIterationFlowNode = (IterationFlowNode)parentNode;
                if (parentIterationFlowNode.getLabelNode() == iNode)
                    return null;
            }
            //  Try for a compile-time constant value.
            IDefinition val = ((ExpressionNodeBase)iNode).resolve(project);

            if ( val instanceof NamespaceDefinition )
            {
                NamespaceDefinition ns = (NamespaceDefinition)val;
                result = ns.resolveAETNamespace(project);
            }
            else if( val instanceof ConstantDefinition )
            {
                ConstantDefinition cd = (ConstantDefinition)val;
                result = cd.resolveValueFrom(project, (NodeBase)iNode);
            }
        }

        return result;
    }

    /**
     *  Transform a leaf AST into a compile-time constant;
     *  note that more complex constant folding is done by
     *  a series of rules in the BURM.  This routine looks
     *  at "terminal" constants, e.g., 1 or foo where foo
     *  is a namespace.
     *  @param iNode - the i-node of interest.
     *  @return the constant value, if there is one.
     *  @see #transformNameToConstantValue(IASNode, ICompilerProject)
     */
    public Object transformNameToConstantValue(IASNode iNode)
    {
        return transformNameToConstantValue(iNode, this.project);
    }

    /**
     * Check for identifiers that don't have a namespace quailifier. Generates problem if found.
     * 
     * @param scope is the scope where problems will be added
     * @param node is the definition node for the defintion we are checking
     * @param definition is the dfintion we are checking
     * @param className is the class in which the identifier lives, or null if not in a class. Used for error reporting
     */
    public static void checkScopedToDefaultNamespaceProblem(LexicalScope scope, BaseDefinitionNode node, IDefinition definition, String className)
    {
        if (node == null)       // if we don't have a node, it means we didn't come from source code, so there is no point
                                // in doing a semantic check
            return;
        INamespaceReference nsref = definition.getNamespaceReference();
        INamespaceDecorationNode dec = node.getNamespaceNode();
        if ((dec ==null) &&(nsref instanceof INamespaceDefinition.IInternalNamespaceDefinition))
        {
            String type = "declaration";        // should never see this string...
            if (definition instanceof IFunctionDefinition)
                type = "function";
            else if (definition instanceof IClassDefinition)
                type = "class";
            else if (definition instanceof IInterfaceDefinition)
                type = "interface";
            String identifierName = type + " '" + definition.getBaseName() + "'";
            scope.addProblem( new ScopedToDefaultNamespaceProblem( node, identifierName, className));
        }
    }
    
    /**
     * Checks that a given function definition has a return type, and logs a problem if not
     * 
     * @param scope is the scope where problems are to be logged
     * @param node is the function node that is being checked (used for location reporting(
     * @param func is the definition of the function to be checked.
     */
    public static void checkReturnValueHasNoTypeDeclaration(LexicalScope scope, IFunctionNode node, IFunctionDefinition func)
    {
        // constructors aren't supposed to have return types, so skip this check for them
        if (node.isConstructor())
            return;

        // Compiler synthesized nodes may not report return types as we expect. For example
        // the class generated for embedding has an implicit return type node, which causes our 
        // check to think the "user code" has no return type. So skip these kinds of nodes....
        if (node.getStart() == node.getEnd())
            return;
        
        IExpressionNode returnType = node.getReturnTypeNode();
        
        // check for return type declaration
        if (returnType == null)       
        {
            // if none found, derive best source location for problem report
            IASNode sourceLoc = node;
            IExpressionNode nameNode = node.getNameExpressionNode();
            if (nameNode != null)
                sourceLoc = nameNode;
            scope.addProblem(new ReturnValueHasNoTypeDeclarationProblem(sourceLoc, func.getBaseName()));
        }
    }
    
    /**
     * Determines if a list of definitions consists of a matched getter/setter pair.
     * 
     * @param defs The list of definitions to analyze.
     * @param project The context in which they are analyzed.
     * @return <code>true</code> if the definitions are a getter/setter pair.
     */
    public static boolean isGetterSetterPair(List<IDefinition> defs, ICompilerProject project)
    {
        // Must be a pair to be a getter setter pair. A pair plus others doesn't count.
        if (defs.size() != 2)
            return false;
        
        IDefinition def0 = defs.get(0);
        IDefinition def1 = defs.get(1);
        return isGetterSetterPair(def0, def1, project);
    }
    
    /**
     * Determines if a pair of definitions consists of a matched getter/setter pair.
     * 
     * @param def0 The first definitions to analyze.
     * @param def1 The first definitions to analyze.
     * @param project The context in which they are analyzed.
     * @return <code>true</code> if the definitions are a getter/setter pair.
     */
    public static boolean isGetterSetterPair(IDefinition def0, IDefinition def1, ICompilerProject project)
    {
        if (!(def0 instanceof IAccessorDefinition))
            return false;
      
        if (!(def1 instanceof IAccessorDefinition))
            return false;
        
        return ((IAccessorDefinition)def0).resolveCorrespondingAccessor(project) == def1;
    }
    
    /**
     * This is similar to the <code>resolveBaseClass()</code> method of <code>IClassDefinition</code>,
     * except it reports problems if the base class doesn't exist,
     * if it isn't actually a class, or if it is final;
     * it also returns the class definition for <code>Object</code> in these error cases.
     */
    public static ClassDefinition resolveBaseClass(ICommonClassNode classNode,
                                                   ClassDefinition classDefinition,
                                                   ICompilerProject project,
                                                   Collection<ICompilerProblem> problems)
    {
        IReference baseClassReference = classDefinition.getBaseClassReference();
        
        // Object has no base class.
        if (baseClassReference == null)
            return null;
        
        ITypeDefinition superclassDefinition = classDefinition.resolveType(
            baseClassReference, project, DependencyType.INHERITANCE);
        
        if (superclassDefinition == null)
        {
            IASNode problemNode = getBaseClassProblemNode(classNode, classDefinition);
            String baseClassReferenceName = baseClassReference.getName();

            //  The base class reference might be ambiguous.
            IDefinition foundDefinition = baseClassReference.resolve(project, (ASScope)classDefinition.getContainingScope(), DependencyType.INHERITANCE, true);
            if ( AmbiguousDefinition.isAmbiguous(foundDefinition))
                problems.add(new AmbiguousReferenceProblem(problemNode, baseClassReferenceName));
            else
                problems.add(new UnknownSuperclassProblem(problemNode, baseClassReferenceName));
            
            // Repair by making the class extend Object.
            superclassDefinition = getObjectDefinition(project);
        }
        else if (superclassDefinition instanceof IInterfaceDefinition)
        {
            IASNode problemNode = getBaseClassProblemNode(classNode, classDefinition);
            problems.add(new CannotExtendInterfaceProblem(problemNode));
            
            // Repair by making the class extend Object.
            superclassDefinition = getObjectDefinition(project);
        }
        else if (superclassDefinition.isFinal())
        {
            IASNode problemNode = getBaseClassProblemNode(classNode, classDefinition);
            problems.add(new BaseClassIsFinalProblem(problemNode));
            
            // Repair by making the class extend Object.
            superclassDefinition = getObjectDefinition(project);
        }
        else if (superclassDefinition == classDefinition)
        {
            problems.add(new CircularTypeReferenceProblem(classDefinition, classDefinition.getQualifiedName()));
            
            // Repair by making the class extend Object.
            superclassDefinition = getObjectDefinition(project);
        }
        
        // Report a problem if the superclass is deprecated
        // and the reference to it is not within a deprecated API.
        if (superclassDefinition != null && superclassDefinition.isDeprecated())
        {
            IASNode problemNode = getBaseClassProblemNode(classNode, classDefinition);
            if (!SemanticUtils.hasDeprecatedAncestor(problemNode))
            {
                ICompilerProblem problem = SemanticUtils.createDeprecationProblem(superclassDefinition, problemNode);
                problems.add(problem);
            }
        }

        return (ClassDefinition)superclassDefinition;
    }
    
    private static IASNode getBaseClassProblemNode(ICommonClassNode classNode, ClassDefinition classDefinition)
    {
        // If we have a source node for 'class A extends B', return the node for B.
        if (classNode instanceof IClassNode)
            return ((IClassNode)classNode).getBaseClassExpressionNode();
        
        // Otherwise, try to get the class node from the definition.
        return classDefinition.getNode();
    }
    
    private static IClassDefinition getObjectDefinition(ICompilerProject project)
    {
        IClassDefinition objectDefinition = (IClassDefinition)project.getBuiltinType(
            IASLanguageConstants.BuiltinType.OBJECT);
        
        if (objectDefinition == null)
        {
            // Abort codegen with a more comprehensible diagnostic.
            throw new MissingBuiltinException(IASLanguageConstants.Object);
        }
        
        return objectDefinition;
    }
        
    public static boolean isValidImport(IImportNode importNode, ICompilerProject project,
                                        boolean inInvisibleCompilationUnit)
    {
        String importName = importNode.getImportName();
        importName = project.getActualPackageName(importName);
        
        // First check whether the import name matches
        // a definition in the project scope.
        ASProjectScope projectScope = (ASProjectScope)project.getScope();
        if (projectScope.isValidImport(importName))
            return true;
        
        // For an invisible compilation unit, which doesn't contribute
        // definitions to the project scope, we must also check
        // whether it matches an externally visible definition
        // in the file.
        if (inInvisibleCompilationUnit)
        {
            IFileNode fileNode = (IFileNode)importNode.getAncestorOfType(IFileNode.class);
            IDefinition[] externallyVisibleDefinitionsInThisFile =
                fileNode.getTopLevelDefinitions(false, true);
            for (IDefinition d : externallyVisibleDefinitionsInThisFile)
            {
                String qName = d.getQualifiedName();
                if (qName.equals(importName))
                    return true;
                
                String wildcardQName = ImportNode.makeWildcardName(qName);
                if (wildcardQName.equals(importName))
                    return true;
            }
        }
        
        return false;
    }

    /**
     * Determine if a given IDefinition is the definition of a type (class or interface)
     * @param d     the definition to check
     * @return      true if the Definition represents a Type
     */
    public static boolean isType(IDefinition d)
    {
        return d instanceof ITypeDefinition;
    }
    

    /**
     * Check if {@code node} is in an E4X filter expression.
     * 
     * @param node AST node to be checked.
     * @return True if the node is in an E4X filter expression without crossing
     * a block.
     */
    public static boolean isE4XWildcardProperty(final IdentifierNode node)
    {
        assert node != null : "node can't be null";
        IASNode blockOrFilterNode = node.getParent();
        
        // "*::bar", "foo::*", "foo.*", "foo.(@* == 10) are not E4X wildcard properties
        if (blockOrFilterNode instanceof MemberAccessExpressionNode ||
            blockOrFilterNode instanceof UnaryOperatorAtNode)
            return false;
        
        while (blockOrFilterNode != null &&
               blockOrFilterNode.getNodeID() != ASTNodeID.BlockID &&
               blockOrFilterNode.getNodeID() != ASTNodeID.E4XFilterID)
        {
            blockOrFilterNode = blockOrFilterNode.getParent();
        }

        if (blockOrFilterNode != null && blockOrFilterNode.getNodeID() == ASTNodeID.E4XFilterID)
            return true;
        else
            return false;
    }

    /**
     *  Decide if an assignment node is in a context that merits a warning.
     *  @return true if an assignment has a path to a parent's conditional
     *  expression that contains only Boolean operators.
     */
    public static boolean isUnprotectedAssignmentInConditional(final IASNode node)
    {
        IASNode current = node;
        IASNode parent  = current.getParent();

        while ( parent != null )
        {
            switch ( parent.getNodeID() )
            {
                case Op_LogicalAndID:
                case Op_LogicalOrID:
                case Op_LogicalNotID:
                case ContainerID:
                case ConditionalID:
                    current = parent;
                    parent  = current.getParent();
                    break;
                
                case WhileLoopID:
                    return current == getNthChild(parent,0);

                case DoWhileLoopID:
                    return current == getNthChild(parent,1);

                case IfStatementID:
                    return current == getNthChild(parent,0);

                default:
                    return false;
            }
        }

        return false;
    }

    /**
     *  Downcast an IDefinition and get an AET name from it.
     *  @param def - the definition.
     *  @param project - the active project.
     *  @return the definition's AET name.
     */
    public static Name getAETName(IDefinition def, ICompilerProject project)
    {
        return ((DefinitionBase)def).getMName(project);
    }

    /**
     *  Downcast an IDefinition and get an AET name from it.
     *  @param def - the definition.
     *  @return the definition's AET name.
     *  @see #getAETName(IDefinition, ICompilerProject) to which this delegates.
     */
    public Name getAETName(IDefinition def)
    {
        return ((DefinitionBase)def).getMName(this.project);
    }

    /**
     * Determines whether the specified node has an ancestor
     * which is a definition node with <code>[Deprecated]</code> metadata.
     * <p>
     * The node itself is not checked for such metadata.
     * 
     * @param node The node whose ancestors should be checked.
     * @return <code>true</code> if an ancestor is deprecated.
     */
    public static boolean hasDeprecatedAncestor(IASNode node)
    {
        for (IASNode ancestor = node.getParent();
             ancestor != null;
             ancestor = ancestor.getParent())
        {
            if (ancestor instanceof IDefinitionNode)
            {
                IDefinition definition = ((IDefinitionNode)ancestor).getDefinition();
                if (definition.isDeprecated())
                    return true;
            }
        }
        
        return false;
    }

    /**
     * Uses the <code>[Deprecated]</code> metadata of the specified definition
     * to create one of five different deprecation problems, depending on
     * the presence of <code>message</code>, <code>since</code>, and
     * <code>replacement</code> attributes in the metadata.
     * 
     * @param definition A deprecated definition.
     * @param site The node referring to the deprecated definition,
     * which determines the problem location.
     * @return An {@link ICompilerProblem}.
     */
    public static ICompilerProblem createDeprecationProblem(IDefinition definition, IASNode site)
    {
        ICompilerProblem problem = null;

        IDeprecationInfo deprecationInfo = definition.getDeprecationInfo();

        String message = deprecationInfo.getMessage();
        if (message != null)
        {
            problem = new DeprecatedAPIWithMessageProblem(site, message);
        }
        else
        {
            String name = definition.getBaseName();
            String since = deprecationInfo.getSince();
            String replacement = deprecationInfo.getReplacement();

            if (since == null && replacement == null)
                problem = new DeprecatedAPIProblem(site, name);
            
            else if (since != null && replacement == null)
                problem = new DeprecatedAPIWithSinceProblem(site, name, since);
            
            else if (since == null && replacement != null)
                problem = new DeprecatedAPIWithReplacementProblem(site, name, replacement);
            
            else if (since != null && replacement != null)
                problem = new DeprecatedAPIWithSinceAndReplacementProblem(site, name, since, replacement);
        }
        
        return problem;
    }

    /**
     *  Locate a FunctionDefinition that encloses the given i-node.
     *  @param iNode - the i-node of interest.
     *  @return the nearest enclosing function definition, or null.
     */
    public static FunctionDefinition getFunctionDefinition(IASNode iNode)
    {
        FunctionNode func_node;
        
        if ( iNode instanceof FunctionNode )
            func_node = (FunctionNode) iNode;
        else
            func_node = (FunctionNode) iNode.getAncestorOfType(FunctionNode.class);

        if ( func_node != null )
            return func_node.getDefinition();
        else
            return null;
    }

    /**
     *  Does the subject function require a non-void return value?
     *  @param iNode - an AST node in (or at the border of) the function.
     *  @return true if the function's definition is known, the definition's
     *    return type is known (and the function is not a constructor),
     *    and the return type is not void or *
     */
    public static boolean functionMustReturnValue(IASNode iNode, ICompilerProject project)
    {
        FunctionDefinition func_def = SemanticUtils.getFunctionDefinition(iNode);

        if ( func_def != null )
        {
            IDefinition return_type = func_def.resolveReturnType(project);
            //  The function must return a value unless its return type is void or *,
            //  or if it's a constructor (its definition lies).
            return !( 
                return_type == null ||
                return_type.equals(ClassDefinition.getVoidClassDefinition()) ||
                return_type.equals(ClassDefinition.getAnyTypeClassDefinition()) ||
                func_def.isConstructor()
            );
        }
        else
        {
            //  Nothing known about this function.
            return false;
        }
    }

    /**
     * Calculate the type of the RHS of an assignment.  Works with either BinaryOperatorAssignmentNodes
     * or VariableNodes
     * @param project   the active project
     * @param iNode     the node representing the assignment
     * @return          the type of the RHS, or null if one couldn't be determined.
     */
    public static ITypeDefinition resolveRHSTypeOfAssignment (ICompilerProject project, IASNode iNode)
    {
        ITypeDefinition srcType = null;
        if( iNode instanceof BinaryOperatorAssignmentNode)
        {
            if ( iNode.getChildCount() > 1 && iNode.getChild(1) instanceof IExpressionNode)
            {
                IExpressionNode rhs = (IExpressionNode)iNode.getChild(1);
                srcType = rhs.resolveType(project);
            }
        }
        else if(iNode instanceof IVariableNode)
        {
            IExpressionNode rhs = ((IVariableNode)iNode).getAssignedValueNode();
            srcType = rhs != null ? rhs.resolveType(project) : null;
        }
        return srcType;
    }

    /**
     * Helper method to get the file path from an IASNode
     * @param iNode the node you want the file path for
     * @return A String representing the file path for the file the node came from
     */
    public static String getFileName(IASNode iNode)
    {
        String file_name = iNode.getSourcePath();

        if ( file_name == null )
        {
            //  Fall back on the file specification.
            //  This may also be null (or assert if it's feeling moody),
            //  in which case the file name remains null and file/line
            //  processing noops.
            try
            {
                IFileSpecification fs = iNode.getFileSpecification();
                if ( fs != null)
                    file_name = fs.getPath();
            }
            catch (Exception e)
            {
                //  No file specification available, probably
                //  because this node is some kind of syntho.
            }
        }
        return file_name;
    }
}
