/*
* 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.
*/
/*
* TypeVariableRenamer.java
* Created: Sep 26, 2007
* By: Joseph
*/
package org.openquark.cal.compiler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openquark.cal.compiler.IdentifierOccurrence.Binding;
import org.openquark.cal.compiler.IdentifierOccurrence.Reference;
import org.openquark.cal.compiler.IdentifierOccurrenceFinder.FinderState;
import org.openquark.cal.compiler.IdentifierResolver.SymbolTable;
import org.openquark.cal.compiler.IdentifierResolver.TypeVariableScope;
import org.openquark.cal.compiler.IdentifierResolver.VisitorArgument;
import org.openquark.cal.compiler.SourceModel.FunctionDefn;
import org.openquark.cal.compiler.SourceModel.InstanceDefn;
import org.openquark.cal.compiler.SourceModel.SourceElement;
import org.openquark.cal.compiler.SourceModel.TypeClassDefn;
import org.openquark.cal.compiler.SourceModel.TypeConstructorDefn;
import org.openquark.cal.compiler.SourceModel.TypeSignature;
/**
* This class encapsulates the renaming logic for handling type variables.
* <p>
* The renaming logic works in two passes:
* <ol>
* <li>
* The first pass gathers the following information:
* <ul>
* <li>which "top level" type variable scope contains the type variable (we will call this the
* "type container")
* <p>
* To be precise, a type container can be:
* <ul>
* <li>an algebraic type definition
* <li>a type class definition
* <li>an instance definition
* <li>a type signature not in the scope of the above
* <li>(a data constructor definition, but only if that is the only fragment being visited)
* </ul>
* <li>what are the other names defined locally within that type container
* <li>what are the type variables that are <i>shadowed</i> by the renamed definition.
* <p>
* For example:
* <pre>
* class C x where
* m1 :: y -> x -> y;
* ;
* </pre>
* In the above, if y is renamed to x, the existing x reference must be renamed to avoid a conflict.
* </ul>
* <li>
* The second pass takes the information from the first pass, and calculates:
* <ul>
* <li>which type variables <i>shadow</i> the renamed definition. These are to be renamed so as to
* not conflict with the renamed definition.
* <p>
* For example:
* <pre>
* class C x where
* m1 :: y -> x -> y;
* m2 :: x -> y;
* ;
* </pre>
* If x is renamed to y in the above, then the y in both m1 and m2's definitions need to be
* renamed so as to preserve semantics.
* <li>where are all the occurrences of the variable to be renamed
* <li>which occurrences correspond to the shadowed definitions and the shadowing definitions
* - these will need to be renamed to something else to avoid conflicts (these are termed "collateral damage")
* </ul>
* </ol>
* The renaming algorithm takes the information gathered by the two passes to produce a set of
* required source modifications.
*
* @author Joseph Wong
*/
final class TypeVariableRenamer {
/**
* The first pass gathers the following information:
* <ul>
* <li>which "top level" type variable scope contains the type variable (we will call this the
* "type container")
* <li>what are the other names defined locally within that type container
* <li>what are the type variables that are <i>shadowed</i> by the renamed definition.
* </ul>
*
* @author Joseph Wong
*/
private static final class FirstPass extends IdentifierOccurrenceFinder<Void> {
/**
* The target to be renamed.
*/
private final IdentifierInfo.TypeVariable target;
/**
* The new name for the target.
*/
private final String newName;
/**
* The current "type container" - it is non-null only if one is being visited.
*/
private SourceElement currentTypeContainer;
/**
* The current algebraic function - it is non-null only if an algebraic function is being visited.
*/
private FunctionDefn.Algebraic currentAlgebraicFunction;
/**
* The set of type variable names defined in the current type container.
*/
private final Set<String> typeVarsInCurrentTypeContainer = new HashSet<String>();
/**
* The type container containing the target - this is null until the target is found.
*/
private SourceElement typeContainerContainingTarget;
/**
* The algebraic function containing the target.
*
* Can be null if the target is not in an algebraic function.
*/
private FunctionDefn.Algebraic algebraicFunctionContainingTarget;
/**
* The set of type variable names defined in the type container containing the target - this is empty until
* the target is found.
*/
private final Set<String> typeVarsInTypeContainerContainingTarget = new HashSet<String>();
/**
* A map of all type variable definitions that are shadowed by the new name for the target.
*/
private final Map<IdentifierInfo.TypeVariable, Binding<IdentifierInfo.TypeVariable>> shadowedTypeVars =
new HashMap<IdentifierInfo.TypeVariable, Binding<IdentifierInfo.TypeVariable>>();
/**
* Construct an instance of this class.
* @param currentModuleName the name of the module associated with the source being visited.
* @param target the target to be renamed.
* @param newName the new name for the target.
*/
FirstPass(final ModuleName currentModuleName, final IdentifierInfo.TypeVariable target, final String newName) {
super(currentModuleName);
if (target == null || newName == null) {
throw new NullPointerException();
}
this.target = target;
this.newName = newName;
}
/**
* @return the algebraic function containing the target.
*
* Can be null if the target is not in an algebraic function.
*/
FunctionDefn.Algebraic getAlgebraicFunctionContainingTarget() {
return algebraicFunctionContainingTarget;
}
/**
* @return the set of type variable names defined in the type container containing the
* target - this is empty until the target is found.
*/
Set<String> getTypeVarsInTypeContainerContainingTarget() {
return typeVarsInTypeContainerContainingTarget;
}
/**
* @return a map of all type variable definitions that are shadowed by the new name for the target.
*/
Map<IdentifierInfo.TypeVariable, Binding<IdentifierInfo.TypeVariable>> getShadowedTypeVars() {
return shadowedTypeVars;
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_FunctionDefn_Algebraic(final FunctionDefn.Algebraic algebraic, final VisitorArgument<FinderState> arg) {
// optimization: if already found the source element containing the target, just return
// note that we are not checking against algebraicFunctionContainingTarget, which may be null
// even *after* the target has been found, because the target may have been found outside of an algebraic function
if (typeContainerContainingTarget != null && typeContainerContainingTarget != currentTypeContainer) {
return null;
}
currentAlgebraicFunction = algebraic;
super.visit_FunctionDefn_Algebraic(algebraic, arg);
currentAlgebraicFunction = null;
return null;
}
////
/// Visitor methods - elements which introduce new type variable scopes
//
/**
* {@inheritDoc}
*/
@Override
public Void visit_InstanceDefn(final InstanceDefn defn, final VisitorArgument<FinderState> arg) {
// optimization: if already found the source element containing the target, just return
if (typeContainerContainingTarget != null && typeContainerContainingTarget != currentTypeContainer) {
return null;
}
if (currentTypeContainer == null) {
preProcessTypeContainer(defn);
super.visit_InstanceDefn(defn, arg);
postProcessTypeContainer(defn);
} else {
super.visit_InstanceDefn(defn, arg);
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_TypeClassDefn(final TypeClassDefn defn, final VisitorArgument<FinderState> arg) {
// optimization: if already found the source element containing the target, just return
if (typeContainerContainingTarget != null && typeContainerContainingTarget != currentTypeContainer) {
return null;
}
if (currentTypeContainer == null) {
preProcessTypeContainer(defn);
super.visit_TypeClassDefn(defn, arg);
postProcessTypeContainer(defn);
} else {
super.visit_TypeClassDefn(defn, arg);
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(final TypeConstructorDefn.AlgebraicType.DataConsDefn defn, final VisitorArgument<FinderState> arg) {
// optimization: if already found the source element containing the target, just return
if (typeContainerContainingTarget != null && typeContainerContainingTarget != currentTypeContainer) {
return null;
}
if (currentTypeContainer == null) {
preProcessTypeContainer(defn);
super.visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(defn, arg);
postProcessTypeContainer(defn);
} else {
super.visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(defn, arg);
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_TypeConstructorDefn_AlgebraicType(final TypeConstructorDefn.AlgebraicType type, final VisitorArgument<FinderState> arg) {
// optimization: if already found the source element containing the target, just return
if (typeContainerContainingTarget != null && typeContainerContainingTarget != currentTypeContainer) {
return null;
}
if (currentTypeContainer == null) {
preProcessTypeContainer(type);
super.visit_TypeConstructorDefn_AlgebraicType(type, arg);
postProcessTypeContainer(type);
} else {
super.visit_TypeConstructorDefn_AlgebraicType(type, arg);
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_TypeSignature(final TypeSignature signature, final VisitorArgument<FinderState> arg) {
// optimization: if already found the source element containing the target, just return
if (typeContainerContainingTarget != null && typeContainerContainingTarget != currentTypeContainer) {
return null;
}
if (currentTypeContainer == null) {
preProcessTypeContainer(signature);
super.visit_TypeSignature(signature, arg);
postProcessTypeContainer(signature);
} else {
super.visit_TypeSignature(signature, arg);
}
return null;
}
/**
* Sets up the visitor on entering a "type container".
* @param typeContainer the associated source element.
*/
private void preProcessTypeContainer(final SourceElement typeContainer) {
currentTypeContainer = typeContainer;
typeVarsInCurrentTypeContainer.clear();
}
/**
* Performs wrap-up on exiting a "type container".
* @param typeContainer the associated source element.
*/
private void postProcessTypeContainer(final SourceElement typeContainer) {
if (typeContainerContainingTarget == typeContainer) {
// we have found the right container source element, so copy so the type vars
typeVarsInTypeContainerContainingTarget.addAll(typeVarsInCurrentTypeContainer);
}
// reset the state
currentTypeContainer = null;
typeVarsInCurrentTypeContainer.clear();
}
////
/// Overridden handler methods
//
/**
* {@inheritDoc}
*/
@Override
protected void handleTypeVariableBinding(final Binding<IdentifierInfo.TypeVariable> binding, final TypeVariableScope scope) {
if (target.equals(binding.getIdentifierInfo())) {
// set the fields to remember the match
typeContainerContainingTarget = currentTypeContainer;
algebraicFunctionContainingTarget = currentAlgebraicFunction;
// find all the local definitions that are shadowed
TypeVariableScope scopeToCheck = scope;
while (scopeToCheck != null) {
for (final Binding<IdentifierInfo.TypeVariable> bindingInScope : scopeToCheck.getBindings()) {
if (newName.equals(bindingInScope.getIdentifierInfo().getTypeVarName())) {
shadowedTypeVars.put(bindingInScope.getIdentifierInfo(), bindingInScope);
}
}
scopeToCheck = scopeToCheck.getParent();
}
}
typeVarsInCurrentTypeContainer.add(binding.getIdentifierInfo().getTypeVarName());
super.handleTypeVariableBinding(binding, scope);
}
}
/**
* The second pass takes the information from the first pass, and calculates:
* <ul>
* <li>which type variables <i>shadow</i> the renamed definition. These are to be renamed so
* as to not conflict with the renamed definition.
* <li>where are all the occurrences of the variable to be renamed
* <li>which occurrences correspond to the shadowed definitions and the shadowing definitions -
* these will need to be renamed to something else to avoid conflicts (these are termed
* "collateral damage")
* </ul>
*
* @author Joseph Wong
*/
private static final class SecondPass extends IdentifierOccurrenceFinder<Void> {
/**
* The target to be renamed.
*/
private final IdentifierInfo.TypeVariable target;
/**
* The new name for the target.
*/
private final String newName;
/**
* A map of all type variable definitions that are shadowed by the new name for the target. Can *not* be null.
*/
private final Map<IdentifierInfo.TypeVariable, Binding<IdentifierInfo.TypeVariable>> shadowedTypeVars;
/**
* The scope that defines the target. Can *not* be null.
*/
private TypeVariableScope definingScopeOfTarget;
/**
* A set for collecting the type variables that would shadow the renamed target.
*/
private final Set<IdentifierInfo.TypeVariable> shadowingTypeVars = new HashSet<IdentifierInfo.TypeVariable>();
/**
* The algebraic function containing the target.
*
* Can be null if the target is not in an algebraic function.
*/
private final FunctionDefn.Algebraic algebraicFunctionContainingTarget;
/**
* A list of the occurrences of the target to be renamed.
*/
private final List<IdentifierOccurrence<?>> occurrencesToRename = new ArrayList<IdentifierOccurrence<?>>();
/**
* A list of the occurrences corresponding to the shadowed definitions and the shadowing
* definitions - these will need to be renamed to something else to avoid conflicts (these
* are termed "collateral damage").
*/
private final List<IdentifierOccurrence<?>> collaterallyDamagedTypeVarOccurrences = new ArrayList<IdentifierOccurrence<?>>();
/**
* Constructs an instance of this class.
* @param currentModuleName the name of the module associated with the source being visited.
* @param target the target to be renamed.
* @param newName the new name for the target.
* @param algebraicFunctionContainingTarget the algebraic function containing the target.
* Can be null if the target is not in an algebraic function.
* @param shadowedTypeVars a map of all type variable definitions that are shadowed by the new name for the target. Can *not* be null.
*/
SecondPass(final ModuleName currentModuleName, final IdentifierInfo.TypeVariable target, final String newName, final FunctionDefn.Algebraic algebraicFunctionContainingTarget, final Map<IdentifierInfo.TypeVariable, Binding<IdentifierInfo.TypeVariable>> shadowedTypeVars) {
super(currentModuleName);
if (target == null || newName == null || shadowedTypeVars == null) {
throw new NullPointerException();
}
this.target = target;
this.newName = newName;
this.algebraicFunctionContainingTarget = algebraicFunctionContainingTarget;
this.shadowedTypeVars = shadowedTypeVars;
}
/**
* @return alist of the occurrences of the target to be renamed.
*/
List<IdentifierOccurrence<?>> getOccurrencesToRename() {
return occurrencesToRename;
}
/**
* @return a list of the occurrences corresponding to the shadowed definitions and the
* shadowing definitions - these will need to be renamed to something else to avoid
* conflicts (these are termed "collateral damage").
*/
List<IdentifierOccurrence<?>> getCollaterallyDamagedTypeVarOccurrences() {
return collaterallyDamagedTypeVarOccurrences;
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_FunctionDefn_Algebraic(final FunctionDefn.Algebraic algebraic, final VisitorArgument<FinderState> arg) {
// optimization: skip everything except the function containing the target
if (algebraic != algebraicFunctionContainingTarget) {
return null;
}
return super.visit_FunctionDefn_Algebraic(algebraic, arg);
}
/**
* {@inheritDoc}
*/
@Override
protected void handleNewTypeVariableScope(final TypeVariableScope scope) {
// first loop: set the definingScopeOfTarget if the target is indeed defined in this scope
for (final Binding<IdentifierInfo.TypeVariable> binding : scope.getBindings()) {
if (target.equals(binding.getIdentifierInfo())) {
definingScopeOfTarget = scope;
}
}
// second loop - done by superclass implementation:
// determining if the binding corresponds to the one needing to be renamed
// or to one of the "collaterally damaged" names that need to be renamed to something else
super.handleNewTypeVariableScope(scope);
}
/**
* {@inheritDoc}
*/
@Override
protected void handleTypeVariableBinding(final Binding<IdentifierInfo.TypeVariable> binding, final TypeVariableScope scope) {
if (target.equals(binding.getIdentifierInfo())) {
occurrencesToRename.add(binding);
} else if (shadowedTypeVars.containsKey(binding.getIdentifierInfo())) {
collaterallyDamagedTypeVarOccurrences.add(binding);
} else if (isSameOrDescendantScope(scope, definingScopeOfTarget) && newName.equals(binding.getIdentifierInfo().getTypeVarName())) {
// check to see if this definition shadows the target
shadowingTypeVars.add(binding.getIdentifierInfo());
collaterallyDamagedTypeVarOccurrences.add(binding);
}
super.handleTypeVariableBinding(binding, scope);
}
/**
* {@inheritDoc}
*/
@Override
protected void handleTypeVariableReference(final Reference<IdentifierInfo.TypeVariable> reference, final Binding<IdentifierInfo.TypeVariable> binding, final TypeVariableScope scope) {
if (target.equals(reference.getIdentifierInfo())) {
occurrencesToRename.add(reference);
} else if (shadowingTypeVars.contains(reference.getIdentifierInfo())) {
collaterallyDamagedTypeVarOccurrences.add(reference);
} else if (shadowedTypeVars.containsKey(reference.getIdentifierInfo())) {
collaterallyDamagedTypeVarOccurrences.add(reference);
}
super.handleTypeVariableReference(reference, binding, scope);
}
}
/** Private constructor. */
private TypeVariableRenamer() {}
/**
* Helper method for determining whether a scope is the same, or is a descendant of, another scope.
* @param potentialDescendantScope the potential descendant scope.
* @param potentialAncestorScope the potential ancestor scope.
* @return true if the first scope is the same, or a descendant of, the second scope.
*/
private static boolean isSameOrDescendantScope(final TypeVariableScope potentialDescendantScope, final TypeVariableScope potentialAncestorScope) {
if (potentialAncestorScope == null) {
return false;
}
if (potentialDescendantScope == potentialAncestorScope) {
return true;
}
TypeVariableScope parent = potentialDescendantScope.getParent();
while (parent != null) {
if (parent == potentialAncestorScope) {
return true;
}
parent = parent.getParent();
}
return false;
}
/**
* Runs the renaming logic for a type variable and returns the necessary modifications in
* a {@link SourceModifier}.
*
* @param moduleTypeInfo ModuleTypeInfo for the module to process
* @param oldName the identifier being renamed
* @param newName the name name
* @param messageLogger CompilerMessageLogger for logging failures
* @return a SourceModifier that will apply the renaming to the source.
*/
static SourceModifier getSourceModifier(final ModuleTypeInfo moduleTypeInfo, final String sourceText, final IdentifierInfo.TypeVariable oldName, final String newName, final CompilerMessageLogger messageLogger) {
final SourceModifier sourceModifier = new SourceModifier();
// if the module is a sourceless module, then there is not much we can do with it.
if (sourceText.length() == 0) {
return sourceModifier;
}
final ModuleName moduleName = moduleTypeInfo.getModuleName();
final SourceModel.ModuleDefn moduleDefn = SourceModelUtilities.TextParsing.parseModuleDefnIntoSourceModel(sourceText, messageLogger);
if (moduleDefn == null) {
return sourceModifier;
}
final FirstPass firstPass = new FirstPass(moduleName, oldName, newName);
moduleDefn.accept(firstPass, VisitorArgument.make(SymbolTable.makeRoot(moduleName, IdentifierResolver.makeContext(moduleTypeInfo)), FinderState.make()));
final Set<String> typeVarsInTypeContainerContainingTarget = firstPass.getTypeVarsInTypeContainerContainingTarget();
final SecondPass secondPass = new SecondPass(moduleName, oldName, newName, firstPass.getAlgebraicFunctionContainingTarget(), firstPass.getShadowedTypeVars());
moduleDefn.accept(secondPass, VisitorArgument.make(SymbolTable.makeRoot(moduleName, IdentifierResolver.makeContext(moduleTypeInfo)), FinderState.make()));
for (final IdentifierOccurrence<?> occurrenceToRename : secondPass.getOccurrencesToRename()) {
addTypeVarRenameModification(sourceModifier, sourceText, occurrenceToRename, newName);
}
if (!secondPass.getCollaterallyDamagedTypeVarOccurrences().isEmpty()) {
final String newNameForCollateralDamage = getNewNameForCollateralDamage(newName, typeVarsInTypeContainerContainingTarget);
for (final IdentifierOccurrence<?> collateralDamage : secondPass.getCollaterallyDamagedTypeVarOccurrences()) {
addTypeVarRenameModification(sourceModifier, sourceText, collateralDamage, newNameForCollateralDamage);
}
}
return sourceModifier;
}
/**
* Produces a non-conflicting name for definitions deemed "collateral damage".
* @param newName the new name of the target.
* @param typeVarsInTypeContainerContainingTarget the other type variable names in the type container containing the target <i>to be avoided</i>.
* @return a non-conflicting name.
*/
private static String getNewNameForCollateralDamage(final String newName, final Set<String> typeVarsInTypeContainerContainingTarget) {
String newNameForCollateralDamage;
int disambiguator = 1;
do {
newNameForCollateralDamage = newName + "_" + disambiguator;
disambiguator++;
} while (typeVarsInTypeContainerContainingTarget.contains(newNameForCollateralDamage));
return newNameForCollateralDamage;
}
/**
* Adds a source modification corresponding to a type variable name renaming.
* @param sourceModifier the source modifier to add to.
* @param sourceText the source text.
* @param occurrenceToRename the occurrence to rename.
* @param newName the new name.
*/
private static void addTypeVarRenameModification(final SourceModifier sourceModifier, final String sourceText, final IdentifierOccurrence<?> occurrenceToRename, final String newName) {
sourceModifier.addSourceModification(IdentifierRenamer.makeReplaceText(sourceText, occurrenceToRename.getSourceRange(), newName));
}
}