Package org.openquark.cal.compiler

Source Code of org.openquark.cal.compiler.IdentifierResolver$Visitor

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

/*
* IdentifierResolver.java
* Created: Sep 5, 2007
* By: Joseph Wong
*/

package org.openquark.cal.compiler;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;

import org.openquark.cal.compiler.IdentifierOccurrence.Binding;
import org.openquark.cal.compiler.IdentifierOccurrence.ForeignDescriptor;
import org.openquark.cal.compiler.SourceIdentifier.Category;
import org.openquark.cal.compiler.SourceModel.ArgBindings;
import org.openquark.cal.compiler.SourceModel.CALDoc;
import org.openquark.cal.compiler.SourceModel.Expr;
import org.openquark.cal.compiler.SourceModel.FieldPattern;
import org.openquark.cal.compiler.SourceModel.FunctionDefn;
import org.openquark.cal.compiler.SourceModel.FunctionTypeDeclaration;
import org.openquark.cal.compiler.SourceModel.Import;
import org.openquark.cal.compiler.SourceModel.InstanceDefn;
import org.openquark.cal.compiler.SourceModel.LocalDefn;
import org.openquark.cal.compiler.SourceModel.ModuleDefn;
import org.openquark.cal.compiler.SourceModel.Name;
import org.openquark.cal.compiler.SourceModel.Parameter;
import org.openquark.cal.compiler.SourceModel.Pattern;
import org.openquark.cal.compiler.SourceModel.SourceElement;
import org.openquark.cal.compiler.SourceModel.TopLevelSourceElement;
import org.openquark.cal.compiler.SourceModel.TypeClassDefn;
import org.openquark.cal.compiler.SourceModel.TypeConstructorDefn;
import org.openquark.cal.compiler.SourceModel.TypeSignature;
import org.openquark.cal.compiler.SourceModel.Name.Module;
import org.openquark.util.Pair;

/**
* This is a non-instantiatable class containing a collection of inner classes related to the task of resolving identifiers
* in source code. The public API exposed by this class consists of:
*
* <dl>
* <dt>The {@link org.openquark.cal.compiler.IdentifierResolver.Context Context} interface
*
* <dd>This public interface encapsulates the notion of an external context for resolving identifiers -
* when an identifier could not be resolved within the scopes that are lexically declared in the source being analyzed,
* the context is consulted to see if the name is bound in the context.
* <p>
* There are factory methods to construct contexts based on {@link ModuleTypeInfo} and {@link CodeQualificationMap},
* as well as methods to construct empty contexts, and methods which combine two contexts.
*
*
* <dt>The {@link org.openquark.cal.compiler.IdentifierResolver.Visitor Visitor} class
*
* <dd>This is the base visitor that traverses the source model, gathers up the bindings declared therein, and visits each
* source element with the appropriate symbol table passed in as a visitation argument. This is the base class that should
* be extended if one wants to analyze a source model with information about which symbols are in scope.
* <p>
* The task of gathering up <i>references</i> (non-binding occurrences) is up to the subclass {@link IdentifierOccurrenceFinder}.
*
*
* <dt>The {@link org.openquark.cal.compiler.IdentifierResolver.VisitorArgument VisitorArgument} class
*
* <dd>This class encapsulates the visitation argument used with the visitor class. It contains information necessary for
* resolving identifiers, including the relevant symbol table and {@link LocalFunctionIdentifierGenerator}, as well as any
* user state which may be passed along.
* <p>
* This class is designed as an immutable class, with update methods which produce new copies with the appropriate fields updated.
* This means that, for example, one does not need to keep an explicitly stack of state (e.g. the symbol table) as one
* traverses up and down the source model.
*
*
* <dt>The {@link org.openquark.cal.compiler.IdentifierResolver.SymbolTable SymbolTable} class
*
* <dd>This family of immutable classes encapsulates a symbol table, representing a particular scope for identifier resolution. Symbol tables
* can correspond to three kinds of scopes:
* <ol>
* <li>top level scope - the scope where top-level (scoped) entities are defined.
* <li>local scope - a scope where local variables are defined. Local scopes can be nested in other local scopes, or be
*     directly under the top level scope.
* <li>root scope - this is the scope above the top level scope, which is associated with an external context.
* </ol>
* Symbol tables handle the lookup of module names, qualified names, and unqualified names.
*
*
* <dt>The {@link org.openquark.cal.compiler.IdentifierResolver.TypeVariableScope TypeVariableScope} class
* <dd>This immutable class encapsulates a scope for type variables.
* </dl>
*
* @author Joseph Wong
*/
public final class IdentifierResolver {

    /**
     * This public interface encapsulates the notion of an external context for resolving identifiers -
     * when an identifier could not be resolved within the scopes that are lexically declared in the source being analyzed,
     * the context is consulted to see if the name is bound in the context.
     * <p>
     * There are factory methods in {@link IdentifierResolver} to construct contexts based on {@link ModuleTypeInfo}
     * and {@link CodeQualificationMap}, as well as methods to construct empty contexts, and methods which combine two contexts.
     *
     * @author Joseph Wong
     */
    public static interface Context {
        /**
         * Resolves a module name, which may be partially qualified.
         * @param moduleName the module name. Can *not* be null.
         * @return the resolution result.
         */
        public ModuleNameResolver.ResolutionResult resolveModuleName(Name.Module moduleName);
        /**
         * Resolves an unqualified function or class method name.
         * @param unqualifiedFunctionOrClassMethodName the function or class method name. Can *not* be null.
         * @return the corresponding fully qualified name, or null if the name cannot be resolved.
         */
        public QualifiedName resolveFunctionOrClassMethodName(String unqualifiedFunctionOrClassMethodName);
        /**
         * Resolves an unqualified type constructor name.
         * @param unqualifiedTypeConsName the type constructor name. Can *not* be null.
         * @return the corresponding fully qualified name, or null if the name cannot be resolved.
         */
        public QualifiedName resolveTypeConsName(String unqualifiedTypeConsName);
        /**
         * Resolves an unqualified data constructor name.
         * @param unqualifiedDataConsName the data constructor name. Can *not* be null.
         * @return the corresponding fully qualified name, or null if the name cannot be resolved.
         */
        public QualifiedName resolveDataConsName(String unqualifiedDataConsName);
        /**
         * Resolves an unqualified type class name.
         * @param unqualifiedTypeClassName the type class name. Can *not* be null.
         * @return the corresponding fully qualified name, or null if the name cannot be resolved.
         */
        public QualifiedName resolveTypeClassName(String unqualifiedTypeClassName);
       
        /**
         * @return an external entity visibility checker, or null if there is none associated with this context.
         */
        public ExternalEntityVisibilityChecker getExternalEntityVisibilityChecker();
       
        /**
         * This public interface provides methods for checking the visibility of a given external entity in the context
         * of a particular module.
         *
         * @author Joseph Wong
         */
        public static interface ExternalEntityVisibilityChecker {
            /**
             * Checks whether the named function or class method is visible to the specified module.
             * @param currentModuleName the module from which the reference is made. Can *not* be null.
             * @param qualifiedFunctionOrClassMethodName the fully qualified name of the function or class method to check. Can *not* be null.
             * @return true if the named entity is visible; false otherwise.
             */
            public boolean isFunctionOrClassMethodVisible(ModuleName currentModuleName, QualifiedName qualifiedFunctionOrClassMethodName);
            /**
             * Checks whether the named type constructor is visible to the specified module.
             * @param currentModuleName the module from which the reference is made. Can *not* be null.
             * @param qualifiedTypeConsName the fully qualified name of the type constructor to check. Can *not* be null.
             * @return true if the named entity is visible; false otherwise.
             */
            public boolean isTypeConsVisible(ModuleName currentModuleName, QualifiedName qualifiedTypeConsName);
            /**
             * Checks whether the named data constructor is visible to the specified module.
             * @param currentModuleName the module from which the reference is made. Can *not* be null.
             * @param qualifiedDataConsName the fully qualified name of the data constructor to check. Can *not* be null.
             * @return true if the named entity is visible; false otherwise.
             */
            public boolean isDataConsVisible(ModuleName currentModuleName, QualifiedName qualifiedDataConsName);
            /**
             * Checks whether the named field exists in the named data constructor, and whether the data constructor is visible to the specified module.
             * @param currentModuleName the module from which the reference is made. Can *not* be null.
             * @param qualifiedDataConsName the fully qualified name of the data constructor to check. Can *not* be null.
             * @param fieldName the associated field name of the data constructor. Can *not* be null.
             * @return true if the named entity is visible; false otherwise.
             */
            public boolean isDataConsFieldNameVisible(ModuleName currentModuleName, QualifiedName qualifiedDataConsName, FieldName fieldName);
            /**
             * Checks whether the named type class is visible to the specified module.
             * @param currentModuleName the module from which the reference is made. Can *not* be null.
             * @param qualifiedTypeClassName the fully qualified name of the type class to check. Can *not* be null.
             * @return true if the named entity is visible; false otherwise.
             */
            public boolean isTypeClassVisible(ModuleName currentModuleName, QualifiedName qualifiedTypeClassName);
        }
    }
   
    /**
     * An empty context which binds no names.
     * This class is meant to be instantiated only by {@link IdentifierResolver#makeEmptyContext}.
     *
     * @author Joseph Wong
     */
    private static final class EmptyContext implements Context {
        /**
         * An empty module name resolver for producing module name resolutions.
         */
        private final ModuleNameResolver emptyModuleNameResolver = ModuleNameResolver.make(Collections.<ModuleName>emptySet());
        /**
         * An external entity visibility checker, or null if there is none associated with this context.
         */
        private final ExternalEntityVisibilityChecker visibilityChecker;
       
        /**
         * Constructs an instance of this class.
         * @param visibilityChecker an external entity visibility checker. Can be null.
         */
        EmptyContext(final ExternalEntityVisibilityChecker visibilityChecker) {
            this.visibilityChecker = visibilityChecker;
        }
       
        /** {@inheritDoc} */
        public ModuleNameResolver.ResolutionResult resolveModuleName(final Name.Module moduleName) {
            return emptyModuleNameResolver.resolve(SourceModel.Name.Module.toModuleName(moduleName));
        }
        /** {@inheritDoc} */
        public QualifiedName resolveFunctionOrClassMethodName(final String unqualifiedFunctionOrClassMethodName) {
            return null;
        }
        /** {@inheritDoc} */
        public QualifiedName resolveTypeConsName(final String unqualifiedTypeConsName) {
            return null;
        }
        /** {@inheritDoc} */
        public QualifiedName resolveDataConsName(final String unqualifiedDataConsName) {
            return null;
        }
        /** {@inheritDoc} */
        public QualifiedName resolveTypeClassName(final String unqualifiedTypeClassName) {
            return null;
        }
        /** {@inheritDoc} */
        public ExternalEntityVisibilityChecker getExternalEntityVisibilityChecker() {
            return visibilityChecker;
        }
    }
   
    /**
     * An external context for resolving identifiers based on a {@link ModuleTypeInfo} instance.
     * This is also an external entity visibility checker that works off of the information in the module type info.
     * This class is meant to be instantiated only by {@link IdentifierResolver#makeContext(ModuleTypeInfo)}.
     *
     * @author Joseph Wong
     */
    private static final class ModuleTypeInfoContext implements Context, Context.ExternalEntityVisibilityChecker {
       
        /**
         * The backing module type info, on which name resolutions will be based.
         */
        private final ModuleTypeInfo moduleTypeInfo;
       
        /**
         * Constructs an instance of this class.
         * @param moduleTypeInfo the backing module type info, on which name resolutions will be based.
         */
        ModuleTypeInfoContext(final ModuleTypeInfo moduleTypeInfo) {
            if (moduleTypeInfo == null) {
                throw new NullPointerException();
            }
            this.moduleTypeInfo = moduleTypeInfo;
        }

        /** {@inheritDoc} */
        public ModuleNameResolver.ResolutionResult resolveModuleName(final Name.Module moduleName) {
            return moduleTypeInfo.getModuleNameResolver().resolve(SourceModel.Name.Module.toModuleName(moduleName));
        }
       
        /** {@inheritDoc} */
        public QualifiedName resolveFunctionOrClassMethodName(final String unqualifiedFunctionOrClassMethodName) {
           
            final ModuleName importedModule =
                moduleTypeInfo.getModuleOfUsingFunctionOrClassMethod(unqualifiedFunctionOrClassMethodName);
           
            if (importedModule != null) {
                // the name is imported via an import using clause
                return QualifiedName.make(importedModule, unqualifiedFunctionOrClassMethodName);
               
            } else if (moduleTypeInfo.getFunctionOrClassMethod(unqualifiedFunctionOrClassMethodName) != null) {
                // the name is defined in this module
                return QualifiedName.make(moduleTypeInfo.getModuleName(), unqualifiedFunctionOrClassMethodName);
            }
           
            return null;
        }
       
        /** {@inheritDoc} */
        public QualifiedName resolveTypeConsName(final String unqualifiedTypeConsName) {
           
            final ModuleName importedModule =
                moduleTypeInfo.getModuleOfUsingTypeConstructor(unqualifiedTypeConsName);
           
            if (importedModule != null) {
                // the name is imported via an import using clause
                return QualifiedName.make(importedModule, unqualifiedTypeConsName);
               
            } else if (moduleTypeInfo.getTypeConstructor(unqualifiedTypeConsName) != null) {
                // the name is defined in this module
                return QualifiedName.make(moduleTypeInfo.getModuleName(), unqualifiedTypeConsName);
            }
           
            return null;
        }
       
        /** {@inheritDoc} */
        public QualifiedName resolveDataConsName(final String unqualifiedDataConsName) {
           
            final ModuleName importedModule =
                moduleTypeInfo.getModuleOfUsingDataConstructor(unqualifiedDataConsName);
           
            if (importedModule != null) {
                // the name is imported via an import using clause
                return QualifiedName.make(importedModule, unqualifiedDataConsName);
               
            } else if (moduleTypeInfo.getDataConstructor(unqualifiedDataConsName) != null) {
                // the name is defined in this module
                return QualifiedName.make(moduleTypeInfo.getModuleName(), unqualifiedDataConsName);
            }
           
            return null;
        }

        /** {@inheritDoc} */
        public QualifiedName resolveTypeClassName(final String unqualifiedTypeClassName) {
           
            final ModuleName importedModule =
                moduleTypeInfo.getModuleOfUsingTypeClass(unqualifiedTypeClassName);
           
            if (importedModule != null) {
                // the name is imported via an import using clause
                return QualifiedName.make(importedModule, unqualifiedTypeClassName);
               
            } else if (moduleTypeInfo.getTypeClass(unqualifiedTypeClassName) != null) {
                // the name is defined in this module
                return QualifiedName.make(moduleTypeInfo.getModuleName(), unqualifiedTypeClassName);
            }
           
            return null;
        }

        /**
         * {@inheritDoc}
         * @return this instance, which is also an external entity visibility checker.
         */
        public ExternalEntityVisibilityChecker getExternalEntityVisibilityChecker() {
            return this;
        }

        /** {@inheritDoc} */
        public boolean isDataConsVisible(final ModuleName currentModuleName, final QualifiedName qualifiedDataConsName) {
            // we only support checking the visibility of entities referenced in the module associated with the module type info
            if (!moduleTypeInfo.getModuleName().equals(currentModuleName)) {
                throw new IllegalArgumentException();
            }
            return moduleTypeInfo.getVisibleDataConstructor(qualifiedDataConsName) != null;
        }
       
        /** {@inheritDoc} */
        public boolean isDataConsFieldNameVisible(ModuleName currentModuleName, QualifiedName qualifiedDataConsName, FieldName fieldName) {
            // we only support checking the visibility of entities referenced in the module associated with the module type info
            if (!moduleTypeInfo.getModuleName().equals(currentModuleName)) {
                throw new IllegalArgumentException();
            }
            // first make sure the data constructor is visible
            final DataConstructor dataConstructor = moduleTypeInfo.getVisibleDataConstructor(qualifiedDataConsName);
            if (dataConstructor == null) {
                return false;
            } else {
                // then check whether the field actually exists in that data cons
                return dataConstructor.getFieldIndex(fieldName) != -1;
            }
        }

        /** {@inheritDoc} */
        public boolean isFunctionOrClassMethodVisible(final ModuleName currentModuleName, final QualifiedName qualifiedFunctionOrClassMethodName) {
            // we only support checking the visibility of entities referenced in the module associated with the module type info
            if (!moduleTypeInfo.getModuleName().equals(currentModuleName)) {
                throw new IllegalArgumentException();
            }
            return
                moduleTypeInfo.getVisibleClassMethod(qualifiedFunctionOrClassMethodName) != null
                || moduleTypeInfo.getVisibleFunction(qualifiedFunctionOrClassMethodName) != null;
        }

        /** {@inheritDoc} */
        public boolean isTypeClassVisible(final ModuleName currentModuleName, final QualifiedName qualifiedTypeClassName) {
            // we only support checking the visibility of entities referenced in the module associated with the module type info
            if (!moduleTypeInfo.getModuleName().equals(currentModuleName)) {
                throw new IllegalArgumentException();
            }
            return moduleTypeInfo.getVisibleTypeClass(qualifiedTypeClassName) != null;
        }

        /** {@inheritDoc} */
        public boolean isTypeConsVisible(final ModuleName currentModuleName, final QualifiedName qualifiedTypeConsName) {
            // we only support checking the visibility of entities referenced in the module associated with the module type info
            if (!moduleTypeInfo.getModuleName().equals(currentModuleName)) {
                throw new IllegalArgumentException();
            }
            return moduleTypeInfo.getVisibleTypeConstructor(qualifiedTypeConsName) != null;
        }
    }
   
    /**
     * An external context for resolving identifiers based on a {@link CodeQualificationMap} instance.
     * This class is meant to be instantiated only by {@link IdentifierResolver#makeContext(CodeQualificationMap)}.
     *
     * @author Joseph Wong
     */
    private static final class CodeQualificationMapContext implements Context {
       
