/* * 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. */ /* * IdentifierRenamer.java * Creation date: (Apr 5, 2006) * By: James Wright */ package org.openquark.cal.compiler; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.openquark.cal.compiler.SourceIdentifier.Category; import org.openquark.cal.compiler.SourceModel.CALDoc; import org.openquark.cal.compiler.SourceModel.Friend; 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.ModuleDefn; import org.openquark.cal.compiler.SourceModel.Name; import org.openquark.cal.compiler.SourceModel.TypeClassDefn; import org.openquark.cal.compiler.SourceModel.CALDoc.CrossReference; import org.openquark.cal.compiler.SourceModel.CALDoc.CrossReference.CanAppearWithoutContext; import org.openquark.cal.compiler.SourceModel.CALDoc.CrossReference.Module; import org.openquark.cal.compiler.SourceModel.CALDoc.TaggedBlock.See; import org.openquark.cal.compiler.SourceModel.CALDoc.TextSegment.InlineTag.Link; import org.openquark.cal.compiler.SourceModel.FunctionDefn.Algebraic; import org.openquark.cal.compiler.SourceModel.FunctionDefn.Foreign; import org.openquark.cal.compiler.SourceModel.FunctionDefn.Primitive; import org.openquark.cal.compiler.SourceModel.Import.UsingItem; import org.openquark.cal.compiler.SourceModel.InstanceDefn.InstanceMethod; import org.openquark.cal.compiler.SourceModel.TypeClassDefn.ClassMethodDefn; import org.openquark.cal.compiler.SourceModel.TypeConstructorDefn.AlgebraicType; import org.openquark.cal.compiler.SourceModel.TypeConstructorDefn.ForeignType; import org.openquark.cal.compiler.SourceModel.TypeConstructorDefn.AlgebraicType.DataConsDefn; import org.openquark.util.Pair; /** * IdentifierRenamer is a helper class for performing the Rename refactoring. * It calculates a set of SourceModifications that must be applied in order to * perform a given renaming without conflicts. * * <p> * This class supersedes the RenamedIdentifierFinder class (by Iulian Radu). * * <p> * <b>Binding conflicts</b> * <p> * One type of potential conflict occurs when an unqualified reference to the identifier * being renamed occurs in a context where the new name is already bound. Eg, when * renaming r to s in the following code: * <pre> * let * s = 10; * in * r s; * </pre> * * <p> * In these situations, the reference to r is renamed to a fully-qualified reference * to s (which refers to top-level identifiers only): * <pre> * let * s = 10; * in * M.s s; * </pre> * * <p> * <b>Import-using conflict</b> * <p> * Another type of potential conflict occurs when an existing import-using clause * is changed to something that conflicts with either another import-using clause, * or with a toplevel definition. For example, in the following code: * <pre> * module Module; * import Prelude using * function = head, tail; * ; * import List using * function = first; * ; * * last x = ... * </pre> * <p> * Renaming Prelude.tail to Prelude.last could cause a conflict with the toplevel * definition for last. IdentifierRenamer deals with this by removing the using * clause for Prelude.tail/Prelude.last and fully qualifying all references to * Prelude.tail when it renames them. * * <p> * Similarly, renaming Prelude.head to Prelude.first in the above code could cause * a conflict (since the "first" symbol will appear in two using clauses). IdentifierRenamer * deals with this situation in the same way: by removing the using-clause for the * renamed identifier and fully qualifying all references to the renamed identifier. * * <p> * In the case of the home module where the renamed identifier is defined, we * deal with this style of conflict by removing the conflicting using clause * and fully qualifying references to its identifier. * * @author James Wright */ final class IdentifierRenamer { /** Not instantiable */ private IdentifierRenamer() {} /** * Helper class for finding all references to a specific top-level identifier * in the context of a renaming operation. * * <p> * References are tracked in several disjoint collections: * <ul> * <li> Fully-qualified references * <li> "Unobstructed" unqualified references * <li> "Obstructed" unqualified references. * <li> definition references * <li> CALDoc elements that will become ambiguous * </ul> * * <p> * An obstructed unqualified reference is an unqualified reference to targetName * that occurs in a context where newName is bound (so that a simple change * from unqualified-targetName to unqualified-newName would change which value * was being referenced). * * <p> * An unobstructed unqualified reference is an unqualified reference to targetName * that occurs in a context where newName is not bound. * * <p> * A definition reference is a reference that occurs in a declaration, or in a * similar context where an unqualified name is always unambiguous and a qualified * name is forbidden (eg, in using clauses, class method specifiers in instance * definitions, etc). * * <p> * We also track CALDoc elements that contain without-context cons references that * will become ambiguous after the renaming. * * <p> * There are a couple of suppression flags to support special cases. In particular, * you can specify that import-using clauses should not be traversed, and you can * provide a collection of See.WithoutContext elements to skip. * * @author James Wright */ private static class RenamableReferenceFinder extends BindingTrackingSourceModelTraverser<Object> { /** ModuleTypeInfo for the module being walked */ private final ModuleTypeInfo moduleTypeInfo; /** Top-level identifier to find references to */ private final QualifiedName targetName; /** Top-level identifier that targetName will be renamed to */ private final QualifiedName newName; /** Category of targetName */ private final Category category; /** When true, we will report references that occur in using clauses; when false, we won't. */ private final boolean searchUsingClauses; /** * Contains SeeBlock objects that should not be processed. This allows us to avoid reporting * SourceRanges inside of seeBlocks that will be entirely re-generated. */ private final Set<CALDoc> ignorableSeeBlocks = new HashSet<CALDoc>(); /** List (SourceRange) of matching unobstructed unqualified Name elements */ private final List<SourceRange> unobstructedUnqualifiedReferences = new ArrayList<SourceRange>(); /** List (SourceRange) of matching obstructed unqualified Name elements */ private final List<SourceRange> obstructedUnqualifiedReferences = new ArrayList<SourceRange>(); /** List (SourceRange) of matching fully-qualified Name elements */ private final List<SourceRange> fullyQualifiedReferences = new ArrayList<SourceRange>(); /** List (SourceRange) of SourceRanges of definition references to targetName */ private final List<SourceRange> definitionReferences = new ArrayList<SourceRange>(); /** List (SourceRange) of SourceRanges of references to targetName (which is a module) appearing in module, friend and import statements. */ private final List<SourceRange> moduleFriendImportStatementReferences = new ArrayList<SourceRange>(); /** * List (See.WithoutContext or Link.ConsNameWithoutContext) of contextless CALDoc elements * that will become ambiguous as a result of renaming targetName to newName. */ private final List<CALDoc> ambiguousCALDocElements = new ArrayList<CALDoc>(); /** * A mapping which maps module names that are affected by a module renaming to * their corresponding disambiguated names. */ private final ModuleNameResolver.RenameMapping collateralDamageModuleRenameMapping; /** * A list of pairs (module name source range, module name) representing the module names that are affected * by a module renaming (and thus would need disambiguation). */ private final List<Pair<SourceRange, ModuleName>> collateralDamageModuleNames = new ArrayList<Pair<SourceRange, ModuleName>>(); /** * * @param targetName QualifiedName that we are looking for references to * @param newName QualifiedName that targetName will be renamed to. We use this to determine when an identifier * is obstructed. * @param category Category of targetName * @param searchUsingClauses When true, we will report references that occur in using clauses; when false, we won't. * @param ignorableSeeBlocks Collection (See.WithoutContext) of see blocks that should not be processed. * Uses object identity for equality, so these blocks must be from the same SourceModel * that this object will be traversing. * @param moduleTypeInfo ModuleTypeInfo for the module being traversed * @param collateralDamageModuleRenameMapping a mapping which maps module names that are affected by a module renaming to * their corresponding disambiguated names. */ RenamableReferenceFinder(QualifiedName targetName, QualifiedName newName, Category category, boolean searchUsingClauses, Collection<CALDoc> ignorableSeeBlocks, ModuleTypeInfo moduleTypeInfo, ModuleNameResolver.RenameMapping collateralDamageModuleRenameMapping) { if(targetName == null || category == null || moduleTypeInfo == null || collateralDamageModuleRenameMapping == null) { throw new NullPointerException(); } if(category == Category.LOCAL_VARIABLE || category == Category.LOCAL_VARIABLE_DEFINITION) { throw new UnsupportedOperationException(); } this.moduleTypeInfo = moduleTypeInfo; this.targetName = targetName; this.newName = newName; this.category = category; this.searchUsingClauses = searchUsingClauses; if(ignorableSeeBlocks != null) { this.ignorableSeeBlocks.addAll(ignorableSeeBlocks); } this.collateralDamageModuleRenameMapping = collateralDamageModuleRenameMapping; } /** {@inheritDoc} */ @Override public Object visit_ModuleDefn(ModuleDefn defn, Object arg) { if(category == Category.MODULE_NAME && org.openquark.cal.compiler.SourceModel.Name.Module.toModuleName(defn.getModuleName()).equals(targetName.getModuleName())) { moduleFriendImportStatementReferences.add(defn.getSourceRangeOfModuleName()); } return super.visit_ModuleDefn(defn, arg); } /** {@inheritDoc} */ @Override public Object visit_Friend(Friend friend, Object arg) { if(category == Category.MODULE_NAME && org.openquark.cal.compiler.SourceModel.Name.Module.toModuleName(friend.getFriendModuleName()).equals(targetName.getModuleName())) { moduleFriendImportStatementReferences.add(friend.getFriendModuleName().getSourceRange()); } return super.visit_Friend(friend, arg); } /** {@inheritDoc} */ @Override public Object visit_Import(Import importStatement, Object arg) { if(category == Category.MODULE_NAME && org.openquark.cal.compiler.SourceModel.Name.Module.toModuleName(importStatement.getImportedModuleName()).equals(targetName.getModuleName())) { moduleFriendImportStatementReferences.add(importStatement.getImportedModuleName().getSourceRange()); } return super.visit_Import(importStatement, arg); } /** {@inheritDoc} */ @Override public Object visit_Import_UsingItem_Function(UsingItem.Function usingItemFunction, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("RenameableReferenceFinder.visitImportUsingItemFunction expects to be passed a module name as its arg"); } // Ignore using items of the wrong category if(category != Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) { return super.visit_Import_UsingItem_Function(usingItemFunction, arg); } // Skip any import that isn't for the target module ModuleName importedModuleName = (ModuleName)arg; if(!searchUsingClauses || !importedModuleName.equals(targetName.getModuleName())) { return super.visit_Import_UsingItem_Function(usingItemFunction, arg); } String[] usingNames = usingItemFunction.getUsingNames(); SourceRange[] usingNameSourceRanges = usingItemFunction.getUsingNameSourceRanges(); for(int i = 0, nNames = usingNames.length; i < nNames; i++) { if(usingNames[i].equals(targetName.getUnqualifiedName())) { definitionReferences.add(usingNameSourceRanges[i]); } } return super.visit_Import_UsingItem_Function(usingItemFunction, arg); } /** {@inheritDoc} */ @Override public Object visit_Import_UsingItem_DataConstructor(UsingItem.DataConstructor usingItemDataConstructor, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("RenameableReferenceFinder.visitImportUsingItemDataConstructor expects to be passed a module name as its arg"); } // Ignore using items of the wrong category if(category != Category.DATA_CONSTRUCTOR) { return super.visit_Import_UsingItem_DataConstructor(usingItemDataConstructor, arg); } // Skip any import that isn't for the target module ModuleName importedModuleName = (ModuleName)arg; if(!searchUsingClauses || !importedModuleName.equals(targetName.getModuleName())) { return super.visit_Import_UsingItem_DataConstructor(usingItemDataConstructor, arg); } String[] usingNames = usingItemDataConstructor.getUsingNames(); SourceRange[] usingNameSourceRanges = usingItemDataConstructor.getUsingNameSourceRanges(); for(int i = 0, nNames = usingNames.length; i < nNames; i++) { if(usingNames[i].equals(targetName.getUnqualifiedName())) { definitionReferences.add(usingNameSourceRanges[i]); } } return super.visit_Import_UsingItem_DataConstructor(usingItemDataConstructor, arg); } /** {@inheritDoc} */ @Override public Object visit_Import_UsingItem_TypeConstructor(UsingItem.TypeConstructor usingItemTypeConstructor, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("RenameableReferenceFinder.visitImportUsingItemTypeConstructor expects to be passed a module name as its arg"); } // Ignore using items of the wrong category if(category != Category.TYPE_CONSTRUCTOR) { return super.visit_Import_UsingItem_TypeConstructor(usingItemTypeConstructor, arg); } // Skip any import that isn't for the target module ModuleName importedModuleName = (ModuleName)arg; if(!searchUsingClauses || !importedModuleName.equals(targetName.getModuleName())) { return super.visit_Import_UsingItem_TypeConstructor(usingItemTypeConstructor, arg); } String[] usingNames = usingItemTypeConstructor.getUsingNames(); SourceRange[] usingNameSourceRanges = usingItemTypeConstructor.getUsingNameSourceRanges(); for(int i = 0, nNames = usingNames.length; i < nNames; i++) { if(usingNames[i].equals(targetName.getUnqualifiedName())) { definitionReferences.add(usingNameSourceRanges[i]); } } return super.visit_Import_UsingItem_TypeConstructor(usingItemTypeConstructor, arg); } /** {@inheritDoc} */ @Override public Object visit_Import_UsingItem_TypeClass(UsingItem.TypeClass usingItemTypeClass, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("RenameableReferenceFinder.visitImportUsingItemTypeClass expects to be passed a module name as its arg"); } // Ignore using items of the wrong category if(category != Category.TYPE_CLASS) { return super.visit_Import_UsingItem_TypeClass(usingItemTypeClass, arg); } // Skip any import that isn't for the target module ModuleName importedModuleName = (ModuleName)arg; if(!searchUsingClauses || !importedModuleName.equals(targetName.getModuleName())) { return super.visit_Import_UsingItem_TypeClass(usingItemTypeClass, arg); } String[] usingNames = usingItemTypeClass.getUsingNames(); SourceRange[] usingNameSourceRanges = usingItemTypeClass.getUsingNameSourceRanges(); for(int i = 0, nNames = usingNames.length; i < nNames; i++) { if(usingNames[i].equals(targetName.getUnqualifiedName())) { definitionReferences.add(usingNameSourceRanges[i]); } } return super.visit_Import_UsingItem_TypeClass(usingItemTypeClass, arg); } /** {@inheritDoc} */ @Override public Object visit_CALDoc_TextSegment_InlineTag_Link_ConsNameWithoutContext(Link.ConsNameWithoutContext segment, Object arg) { Object needsContext = segment.getReference().accept(this, arg); if(needsContext == Boolean.TRUE) { ambiguousCALDocElements.add(segment); } // No need to call super implementation since all it does is call: segment.getReference().accept(this, arg); // which is already done above. // Repeating the visitation to the reference would mean that visit_Name_WithoutContextCons() // would be called twice on the same element, which can potentially corrupt the various data structures. return null; } /** {@inheritDoc} */ @Override public Object visit_CALDoc_TaggedBlock_See_WithoutContext(See.WithoutContext seeBlock, Object arg) { if(ignorableSeeBlocks.contains(seeBlock)) { // Don't process this block at all (including recursive processing) if it is ignorable return null; } // First check to see if we need to add context to any of our references for (int i = 0, nReferencedNames = seeBlock.getNReferencedNames(); i < nReferencedNames; i++) { CanAppearWithoutContext reference = seeBlock.getNthReferencedName(i); if(reference instanceof CrossReference.WithoutContextCons) { CrossReference.WithoutContextCons consReference = (CrossReference.WithoutContextCons)reference; if(willConflictWith(moduleTypeInfo, consReference.getName(), newName, category)) { // We do /not/ continue recursive processing if we discover that we need to add context to a reference. // That's because adding context requires us to replace the entire @see block. Recursive processing // might discover some individual references (different from the conflicting ones) that need to be // renamed, which could cause us to attempt overlapping SourceModifications, which is an error. // So we will process the see block as a whole and handle renamings and conflict resolution in one pass. ambiguousCALDocElements.add(seeBlock); return null; } } } // If there's no conflicts, then we don't need to do any see-block-wide replacements, so it's safe to // consider each sub-reference individually. return super.visit_CALDoc_TaggedBlock_See_WithoutContext(seeBlock, arg); } /** {@inheritDoc} */ @Override public Object visit_CALDoc_CrossReference_WithoutContextCons(org.openquark.cal.compiler.SourceModel.CALDoc.CrossReference.WithoutContextCons reference, Object arg) { return reference.getName().accept(this, arg); } /** {@inheritDoc} */ @Override public Object visit_Name_WithoutContextCons(Name.WithoutContextCons cons, Object arg) { // Special case: When we're renaming an entity to have the name as an existing context-free cons reference, // then we will need to add context to the reference (since it is no longer unambiguous). if(willConflictWith(moduleTypeInfo, cons, newName, category)) { super.visit_Name_WithoutContextCons(cons, arg); // Indicate to our CrossReference parent that this name needs context return Boolean.TRUE; } // Not interested in wrong category or wrong unqualified name Category consCategory = getCategory(moduleTypeInfo, cons); if (consCategory == category) { if (category == Category.MODULE_NAME) { ModuleName targetModuleName = targetName.getModuleName(); // the entire WithoutContextCons could be a module name ModuleName rawModuleName = ModuleName.make(cons.toSourceText()); ModuleName resolvedModuleName = moduleTypeInfo.getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); if (resolvedModuleName.equals(targetModuleName)) { definitionReferences.add(cons.getSourceRange()); } else { if (collateralDamageModuleRenameMapping.hasNewName(rawModuleName)) { ModuleName newModuleName = collateralDamageModuleRenameMapping.getNewNameForModule(rawModuleName); collateralDamageModuleNames.add(new Pair<SourceRange, ModuleName>(cons.getSourceRange(), newModuleName)); } } } if (cons.getUnqualifiedName().equals(targetName.getUnqualifiedName())) { ModuleName consModuleName = org.openquark.cal.compiler.SourceModel.Name.Module.maybeToModuleName(cons.getModuleName()); // may be null if(consModuleName == null) { unobstructedUnqualifiedReferences.add(cons.getSourceRange()); } else { ModuleName resolvedConsModuleName = moduleTypeInfo.getModuleNameResolver().resolve(consModuleName).getResolvedModuleName(); if (resolvedConsModuleName.equals(targetName.getModuleName())) { fullyQualifiedReferences.add(cons.getSourceRange()); } } } } return super.visit_Name_WithoutContextCons(cons, arg); } /** * Process a constructor name (type constructor, data constructor or type class name). * @param name the constructor name. * @param nameCategory the category of the name. */ private void processConsName(Name.Qualifiable name, Category nameCategory) { if (category == Category.MODULE_NAME) { if (name.getModuleName() != null) { ModuleName rawModuleName = org.openquark.cal.compiler.SourceModel.Name.Module.toModuleName(name.getModuleName()); ModuleName resolvedModuleName = moduleTypeInfo.getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); if (targetName.getModuleName().equals(resolvedModuleName)) { definitionReferences.add(name.getModuleName().getSourceRange()); } else { if (collateralDamageModuleRenameMapping.hasNewName(rawModuleName)) { ModuleName newModuleName = collateralDamageModuleRenameMapping.getNewNameForModule(rawModuleName); collateralDamageModuleNames.add(new Pair<SourceRange, ModuleName>(name.getModuleName().getSourceRange(), newModuleName)); } } } } if (category == nameCategory && name.getUnqualifiedName().equals(targetName.getUnqualifiedName())) { if(name.getModuleName() == null && targetName.equals(getQualifiedName(name, moduleTypeInfo.getModuleNameResolver()))) { unobstructedUnqualifiedReferences.add(name.getSourceRange()); } else if (name.getModuleName() != null) { ModuleName consModuleName = org.openquark.cal.compiler.SourceModel.Name.Module.toModuleName(name.getModuleName()); ModuleName resolvedConsModuleName = moduleTypeInfo.getModuleNameResolver().resolve(consModuleName).getResolvedModuleName(); if (resolvedConsModuleName.equals(targetName.getModuleName())) { fullyQualifiedReferences.add(name.getSourceRange()); } } } } /** {@inheritDoc} */ @Override public Object visit_Name_DataCons(Name.DataCons cons, Object arg) { processConsName(cons, Category.DATA_CONSTRUCTOR); return super.visit_Name_DataCons(cons, arg); } /** {@inheritDoc} */ @Override public Object visit_Name_TypeClass(Name.TypeClass typeClass, Object arg) { processConsName(typeClass, Category.TYPE_CLASS); return super.visit_Name_TypeClass(typeClass, arg); } /** {@inheritDoc} */ @Override public Object visit_Name_TypeCons(SourceModel.Name.TypeCons cons, Object arg) { processConsName(cons, Category.TYPE_CONSTRUCTOR); return super.visit_Name_TypeCons(cons, arg); } /** {@inheritDoc} */ @Override public Object visit_Name_Function(Name.Function function, Object arg) { if (category == Category.MODULE_NAME) { if (function.getModuleName() != null) { ModuleName rawModuleName = org.openquark.cal.compiler.SourceModel.Name.Module.toModuleName(function.getModuleName()); ModuleName resolvedModuleName = moduleTypeInfo.getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); if (targetName.getModuleName().equals(resolvedModuleName)) { definitionReferences.add(function.getModuleName().getSourceRange()); } else { if (collateralDamageModuleRenameMapping.hasNewName(rawModuleName)) { ModuleName newModuleName = collateralDamageModuleRenameMapping.getNewNameForModule(rawModuleName); collateralDamageModuleNames.add(new Pair<SourceRange, ModuleName>(function.getModuleName().getSourceRange(), newModuleName)); } } } } if (category == Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD && function.getUnqualifiedName().equals(targetName.getUnqualifiedName())) { if(function.getModuleName() == null && targetName.equals(getQualifiedName(function, moduleTypeInfo.getModuleNameResolver()))) { if(isObstructed()) { obstructedUnqualifiedReferences.add(function.getSourceRange()); } else { unobstructedUnqualifiedReferences.add(function.getSourceRange()); } } else if (function.getModuleName() != null) { ModuleName consModuleName = org.openquark.cal.compiler.SourceModel.Name.Module.toModuleName(function.getModuleName()); ModuleName resolvedConsModuleName = moduleTypeInfo.getModuleNameResolver().resolve(consModuleName).getResolvedModuleName(); if (resolvedConsModuleName.equals(targetName.getModuleName())) { fullyQualifiedReferences.add(function.getSourceRange()); } } } return super.visit_Name_Function(function, arg); } /** {@inheritDoc} */ @Override public Object visit_FunctionDefn_Algebraic(Algebraic algebraic, Object arg) { if(category != Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD || !targetName.getModuleName().equals(getModuleName())) { return super.visit_FunctionDefn_Algebraic(algebraic, arg); } if(algebraic.getName().equals(targetName.getUnqualifiedName())) { definitionReferences.add(algebraic.getNameSourceRange()); } return super.visit_FunctionDefn_Algebraic(algebraic, arg); } /** {@inheritDoc} */ @Override public Object visit_FunctionDefn_Foreign(Foreign foreign, Object arg) { if(category != Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD || !targetName.getModuleName().equals(getModuleName())) { return super.visit_FunctionDefn_Foreign(foreign, arg); } if(foreign.getName().equals(targetName.getUnqualifiedName())) { definitionReferences.add(foreign.getNameSourceRange()); } return super.visit_FunctionDefn_Foreign(foreign, arg); } /** {@inheritDoc} */ @Override public Object visit_FunctionDefn_Primitive(Primitive primitive, Object arg) { if(category != Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD || !targetName.getModuleName().equals(getModuleName())) { return super.visit_FunctionDefn_Primitive(primitive, arg); } if(primitive.getName().equals(targetName.getUnqualifiedName())) { definitionReferences.add(primitive.getNameSourceRange()); } return super.visit_FunctionDefn_Primitive(primitive, arg); } /** {@inheritDoc} */ @Override public Object visit_TypeConstructorDefn_AlgebraicType(AlgebraicType type, Object arg) { if(category != Category.TYPE_CONSTRUCTOR || !targetName.getModuleName().equals(getModuleName())) { return super.visit_TypeConstructorDefn_AlgebraicType(type, arg); } if(type.getTypeConsName().equals(targetName.getUnqualifiedName())) { definitionReferences.add(type.getSourceRangeOfName()); } return super.visit_TypeConstructorDefn_AlgebraicType(type, arg); } /** {@inheritDoc} */ @Override public Object visit_TypeConstructorDefn_ForeignType(ForeignType type, Object arg) { if(category != Category.TYPE_CONSTRUCTOR || !targetName.getModuleName().equals(getModuleName())) { return super.visit_TypeConstructorDefn_ForeignType(type, arg); } if(type.getTypeConsName().equals(targetName.getUnqualifiedName())) { definitionReferences.add(type.getSourceRangeOfName()); } return super.visit_TypeConstructorDefn_ForeignType(type, arg); } /** {@inheritDoc} */ @Override public Object visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(DataConsDefn defn, Object arg) { if(category != Category.DATA_CONSTRUCTOR || !targetName.getModuleName().equals(getModuleName())) { return super.visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(defn, arg); } if(defn.getDataConsName().equals(targetName.getUnqualifiedName())) { definitionReferences.add(defn.getSourceRangeOfName()); } return super.visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(defn, arg); } /** {@inheritDoc} */ @Override public Object visit_TypeClassDefn(TypeClassDefn defn, Object arg) { if(category != Category.TYPE_CLASS || !targetName.getModuleName().equals(getModuleName())) { return super.visit_TypeClassDefn(defn, arg); } if(defn.getTypeClassName().equals(targetName.getUnqualifiedName())) { definitionReferences.add(defn.getSourceRangeOfName()); } return super.visit_TypeClassDefn(defn, arg); } /** {@inheritDoc} */ @Override public Object visit_TypeClassDefn_ClassMethodDefn(ClassMethodDefn defn, Object arg) { if(category != Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD || !targetName.getModuleName().equals(getModuleName())) { return super.visit_TypeClassDefn_ClassMethodDefn(defn, arg); } if(defn.getMethodName().equals(targetName.getUnqualifiedName())) { definitionReferences.add(defn.getSourceRangeOfName()); } return super.visit_TypeClassDefn_ClassMethodDefn(defn, arg); } /** {@inheritDoc} */ @Override public Object visit_FunctionTypeDeclaraction(FunctionTypeDeclaration declaration, Object arg) { if(category != Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD || !targetName.getModuleName().equals(getModuleName())) { return super.visit_FunctionTypeDeclaraction(declaration, arg); } if(declaration.getFunctionName().equals(targetName.getUnqualifiedName())) { definitionReferences.add(declaration.getSourceRangeOfName()); } return super.visit_FunctionTypeDeclaraction(declaration, arg); } /** {@inheritDoc} */ @Override public Object visit_InstanceDefn(InstanceDefn defn, Object arg) { // We pass the type class name so that the method visitor methods can infer // the module name of the class methods. return super.visit_InstanceDefn(defn, defn.getTypeClassName()); } /** {@inheritDoc} */ @Override public Object visit_InstanceDefn_InstanceMethod(InstanceMethod method, Object arg) { SourceModel.Name.Qualifiable typeClassName = (SourceModel.Name.Qualifiable)arg; QualifiedName qualifiedTypeClass = getQualifiedName(typeClassName, moduleTypeInfo.getModuleNameResolver()); if(category == Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD && targetName.getModuleName().equals(qualifiedTypeClass.getModuleName()) && targetName.getUnqualifiedName().equals(method.getClassMethodName())) { definitionReferences.add(method.getClassMethodNameSourceRange()); } return super.visit_InstanceDefn_InstanceMethod(method, arg); } /** {@inheritDoc} */ @Override public Object visit_CALDoc_CrossReference_Module(Module reference, Object arg) { if (category == Category.MODULE_NAME) { ModuleName rawModuleName = org.openquark.cal.compiler.SourceModel.Name.Module.toModuleName(reference.getName()); ModuleName resolvedModuleName = moduleTypeInfo.getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName(); if (targetName.getModuleName().equals(resolvedModuleName)) { definitionReferences.add(reference.getName().getSourceRange()); } else { if (collateralDamageModuleRenameMapping.hasNewName(rawModuleName)) { ModuleName newModuleName = collateralDamageModuleRenameMapping.getNewNameForModule(rawModuleName); collateralDamageModuleNames.add(new Pair<SourceRange, ModuleName>(reference.getName().getSourceRange(), newModuleName)); } } } return super.visit_CALDoc_CrossReference_Module(reference, arg); } /** * @return True if the currently-visited source element is obstructed, or false otherwise. * When there is no newName, this always returns false. */ private boolean isObstructed() { if(newName == null) { return false; } return isBound(newName.getUnqualifiedName()); } /** * @return List (SourceRange) of all unobstructed unqualified Name elements that matched the * search criteria. */ List<SourceRange> getUnobstructedUnqualifiedReferences() { return Collections.unmodifiableList(unobstructedUnqualifiedReferences); } /** * @return List (SourceRange) of all obstructed unqualified Name elements that matched the * search criteria. */ List<SourceRange> getObstructedUnqualifiedReferences() { return Collections.unmodifiableList(obstructedUnqualifiedReferences); } /** * @return List (SourceRange) of all qualified Name elements that matched the search criteria. */ List<SourceRange> getFullyQualifiedReferences() { return Collections.unmodifiableList(fullyQualifiedReferences); } /** * @return List (SourceRange) of all definition references that matched the search criteria */ List<SourceRange> getDefinitionReferences() { return Collections.unmodifiableList(definitionReferences); } /** * @return List (SourceRange) of SourceRanges of references to targetName (which is a module) appearing in module, friend and import statements. */ List<SourceRange> getModuleFriendImportStatementReferences() { return Collections.unmodifiableList(moduleFriendImportStatementReferences); } /** * @return List (CrossReference.WithoutContextCons) of CrossReference.WithoutContextConses that refer either * to newName's entity or to an entity with the same unqualified name as newName, and which will become ambiguous * after the renaming. */ List<CALDoc> getAmbiguousCALDocElements() { return Collections.unmodifiableList(ambiguousCALDocElements); } /** * @return a list of pairs (module name source range, module name) representing the module names that are affected * by a module renaming (and thus would need disambiguation). */ List<Pair<SourceRange, ModuleName>> getCollateralDamageModuleNames() { return Collections.unmodifiableList(collateralDamageModuleNames); } } /** * @param moduleTypeInfo ModuleTypeInfo for the module to process * @param oldName QualifiedName of the identifier being renamed * @param newName QualifiedName to rename oldName to * @param category Category of the identifier being renamed * @param messageLogger CompilerMessageLogger for logging failures * @return a SourceModifier that will apply the renaming to the module specified by moduleName. */ static SourceModifier getSourceModifier(ModuleTypeInfo moduleTypeInfo, String sourceText, QualifiedName oldName, QualifiedName newName, Category category, CompilerMessageLogger messageLogger) { 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; } ModuleName moduleName = moduleTypeInfo.getModuleName(); String qualifiedNewName = newName.getQualifiedName(); String unqualifiedNewName = newName.getUnqualifiedName(); SourceModel.ModuleDefn moduleDefn = SourceModelUtilities.TextParsing.parseModuleDefnIntoSourceModel(sourceText, messageLogger); if(moduleDefn == null) { return sourceModifier; } ModuleNameResolver.RenameMapping collateralDamageModuleRenameMapping; if (category == Category.MODULE_NAME) { ModuleName oldModuleName = oldName.getModuleName(); ModuleName newModuleName = newName.getModuleName(); collateralDamageModuleRenameMapping = moduleTypeInfo.getModuleNameResolver().makeRenameMapping(oldModuleName, newModuleName); } else { collateralDamageModuleRenameMapping = moduleTypeInfo.getModuleNameResolver().makeEmptyRenameMapping(); } ModuleName newUsingModule = getModuleOfUsingIdentifier(moduleTypeInfo, unqualifiedNewName, category); ModuleName oldUsingModule = getModuleOfUsingIdentifier(moduleTypeInfo, oldName.getUnqualifiedName(), category); // Special case: if this module has a toplevel definition called unqualifiedNewName, // and has an import-using clause for oldName, then we need to remove the import-using // clause for oldName and make all new references to newName fully-qualified. boolean qualifyAllRenamedReferences = false; boolean renameInUsingClauses = true; if(oldUsingModule != null && getEntityFromModule(moduleTypeInfo, unqualifiedNewName, category) != null) { // Remove oldName's import-using clause sourceModifier.addSourceModification(makeUsingClauseRemovalModification(sourceText, moduleDefn, oldName, category)); // Don't rename oldName's import-using clause, because it's being removed renameInUsingClauses = false; // All references to newName need to be fully-qualified qualifyAllRenamedReferences = true; } RenamableReferenceFinder oldNameFinder = new RenamableReferenceFinder(oldName, newName, category, renameInUsingClauses, null, moduleTypeInfo, collateralDamageModuleRenameMapping); oldNameFinder.visit_ModuleDefn(moduleDefn, null); // Special case: If this is the module where oldName is defined and an identifier whose unqualified // name is the same as newName's is already imported in an import-using clause, then we need to: // 1) remove N.g's using clause // 2) fully-qualify every reference to N.g // // Similar special case: If this module has import-using clauses for both oldName and also for // a different identifier with the same unqualified name as newName, then we need to resolve that // conflict as well. We resolve it in the same way: Remove the occurrence of newName from the // using clause and explicitly qualify all of its references. if((oldName.getModuleName().equals(moduleName) && newUsingModule != null) || (newUsingModule != null && oldUsingModule != null)) { QualifiedName fullQualificationTarget = QualifiedName.make(newUsingModule, unqualifiedNewName); String fullQualificationTargetStr = fullQualificationTarget.getQualifiedName(); // Remove newUsingModule.newUnqualifiedName's import-using clause sourceModifier.addSourceModification(makeUsingClauseRemovalModification(sourceText, moduleDefn, fullQualificationTarget, category)); // Unqualified references to newName become fully-qualified references to newName RenamableReferenceFinder fullQualificationTargetFinder = new RenamableReferenceFinder(fullQualificationTarget, null, category, false, oldNameFinder.getAmbiguousCALDocElements(), moduleTypeInfo, collateralDamageModuleRenameMapping); fullQualificationTargetFinder.visit_ModuleDefn(moduleDefn, null); for (final SourceRange sourceRange : fullQualificationTargetFinder.getUnobstructedUnqualifiedReferences()) { sourceModifier.addSourceModification(makeReplaceText(sourceText, sourceRange, fullQualificationTargetStr)); } } // Fully-qualified references to oldName become fully-qualified references to newName for (final SourceRange sourceRange : oldNameFinder.getFullyQualifiedReferences()) { sourceModifier.addSourceModification(makeReplaceText(sourceText, sourceRange, qualifiedNewName)); } // Obstructed unqualified references to oldName become fully-qualified references to newName for (final SourceRange sourceRange : oldNameFinder.getObstructedUnqualifiedReferences()) { sourceModifier.addSourceModification(makeReplaceText(sourceText, sourceRange, qualifiedNewName)); } // Unobstructed unqualified references to oldName become unqualified references to newName // (unless we are in a special case flagged by qualifyAllRenamedReferences) for (final SourceRange sourceRange : oldNameFinder.getUnobstructedUnqualifiedReferences()) { if(qualifyAllRenamedReferences) { sourceModifier.addSourceModification(makeReplaceText(sourceText, sourceRange, qualifiedNewName)); } else { sourceModifier.addSourceModification(makeReplaceText(sourceText, sourceRange, unqualifiedNewName)); } } // Definition references to oldName become definition references to newName final String definitionReferenceReplacementName; if (category == Category.MODULE_NAME) { // if it is a module renaming, we try to be smart and use the minimally qualified form of the new module name. ModuleNameResolver newResolver = moduleTypeInfo.getModuleNameResolver().getResolverAfterRenaming(oldName.getModuleName(), newName.getModuleName()); ModuleName minimallyQualifiedNewModuleName = newResolver.getMinimallyQualifiedModuleName(newName.getModuleName()); definitionReferenceReplacementName = minimallyQualifiedNewModuleName.toSourceText(); } else { definitionReferenceReplacementName = unqualifiedNewName; } for (final SourceRange sourceRange : oldNameFinder.getDefinitionReferences()) { sourceModifier.addSourceModification(makeReplaceText(sourceText, sourceRange, definitionReferenceReplacementName)); } // Module, import and friend statement references to oldName become references to newName, in its fully qualified form. for (final SourceRange sourceRange : oldNameFinder.getModuleFriendImportStatementReferences()) { sourceModifier.addSourceModification(makeReplaceText(sourceText, sourceRange, newName.getModuleName().toSourceText())); } // Ambiguous CALDoc elements that contain references to WithoutContextConses // need to be "contextified". for (final CALDoc caldocElement : oldNameFinder.getAmbiguousCALDocElements()) { if(caldocElement instanceof Link.ConsNameWithoutContext) { sourceModifier.addSourceModification(makeContextifyLinkModification(moduleTypeInfo, sourceText, (Link.ConsNameWithoutContext)caldocElement, collateralDamageModuleRenameMapping)); } else if(caldocElement instanceof See.WithoutContext) { sourceModifier.addSourceModification(makeContextifySeeBlockModification(moduleTypeInfo, sourceText, (See.WithoutContext)caldocElement, oldName, newName, category, qualifyAllRenamedReferences, collateralDamageModuleRenameMapping)); } } // (Short-form) module names that are affected by a module renaming become the fully-qualified module names they resolve to for (final Pair<SourceRange, ModuleName> pair : oldNameFinder.getCollateralDamageModuleNames()) { SourceRange collateralDamageModuleNameSourceRange = pair.fst(); ModuleName fixedModuleName = pair.snd(); sourceModifier.addSourceModification(makeReplaceText(sourceText, collateralDamageModuleNameSourceRange, fixedModuleName.toSourceText())); } return sourceModifier; } /** * Generates a SourceModification that will replace segment with a new link that includes an explicit * context specifier. * @param moduleTypeInfo ModuleTypeInfo object for the module being processed * @param sourceText String sourceText being processed * @param segment Link.ConsNameWithoutContext A context-free CALDoc link element for a cons name * @param collateralDamageModuleRenameMapping a mapping which maps module names that are affected by a module renaming to * their corresponding disambiguated names. * @return A SourceModifier that will replace segment with an equivalent CALDoc element that explicitly * specifies the context of its reference. */ private static SourceModification makeContextifyLinkModification(ModuleTypeInfo moduleTypeInfo, String sourceText, Link.ConsNameWithoutContext segment, ModuleNameResolver.RenameMapping collateralDamageModuleRenameMapping) { CrossReference.WithoutContextCons oldReference = segment.getReference(); Name.WithoutContextCons cons = oldReference.getName(); Category consCategory = getCategory(moduleTypeInfo, cons); CALDoc.TextSegment.InlineTag.Link newSegment; if(consCategory == Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD || consCategory == Category.LOCAL_VARIABLE || consCategory == Category.LOCAL_VARIABLE_DEFINITION) { throw new IllegalStateException("WithoutContextCons shouldn't have a function/method category!"); } else if(consCategory == Category.MODULE_NAME) { ModuleName consNameAsModuleName = ModuleName.make(cons.toSourceText()); Name.Module moduleName = Name.Module.make(collateralDamageModuleRenameMapping.getNewNameForModule(consNameAsModuleName)); newSegment = CALDoc.TextSegment.InlineTag.Link.Module.make(CrossReference.Module.make(moduleName, oldReference.isChecked())); } else { ModuleName moduleName = org.openquark.cal.compiler.SourceModel.Name.Module.maybeToModuleName(cons.getModuleName()); // may be null if(consCategory == Category.DATA_CONSTRUCTOR) { Name.DataCons dataCons = Name.DataCons.make(collateralDamageModuleRenameMapping.getNewNameForModule(moduleName), cons.getUnqualifiedName()); newSegment = CALDoc.TextSegment.InlineTag.Link.DataCons.make(CrossReference.DataCons.make(dataCons, oldReference.isChecked())); } else if(consCategory == Category.TYPE_CONSTRUCTOR) { Name.TypeCons typeCons = SourceModel.Name.TypeCons.make(collateralDamageModuleRenameMapping.getNewNameForModule(moduleName), cons.getUnqualifiedName()); newSegment = CALDoc.TextSegment.InlineTag.Link.TypeCons.make(CrossReference.TypeCons.make(typeCons, oldReference.isChecked())); } else if(consCategory == Category.TYPE_CLASS) { Name.TypeClass typeClass = Name.TypeClass.make(collateralDamageModuleRenameMapping.getNewNameForModule(moduleName), cons.getUnqualifiedName()); newSegment = CALDoc.TextSegment.InlineTag.Link.TypeClass.make(CrossReference.TypeClass.make(typeClass, oldReference.isChecked())); } else { throw new IllegalStateException("Unexpected category"); } } return makeReplaceText(sourceText, segment.getSourceRange(), newSegment.toString()); } /** * Checks whether a context-free cons name will conflict with the entity specified by entityName and entityCategory. * This is a weaker claim than the claim that it refers to the entity. * * We assume that cons isn't /currently/ in conflict (ie, that it is an unambiguous reference). * * @param moduleTypeInfo ModuleTypeInfo for the module being processed * @param cons Name to check for conflicts * @param entityName Name of the entity to check for conflicts with cons * @param entityCategory Category of the entity to check for conflicts with cons * @return true if cons conflicts with the entity specified by entityName and entityCategory */ private static boolean willConflictWith(ModuleTypeInfo moduleTypeInfo, Name.WithoutContextCons cons, QualifiedName entityName, Category entityCategory) { if(entityName == null) { return false; } if(entityCategory == Category.MODULE_NAME) { ModuleName moduleName = entityName.getModuleName(); // the entire WithoutContextCons could be a module name // e.g. renaming module M -> L.M makes {@link L.M@} ambiguous if module L (or X.L, X.Y.L etc.) has visible entity M // no module name resolution required here, since we're doing a purely lexical comparison return ModuleName.make(cons.toSourceText()).equals(moduleName); } String consUnqualifiedName = cons.getUnqualifiedName(); ModuleName consModule = org.openquark.cal.compiler.SourceModel.Name.Module.maybeToModuleName(cons.getModuleName()); // may be null return entityName.getUnqualifiedName().equals(consUnqualifiedName) && (consModule == null || entityName.getModuleName().equals(consModule)); } /** * Checks whether name refers to the entity specified by qualifiedName and category. WithoutContextCons names are * assumed to be unambiguous. * * @param moduleTypeInfo ModuleTypeInfo for the module being processed * @param name Name to check * @param qualifiedName Name of the entity that name perhaps refers to * @param category Category of the entity that name perhaps refers to * @return True if name refers to the entity specified by qualifiedName and category */ private static boolean refersTo(ModuleTypeInfo moduleTypeInfo, Name.Qualifiable name, QualifiedName qualifiedName, Category category) { ModuleName qualifiedModuleName = qualifiedName.getModuleName(); if (category == Category.MODULE_NAME) { if (name instanceof Name.WithoutContextCons) { ModuleName consNameAsModuleName = ModuleName.make(name.toSourceText()); ModuleName resolvedModuleName = moduleTypeInfo.getModuleNameResolver().resolve(consNameAsModuleName).getResolvedModuleName(); // the module name to be checked against will be in qualifiedName's module name field. return resolvedModuleName.equals(qualifiedModuleName); } else { return false; } } String unqualifiedName = name.getUnqualifiedName(); if(!unqualifiedName.equals(qualifiedName.getUnqualifiedName())) { return false; } Category nameCategory; if(name instanceof Name.Function) { nameCategory = Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD; } else if(name instanceof Name.DataCons) { nameCategory = Category.DATA_CONSTRUCTOR; } else if(name instanceof Name.TypeCons) { nameCategory = Category.TYPE_CONSTRUCTOR; } else if(name instanceof Name.TypeClass) { nameCategory = Category.TYPE_CLASS; } else if(name instanceof Name.WithoutContextCons) { nameCategory = getCategory(moduleTypeInfo, (Name.WithoutContextCons)name); } else { throw new IllegalStateException("unexpected Name subtype"); } if(category != nameCategory) { return false; } ModuleName moduleName = org.openquark.cal.compiler.SourceModel.Name.Module.maybeToModuleName(name.getModuleName()); // may be null if(moduleName != null) { ModuleName resolvedModuleName = moduleTypeInfo.getModuleNameResolver().resolve(moduleName).getResolvedModuleName(); return resolvedModuleName.equals(qualifiedModuleName); } // At this point, the categories match, the unqualified names match, and name's moduleName is null // If this module contains an entity of this name, then that's what unqualified references refer to if(getEntityFromModule(moduleTypeInfo, unqualifiedName, nameCategory) != null) { return qualifiedModuleName.equals(moduleTypeInfo.getModuleName()); } // Otherwise, unqualified references refer to identifiers imported specified in using-clauses return qualifiedModuleName.equals(getModuleOfUsingIdentifier(moduleTypeInfo, unqualifiedName, nameCategory)); } /** * Generates a SourceModification that will replace seeBlock with one or more new see blocks that include * explicit context specifiers for any identifiers that conflict with newName. References to oldName will * also be converted to references to newName. * * @param moduleTypeInfo ModuleTypeInfo for the module being processed * @param sourceText String containing the source code for the module being processed * @param seeBlock See block to process * @param oldName QualifiedName of the identifier being renamed * @param newName QualifiedName that oldName is being renamed to * @param category Category of oldName/newName's entity * @param qualifyAllRenamedReferences boolean When true, renamed references to oldName will be fully qualified * (eg, "@see oldUnqualified" will become "@see NewModule.newUnqualified"). * @param collateralDamageModuleRenameMapping a mapping which maps module names that are affected by a module renaming to * their corresponding disambiguated names. * @return A SourceModification that replaces seeBlock with one or more new see blocks with the changes described above. */ private static SourceModification makeContextifySeeBlockModification(ModuleTypeInfo moduleTypeInfo, String sourceText, See.WithoutContext seeBlock, QualifiedName oldName, QualifiedName newName, Category category, boolean qualifyAllRenamedReferences, ModuleNameResolver.RenameMapping collateralDamageModuleRenameMapping) { StringBuilder sb = new StringBuilder(); for(int i = 0, nReferences = seeBlock.getNReferencedNames(); i < nReferences; i++) { CanAppearWithoutContext reference = seeBlock.getNthReferencedName(i); if(reference instanceof CanAppearWithoutContext.Function) { CanAppearWithoutContext.Function functionReference = (CanAppearWithoutContext.Function)reference; Name.Function functionName = functionReference.getName(); CanAppearWithoutContext newReference = reference; if(refersTo(moduleTypeInfo, functionName, oldName, category)) { if(functionName.getModuleName() == null) { newReference = CanAppearWithoutContext.Function.make(Name.Function.makeUnqualified(newName.getUnqualifiedName()), reference.isChecked()); } else { newReference = CanAppearWithoutContext.Function.make(Name.Function.make(newName.getModuleName(), newName.getUnqualifiedName()), reference.isChecked()); } } else { ModuleName oldModuleName = org.openquark.cal.compiler.SourceModel.Name.Module.maybeToModuleName(functionName.getModuleName()); // may be null if (oldModuleName != null && collateralDamageModuleRenameMapping.hasNewName(oldModuleName)) { ModuleName newModuleName = collateralDamageModuleRenameMapping.getNewNameForModule(oldModuleName); newReference = CanAppearWithoutContext.Function.make(Name.Function.make(newModuleName, functionName.getUnqualifiedName()), reference.isChecked()); } } See.WithoutContext newSee = See.WithoutContext.make(new CanAppearWithoutContext[] { newReference }); newSee.toSourceText(sb); } else if(reference instanceof CanAppearWithoutContext.WithoutContextCons) { CanAppearWithoutContext.WithoutContextCons consReference = (CanAppearWithoutContext.WithoutContextCons)reference; Name.WithoutContextCons name = consReference.getName(); if(willConflictWith(moduleTypeInfo, name, newName, category)) { Category consCategory = getCategory(moduleTypeInfo, name); See newSee; if(consCategory == Category.MODULE_NAME) { newSee = See.Module.make(new CrossReference.Module[] { CrossReference.Module.make(Name.Module.make(collateralDamageModuleRenameMapping.getNewNameForModule(ModuleName.make(name.toSourceText()))), consReference.isChecked()) }); } else if(consCategory == Category.DATA_CONSTRUCTOR) { newSee = See.DataCons.make(new CrossReference.DataCons[] { CrossReference.DataCons.make(Name.DataCons.make(collateralDamageModuleRenameMapping.getNewNameForModule(org.openquark.cal.compiler.SourceModel.Name.Module.maybeToModuleName(name.getModuleName())), name.getUnqualifiedName()), consReference.isChecked()) }); } else if(consCategory == Category.TYPE_CONSTRUCTOR) { newSee = See.TypeCons.make(new CrossReference.TypeCons[] { CrossReference.TypeCons.make(Name.TypeCons.make(collateralDamageModuleRenameMapping.getNewNameForModule(org.openquark.cal.compiler.SourceModel.Name.Module.maybeToModuleName(name.getModuleName())), name.getUnqualifiedName()), consReference.isChecked()) }); } else if(consCategory == Category.TYPE_CLASS) { newSee = See.TypeClass.make(new CrossReference.TypeClass[] { CrossReference.TypeClass.make(Name.TypeClass.make(collateralDamageModuleRenameMapping.getNewNameForModule(org.openquark.cal.compiler.SourceModel.Name.Module.maybeToModuleName(name.getModuleName())), name.getUnqualifiedName()), consReference.isChecked()) }); } else { throw new IllegalStateException("unexpected category"); } newSee.toSourceText(sb); } else if(refersTo(moduleTypeInfo, name, oldName, category)) { See newSee; if(category == Category.MODULE_NAME) { newSee = See.Module.make(new CrossReference.Module[] { CrossReference.Module.make(Name.Module.make(newName.getModuleName()), consReference.isChecked()) }); } else if(category == Category.DATA_CONSTRUCTOR) { CrossReference.DataCons newReference; if(name.getModuleName() != null || qualifyAllRenamedReferences) { newReference = CrossReference.DataCons.make(Name.DataCons.make(newName.getModuleName(), newName.getUnqualifiedName()), consReference.isChecked()); } else { newReference = CrossReference.DataCons.make(Name.DataCons.makeUnqualified(newName.getUnqualifiedName()), consReference.isChecked()); } newSee = See.DataCons.make(new CrossReference.DataCons[] { newReference }); } else if(category == Category.TYPE_CONSTRUCTOR) { CrossReference.TypeCons newReference; if(name.getModuleName() != null || qualifyAllRenamedReferences) { newReference = CrossReference.TypeCons.make(Name.TypeCons.make(newName.getModuleName(), newName.getUnqualifiedName()), consReference.isChecked()); } else { newReference = CrossReference.TypeCons.make(Name.TypeCons.makeUnqualified(newName.getUnqualifiedName()), consReference.isChecked()); } newSee = See.TypeCons.make(new CrossReference.TypeCons[] { newReference }); } else if(category == Category.TYPE_CLASS) { CrossReference.TypeClass newReference; if(name.getModuleName() != null || qualifyAllRenamedReferences) { newReference = CrossReference.TypeClass.make(Name.TypeClass.make(newName.getModuleName(), newName.getUnqualifiedName()), consReference.isChecked()); } else { newReference = CrossReference.TypeClass.make(Name.TypeClass.makeUnqualified(newName.getUnqualifiedName()), consReference.isChecked()); } newSee = See.TypeClass.make(new CrossReference.TypeClass[] { newReference }); } else { throw new IllegalStateException("unexpected category"); } newSee.toSourceText(sb); } else { // If the constructor name without context can be a module name or a qualified cons name where the // module name is affected by a module renaming and needs to be disambiguated. CanAppearWithoutContext.WithoutContextCons newReference = consReference; if (getCategory(moduleTypeInfo, name) == Category.MODULE_NAME) { // if the constructor name without context is a module name, disambiguate it if required. ModuleName rawModuleName = ModuleName.make(name.toSourceText()); if (collateralDamageModuleRenameMapping.hasNewName(rawModuleName)) { ModuleName fixedModuleName = collateralDamageModuleRenameMapping.getNewNameForModule(rawModuleName); String fixedModuleNameAsString = fixedModuleName.toSourceText(); newReference = CrossReference.WithoutContextCons.make(Name.WithoutContextCons.make(fixedModuleNameAsString), reference.isChecked()); } } else { // the constructor name without context is not a module name, // so if it is a qualified cons name, disambiguate the module name component if required. ModuleName moduleName = org.openquark.cal.compiler.SourceModel.Name.Module.maybeToModuleName(name.getModuleName()); // may be null if (moduleName != null) { if (collateralDamageModuleRenameMapping.hasNewName(moduleName)) { ModuleName fixedModuleName = collateralDamageModuleRenameMapping.getNewNameForModule(moduleName); newReference = CrossReference.WithoutContextCons.make(Name.WithoutContextCons.make(fixedModuleName, name.getUnqualifiedName()), reference.isChecked()); } } } See.WithoutContext newSee = See.WithoutContext.make(new CanAppearWithoutContext[] { newReference }); newSee.toSourceText(sb); } } } return makeReplaceText(sourceText, seeBlock.getSourceRange(), sb.toString()); } /** * @return The name of the module that unqualified name is imported from in a using clause, if any, * or null otherwise. * @param moduleTypeInfo ModuleTypeInfo of the module to check for imports * @param unqualifiedName * @param category */ private static ModuleName getModuleOfUsingIdentifier(ModuleTypeInfo moduleTypeInfo, String unqualifiedName, Category category) { if(category == Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) { return moduleTypeInfo.getModuleOfUsingFunctionOrClassMethod(unqualifiedName); } else if(category == Category.DATA_CONSTRUCTOR) { return moduleTypeInfo.getModuleOfUsingDataConstructor(unqualifiedName); } else if(category == Category.TYPE_CONSTRUCTOR) { return moduleTypeInfo.getModuleOfUsingTypeConstructor(unqualifiedName); } else if(category == Category.TYPE_CLASS) { return moduleTypeInfo.getModuleOfUsingTypeClass(unqualifiedName); } else if(category == Category.MODULE_NAME) { return null; } else if(category == Category.LOCAL_VARIABLE) { return null; } else if(category == Category.LOCAL_VARIABLE_DEFINITION) { return null; } else { throw new IllegalArgumentException("Unrecognized category"); } } /** * @return the entity in moduleTypeInfo's module named unqualifiedName and having * category category, if any exists, or null otherwise. */ private static ScopedEntity getEntityFromModule(ModuleTypeInfo moduleTypeInfo, String unqualifiedName, Category category) { if(category == Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) { return moduleTypeInfo.getFunctionOrClassMethod(unqualifiedName); } else if(category == Category.DATA_CONSTRUCTOR) { return moduleTypeInfo.getDataConstructor(unqualifiedName); } else if(category == Category.TYPE_CONSTRUCTOR) { return moduleTypeInfo.getTypeConstructor(unqualifiedName); } else if(category == Category.TYPE_CLASS) { return moduleTypeInfo.getTypeClass(unqualifiedName); } else if(category == Category.MODULE_NAME) { throw new UnsupportedOperationException(); } else if(category == Category.LOCAL_VARIABLE) { throw new UnsupportedOperationException(); } else if(category == Category.LOCAL_VARIABLE_DEFINITION) { throw new UnsupportedOperationException(); } else { throw new IllegalArgumentException("Unrecognized category"); } } /** * Constructs and returns a SoruceModification that replaces the text at sourceRange of sourceText with newText. * @param sourceText * @param sourceRange * @param newText * @return A SourceModification.ReplaceText */ static SourceModification makeReplaceText(String sourceText, SourceRange sourceRange, String newText) { SourcePosition startPosition = sourceRange.getStartSourcePosition(); SourcePosition endPosition = sourceRange.getEndSourcePosition(); int startIndex = startPosition.getPosition(sourceText); int endIndex = endPosition.getPosition(sourceText, startPosition, startIndex); String oldText = sourceText.substring(startIndex, endIndex); return new SourceModification.ReplaceText(oldText, newText, startPosition); } /** * Constructs and returns a SourceModification that will replace the import statement for targetName's module * with a new one that does not include targetName in its using clauses. An exception will be raised if * targetName is not in the using clause of the appropriate import definition. * @param sourceText * @param moduleDefn * @param targetName * @param category * @return SourceModification that removes targetName from the appropriate import-using clause */ private static SourceModification makeUsingClauseRemovalModification(String sourceText, SourceModel.ModuleDefn moduleDefn, QualifiedName targetName, Category category) { ModuleName targetModule = targetName.getModuleName(); // Remove targetName from the appropriate import-using clause for(int i = 0, nImports = moduleDefn.getNImportedModules(); i < nImports; i++) { SourceModel.Import moduleImport = moduleDefn.getNthImportedModule(i); if(!org.openquark.cal.compiler.SourceModel.Name.Module.toModuleName(moduleImport.getImportedModuleName()).equals(targetModule)) { continue; } SourcePosition startPosition = moduleImport.getSourceRange().getStartSourcePosition(); SourcePosition endPosition = moduleImport.getSourceRange().getEndSourcePosition(); int startIndex = startPosition.getPosition(sourceText); int endIndex = endPosition.getPosition(sourceText, startPosition, startIndex); String oldImportText = sourceText.substring(startIndex, endIndex); SourceModel.Import newImport = removeUsingClauseEntry(moduleImport, targetName.getUnqualifiedName(), category); return new SourceModification.ReplaceText(oldImportText, newImport.toSourceText(), startPosition); } throw new IllegalArgumentException("targetName has no import-using entry in the provided moduleDefn"); } /** * Constructs and returns a new import statement which does not contain unqualifiedName in the using-clause * specified by category. An exception will be raised if unqualifiedName is not in the expected using-clause. * @param importStatement * @param unqualifiedName * @param category * @return SourceModel.Import */ private static SourceModel.Import removeUsingClauseEntry(SourceModel.Import importStatement, String unqualifiedName, Category category) { List<UsingItem> newUsingItems = new ArrayList<UsingItem>(); UsingItem[] oldItems = importStatement.getUsingItems(); for (final UsingItem oldItem : oldItems) { UsingItem newItem = removeUsingItemEntry(oldItem, unqualifiedName, category); if(newItem != null) { newUsingItems.add(newItem); } } return SourceModel.Import.make(importStatement.getImportedModuleName(), newUsingItems.toArray(new UsingItem[0])); } /** * Constructs and returns a new UsingItem based on usingItem which does not contain unqualifiedName if usingItem * is of the specified category; if it isn't, usingItem is returned unchanged. * @param usingItem UsingItem to mostly duplicate * @param unqualifiedName Name to be possibly removed from usingItem * @param category * @return A new UsingItem that is of the same category as usingItem and which has all the same names * (except unqualifiedName if category is a match). */ private static UsingItem removeUsingItemEntry(UsingItem usingItem, String unqualifiedName, Category category) { // If this using item doesn't contain the same category of names as the target, then return it unchanged if((usingItem instanceof UsingItem.Function && category != Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) || (usingItem instanceof UsingItem.DataConstructor && category != Category.DATA_CONSTRUCTOR) || (usingItem instanceof UsingItem.TypeConstructor && category != Category.TYPE_CONSTRUCTOR) || (usingItem instanceof UsingItem.TypeClass && category != Category.TYPE_CLASS)) { return usingItem; } List<String> newNames = new ArrayList<String>(); String[] oldNames = usingItem.getUsingNames(); for (final String oldName : oldNames) { if(oldName.equals(unqualifiedName)) { continue; } newNames.add(oldName); } if(newNames.size() == 0) { return null; } if(usingItem instanceof UsingItem.Function) { return UsingItem.Function.make(newNames.toArray(new String[0])); } else if(usingItem instanceof UsingItem.DataConstructor) { return UsingItem.DataConstructor.make(newNames.toArray(new String[0])); } else if(usingItem instanceof UsingItem.TypeConstructor) { return UsingItem.TypeConstructor.make(newNames.toArray(new String[0])); } else if(usingItem instanceof UsingItem.TypeClass) { return UsingItem.TypeClass.make(newNames.toArray(new String[0])); } else { throw new IllegalStateException("unexpected UsingItem subclass"); } } /** * Calculates the category of a Name.WithoutContextCons. It is assumed to be a valid (ie, unambiguous) * reference. * @param moduleTypeInfo ModuleTypeInfo of the module that cons appears in * @param cons Name.WithoutContextCons to find the category of * @return Category of cons */ static private Category getCategory(ModuleTypeInfo moduleTypeInfo, Name.WithoutContextCons cons) { ModuleName consModuleName = org.openquark.cal.compiler.SourceModel.Name.Module.maybeToModuleName(cons.getModuleName()); // may be null String consUnqualifiedName = cons.getUnqualifiedName(); if(consModuleName != null) { QualifiedName qualifiedName = QualifiedName.make(moduleTypeInfo.getModuleNameResolver().resolve(consModuleName).getResolvedModuleName(), consUnqualifiedName); if(moduleTypeInfo.getVisibleFunction(qualifiedName) != null || moduleTypeInfo.getVisibleClassMethod(qualifiedName) != null) { return Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD; } else if(moduleTypeInfo.getVisibleDataConstructor(qualifiedName) != null) { return Category.DATA_CONSTRUCTOR; } else if(moduleTypeInfo.getVisibleTypeConstructor(qualifiedName) != null) { return Category.TYPE_CONSTRUCTOR; } else if(moduleTypeInfo.getVisibleTypeClass(qualifiedName) != null) { return Category.TYPE_CLASS; } else { return Category.MODULE_NAME; } } if(moduleTypeInfo.getDataConstructor(consUnqualifiedName) != null || moduleTypeInfo.getModuleOfUsingDataConstructor(consUnqualifiedName) != null) { return Category.DATA_CONSTRUCTOR; } else if(moduleTypeInfo.getFunctionOrClassMethod(consUnqualifiedName) != null || moduleTypeInfo.getModuleOfUsingFunctionOrClassMethod(consUnqualifiedName) != null) { return Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD; } else if(moduleTypeInfo.getTypeConstructor(consUnqualifiedName) != null || moduleTypeInfo.getModuleOfUsingTypeConstructor(consUnqualifiedName) != null) { return Category.TYPE_CONSTRUCTOR; } else if(moduleTypeInfo.getTypeClass(consUnqualifiedName) != null || moduleTypeInfo.getModuleOfUsingTypeClass(consUnqualifiedName) != null) { return Category.TYPE_CLASS; } else { return Category.MODULE_NAME; } } }