/*
* 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() {}
}