        /**
         * The module name resolver for resolving module names.
         */
        private final ModuleNameResolver moduleNameResolver;
       
        /**
         * The backing code qualification map, on which name resolutions will be based.
         */
        private final CodeQualificationMap codeQualificationMap;
       
        /**
         * Constructs an instance of this class.
         * @param codeQualificationMap the backing code qualification map, on which name resolutions will be based.
         */
        CodeQualificationMapContext(final CodeQualificationMap codeQualificationMap) {
            if (codeQualificationMap == null) {
                throw new NullPointerException();
            }
            // todo-jowong in the future, we can support custom mappings-based module name resolution as well
            this.moduleNameResolver = ModuleNameResolver.make(Collections.<ModuleName>emptySet());
            this.codeQualificationMap = codeQualificationMap;
        }

        /** {@inheritDoc} */
        public ModuleNameResolver.ResolutionResult resolveModuleName(final Name.Module moduleName) {
            return moduleNameResolver.resolve(SourceModel.Name.Module.toModuleName(moduleName));
        }
       
        /** {@inheritDoc} */
        public QualifiedName resolveFunctionOrClassMethodName(final String unqualifiedFunctionOrClassMethodName) {
            return codeQualificationMap.getQualifiedName(
                unqualifiedFunctionOrClassMethodName, Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD);
        }
       
        /** {@inheritDoc} */
        public QualifiedName resolveTypeConsName(final String unqualifiedTypeConsName) {
            return codeQualificationMap.getQualifiedName(unqualifiedTypeConsName, Category.TYPE_CONSTRUCTOR);
        }
       
        /** {@inheritDoc} */
        public QualifiedName resolveDataConsName(final String unqualifiedDataConsName) {
            return codeQualificationMap.getQualifiedName(unqualifiedDataConsName, Category.DATA_CONSTRUCTOR);
        }

        /** {@inheritDoc} */
        public QualifiedName resolveTypeClassName(final String unqualifiedTypeClassName) {
            return codeQualificationMap.getQualifiedName(unqualifiedTypeClassName, Category.TYPE_CLASS);
        }

        /** {@inheritDoc} */
        public ExternalEntityVisibilityChecker getExternalEntityVisibilityChecker() {
            return null;
        }
    }
   
    /**
     * This is a context combiner which joins two contexts into one, with one acting as the base, and the other acting as
     * the override (the override takes precedence for resolutions).
     *
     * @author Joseph Wong
     */
    private static final class OverrideContext implements Context {
       
        /**
         * The base context.
         */
        private final Context baseContext;
       
        /**
         * The override context - this has precedence over the base context.
         */
        private final Context overrideContext;
       
        /**
         * Constructs an instance of this class.
         * @param baseContext the base context.
         * @param overrideContext the override context - this has precedence over the base context.
         */
        OverrideContext(final Context baseContext, final Context overrideContext) {
            if (baseContext == null || overrideContext == null) {
                throw new NullPointerException();
            }
            this.baseContext = baseContext;
            this.overrideContext = overrideContext;
        }

        /** {@inheritDoc} */
        public ModuleNameResolver.ResolutionResult resolveModuleName(final Name.Module moduleName) {
            final ModuleNameResolver.ResolutionResult override = overrideContext.resolveModuleName(moduleName);
            if (override.isKnownUnambiguous()) {
                return override;
            } else {
                return baseContext.resolveModuleName(moduleName);
            }
        }
       
        /** {@inheritDoc} */
        public QualifiedName resolveFunctionOrClassMethodName(final String unqualifiedFunctionOrClassMethodName) {
            final QualifiedName override = overrideContext.resolveFunctionOrClassMethodName(unqualifiedFunctionOrClassMethodName);
            if (override != null) {
                return override;
            } else {
                return baseContext.resolveFunctionOrClassMethodName(unqualifiedFunctionOrClassMethodName);
            }
        }
       
        /** {@inheritDoc} */
        public QualifiedName resolveTypeConsName(final String unqualifiedTypeConsName) {
            final QualifiedName override = overrideContext.resolveTypeConsName(unqualifiedTypeConsName);
            if (override != null) {
                return override;
            } else {
                return baseContext.resolveTypeConsName(unqualifiedTypeConsName);
            }
        }
       
        /** {@inheritDoc} */
        public QualifiedName resolveDataConsName(final String unqualifiedDataConsName) {
            final QualifiedName override = overrideContext.resolveDataConsName(unqualifiedDataConsName);
            if (override != null) {
                return override;
            } else {
                return baseContext.resolveDataConsName(unqualifiedDataConsName);
            }
        }

        /** {@inheritDoc} */
        public QualifiedName resolveTypeClassName(final String unqualifiedTypeClassName) {
            final QualifiedName override = overrideContext.resolveTypeClassName(unqualifiedTypeClassName);
            if (override != null) {
                return override;
            } else {
                return baseContext.resolveTypeClassName(unqualifiedTypeClassName);
            }
        }

        /** {@inheritDoc} */
        public ExternalEntityVisibilityChecker getExternalEntityVisibilityChecker() {
            final ExternalEntityVisibilityChecker override = overrideContext.getExternalEntityVisibilityChecker();
            if (override != null) {
                return override;
            } else {
                return baseContext.getExternalEntityVisibilityChecker();
            }
        }
    }
   
   
    /**
     * An exception representing a repeated definition in a particular scope.
     *
     * @author Joseph Wong
     */
    public static final class RepeatedDefinition extends RuntimeException {
        private static final long serialVersionUID = -4578199360030867990L;
       
        /**
         * The name being defined repeatedly.
         */
        private final String name;
        /**
         * The corresponding identifier info.
         */
        private final IdentifierInfo identifierInfo;
       
        /**
         * Constructs an instance of this exception.
         * @param name the name being defined repeatedly.
         * @param identifierInfo the corresponding identifier info.
         */
        RepeatedDefinition(final String name, final IdentifierInfo identifierInfo) {
            super("Repeated definition of the variable " + name);
            this.name = name;
            this.identifierInfo = identifierInfo;
        }

        /**
         * @return the name being defined repeatedly.
         */
        public String getName() {
            return name;
        }

        /**
         * @return the corresponding identifier info.
         */
        public IdentifierInfo getIdentifierInfo() {
            return identifierInfo;
        }
    }
   
    /**
     * A subclass of {@link LinkedHashMap} that maps names to their corresponding bindings, and handling the detection
     * of repeated bindings in the same scope.
     * @param <I> the kind of identifiers handled by this map.
     *
     * @author Joseph Wong
     */
    private static class BindingsMap<I extends IdentifierInfo> extends LinkedHashMap<String, Binding<I>> {
        private static final long serialVersionUID = 1384828040556279035L;
       
        /**
         * Factory method for constructing an instance.
         * @return a new instance of this class.
         */
        static <I extends IdentifierInfo> BindingsMap<I> make() {
            return new BindingsMap<I>();
        }

        /**
         * {@inheritDoc}
         * @throws RepeatedDefinition if the name has already been defined.
         */
        @Override
        public Binding<I> put(final String key, final Binding<I> value) {
            if (containsKey(key)) {
                throw new RepeatedDefinition(key, value.getIdentifierInfo());
            }
            return super.put(key, value);
        }
    }
   
    /**
     * Abstract base class for a family of immutable classes that encapsulates a symbol table,
     * representing a particular scope for identifier resolution. Symbol tables handle the lookup of
     * module names, qualified names, and unqualified names.
     *
     * @author Joseph Wong
     */
    /*
     * @history
     *
     * This class is based on the class CALSourceGenerator.LambdaDefiningExprScopeAnalyzer.Scope.
     */
    public static abstract class SymbolTable {
       
        /**
         * Represents a scope above the top level scope that is associated with an external name resolution context.
         *
         * @author Joseph Wong
         */
        public static final class RootScope extends SymbolTable {

            /**
             * The name of the current module. Can *not* be null.
             */
            private final ModuleName currentModuleName;
           
            /**
             * The external context for resolving identifiers. Can *not* be null.
             */
            private final Context context;

            /**
             * Constructs an instance of this class.
             * @param currentModuleName the name of the current module. Can *not* be null.
             * @param context the external context for resolving identifiers. Can *not* be null.
             */
            private RootScope(final ModuleName currentModuleName, final Context context) {
                if (currentModuleName == null || context == null) {
                    throw new NullPointerException();
                }
                this.currentModuleName = currentModuleName;
                this.context = context;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            Binding<? extends IdentifierInfo> findVariableDefinition(final String varName) {
                // this is a top-level scope, so just delegate to findTopLevelFunctionOrClassMethodDefinition()
                return findTopLevelFunctionOrClassMethodDefinition(varName);
            }

            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> findTopLevelFunctionOrClassMethodDefinition(final String varName) {
                final QualifiedName resolvedName = context.resolveFunctionOrClassMethodName(varName);
                if (resolvedName != null) {
                    return Binding.External.make(
                        new IdentifierInfo.TopLevel.FunctionOrClassMethod(resolvedName));
                } else {
                    return null;
                }
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> findQualifiedFunctionOrClassMethod(final Name.Function functionName, final SymbolTable innermostScope) {
                assert functionName.getModuleName() != null;
               
                final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(functionName.getModuleName());
                if (moduleNameBinding != null) {
                    final QualifiedName qualifiedFunctionName = QualifiedName.make(moduleNameBinding.getIdentifierInfo().getResolvedName(), functionName.getUnqualifiedName());
                   
                    // only return a binding if the named function is visible in this module
                    final boolean isVisible;
                    if (context.getExternalEntityVisibilityChecker() != null) {
                        isVisible = context.getExternalEntityVisibilityChecker().isFunctionOrClassMethodVisible(currentModuleName, qualifiedFunctionName);
                    } else {
                        // no visibility checker, so just assume the name is visible
                        isVisible = true;
                    }
                   
                    if (isVisible) {
                        return Binding.External.make(new IdentifierInfo.TopLevel.FunctionOrClassMethod(qualifiedFunctionName));
                    } else {
                        return null;
                    }
                } else {
                    return null;
                }
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.DataCons> findDataCons(final Name.DataCons dataConsName, final SymbolTable innermostScope) {

                final Name.Module moduleName = dataConsName.getModuleName();
                if (moduleName != null) {
                    final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(moduleName);

                    if (moduleNameBinding != null) {
                        final QualifiedName qualifiedDataConsName = QualifiedName.make(moduleNameBinding.getIdentifierInfo().getResolvedName(), dataConsName.getUnqualifiedName());
                       
                        // only return a binding if the named data cons is visible in this module
                        final boolean isVisible;
                        if (context.getExternalEntityVisibilityChecker() != null) {
                            isVisible = context.getExternalEntityVisibilityChecker().isDataConsVisible(currentModuleName, qualifiedDataConsName);
                        } else {
                            // no visibility checker, so just assume the name is visible
                            isVisible = true;
                        }

                        if (isVisible) {
                            return Binding.External.make(new IdentifierInfo.TopLevel.DataCons(qualifiedDataConsName));
                        } else {
                            return null;
                        }
                    }

                } else {
                    final QualifiedName resolvedDataConsName = context.resolveDataConsName(dataConsName.getUnqualifiedName());

                    if (resolvedDataConsName != null) {
                        return Binding.External.make(
                            new IdentifierInfo.TopLevel.DataCons(resolvedDataConsName));
                    }
                }
               
                return null;
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.DataConsFieldName> findDataConsFieldName(final Name.DataCons dataConsName, final Name.Field fieldName, final SymbolTable innermostScope) {
                final Binding<IdentifierInfo.TopLevel.DataCons> dataConsBinding = innermostScope.findDataCons(dataConsName, innermostScope);
                if (dataConsBinding != null) {
                    final IdentifierInfo.TopLevel.DataCons dataConsIdentifier = dataConsBinding.getIdentifierInfo();
                   
                    // only return a binding if the named data cons field is visible in this module
                    final boolean isVisible;
                    if (context.getExternalEntityVisibilityChecker() != null) {
                        isVisible = context.getExternalEntityVisibilityChecker().isDataConsFieldNameVisible(currentModuleName, dataConsIdentifier.getResolvedName(), fieldName.getName());
                    } else {
                        // no visibility checker, so just assume the name is visible
                        isVisible = true;
                    }

                    if (isVisible) {
                        return Binding.External.make(
                            new IdentifierInfo.DataConsFieldName(
                                fieldName.getName(), Collections.singletonList(dataConsIdentifier)));
                    } else {
                        return null;
                    }
                }
               
                return null;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.TypeCons> findTypeCons(final Name.TypeCons typeConsName, final SymbolTable innermostScope) {

                final Name.Module moduleName = typeConsName.getModuleName();
                if (moduleName != null) {
                    final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(moduleName);

                    if (moduleNameBinding != null) {
                        final QualifiedName qualifiedTypeConsName = QualifiedName.make(moduleNameBinding.getIdentifierInfo().getResolvedName(), typeConsName.getUnqualifiedName());

                        // only return a binding if the named type cons is visible in this module
                        final boolean isVisible;
                        if (context.getExternalEntityVisibilityChecker() != null) {
                            isVisible = context.getExternalEntityVisibilityChecker().isTypeConsVisible(currentModuleName, qualifiedTypeConsName);
                        } else {
                            // no visibility checker, so just assume the name is visible
                            isVisible = true;
                        }

                        if (isVisible) {
                            return Binding.External.make(new IdentifierInfo.TopLevel.TypeCons(qualifiedTypeConsName));
                        } else {
                            return null;
                        }
                    }

                } else {
                    final QualifiedName resolvedTypeConsName = context.resolveTypeConsName(typeConsName.getUnqualifiedName());

                    if (resolvedTypeConsName != null) {
                        return Binding.External.make(
                            new IdentifierInfo.TopLevel.TypeCons(resolvedTypeConsName));
                    }
                }
               
                return null;
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.TypeClass> findTypeClass(final Name.TypeClass typeClassName, final SymbolTable innermostScope) {

                final Name.Module moduleName = typeClassName.getModuleName();
                if (moduleName != null) {
                    final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(moduleName);

                    if (moduleNameBinding != null) {
                        final QualifiedName qualifiedTypeClassName = QualifiedName.make(moduleNameBinding.getIdentifierInfo().getResolvedName(), typeClassName.getUnqualifiedName());

                        // only return a binding if the named type class is visible in this module
                        final boolean isVisible;
                        if (context.getExternalEntityVisibilityChecker() != null) {
                            isVisible = context.getExternalEntityVisibilityChecker().isTypeClassVisible(currentModuleName, qualifiedTypeClassName);
                        } else {
                            // no visibility checker, so just assume the name is visible
                            isVisible = true;
                        }

                        if (isVisible) {
                            return Binding.External.make(new IdentifierInfo.TopLevel.TypeClass(qualifiedTypeClassName));
                        } else {
                            return null;
                        }
                    }

                } else {
                    final QualifiedName resolvedTypeClassName = context.resolveTypeClassName(typeClassName.getUnqualifiedName());

                    if (resolvedTypeClassName != null) {
                        return Binding.External.make(
                            new IdentifierInfo.TopLevel.TypeClass(resolvedTypeClassName));
                    }
                }
               
                return null;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.Module> resolveMaybeModuleName(final Name.Module moduleName) {
                if (moduleName == null) {
                    return null;
                }
               
                final ModuleNameResolver.ResolutionResult resolution = context.resolveModuleName(moduleName);
                if (resolution.isKnownUnambiguous()) {
                    return Binding.External.make(
                        new IdentifierInfo.Module(resolution.getResolvedModuleName()));
                } else {
                    return null;
                }
            }
        }

        /**
         * Represents the scope where top-level (scoped) entities are defined.
         *
         * @author Joseph Wong
         */
        public static final class TopLevelScope extends SymbolTable {
            /**
             * The parent scope.
             */
            private final SymbolTable parent;

            /**
             * The binding for the name of the current module.
             */
            private final Binding<IdentifierInfo.Module> currentModuleNameBinding;
           
            /**
             * A map from unqualified names to bindings of functions and class methods.
             * (The source ranges stored may be null.)
             */
            private final BindingsMap<IdentifierInfo.TopLevel.FunctionOrClassMethod> functionAndClassMethodBindings;

            /**
             * A map from unqualified names to bindings of type constructors.
             * (The source ranges stored may be null.)
             */
            private final BindingsMap<IdentifierInfo.TopLevel.TypeCons> typeConsBindings;

            /**
             * A map from unqualified names to bindings of data constructors.
             * (The source ranges stored may be null.)
             */
            private final BindingsMap<IdentifierInfo.TopLevel.DataCons> dataConsBindings;
           
            /**
             * A map from names to bindings of data constructors.
             * The name with which we associate a data cons field name is "<dataConsName>.<fieldName>".
             * (The source ranges stored may be null.)
             */
            private final BindingsMap<IdentifierInfo.DataConsFieldName> dataConsFieldNameBindings;

            /**
             * A map from unqualified names to bindings of type classes.
             * (The source ranges stored may be null.)
             */
            private final BindingsMap<IdentifierInfo.TopLevel.TypeClass> typeClassBindings;
           
            /**
             * An ordered set of the imported module names.
             */
            private final LinkedHashSet<ModuleName> importedModuleNames;
           
            /**
             * The module name resolver built upon the imported modules and the current module name.
             */
            private final ModuleNameResolver moduleNameResolver;

            /**
             * Constructs an instance of this class.
             * @param parent the parent scope.
             * @param currentModuleNameBinding the binding for the name of the current module.
             * @param functionAndClassMethodBindings a map from unqualified names to bindings of functions and class methods.
             * @param typeConsBindings a map from unqualified names to bindings of type constructors.
             * @param dataConsBindings a map from unqualified names to bindings of data constructors.
             * @param dataConsFieldNameBindings a map from names to bindings of data constructors.
             * The name with which we associate a data cons field name is "<dataConsName>.<fieldName>".
             * @param typeClassBindings a map from unqualified names to bindings of type classes.
             * @param importedModuleNames an ordered collection of the imported module names.
             */
            TopLevelScope(
                final SymbolTable parent,
                final Binding<IdentifierInfo.Module> currentModuleNameBinding,
                final BindingsMap<IdentifierInfo.TopLevel.FunctionOrClassMethod> functionAndClassMethodBindings,
                final BindingsMap<IdentifierInfo.TopLevel.TypeCons> typeConsBindings,
                final BindingsMap<IdentifierInfo.TopLevel.DataCons> dataConsBindings,
                final BindingsMap<IdentifierInfo.DataConsFieldName> dataConsFieldNameBindings,
                final BindingsMap<IdentifierInfo.TopLevel.TypeClass> typeClassBindings,
                final Collection<ModuleName> importedModuleNames) {
               
                if (parent == null || currentModuleNameBinding == null || functionAndClassMethodBindings == null || typeConsBindings == null
                        || dataConsBindings == null || dataConsFieldNameBindings == null || typeClassBindings == null || importedModuleNames == null) {
                    throw new NullPointerException();
                }
                this.parent = parent;
                this.currentModuleNameBinding = currentModuleNameBinding;
                this.functionAndClassMethodBindings = functionAndClassMethodBindings;
                this.typeConsBindings = typeConsBindings;
                this.dataConsBindings = dataConsBindings;
                this.dataConsFieldNameBindings = dataConsFieldNameBindings;
                this.typeClassBindings = typeClassBindings;
                this.importedModuleNames = new LinkedHashSet<ModuleName>(importedModuleNames);
                this.moduleNameResolver = ModuleNameResolver.make(currentModuleNameBinding.getIdentifierInfo().getResolvedName(), this.importedModuleNames);
            }
           
            /**
             * @return the parent scope.
             */
            public SymbolTable getParent() {
                return parent;
            }
           
            /**
             * @return the binding for the name of the current module.
             */
            public Binding<IdentifierInfo.Module> getCurrentModuleNameBinding() {
                return currentModuleNameBinding;
            }

            /**
             * @return an iterable collection of the function and class method bindings.
             */
            public Iterable<Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod>> getFunctionAndClassMethodBindings() {
                return Collections.unmodifiableCollection(functionAndClassMethodBindings.values());
            }

            /**
             * @return an iterable collection of the type constructor bindings.
             */
            public Iterable<Binding<IdentifierInfo.TopLevel.TypeCons>> getTypeConsBindings() {
                return Collections.unmodifiableCollection(typeConsBindings.values());
            }

            /**
             * @return an iterable collection of the data constructor bindings.
             */
            public Iterable<Binding<IdentifierInfo.TopLevel.DataCons>> getDataConsBindings() {
                return Collections.unmodifiableCollection(dataConsBindings.values());
            }

            /**
             * @return an iterable collection of the data constructor field name bindings.
             */
            public Iterable<Binding<IdentifierInfo.DataConsFieldName>> getDataConsFieldNameBindings() {
                return Collections.unmodifiableCollection(dataConsFieldNameBindings.values());
            }

            /**
             * @return an iterable collection of the type class bindings.
             */
            public Iterable<Binding<IdentifierInfo.TopLevel.TypeClass>> getTypeClassBindings() {
                return Collections.unmodifiableCollection(typeClassBindings.values());
            }

            /**
             * {@inheritDoc}
             */
            @Override
            Binding<? extends IdentifierInfo> findVariableDefinition(final String varName) {
                // this is a top-level scope, so just delegate to findTopLevelFunctionOrClassMethodDefinition()
                return findTopLevelFunctionOrClassMethodDefinition(varName);
            }

            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> findTopLevelFunctionOrClassMethodDefinition(final String varName) {
                // check this scope first
                final Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> binding = functionAndClassMethodBindings.get(varName);
                if (binding != null) {
                    return binding;
                }

                // unqualified name not found in this scope, so delegate to parent
                return parent.findTopLevelFunctionOrClassMethodDefinition(varName);
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> findQualifiedFunctionOrClassMethod(final Name.Function functionName, final SymbolTable innermostScope) {
                assert functionName.getModuleName() != null;
               
                final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(functionName.getModuleName());
                if (moduleNameBinding != null) {
                    if (moduleNameBinding.getIdentifierInfo().getResolvedName().equals(currentModuleNameBinding.getIdentifierInfo().getResolvedName())) {
                        // check this scope first
                        final Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> binding = functionAndClassMethodBindings.get(functionName.getUnqualifiedName());
                        if (binding != null) {
                            return binding;
                        }
                    }
                   
                    if (importedModuleNames.contains(moduleNameBinding.getIdentifierInfo().getResolvedName())) {
                        // defined in an imported module, so delegate to parent
                        return parent.findQualifiedFunctionOrClassMethod(functionName, innermostScope);
                    }
                }
               
                // a qualified name whose module name is not in the list of imported module names, so the entity cannot be visible
                // so just return null
                return null;
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.DataCons> findDataCons(final Name.DataCons dataConsName, final SymbolTable innermostScope) {
               
                boolean inCurrentModule = false;
                if (dataConsName.getModuleName() != null) {

                    final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(dataConsName.getModuleName());
                    if (moduleNameBinding != null) {
                        if (moduleNameBinding.getIdentifierInfo().getResolvedName().equals(currentModuleNameBinding.getIdentifierInfo().getResolvedName())) {
                            inCurrentModule = true;
                        }
                    }
                } else {
                    inCurrentModule = true;
                }

                if (inCurrentModule) {
                    final Binding<IdentifierInfo.TopLevel.DataCons> binding = dataConsBindings.get(dataConsName.getUnqualifiedName());
                    if (binding != null) {
                        return binding;
                    }
                }
               
                if (dataConsName.getModuleName() != null) {
                    final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(dataConsName.getModuleName());
                    if (moduleNameBinding != null) {
                        if (importedModuleNames.contains(moduleNameBinding.getIdentifierInfo().getResolvedName())) {
                            // defined in an imported module, so delegate to parent
                            return parent.findDataCons(dataConsName, innermostScope);
                        }
                    }
                } else {
                    // unqualified name not found in this scope, so delegate to parent
                    return parent.findDataCons(dataConsName, innermostScope);
                }
               
                // a qualified name whose module name is not in the list of imported module names, so the entity cannot be visible
                // so just return null
                return null;
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.DataConsFieldName> findDataConsFieldName(final Name.DataCons dataConsName, final Name.Field fieldName, final SymbolTable innermostScope) {
               
                boolean inCurrentModule = false;
                if (dataConsName.getModuleName() != null) {

                    final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(dataConsName.getModuleName());
                    if (moduleNameBinding != null) {
                        if (moduleNameBinding.getIdentifierInfo().getResolvedName().equals(currentModuleNameBinding.getIdentifierInfo().getResolvedName())) {
                            inCurrentModule = true;
                        }
                    }
                } else {
                    // the data cons has to be defined in the current module, not just visible as an unqualified name
                    // in the current module (imported via an import using clause)
                    final Binding<IdentifierInfo.TopLevel.DataCons> dataConsBinding = dataConsBindings.get(dataConsName.getUnqualifiedName());
                    if (dataConsBinding instanceof Binding.Definition) {
                        inCurrentModule = true;
                    }
                }
               
                if (inCurrentModule) {
                    final String key = dataConsName.getUnqualifiedName() + "." + fieldName.getName().getCalSourceForm();
                   
                    final Binding<IdentifierInfo.DataConsFieldName> binding = dataConsFieldNameBindings.get(key);
                    if (binding != null) {
                        return binding;
                    }
                }
               
                if (dataConsName.getModuleName() != null) {
                    final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(dataConsName.getModuleName());
                    if (moduleNameBinding != null) {
                        if (importedModuleNames.contains(moduleNameBinding.getIdentifierInfo().getResolvedName())) {
                            // defined in an imported module, so delegate to parent
                            return parent.findDataConsFieldName(dataConsName, fieldName, innermostScope);
                        }
                    }
                } else {
                    // unqualified name not found in this scope, so delegate to parent
                    return parent.findDataConsFieldName(dataConsName, fieldName, innermostScope);
                }
               
                // a qualified name whose module name is not in the list of imported module names, so the entity cannot be visible
                // so just return null
                return null;
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.TypeCons> findTypeCons(final Name.TypeCons typeConsName, final SymbolTable innermostScope) {
               
                boolean inCurrentModule = false;
                if (typeConsName.getModuleName() != null) {

                    final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(typeConsName.getModuleName());
                    if (moduleNameBinding != null) {
                        if (moduleNameBinding.getIdentifierInfo().getResolvedName().equals(currentModuleNameBinding.getIdentifierInfo().getResolvedName())) {
                            inCurrentModule = true;
                        }
                    }
                } else {
                    inCurrentModule = true;
                }
               
                if (inCurrentModule) {
                    final Binding<IdentifierInfo.TopLevel.TypeCons> binding = typeConsBindings.get(typeConsName.getUnqualifiedName());
                    if (binding != null) {
                        return binding;
                    }
                }
               
                if (typeConsName.getModuleName() != null) {
                    final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(typeConsName.getModuleName());
                    if (moduleNameBinding != null) {
                        if (importedModuleNames.contains(moduleNameBinding.getIdentifierInfo().getResolvedName())) {
                            // defined in an imported module, so delegate to parent
                            return parent.findTypeCons(typeConsName, innermostScope);
                        }
                    }
                } else {
                    // unqualified name not found in this scope, so delegate to parent
                    return parent.findTypeCons(typeConsName, innermostScope);
                }
               
                // a qualified name whose module name is not in the list of imported module names, so the entity cannot be visible
                // so just return null
                return null;
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.TypeClass> findTypeClass(final Name.TypeClass typeClassName, final SymbolTable innermostScope) {
               
                boolean inCurrentModule = false;
                if (typeClassName.getModuleName() != null) {

                    final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(typeClassName.getModuleName());
                    if (moduleNameBinding != null) {
                        if (moduleNameBinding.getIdentifierInfo().getResolvedName().equals(currentModuleNameBinding.getIdentifierInfo().getResolvedName())) {
                            inCurrentModule = true;
                        }
                    }
                } else {
                    inCurrentModule = true;
                }
               
                if (inCurrentModule) {
                    final Binding<IdentifierInfo.TopLevel.TypeClass> binding = typeClassBindings.get(typeClassName.getUnqualifiedName());
                    if (binding != null) {
                        return binding;
                    }
                }
               
                if (typeClassName.getModuleName() != null) {
                    final IdentifierOccurrence<IdentifierInfo.Module> moduleNameBinding = innermostScope.resolveMaybeModuleName(typeClassName.getModuleName());
                    if (moduleNameBinding != null) {
                        if (importedModuleNames.contains(moduleNameBinding.getIdentifierInfo().getResolvedName())) {
                            // defined in an imported module, so delegate to parent
                            return parent.findTypeClass(typeClassName, innermostScope);
                        }
                    }
                } else {
                    // unqualified name not found in this scope, so delegate to parent
                    return parent.findTypeClass(typeClassName, innermostScope);
                }
               
                // a qualified name whose module name is not in the list of imported module names, so the entity cannot be visible
                // so just return null
                return null;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.Module> resolveMaybeModuleName(final Name.Module moduleName) {
                if (moduleName == null) {
                    return null;
                }
               
                final ModuleNameResolver.ResolutionResult resolution = moduleNameResolver.resolve(SourceModel.Name.Module.toModuleName(moduleName));
                if (resolution.isKnownUnambiguous()) {
                    if (resolution.getResolvedModuleName().equals(currentModuleNameBinding.getIdentifierInfo().getResolvedName())) {
                        return currentModuleNameBinding;
                    } else {
                        return Binding.External.make(
                            new IdentifierInfo.Module(resolution.getResolvedModuleName()));
                    }
                } else if (resolution.isUnknown()) {
                    // cannot find the name in this scope, so delegate to parent
                    return parent.resolveMaybeModuleName(moduleName);
                } else {
                    // the name is ambiguous, so we return null in this case
                    return null;
                }
            }
        }
       
        /**
         * Represents a scope where local variables are defined. Local scopes can be nested in other local scopes, or be
         * directly under the top level scope.
         *
         * @author Joseph Wong
         */
        public static final class LocalScope extends SymbolTable {
            /**
             * The parent scope.
             */
            private final SymbolTable parent;
           
            /**
             * A map from unqualified names to bindings of local variables.
             * (The source ranges stored may be null.)
             */
            private final BindingsMap<IdentifierInfo.Local> bindings;

            /**
             * Constructs an instance of this class.
             * @param parent the parent scope.
             * @param bindings a map from unqualified names to bindings of local variables.
             */
            private LocalScope(final SymbolTable parent, final BindingsMap<IdentifierInfo.Local> bindings) {
                if (parent == null) {
                    throw new NullPointerException();
                }
                this.parent = parent;
                this.bindings = bindings;
            }
           
            /**
             * @return the parent scope.
             */
            public SymbolTable getParent() {
                return parent;
            }

            /**
             * @return a map from unqualified names to bindings of local variables.
             */
            public Iterable<Binding<IdentifierInfo.Local>> getBindings() {
                return Collections.unmodifiableCollection(bindings.values());
            }

            /**
             * {@inheritDoc}
             */
            @Override
            Binding<? extends IdentifierInfo> findVariableDefinition(final String varName) {
                // check this scope first
                final Binding<IdentifierInfo.Local> binding = bindings.get(varName);
                if (binding != null) {
                    return binding;
                }

                // cannot find the name in this scope, so delegate to parent
                return parent.findVariableDefinition(varName);
            }

            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> findTopLevelFunctionOrClassMethodDefinition(final String varName) {
                // this is not a top-level scope, so delegate to parent
                return parent.findTopLevelFunctionOrClassMethodDefinition(varName);
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> findQualifiedFunctionOrClassMethod(final Name.Function functionName, final SymbolTable innermostScope) {
                return parent.findQualifiedFunctionOrClassMethod(functionName, innermostScope);
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.DataCons> findDataCons(final Name.DataCons dataConsName, final SymbolTable innermostScope) {
                return parent.findDataCons(dataConsName, innermostScope);
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.DataConsFieldName> findDataConsFieldName(final Name.DataCons dataConsName, final Name.Field fieldName, final SymbolTable innermostScope) {
                return parent.findDataConsFieldName(dataConsName, fieldName, innermostScope);
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.TypeCons> findTypeCons(final Name.TypeCons typeConsName, final SymbolTable innermostScope) {
                return parent.findTypeCons(typeConsName, innermostScope);
            }
           
            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.TopLevel.TypeClass> findTypeClass(final Name.TypeClass typeClassName, final SymbolTable innermostScope) {
                return parent.findTypeClass(typeClassName, innermostScope);
            }

            /**
             * {@inheritDoc}
             */
            @Override
            Binding<IdentifierInfo.Module> resolveMaybeModuleName(final Name.Module moduleName) {
                return parent.resolveMaybeModuleName(moduleName);
            }
        }
       
        /**
         * Factory method for making a root scope.
         * @param currentModuleName the name of the current module. Can *not* be null.
         * @param context the external context for resolving identifiers. Can *not* be null.
         * @return a root scope instance.
         */
        public static RootScope makeRoot(final ModuleName currentModuleName, final Context context) {
            return new RootScope(currentModuleName, context);
        }
       
        /** Private constructor. */
        private SymbolTable() {}

        /**
         * Looks up the binding for an unqualified variable name.
         * @param varName the variable name.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        abstract Binding<? extends IdentifierInfo> findVariableDefinition(String varName);

        /**
         * Looks up the binding for an unqualified name that must refer to a top-level function or class method.
         * An example of such a case is a CALDoc short-form function reference that is unqualified.
         * @param varName the variable name.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        abstract Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> findTopLevelFunctionOrClassMethodDefinition(String varName);
       
        /**
         * Looks up the binding for a qualified function/class method name.
         * @param functionName the function/class method name.
         * @param innermostScope the innermost scope which is currently being analyzed.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        /*
         * @discussion
         *
         * The use of the innermost scope is necessary, as often the task of resolution is a 2-step process:
         * 1) first resolve the module name
         * 2) then resolve the unqualified name
         * It may be the case that the module name is not resolvable in the current scope (e.g. the root scope),
         * and in fact needs to be resolved by a lower scope (e.g. the top level scope) - thus the need to go from
         * the bottom up via the innermost scope *while* in the context of a particular, higher scope.
         */
        abstract Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> findQualifiedFunctionOrClassMethod(Name.Function functionName, SymbolTable innermostScope);
       
        /**
         * Looks up the binding for a qualified function/class method name.
         * @param functionName the function/class method name.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        /*
         * @discussion
         *
         * Passes 'this' as the innermost scope, as this is the entry point for clients to use for lookups.
         */
        final Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> findQualifiedFunctionOrClassMethod(final Name.Function functionName) {
            return findQualifiedFunctionOrClassMethod(functionName, this);
        }
       
        /**
         * Looks up the binding for a potentially qualified function/method/variable name.
         * @param functionName the function/method/variable name.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        final Binding<? extends IdentifierInfo> findFunction(final Name.Function functionName) {
            if (functionName.getModuleName() == null) {
                // unqualified, so go through findVariableDefinition()
                return findVariableDefinition(functionName.getUnqualifiedName());
            } else {
                // qualified, so go through findQualifiedFunctionOrClassMethod()
                return findQualifiedFunctionOrClassMethod(functionName, this);
            }
        }
       
        /**
         * Looks up the binding for a qualified function/method name.
         * @param functionName the function/method name.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        final Binding<IdentifierInfo.TopLevel.FunctionOrClassMethod> findTopLevelFunctionOrClassMethodDefinition(final Name.Function functionName) {
            if (functionName.getModuleName() == null) {
                // unqualified, so go through findTopLevelFunctionOrClassMethodDefinition()
                return findTopLevelFunctionOrClassMethodDefinition(functionName.getUnqualifiedName());
            } else {
                // qualified, so go through findQualifiedFunctionOrClassMethod()
                return findQualifiedFunctionOrClassMethod(functionName, this);
            }
        }
       
        /**
         * Looks up the binding for a potentially qualified data cons name.
         * @param dataConsName the data cons name.
         * @param innermostScope the innermost scope which is currently being analyzed.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        /* @discussion see above */
        abstract Binding<IdentifierInfo.TopLevel.DataCons> findDataCons(Name.DataCons dataConsName, SymbolTable innermostScope);
       
        /**
         * Looks up the binding for a potentially qualified data cons name.
         * @param dataConsName the data cons name.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        /* @discussion see above */
        final Binding<IdentifierInfo.TopLevel.DataCons> findDataCons(final Name.DataCons dataConsName) {
            return findDataCons(dataConsName, this);
        }
       
        /**
         * Looks up the binding for a field associated with a potentially qualified data cons name.
         * @param dataConsName the data cons name.
         * @param fieldName the field name.
         * @param innermostScope the innermost scope which is currently being analyzed.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        /* @discussion see above */
        abstract Binding<IdentifierInfo.DataConsFieldName> findDataConsFieldName(Name.DataCons dataConsName, Name.Field fieldName, SymbolTable innermostScope);
       
        /**
         * Looks up the binding for a field associated with a potentially qualified data cons name.
         * @param dataConsName the data cons name.
         * @param fieldName the field name.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        /* @discussion see above */
        final Binding<IdentifierInfo.DataConsFieldName> findDataConsFieldName(final Name.DataCons dataConsName, final Name.Field fieldName) {
            return findDataConsFieldName(dataConsName, fieldName, this);
        }

        /**
         * Looks up the binding for a potentially qualified type cons name.
         * @param typeConsName the type cons name.
         * @param innermostScope the innermost scope which is currently being analyzed.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        /* @discussion see above */
        abstract Binding<IdentifierInfo.TopLevel.TypeCons> findTypeCons(Name.TypeCons typeConsName, SymbolTable innermostScope);

        /**
         * Looks up the binding for a potentially qualified type cons name.
         * @param typeConsName the type cons name.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        /* @discussion see above */
        final Binding<IdentifierInfo.TopLevel.TypeCons> findTypeCons(final Name.TypeCons typeConsName) {
            return findTypeCons(typeConsName, this);
        }

        /**
         * Looks up the binding for a potentially qualified type class name.
         * @param typeClassName the type class name.
         * @param innermostScope the innermost scope which is currently being analyzed.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        /* @discussion see above */
        abstract Binding<IdentifierInfo.TopLevel.TypeClass> findTypeClass(Name.TypeClass typeClassName, SymbolTable innermostScope);

        /**
         * Looks up the binding for a potentially qualified type class name.
         * @param typeClassName the type class name.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        /* @discussion see above */
        final Binding<IdentifierInfo.TopLevel.TypeClass> findTypeClass(final Name.TypeClass typeClassName) {
            return findTypeClass(typeClassName, this);
        }
       
        /**
         * Looks up the binding for a potentially partially qualified module name, which *may* be null.
         * @param moduleName the module name. Can be null.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        abstract Binding<IdentifierInfo.Module> resolveMaybeModuleName(Name.Module moduleName);
       
        /**
         * Looks up the binding for a potentially partially qualified module name, which *may* be null.
         * @param moduleName the module name. Can be null.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        final Binding<IdentifierInfo.Module> resolveMaybeModuleName(final ModuleName moduleName) {
            return resolveMaybeModuleName(Name.Module.make(moduleName));
        }
       
        /**
         * Looks up the binding for a potentially qualified constructor name without context (as appearing in CALDoc)
         * @param name the name without context.
         * @return the corresponding binding, or null if the name is irresolvable (according to the special rules of resolving such names).
         */
        final Binding<? extends IdentifierInfo> findConsNameWithoutContext(final Name.WithoutContextCons name) {
           
            // try to resolve the name under all contexts
            final Binding<IdentifierInfo.TopLevel.DataCons> asDataCons =
                findDataCons(Name.DataCons.make(name.getModuleName(), name.getUnqualifiedName()));
           
            final Binding<IdentifierInfo.TopLevel.TypeCons> asTypeCons =
                findTypeCons(Name.TypeCons.make(name.getModuleName(), name.getUnqualifiedName()));
           
            final Binding<IdentifierInfo.TopLevel.TypeClass> asTypeClass =
                findTypeClass(Name.TypeClass.make(name.getModuleName(), name.getUnqualifiedName()));
           
            final Binding<IdentifierInfo.Module> asModuleName =
                resolveMaybeModuleName(Name.Module.make(ModuleName.make(name.toSourceText())));
           
            final List<Binding<? extends IdentifierInfo>> bindingsList =
                new ArrayList<Binding<? extends IdentifierInfo>>();
           
            if (asDataCons != null) {
                bindingsList.add(asDataCons);
            }
            if (asTypeCons != null) {
                bindingsList.add(asTypeCons);
            }
            if (asTypeClass != null) {
                bindingsList.add(asTypeClass);
            }
            if (asModuleName != null) {
                bindingsList.add(asModuleName);
            }
           
            // The name is unambiguously resolved if and only if there is exactly one resolution
            if (bindingsList.size() != 1) {
                return null;
            } else {
                return bindingsList.get(0);
            }
        }
    }
   
    /**
     * An immutable class representing a scope for type variables. A type variable scope can nest within another type variable
     * scope. For example, the type variable associated with the type class name in the class definition is in scope within
     * the entire definition (including the class methods), while other type variables that appear only within a particular
     * class method's definition are local to that method.
     *
     * @author Joseph Wong
     */
    public static final class TypeVariableScope {
       
        /**
         * The parent scope. Can be null if this is the top-most type variable scope.
         */
        private final TypeVariableScope parent;
       
        /**
         * A map from type variables names to their corresponding bindings. Can *not* be null.
         */
        private final LinkedHashMap<String, Binding<IdentifierInfo.TypeVariable>> typeVarBindings;

        /**
         * Constructs an instance of this class.
         * @param parent the parent scope. Can be null if this is the top-most type variable scope.
         * @param typeVarBindings a map from type variables names to their corresponding bindings. Can *not* be null.
         */
        TypeVariableScope(final TypeVariableScope parent, final LinkedHashMap<String, Binding<IdentifierInfo.TypeVariable>> typeVarBindings) {
            super();
            this.parent = parent;
            if (typeVarBindings == null) {
                throw new NullPointerException();
            }
            this.typeVarBindings = typeVarBindings;
        }

        /**
         * @return the parent scope. Can be null if this is the top-most type variable scope.
         */
        public TypeVariableScope getParent() {
            return parent;
        }

        /**
         * @return an iterable collection of the type variable bindings.
         */
        public Iterable<Binding<IdentifierInfo.TypeVariable>> getBindings() {
            return Collections.unmodifiableCollection(typeVarBindings.values());
        }
       
        /**
         * Looks up the binding for a type variable name.
         * @param typeVarName the type variable name.
         * @return the corresponding binding, or null if the name is irresolvable.
         */
        Binding<IdentifierInfo.TypeVariable> findTypeVar(final String typeVarName) {
            final Binding<IdentifierInfo.TypeVariable> binding = typeVarBindings.get(typeVarName);
            if (binding != null) {
                return binding;
            } else if (parent != null) {
                return parent.findTypeVar(typeVarName);
            } else {
                return null;
            }
        }
       
        /**
         * Factory method for constructing a new scope containing bindings for type variables in a type signature,
         * to be used for resolution of type variables within the scope.
         * @param parent the parent scope.
         * @param sourceElement the source element corresponding to the new scope.
         * @param currentModuleName the current module name.
         * @return the new scope.
         */
        static TypeVariableScope newScope(final TypeVariableScope parent, final TypeSignature sourceElement, final ModuleName currentModuleName) {
            return newScopeInternal(parent, new SourceElement[] {sourceElement}, currentModuleName);
        }
       
        /**
         * Factory method for constructing a new scope containing bindings for type variables in a algebraic type definition (not including the data constructor definitions),
         * to be used for resolution of type variables within the scope.
         * @param parent the parent scope.
         * @param sourceElement the source element corresponding to the new scope.
         * @param currentModuleName the current module name.
         * @return the new scope.
         */
        static TypeVariableScope newScope(final TypeVariableScope parent, final TypeConstructorDefn.AlgebraicType sourceElement, final ModuleName currentModuleName) {
            return newScopeInternal(parent, sourceElement.getTypeParameters(), currentModuleName);
        }
       
        /**
         * Factory method for constructing a new scope containing bindings for type variables in a data constructor definition,
         * to be used for resolution of type variables within the scope.
         * @param parent the parent scope.
         * @param sourceElement the source element corresponding to the new scope.
         * @param currentModuleName the current module name.
         * @return the new scope.
         */
        static TypeVariableScope newScope(final TypeVariableScope parent, final TypeConstructorDefn.AlgebraicType.DataConsDefn sourceElement, final ModuleName currentModuleName) {
            return newScopeInternal(parent, new SourceElement[] {sourceElement}, currentModuleName);
        }
       
        /**
         * Factory method for constructing a new scope containing bindings for type variables in a type class definition (not including the method declarations),
         * to be used for resolution of type variables within the scope.
         * @param parent the parent scope.
         * @param sourceElement the source element corresponding to the new scope.
         * @param currentModuleName the current module name.
         * @return the new scope.
         */
        static TypeVariableScope newScope(final TypeVariableScope parent, final TypeClassDefn sourceElement, final ModuleName currentModuleName) {
            return newScopeInternal(parent, new SourceElement[] {sourceElement.getTypeVar()}, currentModuleName);
        }
       
        /**
         * Factory method for constructing a new scope containing bindings for type variables in an instance definition,
         * to be used for resolution of type variables within the scope.
         * @param parent the parent scope.
         * @param sourceElement the source element corresponding to the new scope.
         * @param currentModuleName the current module name.
         * @return the new scope.
         */
        static TypeVariableScope newScope(final TypeVariableScope parent, final InstanceDefn sourceElement, final ModuleName currentModuleName) {
            return newScopeInternal(parent, new SourceElement[] {sourceElement.getInstanceTypeCons()}, currentModuleName);
        }
       
        /**
         * Internal helper method for constructing a new scope based on the type variables in the specified source elements.
         * @param parent the parent scope.
         * @param sourceElements the source elements containing type variables to be processed.
         * @param currentModuleName the current module name.
         * @return the new scope.
         */
        private static TypeVariableScope newScopeInternal(final TypeVariableScope parent, final SourceElement[] sourceElements, final ModuleName currentModuleName) {
            final LinkedHashMap<String, Binding<IdentifierInfo.TypeVariable>> typeVarBindings = new LinkedHashMap<String, Binding<IdentifierInfo.TypeVariable>>();
           
            // For each source element, process the type variables therein
            for (final SourceElement sourceElement : sourceElements) {
                sourceElement.accept(
                    new SourceModelTraverser<Void, Void>() {
                        @Override
                        public Void visit_Name_TypeVar(final Name.TypeVar var, final Void arg) {
                            final String typeVarName = var.getName();
                            if (parent.findTypeVar(typeVarName) == null) {
                                // If the type var has not been bound, it is the first instance of the type var name
                                // and the occurrence is considered the "binding" (or canonical occurrence) for the type variable
                                if (!typeVarBindings.containsKey(typeVarName)) {
                                    typeVarBindings.put(
                                        typeVarName,
                                        Binding.Definition.make(
                                            new IdentifierInfo.TypeVariable(typeVarName, Pair.make(currentModuleName, var.getSourceRange().getStartSourcePosition())),
                                            var,
                                            var.getSourceRange()));
                                }
                            }
                            return super.visit_Name_TypeVar(var, arg);
                        }
                    },
                    null);
            }
            return new TypeVariableScope(parent, typeVarBindings);
        }
    }

    /**
     * This class encapsulates the visitation argument used with the visitor class. It contains information necessary for
     * resolving identifiers, including the relevant symbol table and {@link LocalFunctionIdentifierGenerator}, as well as any
     * user-supplied state which may be passed along.
     * <p>
     * This class is designed as an immutable class, with update methods which produce new copies with the appropriate fields updated.
     * This means that, for example, one does not need to keep an explicitly stack of state (e.g. the symbol table) as one
     * traverses up and down the source model.
     *
     * @param <T> the type of the user-supplied state.
     *
     * @author Joseph Wong
     */
    public static class VisitorArgument<T> {
       
        /**
         * The current scope for resolving module, qualified and unqualified names. Can *not* be null.
         */
        private final SymbolTable scope;
       
        /**
         * The current scope for resolving type variables. Can *not* be null.
         */
        private final TypeVariableScope typeVarScope;
       
        /**
         * The generator of local function identifiers for use to generate IDs for local
         * definitions encountered.
         *
         * Can be null (if visiting source elements that do not contain local functions).
         */
        private final LocalFunctionIdentifierGenerator localFunctionIdentifierGenerator;
       
        /**
         * The generator of identifiers for case-bound and lambda-bound variables.
         *
         * Can be null (if visiting source elements that do not contain local definitions).
         */
        // todo-jowong only needed until the compiler assigns LocalFunctionIdentifiers to all local symbols
        private final LocalFunctionIdentifierGenerator caseAndLambdaBoundLocalFunctionIdentifierGenerator;
       
        /**
         * The current user-supplied state.
         *
         * Can be null.
         */
        private final T userState;
       
        /**
         * Constructs an instance of this class.
         *
         * @param scope
         *            the current scope for resolving module, qualified and unqualified names. Can
         *            *not* be null.
         * @param typeVarScope
         *            the current scope for resolving type variables. Can *not* be null.
         * @param localFunctionIdentifierGenerator
         *            the generator of local function identifiers for use to generate IDs for local
         *            definitions encountered. Can be null (if visiting source elements that do not
         *            contain local functions).
         * @param caseAndLambdaBoundLocalFunctionIdentifierGenerator
         *            the generator of identifiers for case-bound and lambda-bound variables. Can be
         *            null (if visiting source elements that do not contain local definitions).
         * @param userState
         *            the current user-supplied state. Can be null.
         */
        private VisitorArgument(
            final SymbolTable scope,
            final TypeVariableScope typeVarScope,
            final LocalFunctionIdentifierGenerator localFunctionIdentifierGenerator,
            final LocalFunctionIdentifierGenerator caseAndLambdaBoundLocalFunctionIdentifierGenerator,
            final T userState) {
           
            if (scope == null || typeVarScope == null) {
                throw new NullPointerException();
            }
            this.scope = scope;
            this.typeVarScope = typeVarScope;
            this.localFunctionIdentifierGenerator = localFunctionIdentifierGenerator;
            this.caseAndLambdaBoundLocalFunctionIdentifierGenerator = caseAndLambdaBoundLocalFunctionIdentifierGenerator;
            this.userState = userState;
        }
       
        /**
         * Factory method to construct a new instance of this class.
         * @param <T> the type of the user-specified state.
         * @param scope the current scope. Can *not* be null.
         * @param userState the current user-supplied state. Can be null.
         * @return a new instance of this class.
         */
        public static <T> VisitorArgument<T> make(final SymbolTable scope, final T userState) {
            return new VisitorArgument<T>(scope, new TypeVariableScope(null, new LinkedHashMap<String, Binding<IdentifierInfo.TypeVariable>>()), null, null, userState);
        }
       
        /**
         * Constructs a new visitor argument based on this one, but with an updated type variable scope.
         * @param typeVarScope the new type variable scope.
         * @return a new visitor argument.
         */
        VisitorArgument<T> updateTypeVariableScope(final TypeVariableScope typeVarScope) {
            return new VisitorArgument<T>(this.scope, typeVarScope, this.localFunctionIdentifierGenerator, this.caseAndLambdaBoundLocalFunctionIdentifierGenerator, this.userState);
        }
       
        /**
         * Constructs a new visitor argument based on this one, but with an updated scope.
         * @param scope the new scope.
         * @return a new visitor argument.
         */
        public VisitorArgument<T> updateScope(final SymbolTable scope) {
            return new VisitorArgument<T>(scope, this.typeVarScope, this.localFunctionIdentifierGenerator, this.caseAndLambdaBoundLocalFunctionIdentifierGenerator, this.userState);
        }
       
        /**
         * Constructs a new visitor argument based on this one, but with an updated scope and updated
         * local function identifier generators for the current algebraic function.
         * @param scope the new scope.
         * @param algebraicFunction the current algebraic function.
         * @return a new visitor argument.
         */
        VisitorArgument<T> updateScopeWithNewLocalFunctionIdentifierGenerator(final SymbolTable scope, final FunctionDefn.Algebraic algebraicFunction) {
            final LocalFunctionIdentifierGenerator localFunctionIdentifierGenerator = new LocalFunctionIdentifierGenerator();
            localFunctionIdentifierGenerator.reset(algebraicFunction.getName());

            final LocalFunctionIdentifierGenerator caseAndLambdaBoundLocalFunctionIdentifierGenerator = new LocalFunctionIdentifierGenerator();
            caseAndLambdaBoundLocalFunctionIdentifierGenerator.reset(algebraicFunction.getName());

            return new VisitorArgument<T>(scope, this.typeVarScope, localFunctionIdentifierGenerator, caseAndLambdaBoundLocalFunctionIdentifierGenerator, this.userState);
        }
       
        /**
         * Constructs a new visitor argument based on this one, but with an updated user-supplied state.
         * @param userState the new user-supplied state.
         * @return a new visitor argument.
         */
        public VisitorArgument<T> updateUserState(final T userState) {
            return new VisitorArgument<T>(this.scope, this.typeVarScope, this.localFunctionIdentifierGenerator, this.caseAndLambdaBoundLocalFunctionIdentifierGenerator, userState);
        }

        /**
         * @return the current scope for resolving module, qualified and unqualified names. Can *not* be null.
         */
        public SymbolTable getScope() {
            return scope;
        }
       
        /**
         * @return the current scope for resolving type variables. Can *not* be null.
         */
        public TypeVariableScope getTypeVariableScope() {
            return typeVarScope;
        }

        /**
         * @return the generator of local function identifiers for use to generate IDs for local
         *         definitions encountered. Can be null (if visiting source elements that do not
         *         contain local functions).
         */
        LocalFunctionIdentifierGenerator getLocalFunctionIdentifierGenerator() {
            return localFunctionIdentifierGenerator;
        }
       
        /**
         * @return the generator of identifiers for case-bound and lambda-bound variables. Can be
         *         null (if visiting source elements that do not contain local definitions).
         */
        // todo-jowong only needed until the compiler assigns LocalFunctionIdentifiers to all local symbols
        LocalFunctionIdentifierGenerator getCaseAndLambdaBoundLocalFunctionIdentifierGenerator() {
            return caseAndLambdaBoundLocalFunctionIdentifierGenerator;
        }
       
        /**
         * @return the current top-level algebraic function name, or null if not currently in the scope of one.
         */
        public String getCurrentTopLevelFunctionName() {
            if (localFunctionIdentifierGenerator == null) {
                return null;
            } else {
                return localFunctionIdentifierGenerator.getCurrentFunction();
            }
        }

        /**
         * @return the current user-supplied state. Can be null.
         */
        public T getUserState() {
            return userState;
        }
    }

    /**
     * Abstract base visitor that traverses the source model, gathers up the bindings declared
     * therein, and visits each source element with the appropriate symbol table passed in as a
     * visitation argument. This is the base class that should be extended if one wants to analyze a
     * source model with information about which symbols are in scope.
     * <p>
     * The task of gathering up <i>references</i> (non-binding occurrences) is up to the subclass
     * {@link IdentifierOccurrenceFinder}.
     *
     * @author Joseph Wong
     * @author James Wright
     */
    /*
     * @history
     *
     * This class is based on the class CALSourceGenerator.LambdaDefiningExprScopeAnalyzer.
     *
     * Also, a good portion of this class's functionalities have been provided by the
     * BindingTrackingSourceModelTraverser, by James Wright.
     */
    public static class Visitor<T, R> extends SourceModelTraverser<VisitorArgument<T>, R> {

        /**
         * Encapsulates a factory object for building symbol tables representing scopes
         * for use during the visitation. Instances of this immutable class can be configured
         * differently depending on the need.
         *
         * @author Joseph Wong
         */
        /*
         * @history
         *
         * This class is based on the class CALSourceGenerator.LambdaDefiningExprScopeAnalyzer.Scope.
         */
        private final class ScopeBuilder {

            /**
             * Whether new scopes should be recorded by calling the appropriate handler methods.
             */
            // todo-jowong this is only needed because the argument scope of a function (both top-level algebraic and local)
            // needs to be recreated for the CALDoc associated with the type declaration of the function
            // When the type declaration becomes a component of the function definition, this field can go away
            // (because we would always record the scope)
            private final boolean shouldRecordScope;

            /**
             * Constructs an instance of this class.
             * @param shouldRecordScope whether new scopes should be recorded by calling the appropriate handler methods.
             */
            ScopeBuilder(final boolean shouldRecordScope) {
                this.shouldRecordScope = shouldRecordScope;
            }

            /**
             * Creates a new top level scope, and potentially records the new scope.
             * @param parent the parent scope.
             * @param currentModuleNameBinding the binding for the name of the current module.
             * @param functionAndClassMethodBindings a map from unqualified names to bindings of functions and class methods.
             * @param typeConsBindings a map from unqualified names to bindings of type constructors.
             * @param dataConsBindings a map from unqualified names to bindings of data constructors.
             * @param dataConsFieldNameBindings a map from names to bindings of data constructors.
             * The name with which we associate a data cons field name is "<dataConsName>.<fieldName>".
             * @param typeClassBindings a map from unqualified names to bindings of type classes.
             * @param importedModuleNames an ordered collection of the imported module names.
             * @return a new top level scope.
             */
            private SymbolTable makeTopLevelScope(
                final SymbolTable parent,
                final Binding<IdentifierInfo.Module> currentModuleNameBinding,
                final BindingsMap<IdentifierInfo.TopLevel.FunctionOrClassMethod> functionAndClassMethodBindings,
                final BindingsMap<IdentifierInfo.TopLevel.TypeCons> typeConsBindings,
                final BindingsMap<IdentifierInfo.TopLevel.DataCons> dataConsBindings,
                final BindingsMap<IdentifierInfo.DataConsFieldName> dataConsFieldNameBindings,
                final BindingsMap<IdentifierInfo.TopLevel.TypeClass> typeClassBindings,
                final Collection<ModuleName> importedModuleNames) {

                final SymbolTable.TopLevelScope topLevelScope =
                    new SymbolTable.TopLevelScope(parent, currentModuleNameBinding, functionAndClassMethodBindings, typeConsBindings, dataConsBindings, dataConsFieldNameBindings, typeClassBindings, importedModuleNames);

                if (shouldRecordScope) {
                    handleNewTopLevelScope(topLevelScope);
                }
                return topLevelScope;
            }

            /**
             * Creates a new local scope, and potentially records the new scope.
             * @param parent the parent scope.
             * @param bindings a map from unqualified names to bindings of local variables.
             * @return a new local scope.
             */
            private SymbolTable makeLocalScope(final SymbolTable parent, final BindingsMap<IdentifierInfo.Local> bindings) {
                final SymbolTable.LocalScope localScope = new SymbolTable.LocalScope(parent, bindings);
                if (shouldRecordScope) {
                    handleNewLocalScope(localScope);
                }
                return localScope;
            }

            /**
             * Creates a new scope containing bindings for top level entities in a module definition.
             * @param parent the parent scope.
             * @param moduleDefn the source element corresponding to the new scope.
             * @return the new scope.
             */
            SymbolTable newScope(final SymbolTable parent, final ModuleDefn moduleDefn) {
                final BindingsMap<IdentifierInfo.TopLevel.FunctionOrClassMethod> functionAndClassMethodBindings = BindingsMap.make();
                final BindingsMap<IdentifierInfo.TopLevel.TypeCons> typeConsBindings = BindingsMap.make();
                final BindingsMap<IdentifierInfo.TopLevel.DataCons> dataConsBindings = BindingsMap.make();
                final BindingsMap<IdentifierInfo.DataConsFieldName> dataConsFieldNameBindings = BindingsMap.make();
                final BindingsMap<IdentifierInfo.TopLevel.TypeClass> typeClassBindings = BindingsMap.make();

                final LinkedHashSet<ModuleName> importedModuleNames = new LinkedHashSet<ModuleName>();

                ////
                /// Gather up the imported module names and the names in the import using clauses.
                //
               
                for (final Import importStmt : moduleDefn.getImportedModules()) {

                    final Name.Module importedModuleName = importStmt.getImportedModuleName();

                    importedModuleNames.add(SourceModel.Name.Module.toModuleName(importedModuleName));

                    final SourceModelTraverser<Void, Void> usingClauseWalker = new SourceModelTraverser<Void, Void>() {
                        @Override
                        public Void visit_Import_UsingItem_Function(final Import.UsingItem.Function usingItemFunction, final Void arg) {

                            final String[] usingNames = usingItemFunction.getUsingNames();
                            final SourceRange[] usingNameSourceRanges = usingItemFunction.getUsingNameSourceRanges();

                            for (int i = 0, n = usingNames.length; i < n; i++) {
                                final String name = usingNames[i];
                                final SourceRange sourceRange = usingNameSourceRanges[i];

                                functionAndClassMethodBindings.put(
                                    name,
                                    Binding.ImportUsingItem.make(
                                        new IdentifierInfo.TopLevel.FunctionOrClassMethod(
                                            QualifiedName.make(SourceModel.Name.Module.toModuleName(importedModuleName), name)),
                                        usingItemFunction,
                                        i,
                                        sourceRange));
                            }
                            return null;
                        }

                        @Override
                        public Void visit_Import_UsingItem_TypeConstructor(final Import.UsingItem.TypeConstructor usingItemTypeConstructor, final Void arg) {

                            final String[] usingNames = usingItemTypeConstructor.getUsingNames();
                            final SourceRange[] usingNameSourceRanges = usingItemTypeConstructor.getUsingNameSourceRanges();

                            for (int i = 0, n = usingNames.length; i < n; i++) {
                                final String name = usingNames[i];
                                final SourceRange sourceRange = usingNameSourceRanges[i];

                                typeConsBindings.put(
                                    name,
                                    Binding.ImportUsingItem.make(
                                        new IdentifierInfo.TopLevel.TypeCons(
                                            QualifiedName.make(SourceModel.Name.Module.toModuleName(importedModuleName), name)),
                                        usingItemTypeConstructor,
                                        i,
                                        sourceRange));
                            }
                            return null;
                        }

                        @Override
                        public Void visit_Import_UsingItem_DataConstructor(final Import.UsingItem.DataConstructor usingItemDataConstructor, final Void arg) {

                            final String[] usingNames = usingItemDataConstructor.getUsingNames();
                            final SourceRange[] usingNameSourceRanges = usingItemDataConstructor.getUsingNameSourceRanges();

                            for (int i = 0, n = usingNames.length; i < n; i++) {
                                final String name = usingNames[i];
                                final SourceRange sourceRange = usingNameSourceRanges[i];

                                dataConsBindings.put(
                                    name,
                                    Binding.ImportUsingItem.make(
                                        new IdentifierInfo.TopLevel.DataCons(
                                            QualifiedName.make(SourceModel.Name.Module.toModuleName(importedModuleName), name)),
                                        usingItemDataConstructor,
                                        i,
                                        sourceRange));
                            }
                            return null;
                        }

                        @Override
                        public Void visit_Import_UsingItem_TypeClass(final Import.UsingItem.TypeClass usingItemTypeClass, final Void arg) {

                            final String[] usingNames = usingItemTypeClass.getUsingNames();
                            final SourceRange[] usingNameSourceRanges = usingItemTypeClass.getUsingNameSourceRanges();

                            for (int i = 0, n = usingNames.length; i < n; i++) {
                                final String name = usingNames[i];
                                final SourceRange sourceRange = usingNameSourceRanges[i];

                                typeClassBindings.put(
                                    name,
                                    Binding.ImportUsingItem.make(
                                        new IdentifierInfo.TopLevel.TypeClass(
                                            QualifiedName.make(SourceModel.Name.Module.toModuleName(importedModuleName), name)),
                                        usingItemTypeClass,
                                        i,
                                        sourceRange));
                            }
                            return null;
                        }
                    };

                    importStmt.accept(usingClauseWalker, null);
                }

                ////
                /// Now add the top level elements (functions, type and data constructors, type classes and class methods)
                //
               
                addTopLevelElementsToBindings(moduleDefn.getTopLevelDefns(), functionAndClassMethodBindings, typeConsBindings, dataConsBindings, dataConsFieldNameBindings, typeClassBindings);

                final ModuleName moduleName = SourceModel.Name.Module.toModuleName(moduleDefn.getModuleName());

                return makeTopLevelScope(
                    parent,
                    Binding.Definition.make(
                        new IdentifierInfo.Module(moduleName), moduleDefn.getModuleName(), moduleDefn.getModuleName().getSourceRange()),
                    functionAndClassMethodBindings, typeConsBindings, dataConsBindings, dataConsFieldNameBindings, typeClassBindings,
                    importedModuleNames);
            }

            /**
             * Creates a new scope containing bindings for top level entities.
             * @param parent the parent scope.
             * @param topLevelElements the top level source elements.
             * @return the new scope.
             */
            SymbolTable newTopLevelScope(final SymbolTable parent, final TopLevelSourceElement... topLevelElements) {
                final BindingsMap<IdentifierInfo.TopLevel.FunctionOrClassMethod> functionAndClassMethodBindings = BindingsMap.make();
                final BindingsMap<IdentifierInfo.TopLevel.TypeCons> typeConsBindings = BindingsMap.make();
                final BindingsMap<IdentifierInfo.TopLevel.DataCons> dataConsBindings = BindingsMap.make();
                final BindingsMap<IdentifierInfo.DataConsFieldName> dataConsFieldNameBindings = BindingsMap.make();
                final BindingsMap<IdentifierInfo.TopLevel.TypeClass> typeClassBindings = BindingsMap.make();

                addTopLevelElementsToBindings(topLevelElements, functionAndClassMethodBindings, typeConsBindings, dataConsBindings, dataConsFieldNameBindings, typeClassBindings);
                return makeTopLevelScope(
                    parent,
                    Binding.External.make(new IdentifierInfo.Module(currentModuleName)),
                    functionAndClassMethodBindings, typeConsBindings, dataConsBindings, dataConsFieldNameBindings, typeClassBindings,
                    Collections.<ModuleName>emptySet());
            }

            /**
             * Processes the given top level source elements and add the names to the appropriate
             * binding maps.
             * @param topLevelElements the top level source elements.
             * @param functionAndClassMethodBindings a map from unqualified names to bindings of functions and class methods.
             * @param typeConsBindings a map from unqualified names to bindings of type constructors.
             * @param dataConsBindings a map from unqualified names to bindings of data constructors.
             * @param dataConsFieldNameBindings a map from names to bindings of data constructors.
             * The name with which we associate a data cons field name is "<dataConsName>.<fieldName>".
             * @param typeClassBindings a map from unqualified names to bindings of type classes.
             */
            private void addTopLevelElementsToBindings(
                final TopLevelSourceElement[] topLevelElements,
                final BindingsMap<IdentifierInfo.TopLevel.FunctionOrClassMethod> functionAndClassMethodBindings,
                final BindingsMap<IdentifierInfo.TopLevel.TypeCons> typeConsBindings,
                final BindingsMap<IdentifierInfo.TopLevel.DataCons> dataConsBindings,
                final BindingsMap<IdentifierInfo.DataConsFieldName> dataConsFieldNameBindings,
                final BindingsMap<IdentifierInfo.TopLevel.TypeClass> typeClassBindings) {

                // loop through the top level elements and process each one
                for (final TopLevelSourceElement element : topLevelElements) {

                    if (element instanceof FunctionDefn) {
                        ////
                        /// Process a function definition
                        //
                       
                        final FunctionDefn function = (FunctionDefn)element;

                        final IdentifierInfo.TopLevel.FunctionOrClassMethod functionIdentifier = new IdentifierInfo.TopLevel.FunctionOrClassMethod(
                            QualifiedName.make(currentModuleName, function.getName()));
                       
                        functionAndClassMethodBindings.put(
                            function.getName(),
                            Binding.Definition.make(functionIdentifier, function, function.getNameSourceRange()));
                       
                        if (function instanceof FunctionDefn.Foreign) {
                            // for a foreign function, we record the foreign descriptor
                            if (shouldRecordScope) {
                                handleForeignFunctionDescriptor(
                                    ForeignDescriptor.make(
                                        functionIdentifier,
                                        function,
                                        ((FunctionDefn.Foreign)function).getExternalNameSourceRange()));
                            }
                        }

                    } else if (element instanceof TypeConstructorDefn) {
                        ////
                        /// Process a type constructor definition
                        //
                       
                        final TypeConstructorDefn typeCons = (TypeConstructorDefn)element;

                        final IdentifierInfo.TopLevel.TypeCons typeConsIdentifier = new IdentifierInfo.TopLevel.TypeCons(
                            QualifiedName.make(currentModuleName, typeCons.getTypeConsName()));
                       
                        typeConsBindings.put(
                            typeCons.getTypeConsName(),
                            Binding.Definition.make(typeConsIdentifier, typeCons, typeCons.getSourceRangeOfName()));

                        if (typeCons instanceof TypeConstructorDefn.ForeignType) {
                            // for a foreign type, we record the foreign descriptor
                            if (shouldRecordScope) {
                                handleForeignTypeDescriptor(
                                    ForeignDescriptor.make(
                                        typeConsIdentifier,
                                        typeCons,
                                        ((TypeConstructorDefn.ForeignType)typeCons).getExternalNameSourceRange()));
                            }
                           
                        } else if (typeCons instanceof TypeConstructorDefn.AlgebraicType) {
                            final TypeConstructorDefn.AlgebraicType algebraic = (TypeConstructorDefn.AlgebraicType)typeCons;

                            for (final TypeConstructorDefn.AlgebraicType.DataConsDefn dataCons : algebraic.getDataConstructors()) {
                                // For an algebraic type, we process each data constructor and its fields
                               
                                final IdentifierInfo.TopLevel.DataCons dataConsIdentifier =
                                    new IdentifierInfo.TopLevel.DataCons(
                                        QualifiedName.make(currentModuleName, dataCons.getDataConsName()));

                                final Binding.Definition<IdentifierInfo.TopLevel.DataCons> dataConsOccurrence =
                                    Binding.Definition.make(dataConsIdentifier, dataCons, dataCons.getSourceRangeOfName());
                               
                                dataConsBindings.put(dataCons.getDataConsName(), dataConsOccurrence);

                                for (final TypeConstructorDefn.AlgebraicType.DataConsDefn.TypeArgument typeArg : dataCons.getTypeArgs()) {
                                    final Name.Field fieldName = typeArg.getFieldName();

                                    // the name we associate a data cons field name is <dataConsName>.<fieldName>
                                    dataConsFieldNameBindings.put(
                                        dataCons.getDataConsName() + "." + fieldName.getName().getCalSourceForm(),
                                        Binding.DataConsFieldName.make(
                                            new IdentifierInfo.DataConsFieldName(fieldName.getName(), Collections.singletonList(dataConsIdentifier)),
                                            dataConsOccurrence,
                                            fieldName,
                                            fieldName.getSourceRange()));
                                }
                            }
                        }
                    } else if (element instanceof TypeClassDefn) {
                        ////
                        /// Process a type class definition
                        //
                       
                        final TypeClassDefn typeClass = (TypeClassDefn)element;

                        typeClassBindings.put(
                            typeClass.getTypeClassName(),
                            Binding.Definition.make(
                                new IdentifierInfo.TopLevel.TypeClass(
                                    QualifiedName.make(currentModuleName, typeClass.getTypeClassName())),
                                typeClass,
                                typeClass.getSourceRangeOfName()));

                        for (final TypeClassDefn.ClassMethodDefn classMethod : typeClass.getClassMethodDefns()) {
                            // We process each class method as well
                           
                            functionAndClassMethodBindings.put(
                                classMethod.getMethodName(),
                                Binding.Definition.make(
                                    new IdentifierInfo.TopLevel.FunctionOrClassMethod(
                                        QualifiedName.make(currentModuleName, classMethod.getMethodName())),
                                    classMethod,
                                    classMethod.getSourceRangeOfName()));
                        }
                    }
                   
                    // there are other top level definitions: instances, function type declarations - those do
                    // not create bindings
                }
            }

            /**
             * Creates a new scope containing bindings for parameters in an algebraic function definition.
             * @param parent the parent scope.
             * @param algebraicFunction the source element corresponding to the new scope.
             * @return the new scope.
             */
            SymbolTable newScope(final SymbolTable parent, final FunctionDefn.Algebraic algebraicFunction) {
                final BindingsMap<IdentifierInfo.Local> bindings = BindingsMap.make();

                final IdentifierInfo.TopLevel.FunctionOrClassMethod functionIdentifierInfo =
                    new IdentifierInfo.TopLevel.FunctionOrClassMethod(QualifiedName.make(currentModuleName, algebraicFunction.getName()));

                for (final Parameter param : algebraicFunction.getParameters()) {
                    bindings.put(
                        param.getName(),
                        Binding.Definition.<IdentifierInfo.Local>make(
                            new IdentifierInfo.Local.Parameter.TopLevelFunctionOrClassMethod(param.getName(), functionIdentifierInfo),
                            param,
                            param.getSourceRangeOfNameNotIncludingPotentialPling()));
                }

                return makeLocalScope(parent, bindings);
            }

            /**
             * Creates a new scope containing bindings for parameters declared *only* in the CALDoc comment of an algebraic function.
             * @param parent the parent scope.
             * @param algebraicFunction the source element corresponding to the new scope.
             * @param caldocComment the CALDoc comment. Can be null.
             * @return the new scope.
             */
            SymbolTable newScopeFromCALDoc(final SymbolTable parent, final FunctionDefn.Algebraic algebraicFunction, final CALDoc.Comment.Function caldocComment) {
                return makeTopLevelFunctionOrClassMethodScopeFromCALDoc(parent, algebraicFunction.getName(), caldocComment);
            }

            /**
             * Creates a new scope containing bindings for parameters declared in the CALDoc comment of a foreign function.
             * @param parent the parent scope.
             * @param foreignFunction the source element corresponding to the new scope.
             * @return the new scope.
             */
            SymbolTable newScopeFromCALDoc(final SymbolTable parent, final FunctionDefn.Foreign foreignFunction) {
                return makeTopLevelFunctionOrClassMethodScopeFromCALDoc(parent, foreignFunction.getName(), foreignFunction.getCALDocComment());
            }

            /**
             * Creates a new scope containing bindings for parameters declared in the CALDoc comment of a primitive function.
             * @param parent the parent scope.
             * @param primitiveFunction the source element corresponding to the new scope.
             * @return the new scope.
             */
            SymbolTable newScopeFromCALDoc(final SymbolTable parent, final FunctionDefn.Primitive primitiveFunction) {
                return makeTopLevelFunctionOrClassMethodScopeFromCALDoc(parent, primitiveFunction.getName(), primitiveFunction.getCALDocComment());
            }

            /**
             * Creates a new scope containing bindings for parameters declared in the CALDoc comment of a class method.
             * @param parent the parent scope.
             * @param classMethod the source element corresponding to the new scope.
             * @return the new scope.
             */
            SymbolTable newScopeFromCALDoc(final SymbolTable parent, final TypeClassDefn.ClassMethodDefn classMethod) {
                return makeTopLevelFunctionOrClassMethodScopeFromCALDoc(parent, classMethod.getMethodName(), classMethod.getCALDocComment());
            }

            /**
             * Internal helper method for creating a new scope containing bindings for parameters declared on in the CALDoc Comment of a top-level function or class method.
             * @param parent the parent scope.
             * @param functionOrClassMethodName the name of the function/method.
             * @param caldocComment the CALDoc comment. Can be null.
             * @return the new scope.
             */
            private SymbolTable makeTopLevelFunctionOrClassMethodScopeFromCALDoc(final SymbolTable parent, final String functionOrClassMethodName, final CALDoc.Comment caldocComment) {
                final BindingsMap<IdentifierInfo.Local> bindings = BindingsMap.make();

                if (caldocComment != null) {
                    final QualifiedName qualifiedFunctionOrClassMethodName =
                        QualifiedName.make(currentModuleName, functionOrClassMethodName);
                   
                    final IdentifierInfo.TopLevel.FunctionOrClassMethod functionIdentifierInfo =
                        new IdentifierInfo.TopLevel.FunctionOrClassMethod(qualifiedFunctionOrClassMethodName);
                   
                    final SourceModelTraverser<Void, Void> argBlocksVisitor = new SourceModelTraverser<Void, Void>() {
                        @Override
                        public Void visit_CALDoc_TaggedBlock_Arg(CALDoc.TaggedBlock.Arg argBlock, Void arg) {

                            final Name.Field argName = argBlock.getArgName();

                            if (argName.getName() instanceof FieldName.Textual) {
                                final String argNameString = argName.getName().getCalSourceForm();
                               
                                // We only create a binding for the parameter if it is not already bound
                                // (e.g. in the case of an algebraic function parameter - it can appear both
                                // in the parameter list and in the CALDoc comment - so the parameter list occurrence
                                // takes precedence, and we do not create a binding from the CALDoc occurrence)
                               
                                final Binding<? extends IdentifierInfo> existingBindingForArgName =
                                    parent.findVariableDefinition(argNameString);
                               
                                boolean alreadyBound = false;
                                if (existingBindingForArgName != null
                                    && existingBindingForArgName.getIdentifierInfo() instanceof IdentifierInfo.Local.Parameter.TopLevelFunctionOrClassMethod) {
                                   
                                    IdentifierInfo.Local.Parameter.TopLevelFunctionOrClassMethod infoForExistingBindingForArgName =
                                        (IdentifierInfo.Local.Parameter.TopLevelFunctionOrClassMethod)existingBindingForArgName.getIdentifierInfo();
                                   
                                    if (qualifiedFunctionOrClassMethodName.equals(infoForExistingBindingForArgName.getAssociatedFunction().getResolvedName())) {
                                        alreadyBound = true;
                                    }
                                }

                                if (!alreadyBound) {
                                    bindings.put(
                                        argNameString,
                                        Binding.Definition.<IdentifierInfo.Local>make(
                                            new IdentifierInfo.Local.Parameter.TopLevelFunctionOrClassMethod(argNameString, functionIdentifierInfo),
                                            argName,
                                            argName.getSourceRange()));
                                }
                            }

                            return super.visit_CALDoc_TaggedBlock_Arg(argBlock, arg);
                        }
                    };

                    caldocComment.accept(argBlocksVisitor, null);
                }

                return makeLocalScope(parent, bindings);
            }

            /**
             * Creates a new scope containing bindings for parameters declared on in the CALDoc Comment of a local function.
             * @param parent the parent scope.
             * @param localFunctionIdentifierInfo the identifier info of the local function.
             * @param caldocComment the CALDoc comment. Can be null.
             * @return the new scope.
             */
            SymbolTable newScopeFromCALDoc(final SymbolTable parent, final IdentifierInfo.Local.Function localFunctionIdentifierInfo, final CALDoc.Comment.Function caldocComment) {
                final BindingsMap<IdentifierInfo.Local> bindings = BindingsMap.make();

                if (caldocComment != null) {
                    final SourceModelTraverser<Void, Void> argBlocksVisitor = new SourceModelTraverser<Void, Void>() {
                        @Override
                        public Void visit_CALDoc_TaggedBlock_Arg(CALDoc.TaggedBlock.Arg argBlock, Void arg) {

                            final Name.Field argName = argBlock.getArgName();

                            if (argName.getName() instanceof FieldName.Textual) {
                                final String argNameString = argName.getName().getCalSourceForm();
                               
                                // We only create a binding for the parameter if it is not already bound
                                // (e.g. in the case of an algebraic function parameter - it can appear both
                                // in the parameter list and in the CALDoc comment - so the parameter list occurrence
                                // takes precedence, and we do not create a binding from the CALDoc occurrence)
                               
                                final Binding<? extends IdentifierInfo> existingBindingForArgName =
                                    parent.findVariableDefinition(argNameString);
                               
                                boolean alreadyBound = false;
                                if (existingBindingForArgName != null
                                    && existingBindingForArgName.getIdentifierInfo() instanceof IdentifierInfo.Local.Parameter.LocalFunction) {
                                   
                                    IdentifierInfo.Local.Parameter.LocalFunction infoForExistingBindingForArgName =
                                        (IdentifierInfo.Local.Parameter.LocalFunction)existingBindingForArgName.getIdentifierInfo();
                                   
                                    if (localFunctionIdentifierInfo.getLocalFunctionIdentifier().equals(infoForExistingBindingForArgName.getAssociatedFunction().getLocalFunctionIdentifier())) {
                                        alreadyBound = true;
                                    }
                                }

                                if (!alreadyBound) {
                                    bindings.put(
                                        argNameString,
                                        Binding.Definition.<IdentifierInfo.Local>make(
                                            new IdentifierInfo.Local.Parameter.LocalFunction(argNameString, localFunctionIdentifierInfo),
                                            argName,
                                            argName.getSourceRange()));
                                }
                            }

                            return super.visit_CALDoc_TaggedBlock_Arg(argBlock, arg);
                        }
                    };

                    caldocComment.accept(argBlocksVisitor, null);
                }

                return makeLocalScope(parent, bindings);
            }

            /**
             * Creates a new scope containing bindings for parameters declared on in the CALDoc Comment of an instance method.
             * @param parent the parent scope.
             * @param instanceMethod the source element corresponding to the new scope.
             * @return the new scope.
             */
            SymbolTable newScopeFromCALDoc(final SymbolTable parent, final InstanceDefn.InstanceMethod instanceMethod) {
                final BindingsMap<IdentifierInfo.Local> bindings = BindingsMap.make();
               
                final CALDoc.Comment.InstanceMethod caldocComment = instanceMethod.getCALDocComment();

                if (caldocComment != null) {
                    final SourceModelTraverser<Void, Void> argBlocksVisitor = new SourceModelTraverser<Void, Void>() {
                        @Override
                        public Void visit_CALDoc_TaggedBlock_Arg(CALDoc.TaggedBlock.Arg argBlock, Void arg) {

                            final Name.Field argName = argBlock.getArgName();

                            if (argName.getName() instanceof FieldName.Textual) {
                                final String argNameString = argName.getName().getCalSourceForm();

                                bindings.put(
                                    argNameString,
                                    Binding.Definition.<IdentifierInfo.Local>make(
                                        new IdentifierInfo.Local.Parameter.InstanceMethodCALDoc(argNameString),
                                        argName,
                                        argName.getSourceRange()));
                            }

                            return super.visit_CALDoc_TaggedBlock_Arg(argBlock, arg);
                        }
                    };

                    caldocComment.accept(argBlocksVisitor, null);
                }

                return makeLocalScope(parent, bindings);
            }

            /**
             * Creates a new scope containing bindings for parameters in a lambda.
             * @param parent the parent scope.
             * @param lambda the source element corresponding to the new scope.
             * @param caseAndLambdaBoundLocalFunctionIdentifierGenerator the identifier generator for case-bound and lambda-bound variables.
             * @return the new scope.
             */
            SymbolTable newScope(final SymbolTable parent, final Expr.Lambda lambda, final LocalFunctionIdentifierGenerator caseAndLambdaBoundLocalFunctionIdentifierGenerator) {
                final BindingsMap<IdentifierInfo.Local> bindings = BindingsMap.make();

                for (final Parameter param : lambda.getParameters()) {
                    bindings.put(
                        param.getName(),
                        Binding.Definition.<IdentifierInfo.Local>make(
                            new IdentifierInfo.Local.Parameter.Lambda(
                                param.getName(),
                                caseAndLambdaBoundLocalFunctionIdentifierGenerator.generateLocalFunctionIdentifier(currentModuleName, param.getName())),
                            param,
                            param.getSourceRangeOfNameNotIncludingPotentialPling()));
                }

                return makeLocalScope(parent, bindings);
            }

            /**
             * Creates a new scope containing bindings for parameters in a local function definition.
             * @param parent the parent scope.
             * @param localFunction the source element corresponding to the new scope.
             * @return the new scope.
             */
            SymbolTable newScope(final SymbolTable parent, final LocalDefn.Function.Definition localFunction, final IdentifierInfo.Local.Function localFunctionIdentifierInfo) {
                final BindingsMap<IdentifierInfo.Local> bindings = BindingsMap.make();

                for (final Parameter param : localFunction.getParameters()) {
                    bindings.put(
                        param.getName(),
                        Binding.Definition.<IdentifierInfo.Local>make(
                            new IdentifierInfo.Local.Parameter.LocalFunction(param.getName(), localFunctionIdentifierInfo),
                            param,
                            param.getSourceRangeOfNameNotIncludingPotentialPling()));
                }

                return makeLocalScope(parent, bindings);
            }

            /**
             * Creates a new scope containing bindings for local variables bound in a let expression.
             * @param parent the parent scope.
             * @param letExpr the source element corresponding to the new scope.
             * @param localFunctionIdentifierGenerator the generator of local function identifiers to use.
             * @return the new scope.
             */
            SymbolTable newScope(final SymbolTable parent, final Expr.Let letExpr, final LocalFunctionIdentifierGenerator localFunctionIdentifierGenerator) {
                final BindingsMap<IdentifierInfo.Local> bindings = BindingsMap.make();

                /**
                 * Handles the adding of bindings for local definitions (both local functions and local pattern match declarations).
                 * This is done by walking the local definitions and adding a binding for each local function / pattern-bound variable
                 * encountered. The synthetic local function generated by the compiler for desugaring a local pattern match declaration
                 * is also taken into account.
                 *
                 * @author Joseph Wong
                 */
                class LocallyDefinedNamesCollector extends LocalBindingsProcessor<LinkedHashSet<String>, Void> {
                    @Override
                    void processLocalDefinitionBinding(final String name, final SourceElement localDefinition, final LinkedHashSet<String> arg) {
                        // do nothing... just defer to the additional processing methods
                    }

                    @Override
                    void additionallyProcessLocalDefnFunctionDefinition(final LocalDefn.Function.Definition function, final LinkedHashSet<String> patternVarNames) {
                        final String functionName = function.getName();
                        bindings.put(
                            functionName,
                            Binding.Definition.<IdentifierInfo.Local>make(
                                new IdentifierInfo.Local.Function(
                                    functionName,
                                    localFunctionIdentifierGenerator.generateLocalFunctionIdentifier(currentModuleName, functionName)),
                                function,
                                function.getNameSourceRange()));
                    }

                    @Override
                    void additionallyProcessPatternVar(final Pattern.Var var, final LinkedHashSet<String> patternVarNames) {
                        final String varName = var.getName();
                        // add to the set of pattern var names for a local pattern match decl
                        patternVarNames.add(varName);

                        bindings.put(
                            varName,
                            Binding.Definition.<IdentifierInfo.Local>make(
                                new IdentifierInfo.Local.PatternMatchVariable(
                                    varName,
                                    IdentifierInfo.Local.PatternVariableKind.regular,
                                    localFunctionIdentifierGenerator.generateLocalFunctionIdentifier(currentModuleName, varName)),
                                var,
                                var.getSourceRange()));
                    }

                    @Override
                    void additionallyProcessPunnedTextualRecordFieldPattern(final FieldName.Textual fieldName, final Name.Field fieldNameElement, final LinkedHashSet<String> patternVarNames) {
                        final String varName = fieldName.getCalSourceForm();
                        // add to the set of pattern var names for a local pattern match decl
                        patternVarNames.add(varName);

                        bindings.put(
                            varName,
                            Binding.PunnedTextualRecordFieldName.<IdentifierInfo.Local>make(
                                new IdentifierInfo.Local.PatternMatchVariable(
                                    varName,
                                    IdentifierInfo.Local.PatternVariableKind.punnedRecordField,
                                    localFunctionIdentifierGenerator.generateLocalFunctionIdentifier(currentModuleName, varName)),
                                fieldNameElement,
                                fieldNameElement.getSourceRange()));
                    }

                    @Override
                    void additionallyProcessPunnedTextualDataConsFieldPattern(final FieldName.Textual fieldName, final Name.Field fieldNameElement, final Name.DataCons dataConsName, final LinkedHashSet<String> patternVarNames) {
                        final String varName = fieldName.getCalSourceForm();
                        // add to the set of pattern var names for a local pattern match decl
                        patternVarNames.add(varName);

                        final Binding<IdentifierInfo.TopLevel.DataCons> dataConsBinding = parent.findDataCons(dataConsName);

                        if (dataConsBinding != null) {
                            bindings.put(
                                varName,
                                Binding.PunnedTextualDataConsFieldName.<IdentifierInfo.Local>make(
                                    new IdentifierInfo.Local.PatternMatchVariable(
                                        varName,
                                        IdentifierInfo.Local.PatternVariableKind.punnedDataConsField,
                                        localFunctionIdentifierGenerator.generateLocalFunctionIdentifier(currentModuleName, varName)),
                                        Collections.singletonList(dataConsBinding),
                                        fieldNameElement,
                                        fieldNameElement.getSourceRange()));
                        }
                    }

                    /**
                     * Handle the synthetic local function which is generated by the compiler to host the defining
                     * expression of a local pattern match declaration. This is done to keep the local function identifier generator in
                     * sync with what the compiler would do.
                     *
                     * @param patternMatchDecl the pattern match declaration.
                     * @param patternVarNames the LinkedHashSet of the pattern variable names, in source order.
                     */
                    private void handleBindingForSyntheticLocalDefinition(final LocalDefn.PatternMatch patternMatchDecl, final LinkedHashSet<String> patternVarNames) {
                        final String syntheticLocalFunctionName = FreeVariableFinder.makeTempVarNameForDesugaredLocalPatternMatchDecl(patternVarNames);
                        localFunctionIdentifierGenerator.generateLocalFunctionIdentifier(currentModuleName, syntheticLocalFunctionName);
                    }

                    @Override
                    public Void visit_LocalDefn_PatternMatch_UnpackDataCons(final LocalDefn.PatternMatch.UnpackDataCons unpackDataCons, final LinkedHashSet<String> arg) {
                        // visit only the patterns
                        final LinkedHashSet<String> patternVarNames = new LinkedHashSet<String>();
                        super.visit_LocalDefn_PatternMatch_UnpackDataCons(unpackDataCons, patternVarNames);
                        // handle the synthetic definition last
                        handleBindingForSyntheticLocalDefinition(unpackDataCons, patternVarNames);
                        return null;
                    }

                    @Override
                    public Void visit_LocalDefn_PatternMatch_UnpackListCons(final LocalDefn.PatternMatch.UnpackListCons unpackListCons, final LinkedHashSet<String> arg) {
                        // visit only the patterns
                        final LinkedHashSet<String> patternVarNames = new LinkedHashSet<String>();
                        super.visit_LocalDefn_PatternMatch_UnpackListCons(unpackListCons, patternVarNames);
                        // handle the synthetic definition last
                        handleBindingForSyntheticLocalDefinition(unpackListCons, patternVarNames);
                        return null;
                    }

                    @Override
                    public Void visit_LocalDefn_PatternMatch_UnpackRecord(final LocalDefn.PatternMatch.UnpackRecord unpackRecord, final LinkedHashSet<String> arg) {
                        // visit only the field patterns (and not the base record pattern - since we do not support them in local pattern match decl)
                        final LinkedHashSet<String> patternVarNames = new LinkedHashSet<String>();
                        super.visit_LocalDefn_PatternMatch_UnpackRecord(unpackRecord, patternVarNames);
                        // handle the synthetic definition last
                        handleBindingForSyntheticLocalDefinition(unpackRecord, patternVarNames);
                        return null;
                    }

                    @Override
                    public Void visit_LocalDefn_PatternMatch_UnpackTuple(final LocalDefn.PatternMatch.UnpackTuple unpackTuple, final LinkedHashSet<String> arg) {
                        // visit only the patterns
                        final LinkedHashSet<String> patternVarNames = new LinkedHashSet<String>();
                        super.visit_LocalDefn_PatternMatch_UnpackTuple(unpackTuple, patternVarNames);
                        // handle the synthetic definition last
                        handleBindingForSyntheticLocalDefinition(unpackTuple, patternVarNames);
                        return null;
                    }
                }

                final LocallyDefinedNamesCollector localDefinedNamesCollector = new LocallyDefinedNamesCollector() ;

                for (final LocalDefn defn : letExpr.getLocalDefinitions()) {
                    defn.accept(localDefinedNamesCollector, null);
                }

                return makeLocalScope(parent, bindings);
            }

            /**
             * Creates a new scope containing bindings for parameters in a case alternative.
             * @param parent the parent scope.
             * @param tuple the source element corresponding to the new scope.
             * @param caseAndLambdaBoundLocalFunctionIdentifierGenerator the identifier generator for case-bound and lambda-bound variables.
             * @return the new scope.
             */
            SymbolTable newScope(final SymbolTable parent, final Expr.Case.Alt.UnpackTuple tuple, final LocalFunctionIdentifierGenerator caseAndLambdaBoundLocalFunctionIdentifierGenerator) {
                return makeScopeFromCasePatterns(parent, tuple.getPatterns(), caseAndLambdaBoundLocalFunctionIdentifierGenerator);
            }

            /**
             * Creates a new scope containing bindings for parameters in a case alternative.
             * @param parent the parent scope.
             * @param listCons the source element corresponding to the new scope.
             * @param caseAndLambdaBoundLocalFunctionIdentifierGenerator the identifier generator for case-bound and lambda-bound variables.
             * @return the new scope.
             */
            SymbolTable newScope(final SymbolTable parent, final Expr.Case.Alt.UnpackListCons listCons, final LocalFunctionIdentifierGenerator caseAndLambdaBoundLocalFunctionIdentifierGenerator) {
                return makeScopeFromCasePatterns(parent, new Pattern[] {listCons.getHeadPattern(), listCons.getTailPattern()}, caseAndLambdaBoundLocalFunctionIdentifierGenerator);
            }

            /**
             * Internal helper method for creating a new scope containing bindings for parameters in a case alternative.
             * @param parent the parent scope.
             * @param patterns the pattern source elements.
             * @param caseAndLambdaBoundLocalFunctionIdentifierGenerator the identifier generator for case-bound and lambda-bound variables.
             * @return the new scope.
             */
            private SymbolTable makeScopeFromCasePatterns(final SymbolTable parent, final Pattern[] patterns, final LocalFunctionIdentifierGenerator caseAndLambdaBoundLocalFunctionIdentifierGenerator) {
                final BindingsMap<IdentifierInfo.Local> bindings = BindingsMap.make();

                for (final Pattern pattern : patterns) {
                    addCasePatternToBindings(bindings, pattern, caseAndLambdaBoundLocalFunctionIdentifierGenerator);
                }

                return makeLocalScope(parent, bindings);
            }

            /**
             * Processes a case-bound pattern and potentially add it to the bindings map.
             * @param bindings the bindings map.
             * @param pattern the case-bound pattern.
             * @param caseAndLambdaBoundLocalFunctionIdentifierGenerator the identifier generator for case-bound and lambda-bound variables.
             */
            private void addCasePatternToBindings(final BindingsMap<IdentifierInfo.Local> bindings, final Pattern pattern, final LocalFunctionIdentifierGenerator caseAndLambdaBoundLocalFunctionIdentifierGenerator) {
                if (pattern instanceof Pattern.Var) {
                    final Pattern.Var patternVariable = (Pattern.Var)pattern;
                    final String name = patternVariable.getName();

                    bindings.put(
                        name,
                        Binding.Definition.<IdentifierInfo.Local>make(
                            new IdentifierInfo.Local.CasePatternVariable(
                                name,
                                caseAndLambdaBoundLocalFunctionIdentifierGenerator.generateLocalFunctionIdentifier(currentModuleName, name),
                                IdentifierInfo.Local.PatternVariableKind.regular),
                            patternVariable,
                            patternVariable.getSourceRange()));
                }
            }

            /**
             * Creates a new scope containing bindings for parameters in a case alternative.
             * @param parent the parent scope.
             * @param record the source element corresponding to the new scope.
             * @param caseAndLambdaBoundLocalFunctionIdentifierGenerator the identifier generator for case-bound and lambda-bound variables.
             * @return the new scope.
             */
            SymbolTable newScope(final SymbolTable parent, final Expr.Case.Alt.UnpackRecord record, final LocalFunctionIdentifierGenerator caseAndLambdaBoundLocalFunctionIdentifierGenerator) {
                final BindingsMap<IdentifierInfo.Local> bindings = BindingsMap.make();

                if (record.getBaseRecordPattern() != null) {
                    addCasePatternToBindings(bindings, record.getBaseRecordPattern(), caseAndLambdaBoundLocalFunctionIdentifierGenerator);
                }

                for (final FieldPattern fieldPattern : record.getFieldPatterns()) {
                    final Pattern pattern = fieldPattern.getPattern();

                    if (pattern != null) {
                        addCasePatternToBindings(bindings, pattern, caseAndLambdaBoundLocalFunctionIdentifierGenerator);

                    } else {
                        // a punned field pattern
                        final FieldName fieldName = fieldPattern.getFieldName().getName();
                        final String varName = fieldName.getCalSourceForm();

                        if (fieldName instanceof FieldName.Textual) {
                            bindings.put(
                                fieldName.getCalSourceForm(),
                                Binding.PunnedTextualRecordFieldName.<IdentifierInfo.Local>make(
                                    new IdentifierInfo.Local.CasePatternVariable(
                                        varName,
                                        caseAndLambdaBoundLocalFunctionIdentifierGenerator.generateLocalFunctionIdentifier(currentModuleName, varName),
                                        IdentifierInfo.Local.PatternVariableKind.punnedRecordField),
                                    fieldPattern.getFieldName(),
                                    fieldPattern.getFieldName().getSourceRange()));
                        }
                    }
                }

                return makeLocalScope(parent, bindings);
            }

            /**
             * Creates a new scope containing bindings for parameters in a case alternative.
             * @param parent the parent scope.
             * @param dataCons the source element corresponding to the new scope.
             * @param caseAndLambdaBoundLocalFunctionIdentifierGenerator the identifier generator for case-bound and lambda-bound variables.
             * @return the new scope.
             */
            SymbolTable newScope(final SymbolTable parent, final Expr.Case.Alt.UnpackDataCons dataCons, final LocalFunctionIdentifierGenerator caseAndLambdaBoundLocalFunctionIdentifierGenerator) {
                final ArgBindings argBindings = dataCons.getArgBindings();

                if (argBindings instanceof ArgBindings.Matching) {
                    final BindingsMap<IdentifierInfo.Local> bindings = BindingsMap.make();

                    for (final FieldPattern fieldPattern : ((ArgBindings.Matching)argBindings).getFieldPatterns()) {
                        final Pattern pattern = fieldPattern.getPattern();

                        if (pattern != null) {
                            addCasePatternToBindings(bindings, pattern, caseAndLambdaBoundLocalFunctionIdentifierGenerator);

                        } else {
                            // a punned field pattern
                            final FieldName fieldName = fieldPattern.getFieldName().getName();
                            final String varName = fieldName.getCalSourceForm();
                           
                            if (fieldName instanceof FieldName.Textual) {
                                final List<Binding<IdentifierInfo.TopLevel.DataCons>> dataConsBindings =
                                    new ArrayList<Binding<IdentifierInfo.TopLevel.DataCons>>();
                               
                                for (final Name.DataCons dataConsName : dataCons.getDataConsNames()) {
                                    final Binding<IdentifierInfo.TopLevel.DataCons> dataConsBinding = parent.findDataCons(dataConsName);
                                   
                                    if (dataConsBinding != null) {
                                        dataConsBindings.add(dataConsBinding);
                                    }
                                }
                               
                                if (!dataConsBindings.isEmpty()) {
                                    bindings.put(
                                        fieldName.getCalSourceForm(),
                                        Binding.PunnedTextualDataConsFieldName.<IdentifierInfo.Local>make(
                                            new IdentifierInfo.Local.CasePatternVariable(
                                                varName,
                                                caseAndLambdaBoundLocalFunctionIdentifierGenerator.generateLocalFunctionIdentifier(currentModuleName, varName),
                                                IdentifierInfo.Local.PatternVariableKind.punnedDataConsField),
                                            dataConsBindings,
                                            fieldPattern.getFieldName(),
                                            fieldPattern.getFieldName().getSourceRange()));
                                }
                            }
                        }
                    }

                    return makeLocalScope(parent, bindings);

                } else if (argBindings instanceof ArgBindings.Positional) {
                    return makeScopeFromCasePatterns(parent, ((ArgBindings.Positional)argBindings).getPatterns(), caseAndLambdaBoundLocalFunctionIdentifierGenerator);

                } else {
                    throw new IllegalStateException();
                }
            }
        }
       
        ////
        /// Fields
        //
       
        /**
         * The name of the module associated with the source being visited.
         */
        private final ModuleName currentModuleName;
       
        /**
         * The main scope builder for constructing symbol tables.
         */
        private final ScopeBuilder scopeBuilder;
       
        /**
         * A scope builder for constructing symbol tables without recording the new scopes.
         */
        // todo-jowong this is only needed because the argument scope of a function (both top-level algebraic and local)
        // needs to be recreated for the CALDoc associated with the type declaration of the function
        // When the type declaration becomes a component of the function definition, this field can go away
        // (because we would always record the scope)
        private final ScopeBuilder nonRecordingScopeBuilder;
       
        ////
        /// Constructor
        //
       
        /**
         * Constructs an instance of this class.
         * @param currentModuleName the name of the module associated with the source being visited.
         */
        public Visitor(final ModuleName currentModuleName) {
            if (currentModuleName == null) {
                throw new NullPointerException();
            }
            this.currentModuleName = currentModuleName;
            this.scopeBuilder = new ScopeBuilder(true);
            this.nonRecordingScopeBuilder = new ScopeBuilder(false);
        }
       
        ////
        /// Accessors
        //
       
        /**
         * @return the name of the module associated with the source being visited.
         */
        public ModuleName getCurrentModuleName() {
            return currentModuleName;
        }
       
        ////
        /// Handler methods
        //

        /**
         * Processes the creation of a new scope.
         * @param scope the new scope.
         */
        protected void handleNewScope(final SymbolTable scope) {}
       
        /**
         * Processes the creation of a new top level scope. This default implementation delegates to {@link #handleNewScope}.
         * @param scope the new scope.
         */
        protected void handleNewTopLevelScope(final SymbolTable.TopLevelScope scope) {
            handleNewScope(scope);
        }
       
        /**
         * Processes the creation of a new local scope. This default implementation delegates to {@link #handleNewScope}.
         * @param scope the new scope.
         */
        protected void handleNewLocalScope(final SymbolTable.LocalScope scope) {
            handleNewScope(scope);
        }
       
        /**
         * Processes the creation of a new type variable scope.
         * @param scope the new scope.
         */
        protected void handleNewTypeVariableScope(final TypeVariableScope scope) {}
       
        /**
         * Processes a foreign function descriptor occurrence.
         * @param descriptorOccurrence the occurrence.
         */
        protected void handleForeignFunctionDescriptor(
            final ForeignDescriptor<IdentifierInfo.TopLevel.FunctionOrClassMethod> descriptorOccurrence) {}
       
        /**
         * Processes a foreign type descriptor occurrence.
         * @param descriptorOccurrence the occurrence.
         */
        protected void handleForeignTypeDescriptor(
            final ForeignDescriptor<IdentifierInfo.TopLevel.TypeCons> descriptorOccurrence) {}
       
        ////
        /// Visitor methods - elements which introduce new scopes
        //
       
        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_ModuleDefn(final ModuleDefn defn, final VisitorArgument<T> arg) {
            return super.visit_ModuleDefn(defn, arg.updateScope(scopeBuilder.newScope(arg.getScope(), defn)));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_FunctionDefn_Algebraic(final FunctionDefn.Algebraic algebraic, final VisitorArgument<T> arg) {
            // make a new scope with the declared arguments
            final VisitorArgument<T> newArg = makeVisitorArgumentWithAlgebraicFunctionArgumentNames(algebraic, arg, scopeBuilder);

            // we traverse the CALDoc with an augmented scope for non-lexical arguments (those defined only in the
            // CALDoc comment)
            if (algebraic.getCALDocComment() != null) {
                algebraic.getCALDocComment().accept(this, newArg.updateScope(scopeBuilder.newScopeFromCALDoc(newArg.getScope(), algebraic, algebraic.getCALDocComment())));
            }
           
            // we visit the rest of the definition with just the lexically declared argument scope
            for (final Parameter parameter : algebraic.getParameters()) {
                parameter.accept(this, newArg);
            }
           
            algebraic.getDefiningExpr().accept(this, newArg);
            return null;
        }

        /**
         * Constructs a new visitor argument based on an existing one, but with an updated scope and updated
         * local function identifier generators for the current algebraic function.
         * @param algebraic the algebraic function.
         * @param arg the existing visitor argument.
         * @param scopeBuilderToUse the scope builder to use.
         * @return the new visitor argument.
         */
        protected VisitorArgument<T> makeVisitorArgumentWithAlgebraicFunctionArgumentNames(final FunctionDefn.Algebraic algebraic, final VisitorArgument<T> arg, final ScopeBuilder scopeBuilderToUse) {
            final LocalFunctionIdentifierGenerator localFunctionIdentifierGenerator = new LocalFunctionIdentifierGenerator();
            localFunctionIdentifierGenerator.reset(algebraic.getName());
            final VisitorArgument<T> newArg = arg.updateScopeWithNewLocalFunctionIdentifierGenerator(scopeBuilderToUse.newScope(arg.getScope(), algebraic), algebraic);
            return newArg;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_FunctionTypeDeclaraction(final FunctionTypeDeclaration declaration, final VisitorArgument<T> arg) {
            // todo-jowong this special handling won't be necessary if the type declaration is made a component of the
            // algebraic function definition
           
            final Binding<? extends IdentifierInfo> functionBinding =
                arg.getScope().findVariableDefinition(declaration.getFunctionName());
           
            if (functionBinding != null && functionBinding.getSourceElement() instanceof FunctionDefn.Algebraic) {
                final FunctionDefn.Algebraic algebraicFunction = (FunctionDefn.Algebraic)functionBinding.getSourceElement();
               
                final VisitorArgument<T> newArg = makeVisitorArgumentWithAlgebraicFunctionArgumentNames(algebraicFunction, arg, nonRecordingScopeBuilder);
               
                // we traverse the CALDoc with an augmented scope for non-lexical arguments (those defined only in the
                // CALDoc comment)
                if (declaration.getCALDocComment() != null) {
                    declaration.getCALDocComment().accept(this, newArg.updateScope(scopeBuilder.newScopeFromCALDoc(newArg.getScope(), algebraicFunction, declaration.getCALDocComment())));
                }
            } else {
                // we simply traverse the CALDoc with the existing scope
                if (declaration.getCALDocComment() != null) {
                    declaration.getCALDocComment().accept(this, arg);
                }
            }
           
            declaration.getTypeSignature().accept(this, arg);
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_FunctionDefn_Foreign(final FunctionDefn.Foreign foreign, final VisitorArgument<T> arg) {
            // Only the CALDoc comment is under the new scope (which is generated by the comment itself)
            if (foreign.getCALDocComment() != null) {
                foreign.getCALDocComment().accept(this, arg.updateScope(scopeBuilder.newScopeFromCALDoc(arg.getScope(), foreign)));
            }
            // Visit the remainder of the definition
            foreign.getDeclaredType().accept(this, arg);
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_FunctionDefn_Primitive(final FunctionDefn.Primitive primitive, final VisitorArgument<T> arg) {
            // Only the CALDoc comment is under the new scope (which is generated by the comment itself)
            if (primitive.getCALDocComment() != null) {
                primitive.getCALDocComment().accept(this, arg.updateScope(scopeBuilder.newScopeFromCALDoc(arg.getScope(), primitive)));
            }
            // Visit the remainder of the definition
            primitive.getDeclaredType().accept(this, arg);
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_TypeClassDefn_ClassMethodDefn(final TypeClassDefn.ClassMethodDefn defn, final VisitorArgument<T> arg) {
            // Only the CALDoc comment is under the new scope (which is generated by the comment itself)
            if (defn.getCALDocComment() != null) {
                defn.getCALDocComment().accept(this, arg.updateScope(scopeBuilder.newScopeFromCALDoc(arg.getScope(), defn)));
            }
           
            // Visit the remainder of the definition
            defn.getTypeSignature().accept(this, arg);
           
            if (defn.getDefaultClassMethodName() != null) {
                defn.getDefaultClassMethodName().accept(this, arg);
            }
           
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_InstanceDefn_InstanceMethod(final InstanceDefn.InstanceMethod method, final VisitorArgument<T> arg) {
            // Only the CALDoc comment is under the new scope (which is generated by the comment itself)
            if (method.getCALDocComment() != null) {
                method.getCALDocComment().accept(this, arg.updateScope(scopeBuilder.newScopeFromCALDoc(arg.getScope(), method)));
            }
           
            // Visit the remainder of the definition
            method.getResolvingFunctionName().accept(this, arg);
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_Expr_Lambda(final Expr.Lambda lambda, final VisitorArgument<T> arg) {
            return super.visit_Expr_Lambda(lambda, arg.updateScope(scopeBuilder.newScope(arg.getScope(), lambda, arg.getCaseAndLambdaBoundLocalFunctionIdentifierGenerator())));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_Expr_Let(final Expr.Let let, final VisitorArgument<T> arg) {
            return super.visit_Expr_Let(let, arg.updateScope(scopeBuilder.newScope(arg.getScope(), let, arg.getLocalFunctionIdentifierGenerator())));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_LocalDefn_Function_Definition(final LocalDefn.Function.Definition function, final VisitorArgument<T> arg) {
            final VisitorArgument<T> newArg = makeVisitorArgumentWithLocalFunctionArgumentNames(function, arg, scopeBuilder);
           
            final Binding<? extends IdentifierInfo> functionBinding =
                arg.getScope().findVariableDefinition(function.getName());
           
            if (functionBinding != null && functionBinding.getSourceElement() == function) {
                final IdentifierInfo.Local.Function localFunctionIdentifierInfo = (IdentifierInfo.Local.Function)functionBinding.getIdentifierInfo();
               
                // we traverse the CALDoc with an augmented scope for non-lexical arguments (those defined only in the
                // CALDoc comment)
                if (function.getCALDocComment() != null) {
                    function.getCALDocComment().accept(this, newArg.updateScope(scopeBuilder.newScopeFromCALDoc(newArg.getScope(), localFunctionIdentifierInfo, function.getCALDocComment())));
                }
            } else {
                throw new IllegalStateException("This name should have been bound in the scope");
            }
           
            // we visit the rest of the definition with just the lexically declared argument scope
            for (final Parameter parameter : function.getParameters()) {
                parameter.accept(this, newArg);
            }
           
            function.getDefiningExpr().accept(this, newArg);
            return null;
        }

        /**
         * Constructs a new visitor argument based on an existing one, but with an updated scope based on
         * the argument names of a local function.
         * @param function the local function.
         * @param arg the existing visitor argument.
         * @param scopeBuilderToUse the scope builder to use.
         * @return the new visitor argument.
         */
        protected VisitorArgument<T> makeVisitorArgumentWithLocalFunctionArgumentNames(final LocalDefn.Function.Definition function, final VisitorArgument<T> arg, final ScopeBuilder scopeBuilderToUse) {
            final IdentifierOccurrence<? extends IdentifierInfo> binding = arg.getScope().findVariableDefinition(function.getName());
            if (binding == null) {
                throw new IllegalStateException("This name should have been bound in the scope");
            }
            final VisitorArgument<T> newArg = arg.updateScope(scopeBuilderToUse.newScope(arg.getScope(), function, (IdentifierInfo.Local.Function)binding.getIdentifierInfo()));
            return newArg;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_LocalDefn_Function_TypeDeclaration(final LocalDefn.Function.TypeDeclaration declaration, final VisitorArgument<T> arg) {
            // todo-jowong this special handling won't be necessary if the type declaration is made a component of the
            // local function definition
           
            final Binding<? extends IdentifierInfo> functionBinding =
                arg.getScope().findVariableDefinition(declaration.getName());
           
            if (functionBinding != null && functionBinding.getSourceElement() instanceof LocalDefn.Function.Definition) {
                final LocalDefn.Function.Definition localFunction = (LocalDefn.Function.Definition)functionBinding.getSourceElement();
                final IdentifierInfo.Local.Function localFunctionIdentifierInfo = (IdentifierInfo.Local.Function)functionBinding.getIdentifierInfo();

                final VisitorArgument<T> newArg = makeVisitorArgumentWithLocalFunctionArgumentNames(localFunction, arg, nonRecordingScopeBuilder);
               
                // we traverse the CALDoc with an augmented scope for non-lexical arguments (those defined only in the
                // CALDoc comment)
                if (declaration.getCALDocComment() != null) {
                    declaration.getCALDocComment().accept(this, newArg.updateScope(scopeBuilder.newScopeFromCALDoc(newArg.getScope(), localFunctionIdentifierInfo, declaration.getCALDocComment())));
                }
            } else {
                // we simply traverse the CALDoc with the existing scope
                if (declaration.getCALDocComment() != null) {
                    declaration.getCALDocComment().accept(this, arg);
                }
            }
           
            declaration.getDeclaredType().accept(this, arg);
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_Expr_Case_Alt_UnpackDataCons(final Expr.Case.Alt.UnpackDataCons cons, final VisitorArgument<T> arg) {
            return super.visit_Expr_Case_Alt_UnpackDataCons(cons, updateScopeFor(cons, arg));
        }

        /**
         * Constructs a new visitor argument based on an existing one, but with an updated scope corresponding
         * to the specified case alternative.
         * @param cons the case alternative.
         * @param arg the existing visitor argument.
         * @return a new visitor argument.
         */
        protected VisitorArgument<T> updateScopeFor(final Expr.Case.Alt.UnpackDataCons cons, final VisitorArgument<T> arg) {
            return arg.updateScope(scopeBuilder.newScope(arg.getScope(), cons, arg.getCaseAndLambdaBoundLocalFunctionIdentifierGenerator()));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_Expr_Case_Alt_UnpackListCons(final Expr.Case.Alt.UnpackListCons cons, final VisitorArgument<T> arg) {
            return super.visit_Expr_Case_Alt_UnpackListCons(cons, arg.updateScope(scopeBuilder.newScope(arg.getScope(), cons, arg.getCaseAndLambdaBoundLocalFunctionIdentifierGenerator())));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_Expr_Case_Alt_UnpackRecord(final Expr.Case.Alt.UnpackRecord record, final VisitorArgument<T> arg) {
            return super.visit_Expr_Case_Alt_UnpackRecord(record, arg.updateScope(scopeBuilder.newScope(arg.getScope(), record, arg.getCaseAndLambdaBoundLocalFunctionIdentifierGenerator())));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_Expr_Case_Alt_UnpackTuple(final Expr.Case.Alt.UnpackTuple tuple, final VisitorArgument<T> arg) {
            return super.visit_Expr_Case_Alt_UnpackTuple(tuple, arg.updateScope(scopeBuilder.newScope(arg.getScope(), tuple, arg.getCaseAndLambdaBoundLocalFunctionIdentifierGenerator())));
        }

        ////
        /// Visitor methods - elements which introduce new type variable scopes
        //
       
        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_InstanceDefn(final InstanceDefn defn, final VisitorArgument<T> arg) {
            final TypeVariableScope newScope = TypeVariableScope.newScope(arg.getTypeVariableScope(), defn, currentModuleName);
            handleNewTypeVariableScope(newScope);
            return super.visit_InstanceDefn(defn, arg.updateTypeVariableScope(newScope));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_TypeClassDefn(final TypeClassDefn defn, final VisitorArgument<T> arg) {
            final TypeVariableScope newScope = TypeVariableScope.newScope(arg.getTypeVariableScope(), defn, currentModuleName);
            handleNewTypeVariableScope(newScope);
            return super.visit_TypeClassDefn(defn, arg.updateTypeVariableScope(newScope));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(final TypeConstructorDefn.AlgebraicType.DataConsDefn defn, final VisitorArgument<T> arg) {
            final TypeVariableScope newScope = TypeVariableScope.newScope(arg.getTypeVariableScope(), defn, currentModuleName);
            handleNewTypeVariableScope(newScope);
            return super.visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(defn, arg.updateTypeVariableScope(newScope));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_TypeConstructorDefn_AlgebraicType(final TypeConstructorDefn.AlgebraicType type, final VisitorArgument<T> arg) {
            final TypeVariableScope newScope = TypeVariableScope.newScope(arg.getTypeVariableScope(), type, currentModuleName);
            handleNewTypeVariableScope(newScope);
            return super.visit_TypeConstructorDefn_AlgebraicType(type, arg.updateTypeVariableScope(newScope));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_TypeSignature(final TypeSignature signature, final VisitorArgument<T> arg) {
            final TypeVariableScope newScope = TypeVariableScope.newScope(arg.getTypeVariableScope(), signature, currentModuleName);
            handleNewTypeVariableScope(newScope);
            return super.visit_TypeSignature(signature, arg.updateTypeVariableScope(newScope));
        }
    }

    /**
     * A base class for implementing algorithms that handle the processing of bindings for local definitions (both local function
     * definitions and local pattern match declarations).
     *
     * @param <T> the argument type. If the visitation argument is not used, specify {@link Void}.
     * @param <R> the return type. If the return value is not used, specify {@link Void}.
     *
     * @author Joseph Wong
     */
    static abstract class LocalBindingsProcessor<T, R> extends SourceModelTraverser<T, R> {

        /**
         * The name of the data constructor being unpacked - only valid when visiting a field pattern.
         * This is null if the field pattern is associated with a record unpacking.
         */
        private Name.DataCons nameOfDataConsBeingUnpacked = null;

        /**
         * Processes a local definition binding.
         * @param name the name being bound.
         * @param localDefinition the source element corresponding to the definition/binding.
         * @param arg any visitation argument.
         */
        abstract void processLocalDefinitionBinding(String name, SourceElement localDefinition, T arg);

        /**
         * Performs additional processing for a local function definition.
         * @param function the local function definition.
         * @param arg any visitation argument.
         */
        void additionallyProcessLocalDefnFunctionDefinition(final LocalDefn.Function.Definition function, final T arg) {}

        /**
         * Performs additional processing for a pattern-bound variable in a local pattern match declaration.
         * @param var the pattern-bound variable.
         * @param arg any visitation argument.
         */
        void additionallyProcessPatternVar(final Pattern.Var var, final T arg) {}

        /**
         * Performs additional processing for a punned record field pattern in a local pattern match declaration.
         * @param fieldName the punned field name.
         * @param fieldNameElement the source element for the field name.
         * @param arg any visitation argument.
         */
        void additionallyProcessPunnedTextualRecordFieldPattern(final FieldName.Textual fieldName, final Name.Field fieldNameElement, final T arg) {}

        /**
         * Performs additional processing for a punned data cons field pattern in a local pattern match declaration.
         * @param fieldName the punned field name.
         * @param fieldNameElement the source element for the field name.
         * @param arg any visitation argument.
         */
        void additionallyProcessPunnedTextualDataConsFieldPattern(final FieldName.Textual fieldName, final Name.Field fieldNameElement, final Name.DataCons dataConsName, final T arg) {}

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_LocalDefn_Function_Definition(final LocalDefn.Function.Definition function, final T arg) {
            processLocalDefinitionBinding(function.getName(), function, arg); // the function defn is the bound element
            additionallyProcessLocalDefnFunctionDefinition(function, arg);
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_Pattern_Var(final Pattern.Var var, final T arg) {
            processLocalDefinitionBinding(var.getName(), var, arg); // the pattern var is the bound element
            additionallyProcessPatternVar(var, arg);
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_FieldPattern(final FieldPattern fieldPattern, final T arg) {
            // Handle punning
            if (fieldPattern.getPattern() == null) {
                // punning.

                // Textual field names become Vars of the same name.
                // Ordinal field names become wildcards ("_").
                final FieldName fieldName = fieldPattern.getFieldName().getName();
                if (fieldName instanceof FieldName.Textual) {
                    processLocalDefinitionBinding(fieldName.getCalSourceForm(), fieldPattern, arg); // the field pattern is the bound element

                    if (nameOfDataConsBeingUnpacked == null) {
                        additionallyProcessPunnedTextualRecordFieldPattern((FieldName.Textual)fieldName, fieldPattern.getFieldName(), arg);
                    } else {
                        additionallyProcessPunnedTextualDataConsFieldPattern((FieldName.Textual)fieldName, fieldPattern.getFieldName(), nameOfDataConsBeingUnpacked, arg);
                    }
                }
            }

            // call the superclass impl to reach the pattern and visit it (if it is non-null)
            return super.visit_FieldPattern(fieldPattern, arg);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_LocalDefn_PatternMatch_UnpackDataCons(final LocalDefn.PatternMatch.UnpackDataCons unpackDataCons, final T arg) {
            // visit only the patterns
            final Name.DataCons origNameOfDataConsBeingUnpacked = nameOfDataConsBeingUnpacked;
            nameOfDataConsBeingUnpacked = unpackDataCons.getDataConsName();
            try {
                unpackDataCons.getArgBindings().accept(this, arg);
            } finally {
                nameOfDataConsBeingUnpacked = origNameOfDataConsBeingUnpacked;
            }
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_LocalDefn_PatternMatch_UnpackListCons(final LocalDefn.PatternMatch.UnpackListCons unpackListCons, final T arg) {
            // visit only the patterns
            unpackListCons.getHeadPattern().accept(this, arg);
            unpackListCons.getTailPattern().accept(this, arg);
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_LocalDefn_PatternMatch_UnpackRecord(final LocalDefn.PatternMatch.UnpackRecord unpackRecord, final T arg) {
            // visit only the field patterns (and not the base record pattern - since we do not support them in local pattern match decl)
            final int nFieldPatterns = unpackRecord.getNFieldPatterns();
            for (int i = 0; i < nFieldPatterns; i++) {
                unpackRecord.getNthFieldPattern(i).accept(this, arg);
            }
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public R visit_LocalDefn_PatternMatch_UnpackTuple(final LocalDefn.PatternMatch.UnpackTuple unpackTuple, final T arg) {
            // visit only the patterns
            final int nPatterns = unpackTuple.getNPatterns();
            for (int i = 0; i < nPatterns; i++) {
                unpackTuple.getNthPattern(i).accept(this, arg);
            }
            return null;
        }
    }
   
    /**
     * Factory method for constructing an empty name resolution context.
     * @param visibilityChecker an external entity visibility checker. Can be null.
     * @return a new context.
     */
    public static Context makeEmptyContext(final Context.ExternalEntityVisibilityChecker visibilityChecker) {
        return new EmptyContext(visibilityChecker);
    }

    /**
     * Factory method for constructing an external context for resolving identifiers based on a
     * {@link ModuleTypeInfo} instance.
     * @param moduleTypeInfo the backing module type info, on which name resolutions will be based.
     * @return a new context.
     */
    public static Context makeContext(final ModuleTypeInfo moduleTypeInfo) {
        return new ModuleTypeInfoContext(moduleTypeInfo);
    }

    /**
     * Factory method for constructing an external context for resolving identifiers based on a
     * {@link CodeQualificationMap} instance.
     * @param codeQualificationMap the backing code qualification map, on which name resolutions will be based.
     * @return a new context.
     */
    public static Context makeContext(final CodeQualificationMap codeQualificationMap) {
        return new CodeQualificationMapContext(codeQualificationMap);
    }

    /**
     * Combines two contexts into one, with one acting as the base, and the other acting as
     * the override (the override takes precedence for resolutions).
     * @param baseContext the base context.
     * @param overrideContext the override context - this has precedence over the base context.
     * @return a new context.
     */
    public static Context combineContexts(final Context baseContext, final Context overrideContext) {
        return new OverrideContext(baseContext, overrideContext);
    }
   
    /** Private constructor. Not meant to be instantiated. */
    private IdentifierResolver() {}
}
TOP

Related Classes of org.openquark.cal.compiler.IdentifierResolver$Visitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.