/*
* 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.
*/
/*
* RenamedIdentifierFinder.java
* Creation date: (June 22, 2004)
* By: Iulian Radu
*/
package org.openquark.cal.compiler;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openquark.cal.compiler.SourceIdentifier.Category;
import org.openquark.cal.util.ArrayStack;
/**
* Traverses the specified AST with the purpose of renaming a specified identifier.
*
* This class is deprecated; it's only used by the GemCutter's code gem editing
* capabilities, and we'd like to remove it entirely at some point in the near
* future. For renaming toplevel elements (functions, classes, type constructors,
* and data constructors), this class has been superseded by the IdentifierRenamer
* class.
*
* This class still contains logical for "collateral damage" handling (where conflicting
* bindings are renamed to be unique), but that no longer reflects the way that we handle
* binding conflicts.
*
* @author Iulian Radu
*/
final class RenamedIdentifierFinder extends SourceIdentifierFinder<SourceModification, String> {
/**
* Finds occurrences of identifiers which, when renamed, will conflict with locally
* bound variable names.
*
* ex: within the expression "let r = 1.0; in s + r", identifier "s" will cause
* a conflict when renamed to "r"
*
* @author Iulian Radu
*/
private static final class RenameConflictFinder extends SourceIdentifierFinder<SourceIdentifier, String> {
/** Category of identifier being renamed */
private final SourceIdentifier.Category renameCategory;
/** Old name of the identifier */
private final QualifiedName renameOldName;
/** New name of the identifier */
private final QualifiedName renameNewName;
/** Constructor */
RenameConflictFinder(ModuleName currentModuleName, QualifiedName oldName, QualifiedName newName, SourceIdentifier.Category category) {
setCurrentModuleName(currentModuleName);
this.renameOldName = oldName;
this.renameNewName = newName;
this.renameCategory = category;
}
/**
* Adds the current identifier to the list if it is to be renamed and its new name conflicts
* with a locally bound variable.
*/
@Override
void visitFunctionOrClassMethodNameNode(List<SourceIdentifier> identifierList, ArrayStack<String> boundVariablesStack, ParseTreeNode qualifiedNode) {
qualifiedNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR);
ParseTreeNode moduleNameNode = qualifiedNode.firstChild();
visitModuleNameNode(identifierList, moduleNameNode);
ModuleName rawModuleName = ModuleNameUtilities.getModuleNameOrNullFromParseTree(moduleNameNode);
ModuleName moduleName = getModuleNameResolver().resolve(rawModuleName).getResolvedModuleName();
ParseTreeNode varNameNode = moduleNameNode.nextSibling();
String varName = varNameNode.getText();
if (
((moduleName == null) || (moduleName.equals(getCurrentModuleName()))) &&
boundVariablesStack.contains(varName)) {
// Variable is pattern bound (i.e. an argument variable, a lambda bound
// variable or a variable bound by a case alternative). Its module is the current module.
return;
}
if ( varName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) &&
boundVariablesStack.contains(renameNewName)) {
// We are renaming this variable, but we are renaming it to something which
// already exists on the stack (bound by argument, let, lambda, or case).
// So mark this as impossible to change
ModuleName minimallyQualifiedModuleName = getModuleNameResolver().getMinimallyQualifiedModuleName(moduleName);
identifierList.add(new SourceIdentifier.Qualifiable(varName, varNameNode.getSourceRange(), SourceIdentifier.Category.LOCAL_VARIABLE, rawModuleName, moduleName, minimallyQualifiedModuleName, moduleNameNode.getAssemblySourceRange()));
}
return;
}
@Override
void visitCALDocArgNameNode(List<SourceIdentifier> identifierList, ArrayStack<String> boundVariablesStack, ParseTreeNode argNameNode) {}
@Override
void visitDataConsDefnNameNode(List<SourceIdentifier> identifierList, ParseTreeNode dataConsNameNode) {}
@Override
void visitTypeConsDefnNameNode(List<SourceIdentifier> identifierList, ParseTreeNode typeConsNameNode) {}
@Override
void visitClassDefnNameNode(List<SourceIdentifier> identifierList, ParseTreeNode classNameNode) {}
@Override
void visitClassNameNode(List<SourceIdentifier> identifierList, ParseTreeNode qualifiedNode) {}
@Override
void visitInstanceMethodNameNode(List<SourceIdentifier> identifierList, ParseTreeNode instanceMethodNameNode, ParseTreeNode qualifiedNameNode) {}
@Override
void visitDataConsNameNode(List<SourceIdentifier> identifierList, ParseTreeNode qualifiedNode) {}
@Override
void visitTypeConsNameNode(List<SourceIdentifier> identifierList, ParseTreeNode qualifiedNode) {}
@Override
void visitFunctionOrClassMethodDefinitionNameNode(List<SourceIdentifier> identifierList, ParseTreeNode qualifiedNode) {}
@Override
void visitUnqualifiedFunctionOrClassMethodNameNode(List<SourceIdentifier> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {}
@Override
void visitUnqualifiedClassNameNode(List<SourceIdentifier> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {}
@Override
void visitUnqualifiedDataConsNameNode(List<SourceIdentifier> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {}
@Override
void visitUnqualifiedTypeConsNameNode(List<SourceIdentifier> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {}
@Override
void visitModuleNameNode(List<SourceIdentifier> identifierList, ParseTreeNode moduleNameNode) {}
@Override
void visitCALDocCrossReferenceWithoutContextConsNameNode(List<SourceIdentifier> identifierList, ParseTreeNode refNode) {}
/** The identifier list contains SourceIdentifier objects */
@Override
Comparator<SourceIdentifier> getIdentifierListComparator() {
return SourceIdentifier.compareByStartPosition;
}
@Override
void pushLocalVar(ArrayStack<String> boundVariablesStack, ParseTreeNode varNameNode, List<SourceIdentifier> identifierList) {
boundVariablesStack.push(varNameNode.getText());
}
}
/**
* Finds occurrences of CALDoc "@arg" tags that need to be renamed.
*
* For example, in the module M, if M.x is renamed to M.a, then the let expression (in the same module M):
*
* let
* /**
* * @arg a the argument.
* * /
* localFunction a = a + x;
* in
* []
*
* ...would need to be modified to:
*
* let
* /**
* * @arg a2 the argument.
* * /
* localFunction a2 = a2 + a;
* in
* []
*
* @author Joseph Wong
*/
private static final class CALDocArgNameRenamingFinder extends SourceIdentifierFinder<SourceModification, String> {
/** Category of identifier being renamed */
private final SourceIdentifier.Category renameCategory;
/** Old name of the identifier */
private final QualifiedName renameOldName;
/** New name of the identifier */
private final QualifiedName renameNewName;
/** Constructor */
CALDocArgNameRenamingFinder(ModuleName currentModuleName, QualifiedName oldName, QualifiedName newName, SourceIdentifier.Category category) {
setCurrentModuleName(currentModuleName);
this.renameOldName = oldName;
this.renameNewName = newName;
this.renameCategory = category;
}
/**
* Adds a renaming (original to new name) if this node refers the SC or Class Method being renamed.
*
* Note: Local bindings are not checked because the conflict resolution algorithms ensures
* no conflicts occur (progressing outwards).
*/
@Override
void visitCALDocArgNameNode(List<SourceModification> identifierList, ArrayStack<String> boundVariablesStack, ParseTreeNode argNameNode) {
argNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.ORDINAL_FIELD_NAME);
if (argNameNode.getType() == CALTreeParserTokenTypes.ORDINAL_FIELD_NAME) {
return;
}
String varName = argNameNode.getText();
if (varName.equals(renameOldName.getUnqualifiedName()) &&
renameCategory == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD &&
renameOldName.getModuleName().equals(getCurrentModuleName())) {
identifierList.add(new SourceModification.ReplaceText(varName, renameNewName.getUnqualifiedName(), argNameNode.getSourcePosition()));
}
}
@Override
void visitFunctionOrClassMethodNameNode(List<SourceModification> identifierList, ArrayStack<String> boundVariablesStack, ParseTreeNode qualifiedNode) {}
@Override
void visitDataConsDefnNameNode(List<SourceModification> identifierList, ParseTreeNode dataConsNameNode) {}
@Override
void visitTypeConsDefnNameNode(List<SourceModification> identifierList, ParseTreeNode typeConsNameNode) {}
@Override
void visitClassDefnNameNode(List<SourceModification> identifierList, ParseTreeNode classNameNode) {}
@Override
void visitClassNameNode(List<SourceModification> identifierList, ParseTreeNode qualifiedNode) {}
@Override
void visitInstanceMethodNameNode(List<SourceModification> identifierList, ParseTreeNode instanceMethodNameNode, ParseTreeNode qualifiedNameNode) {}
@Override
void visitDataConsNameNode(List<SourceModification> identifierList, ParseTreeNode qualifiedNode) {}
@Override
void visitTypeConsNameNode(List<SourceModification> identifierList, ParseTreeNode qualifiedNode) {}
@Override
void visitFunctionOrClassMethodDefinitionNameNode(List<SourceModification> identifierList, ParseTreeNode qualifiedNode) {}
@Override
void visitUnqualifiedFunctionOrClassMethodNameNode(List<SourceModification> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {}
@Override
void visitUnqualifiedClassNameNode(List<SourceModification> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {}
@Override
void visitUnqualifiedDataConsNameNode(List<SourceModification> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {}
@Override
void visitUnqualifiedTypeConsNameNode(List<SourceModification> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {}
@Override
void visitModuleNameNode(List<SourceModification> identifierList, ParseTreeNode moduleNameNode) {}
@Override
void visitCALDocCrossReferenceWithoutContextConsNameNode(List<SourceModification> identifierList, ParseTreeNode refNode) {}
/** The identifier list contains SourceIdentifier objects */
@Override
Comparator<SourceModification> getIdentifierListComparator() {
return SourceModification.compareByPosition;
}
@Override
void pushLocalVar(ArrayStack<String> boundVariablesStack, ParseTreeNode varNameNode, List<SourceModification> identifierList) {
boundVariablesStack.push(varNameNode.getText());
}
}
/** Original name of identifier to rename */
private final QualifiedName renameOldName;
/** New name of identifier to rename*/
private final QualifiedName renameNewName;
/** Category of identifier renamed */
private final SourceIdentifier.Category renameCategory;
/** QualificationMap for use if we are working with a code gem. */
private final CodeQualificationMap qualificationMap;
/** Flag to indicate that a conflicting name has been found (so that we only report it once) */
private boolean conflictingNameFound = false;
/**
* Constructs a RenamedIdentifierFinder
* @param oldName The original name of the entity to rename
* @param newName The name to rename
* @param category The category of the entity being renamed
*/
RenamedIdentifierFinder(QualifiedName oldName, QualifiedName newName, SourceIdentifier.Category category) {
if (oldName == null || newName == null || category == null) {
throw new NullPointerException();
}
this.renameOldName = oldName;
this.renameNewName = newName;
this.renameCategory = category;
this.qualificationMap = null;
}
/**
* Constructs a RenamedIdentifierFinder
* @param oldName The original name of the entity to rename
* @param newName The name to rename
* @param category The category of the entity being renamed
* @param qualificationMap The qualification map to use if one exists, or null if it does not.
*/
RenamedIdentifierFinder(QualifiedName oldName, QualifiedName newName, SourceIdentifier.Category category, CodeQualificationMap qualificationMap) {
if (oldName == null || newName == null || category == null) {
throw new NullPointerException();
}
this.renameOldName = oldName;
this.renameNewName = newName;
this.renameCategory = category;
this.qualificationMap = qualificationMap;
}
/**
* Collect renamings within a Let declaration.
* A form of the conflict resolution mechanism described above is implemented in this method.
*
*/
@Override
void findIdentifiersInLet(List<SourceModification> identifierList, ArrayStack<String> boundVariablesStack, ParseTreeNode parseTree) {
// : Renaming S to R, resolving any conflicts that R may have with local vars
ModuleName mS = renameOldName.getModuleName();
String S = renameOldName.getUnqualifiedName(), R = renameNewName.getUnqualifiedName();
ParseTreeNode defnListNode = parseTree.firstChild();
defnListNode.verifyType(CALTreeParserTokenTypes.LET_DEFN_LIST);
// Check that S is not on the stack; if it is, then it is a local binding so we don't care about
// it and so we have nothing to do in this let.
if (mS.equals(getCurrentModuleName()) && boundVariablesStack.contains(S)) {
findIdentifiersInCALDocCommentsForLocalDefnsAndNestedExprs(identifierList, boundVariablesStack, defnListNode);
return;
}
// First, collect all the optional CALDoc nodes (for later checking)
Map<String, ParseTreeNode> funcNamesToOptionalCALDocNodes = new HashMap<String, ParseTreeNode>();
for (final ParseTreeNode defnNode : defnListNode) {
defnNode.verifyType(CALTreeParserTokenTypes.LET_DEFN, CALTreeParserTokenTypes.LET_DEFN_TYPE_DECLARATION, CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL);
if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) {
ParseTreeNode optionalCALDocNode = defnNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
ParseTreeNode localSCNameNode = optionalCALDocNode.nextSibling();
localSCNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String scName = localSCNameNode.getText();
if (optionalCALDocNode.firstChild() != null) {
funcNamesToOptionalCALDocNodes.put(scName, optionalCALDocNode);
}
} else if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN_TYPE_DECLARATION) {
ParseTreeNode optionalCALDocNode = defnNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
ParseTreeNode typeDeclNode = optionalCALDocNode.nextSibling();
typeDeclNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION);
ParseTreeNode localSCNameNode = typeDeclNode.firstChild();
localSCNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String scName = localSCNameNode.getText();
if (optionalCALDocNode.firstChild() != null) {
funcNamesToOptionalCALDocNodes.put(scName, optionalCALDocNode);
}
} else { // must be a LET_PATTERN_MATCH_DECL
// there is no CALDoc associated with a local pattern mathc declaration
}
}
// Look at local binding declarations, to know the variables defined at this level
int nLocalSCs = 0;
ParseTreeNode rLocalDeclNameNode = null;
ParseTreeNode rTypeDecl = null;
int rStackPos = -1;
for (final ParseTreeNode defnNode : defnListNode) {
switch (defnNode.getType()) {
case (CALTreeParserTokenTypes.LET_DEFN):
{
ParseTreeNode optionalCALDocNode = defnNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
// we defer the checking of the CALDoc to "Part 1" below, as part of findIdentifiersInLambda
ParseTreeNode localSCNameNode = optionalCALDocNode.nextSibling();
localSCNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String scName = localSCNameNode.getText();
// scName is a bound variable for all declarations in the 'let' and for the expression following the 'in'.
boundVariablesStack.push(scName);
nLocalSCs++;
// If this is defining local variable R, renaming of S to R will
// clash if S is present. So remember this location, for future use.
// Note: There is at most one TYPE_DECLARATION and LET_DEFN per variable
if (scName.equals(R)) {
rLocalDeclNameNode = localSCNameNode;
rStackPos = boundVariablesStack.size() - 1;
}
break;
}
case (CALTreeParserTokenTypes.LET_DEFN_TYPE_DECLARATION):
{
// Check potential clash with variable named R again
ParseTreeNode optionalCALDocNode = defnNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
// we defer the checking of the CALDoc to "Part 1" below, as part of findIdentifiersInLambda
ParseTreeNode typeDeclNode = optionalCALDocNode.nextSibling();
typeDeclNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION);
ParseTreeNode localSCNameNode = typeDeclNode.firstChild();
localSCNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String scName = localSCNameNode.getText();
if (scName.equals(R)) {
rTypeDecl = localSCNameNode;
}
findIdentifiersInTypeDecl(identifierList, typeDeclNode);
break;
}
case CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL:
{
// A local pattern match declaration can declare one or more locally bound variables
// We will loop through the pattern to process each one
final ParseTreeNode patternMatchPatternNode = defnNode.firstChild();
switch (patternMatchPatternNode.getType()) {
case CALTreeParserTokenTypes.PATTERN_CONSTRUCTOR:
{
// a data cons pattern
// e.g. let Cons x y = foo; ...
ParseTreeNode dcNameListNode = patternMatchPatternNode.firstChild();
dcNameListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_LIST, CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_SINGLETON);
ParseTreeNode dcArgBindingsNode = dcNameListNode.nextSibling();
final ParseTreeNode dcNameNode = dcNameListNode.firstChild();
visitDataConsNameNode(identifierList, dcNameNode);
switch (dcArgBindingsNode.getType()) {
case CALTreeParserTokenTypes.PATTERN_VAR_LIST:
// a data cons pattern with positional patterns
// e.g. let Cons x y = foo; ...
for (final ParseTreeNode patternVarNode : dcArgBindingsNode) {
if (patternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) {
final String scName = patternVarNode.getText();
// scName is a bound variable for all declarations in the 'let' and for the expression following the 'in'.
boundVariablesStack.push(scName);
nLocalSCs++;
// If this is defining local variable R, renaming of S to R will
// clash if S is present. So remember this location, for future use.
// Note: There is at most one TYPE_DECLARATION and LET_DEFN per variable
if (scName.equals(R)) {
rLocalDeclNameNode = patternVarNode;
rStackPos = boundVariablesStack.size() - 1;
}
}
}
break;
case CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST:
// a data cons pattern with field-pattern pairs
// e.g. let Cons {head=x, tail} = foo; ...
// Set pattern variables for punned fields
unpunPunnedFields(dcArgBindingsNode);
for (final ParseTreeNode fieldBindingVarAssignmentNode : dcArgBindingsNode) {
final ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild();
final ParseTreeNode patternVarNode = fieldNameNode.nextSibling();
if (patternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) {
final String scName = patternVarNode.getText();
// scName is a bound variable for all declarations in the 'let' and for the expression following the 'in'.
boundVariablesStack.push(scName);
nLocalSCs++;
// If this is defining local variable R, renaming of S to R will
// clash if S is present. So remember this location, for future use.
// Note: There is at most one TYPE_DECLARATION and LET_DEFN per variable
if (scName.equals(R)) {
rLocalDeclNameNode = patternVarNode;
rStackPos = boundVariablesStack.size() - 1;
}
}
}
break;
default:
dcArgBindingsNode.unexpectedParseTreeNode();
break;
}
break;
}
case CALTreeParserTokenTypes.COLON:
case CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR:
{
// a list cons pattern and a tuple pattern can be treated in a similar way, because
// in both cases the node's children is the list of patterns
// list cons pattern, e.g. let a:b = foo; ...
// tuple pattern, e.g. let (a, b, c) = foo; ...
for (final ParseTreeNode patternVarNode : patternMatchPatternNode) {
if (patternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) {
final String scName = patternVarNode.getText();
// scName is a bound variable for all declarations in the 'let' and for the expression following the 'in'.
boundVariablesStack.push(scName);
nLocalSCs++;
// If this is defining local variable R, renaming of S to R will
// clash if S is present. So remember this location, for future use.
// Note: There is at most one TYPE_DECLARATION and LET_DEFN per variable
if (scName.equals(R)) {
rLocalDeclNameNode = patternVarNode;
rStackPos = boundVariablesStack.size() - 1;
}
}
}
break;
}
case CALTreeParserTokenTypes.RECORD_PATTERN:
{
// a record pattern
// e.g. let {_ | a, b=y} = foo; ...
final ParseTreeNode baseRecordPatternNode = patternMatchPatternNode.firstChild();
baseRecordPatternNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD_PATTERN);
final ParseTreeNode fieldBindingVarAssignmentListNode = baseRecordPatternNode.nextSibling();
// Set pattern variables for punned fields
unpunPunnedFields(fieldBindingVarAssignmentListNode);
for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) {
final ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild();
final ParseTreeNode patternVarNode = fieldNameNode.nextSibling();
if (patternVarNode.getType() == CALTreeParserTokenTypes.VAR_ID) {
final String scName = patternVarNode.getText();
// scName is a bound variable for all declarations in the 'let' and for the expression following the 'in'.
boundVariablesStack.push(scName);
nLocalSCs++;
// If this is defining local variable R, renaming of S to R will
// clash if S is present. So remember this location, for future use.
// Note: There is at most one TYPE_DECLARATION and LET_DEFN per variable
if (scName.equals(R)) {
rLocalDeclNameNode = patternVarNode;
rStackPos = boundVariablesStack.size() - 1;
}
}
}
break;
}
default:
{
patternMatchPatternNode.unexpectedParseTreeNode();
break;
}
}
break;
}
default:
{
// Unexpected node type
defnListNode.unexpectedParseTreeNode();
break;
}
}
}
// If S has just been bound, then any references to S will be to the local variable,
// so there is nothing for us to do here, beside performing finding the renamings for the CALDoc
// comments which were previously skipped over.
if (mS.equals(getCurrentModuleName()) && boundVariablesStack.contains(S)) {
findIdentifiersInCALDocCommentsForLocalDefnsAndNestedExprs(identifierList, boundVariablesStack, defnListNode);
return;
}
// Part 1: Find replacements at lower levels
// Look in body of local variable declarations, and let declaration, and collect S-to-R replacements
// (and other renamings if conflicts occur inside)
int oldIdentifierCount = identifierList.size();
for (final ParseTreeNode defnNode : defnListNode) {
if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) {
ParseTreeNode localFunctionNameNode = defnNode.getChild(1);
localFunctionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String scName = localFunctionNameNode.getText();
ParseTreeNode varListNode = localFunctionNameNode.nextSibling();
// get the optional CALDoc node stored in the map.
// this may be the CALDoc associated with the corresponding function type declaration, or if
// there is none, the one associated with this function definition itself.
ParseTreeNode optionalCALDocNodeFromMap = funcNamesToOptionalCALDocNodes.get(scName);
// here, we not only process the body of the local definition, but also its CALDoc comment,
// since its @arg tags may be affected by the renaming of the parameters.
findIdentifiersInLambda(identifierList, boundVariablesStack, varListNode, varListNode.nextSibling(), optionalCALDocNodeFromMap);
} else if (defnNode.getType() == CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL) {
final ParseTreeNode patternMatchPatternNode = defnNode.firstChild();
final ParseTreeNode patternMatchExprNode = patternMatchPatternNode.nextSibling();
findIdentifiersInLambda(identifierList, boundVariablesStack, null, patternMatchExprNode);
}
}
ParseTreeNode exprNode = defnListNode.nextSibling();
findIdentifiersInExpr(identifierList, boundVariablesStack, exprNode);
// Part 2: Resolve conflicts at this level
if (!(mS.equals(getCurrentModuleName()) || mS.equals(getModuleForImportedFunctionOrClassMethod(S))) || (rLocalDeclNameNode == null) || (oldIdentifierCount == identifierList.size())) {
// We do not try to resolve conflicts if either:
// 1. S cannot appear in unqualified form, and not thus conflicting with local variables
// 2. the list of identifiers has not changed, thus no occurrences of S
// have been found and renamed
// 3. R is not defined at our level so we have no conflicts to resolve here
boundVariablesStack.popN(nLocalSCs);
return;
}
// Will rename the local variable R by appending a suffix
int pad = 2;
String R2 = "";
// Repeat the following section until we find a variable name which
// does not cause a conflict with others at this or inner levels
boolean stillLooking = true;
while (stillLooking) {
R2 = rLocalDeclNameNode.getText() + pad++;
// Make sure we don't chose a name conflicting with something already defined
// in this or outer scope
while (boundVariablesStack.contains(R2)) {
R2 = rLocalDeclNameNode.getText() + pad++;
}
// Now make sure this choice doesn't cause conflicts down the road (ie: renaming R to R2
// will not conflict R2 with local variables in inner scopes)
// Change to RenameConflict finding mode (R-R2)
RenameConflictFinder rr2ConflictFinder =
new RenameConflictFinder(getCurrentModuleName(), QualifiedName.make(getCurrentModuleName(), R), QualifiedName.make(getCurrentModuleName(), R2), renameCategory);
// Declare new stack because otherwise RCF will think conflicting thoughts
// Note: Bound variables at this and upper levels have already been checked
// not to contain R2
ArrayStack<String> newBoundVariablesStack = ArrayStack.make();
List<SourceIdentifier> conflictingIdentifiers = new ArrayList<SourceIdentifier>();
for (final ParseTreeNode defnNode : defnListNode) {
if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) {
ParseTreeNode optionalCALDocNode = defnNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
ParseTreeNode localFunctionNameNode = optionalCALDocNode.nextSibling();
ParseTreeNode varListNode = localFunctionNameNode.nextSibling();
rr2ConflictFinder.findIdentifiersInLambda(conflictingIdentifiers, newBoundVariablesStack, varListNode, varListNode.nextSibling());
} else if (defnNode.getType() == CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL) {
defnNode.verifyType(CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL);
final ParseTreeNode patternMatchPatternNode = defnNode.firstChild();
final ParseTreeNode patternMatchExprNode = patternMatchPatternNode.nextSibling();
rr2ConflictFinder.findIdentifiersInLambda(conflictingIdentifiers, newBoundVariablesStack, null, patternMatchExprNode);
}
}
rr2ConflictFinder.findIdentifiersInExpr(conflictingIdentifiers, newBoundVariablesStack, exprNode);
getLogger().logMessages(rr2ConflictFinder.getLogger());
// Continue looking for a new variable name until there are no conflicts
stillLooking = !conflictingIdentifiers.isEmpty();
}
// Now, R2 is a non-conflicting name, so replace all occurrences of R with R2
// Rename declaration
identifierList.add(new SourceModification.ReplaceText(rLocalDeclNameNode.getText(), R2, rLocalDeclNameNode.getSourcePosition()));
if (rTypeDecl != null) {
identifierList.add(new SourceModification.ReplaceText(rTypeDecl.getText(), R2, rTypeDecl.getSourcePosition()));
}
// Rename R to R2 on the bindings stack
// Note: (1) R2 does not exist in local vars already because we have checked
// (2) R2 as a local var from this or upper levels is not referenced from below because (1)
// (3) renaming R to R2 does not clash below with any local variable, because we checked
boundVariablesStack.set(rStackPos, R2);
// TODO: See if this can be improved to less hackish
// Now, on the stack there may be other occurrences of R, which will not allow R-R2 renaming to
// occur (since we check local bindings). These occurrences will be renamed as we move out,
// but for now we will rename them to R2 (this does nothing since we already have R2 on the stack)
List<Integer> stackSpotsRenamed = new ArrayList<Integer>();
for (int i = 0, n = boundVariablesStack.size(); i < n; i++) {
if ((boundVariablesStack.get(i)).equals(R)) {
stackSpotsRenamed.add(Integer.valueOf(i));
boundVariablesStack.set(i, R2);
}
}
// Rename R-R2 in lower levels
// (Note: this should not go through any Part 2s, since there are no conflicts)
RenamedIdentifierFinder rr2RenamingFinder = new RenamedIdentifierFinder(QualifiedName.make(getCurrentModuleName(), R), QualifiedName.make(getCurrentModuleName(), R2), renameCategory);
rr2RenamingFinder.setCurrentModuleName(getCurrentModuleName());
for (final ParseTreeNode defnNode : defnListNode) {
if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) {
ParseTreeNode optionalCALDocNode = defnNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
ParseTreeNode localFunctionNameNode = optionalCALDocNode.nextSibling();
ParseTreeNode varListNode = localFunctionNameNode.nextSibling();
rr2RenamingFinder.findIdentifiersInLambda(identifierList, boundVariablesStack, varListNode, varListNode.nextSibling());
} else if (defnNode.getType() == CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL) {
defnNode.verifyType(CALTreeParserTokenTypes.LET_PATTERN_MATCH_DECL);
final ParseTreeNode patternMatchPatternNode = defnNode.firstChild();
final ParseTreeNode patternMatchExprNode = patternMatchPatternNode.nextSibling();
rr2RenamingFinder.findIdentifiersInLambda(identifierList, boundVariablesStack, null, patternMatchExprNode);
}
}
rr2RenamingFinder.findIdentifiersInExpr(identifierList, boundVariablesStack, exprNode);
getLogger().logMessages(rr2RenamingFinder.getLogger());
// Put back Rs on the stack
for (int i = 0, n = stackSpotsRenamed.size(); i < n; i++) {
boundVariablesStack.set(i, R);
}
boundVariablesStack.popN(nLocalSCs);
}
/**
* Collect renamings within a CALDoc comment associated with a local definition.
*/
private void findIdentifiersInCALDocCommentAssociatedWithLocalDefn(List<SourceModification> identifierList, ArrayStack<String> boundVariablesStack, ParseTreeNode defnNode) {
ParseTreeNode optionalCALDocNode = defnNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
ParseTreeNode localFunctionNameNode = optionalCALDocNode.nextSibling();
ParseTreeNode varListNode = localFunctionNameNode.nextSibling();
int nVars = 0;
if (varListNode != null) {
for (final ParseTreeNode patternVarNode : varListNode) {
switch (patternVarNode.getType()) {
case CALTreeParserTokenTypes.VAR_ID:
case CALTreeParserTokenTypes.LAZY_PARAM:
case CALTreeParserTokenTypes.STRICT_PARAM:
{
String varName = patternVarNode.getText();
++nVars;
// varName is now a bound variable for the body of the
// lambda
boundVariablesStack.push(varName);
break;
}
case CALTreeParserTokenTypes.UNDERSCORE:
break;
default :
// Unexpected type
boundVariablesStack.popN(nVars);
patternVarNode.unexpectedParseTreeNode();
return;
}
}
}
findIdentifiersInCALDocComment(identifierList, boundVariablesStack, optionalCALDocNode);
boundVariablesStack.popN(nVars);
}
/**
* Collect renamings within the CALDoc comments associated with a local
* function definitions and type declarations, as well as any CALDoc
* comments contained in the defining expressions of these local functions.
*/
void findIdentifiersInCALDocCommentsForLocalDefnsAndNestedExprs(List<SourceModification> identifierList, ArrayStack<String> boundVariablesStack, ParseTreeNode defnListNode) {
Map<String, ParseTreeNode> funcNamesToDefnNodes = new HashMap<String, ParseTreeNode>();
// First pass: gather the function definition nodes
for (final ParseTreeNode defnNode : defnListNode) {
if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) {
ParseTreeNode optionalCALDocNode = defnNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
ParseTreeNode localSCNameNode = optionalCALDocNode.nextSibling();
localSCNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String scName = localSCNameNode.getText();
funcNamesToDefnNodes.put(scName, defnNode);
}
}
// Second pass: perform the checks on CALDocs and also check into the defining expressions
for (final ParseTreeNode defnNode : defnListNode) {
defnNode.verifyType(CALTreeParserTokenTypes.LET_DEFN, CALTreeParserTokenTypes.LET_DEFN_TYPE_DECLARATION);
if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) {
// check the CALDoc
findIdentifiersInCALDocCommentAssociatedWithLocalDefn(identifierList, boundVariablesStack, defnNode);
// check the bound expression for nested let blocks with CALDoc comments
ParseTreeNode inExprNode = defnNode.getChild(3);
findIdentifiersInExpr(identifierList, boundVariablesStack, inExprNode);
} else { // must be a LET_DEFN_TYPE_DECLARATION
ParseTreeNode optionalCALDocNode = defnNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
ParseTreeNode typeDeclNode = optionalCALDocNode.nextSibling();
typeDeclNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION);
ParseTreeNode localSCNameNode = typeDeclNode.firstChild();
localSCNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String scName = localSCNameNode.getText();
ParseTreeNode funcDefnNode = funcNamesToDefnNodes.get(scName);
if (funcDefnNode != null) {
// check the CALDoc with the function's definition node
findIdentifiersInCALDocCommentAssociatedWithLocalDefn(identifierList, boundVariablesStack, funcDefnNode);
} else {
getLogger().logMessage(new CompilerMessage(defnNode, new MessageKind.Error.DefinitionMissing(scName)));
}
}
}
}
/**
* Collect renamings within an SC declaration. There can be no conflicts here.
*/
@Override
void findIdentifiersInFunctionDeclaration(List<SourceModification> identifierList, ParseTreeNode scTopLevelTypeDeclarationNode) {
scTopLevelTypeDeclarationNode.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_TYPE_DECLARATION);
ParseTreeNode optionalCALDocNode = scTopLevelTypeDeclarationNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
findIdentifiersInCALDocComment(identifierList, optionalCALDocNode);
ParseTreeNode scTypeDeclarationNode = optionalCALDocNode.nextSibling();
ParseTreeNode scNameNode = scTypeDeclarationNode.firstChild();
visitFunctionOrClassMethodDefinitionNameNode(identifierList, scNameNode);
ParseTreeNode typeSignatureNode = scNameNode.nextSibling();
findIdentifiersInTypeSignature(identifierList, typeSignatureNode);
}
/**
* Collect renamings within a SC definition.
* A form of the conflict resolution mechanism described above is implemented in this method.
*/
@Override
void findIdentifiersInFunctionDefinition(List<SourceModification> identifierList, ParseTreeNode scNode) {
// : Renaming S to R, in SC definition
ModuleName mS = renameOldName.getModuleName();
String S = renameOldName.getUnqualifiedName(), R = renameNewName.getUnqualifiedName();
scNode.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN);
ParseTreeNode optionalCALDocNode = scNode.firstChild();
optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT);
findIdentifiersInCALDocComment(identifierList, optionalCALDocNode);
ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling();
ParseTreeNode scNameNode = accessModifierNode.nextSibling();
visitFunctionOrClassMethodDefinitionNameNode(identifierList, scNameNode);
// Find local argument bindings
ParseTreeNode conflictingArgNode = null;
int conflictingArgPos = -1;
ArrayStack<String> namedArgumentsStack = ArrayStack.make();
ParseTreeNode paramListNode = accessModifierNode.nextSibling().nextSibling();
paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST);
for (final ParseTreeNode varNode : paramListNode) {
varNode.verifyType(CALTreeParserTokenTypes.LAZY_PARAM, CALTreeParserTokenTypes.STRICT_PARAM);
String varName = varNode.getText();
namedArgumentsStack.push(varName);
// If an argument has the name R, it will conflict with the rename if the SC definition
// contains S; thus, keep track of this argument.
if (varName.equals(R)) {
conflictingArgNode = varNode;
conflictingArgPos = namedArgumentsStack.size() - 1;
}
}
// S has just been defined as a sc argument, so will ignore any renamings
if (mS.equals(getCurrentModuleName()) && namedArgumentsStack.contains(S)) {
return;
}
// Part 1 : Collect renamings (S-R, and local conflict resolutions) from the SC declaration
int oldIdentifierCount = identifierList.size();
findIdentifiersInExpr(identifierList, namedArgumentsStack, paramListNode.nextSibling());
// Part 2 : Resolve conflicts between R and local arguments
if (!(mS.equals(getCurrentModuleName()) || mS.equals(getModuleForImportedFunctionOrClassMethod(S))) || (conflictingArgNode == null) || (oldIdentifierCount == identifierList.size())) {
// We do not try to resolve conflicts if either:
// 1. S cannot appear in unqualified form, and not conflicting with local variables
// 2. the list of identifiers has not changed, thus no occurrences of S
// have been found and renamed
// 3. R is not defined at our level so we have no conflicts to resolve here
return;
}
// Will repeat the following section until we find a variable name which
// does not cause a conflict with others at this or inner levels
boolean keepSearching = true;
int pad = 2;
String R2 = "";
while (keepSearching) {
R2 = conflictingArgNode.getText() + pad++;
// Make sure we don't chose a name conflicting with an argument defined
// in this or outer scope
while (namedArgumentsStack.contains(R2)) {
R2 = conflictingArgNode.getText() + pad++;
}
// Now make sure this choice doesn't cause conflicts down the road (ie: renaming R to R2
// will not conflict R2 with local variables in inner scopes)
// Change mode to RenameConflictFinder (renaming R to R2)
RenameConflictFinder rr2ConflictFinder = new RenameConflictFinder(getCurrentModuleName(), QualifiedName.make(getCurrentModuleName(), R), QualifiedName.make(getCurrentModuleName(), R2), renameCategory);
ArrayStack<String> newBoundVariablesStack = ArrayStack.make();
List<SourceIdentifier> conflictingIdentifiers = new ArrayList<SourceIdentifier>();
rr2ConflictFinder.findIdentifiersInExpr(conflictingIdentifiers, newBoundVariablesStack, paramListNode.nextSibling());
getLogger().logMessages(rr2ConflictFinder.getLogger());
// Continue looking for a new variable name until there are no conflicts
keepSearching = !conflictingIdentifiers.isEmpty();
}
// Now, R2 is a non-conflicting name, so replace all occurrences of R with R2
// Mark argument definition to be renamed (R-R2)
identifierList.add(new SourceModification.ReplaceText(conflictingArgNode.getText(), R2 ,conflictingArgNode.getSourcePosition()));
namedArgumentsStack.set(conflictingArgPos, R2);
// Change mode to RF, R-R2
// (Note: this should not go through any Part 2s, since there are no conflicts)
RenamedIdentifierFinder rr2RenamingFinder = new RenamedIdentifierFinder(QualifiedName.make(getCurrentModuleName(), R), QualifiedName.make(getCurrentModuleName(), R2), renameCategory);
rr2RenamingFinder.setCurrentModuleName(getCurrentModuleName());
rr2RenamingFinder.findIdentifiersInExpr(identifierList, namedArgumentsStack, paramListNode.nextSibling());
getLogger().logMessages(rr2RenamingFinder.getLogger());
}
/**
* Collect renamings within a Lambda declaration.
* A form of the conflict resolution mechanism described above is implemented in this method.
*
* @param identifierList
* @param boundVariablesStack
* @param patternVarListNode
* @param boundExprNode
* @param optionalCALDocNodeForLetDefn
*/
@Override
void findIdentifiersInLambda(List<SourceModification> identifierList, ArrayStack<String> boundVariablesStack, ParseTreeNode patternVarListNode, ParseTreeNode boundExprNode, ParseTreeNode optionalCALDocNodeForLetDefn) {
// : Renaming S to R, resolving any conflicts that R may have with local vars
ModuleName mS = renameOldName.getModuleName();
String S = renameOldName.getUnqualifiedName(), R = renameNewName.getUnqualifiedName();
ParseTreeNode conflictingVarNode = null;
int conflictingVarPos = -1;
int nVars = 0;
if (patternVarListNode != null) {
for (final ParseTreeNode patternVarNode : patternVarListNode) {
switch (patternVarNode.getType()) {
case CALTreeParserTokenTypes.VAR_ID:
case CALTreeParserTokenTypes.LAZY_PARAM:
case CALTreeParserTokenTypes.STRICT_PARAM:
{
String varName = patternVarNode.getText();
++nVars;
// varName is now a bound variable for the body of the
// lambda
boundVariablesStack.push(varName);
if (varName.equals(R)) {
// This variable is a potential conflict if we find any S in the body,
// so keep it in case we need to rename it
conflictingVarNode = patternVarNode;
conflictingVarPos = boundVariablesStack.size() - 1;
}
break;
}
case CALTreeParserTokenTypes.UNDERSCORE :
break;
default :
{
// Unexpected type
boundVariablesStack.popN(nVars);
patternVarNode.unexpectedParseTreeNode();
return;
}
}
}
}
// If S was already on the stack, or has just been defined as a lambda argument, will ignore any renamings
if (mS.equals(getCurrentModuleName()) && boundVariablesStack.contains(S)) {
// check the CALDoc
if (optionalCALDocNodeForLetDefn != null) {
findIdentifiersInCALDocComment(identifierList, boundVariablesStack, optionalCALDocNodeForLetDefn);
}
// and also check the bound expression for nested let blocks with CALDoc comments
findIdentifiersInExpr(identifierList, boundVariablesStack, boundExprNode);
boundVariablesStack.popN(nVars);
return;
}
// Part 1: Find replacements at lower levels
int oldIdentifierListSize = identifierList.size();
findIdentifiersInExpr(identifierList, boundVariablesStack, boundExprNode);
// Part 2: Resolve conflicts at this level
if (!(mS.equals(getCurrentModuleName()) || mS.equals(getModuleForImportedFunctionOrClassMethod(S))) || (conflictingVarNode == null) || (oldIdentifierListSize == identifierList.size())) {
// We do not try to resolve conflicts if either:
// 1. S cannot appear in unqualified form and not conflicting with local variables
// 2. the list of identifiers has not changed, thus no occurrences of S
// have been found and renamed
// 3. R is not defined at our level so we have no conflicts to resolve here
if (optionalCALDocNodeForLetDefn != null) {
// just check the CALDoc, since the bound expression has already been traversed
findIdentifiersInCALDocComment(identifierList, boundVariablesStack, optionalCALDocNodeForLetDefn);
}
boundVariablesStack.popN(nVars);
return;
}
// Will rename the local variable R by appending a suffix
int pad = 2;
String R2 = "";
// Will repeat the following section until we find a variable name which
// does not cause a conflict with others at this or inner levels
boolean stillLooking = true;
while (stillLooking) {
R2 = conflictingVarNode.getText() + pad++;
// Make sure we don't chose a name conflicting with something already defined
// in this or outer scope
while (boundVariablesStack.contains(R2)) {
R2 = conflictingVarNode.getText() + pad++;
}
// Now make sure this choice doesn't cause conflicts down the road (ie: renaming R to R2
// will not conflict R2 with local variables in inner scopes)
// Change to RenameConflict finding mode (R-R2)
RenameConflictFinder rr2ConflictFinder = new RenameConflictFinder(getCurrentModuleName(), QualifiedName.make(getCurrentModuleName(), R), QualifiedName.make(getCurrentModuleName(), R2), renameCategory);
ArrayStack<String> newBoundVariablesStack = ArrayStack.make();
List<SourceIdentifier> conflictingIdentifiers = new ArrayList<SourceIdentifier>();
rr2ConflictFinder.findIdentifiersInExpr(conflictingIdentifiers, newBoundVariablesStack, boundExprNode);
getLogger().logMessages(rr2ConflictFinder.getLogger());
// Continue looking for a new variable name until there are no conflicts
stillLooking = !conflictingIdentifiers.isEmpty();
}
// Now, R2 is a non-conflicting name, so replace all occurrences of R with R2
identifierList.add(new SourceModification.ReplaceText(conflictingVarNode.getText(), R2, conflictingVarNode.getSourcePosition()));
boundVariablesStack.set(conflictingVarPos, R2);
// TODO: See if this can be improved to less hackish
// Now, on the stack there may be other occurrences of R, which will not allow R-R2 renaming to
// occur (since we check local bindings). These occurrences will be renamed as we move out,
// but for now we will rename them to R2 (this does nothing since we already have R2 on the stack)
List<Integer> stackSpotsRenamed = new ArrayList<Integer>();
for (int i = 0, n = boundVariablesStack.size(); i < n; i++) {
if ((boundVariablesStack.get(i)).equals(R)) {
stackSpotsRenamed.add(Integer.valueOf(i));
boundVariablesStack.set(i, R2);
}
}
// Change mode to FindRenamings (R-R2)
// (Note: this should not go through any Part 2s, since there are no conflicts)
RenamedIdentifierFinder rr2RenamingFinder = new RenamedIdentifierFinder(QualifiedName.make(getCurrentModuleName(), R), QualifiedName.make(getCurrentModuleName(), R2), renameCategory);
rr2RenamingFinder.setCurrentModuleName(getCurrentModuleName());
rr2RenamingFinder.findIdentifiersInExpr(identifierList, boundVariablesStack, boundExprNode);
getLogger().logMessages(rr2RenamingFinder.getLogger());
// Make the appropriate R-R2 renamings in the CALDoc for the let definition which is being
// checked here (pretending to be a lambda).
if (optionalCALDocNodeForLetDefn != null) {
CALDocArgNameRenamingFinder caldocRR2RenamingFinder = new CALDocArgNameRenamingFinder(getCurrentModuleName(), QualifiedName.make(getCurrentModuleName(), R), QualifiedName.make(getCurrentModuleName(), R2), renameCategory);
caldocRR2RenamingFinder.findIdentifiersInCALDocComment(identifierList, boundVariablesStack, optionalCALDocNodeForLetDefn);
getLogger().logMessages(caldocRR2RenamingFinder.getLogger());
}
// Put back Rs on the stack
for (int i = 0, n = stackSpotsRenamed.size(); i < n; i++) {
boundVariablesStack.set(i, R);
}
boundVariablesStack.popN(nVars);
return;
}
/**
* Finds identifiers in a case expression containing a field binding pattern.
* A form of the conflict resolution mechanism described above is implemented in this method,
* because local variables may be bound to field bindings
*
* @param identifierList
* @param boundVariablesStack
* @param fieldBindingVarAssignmentListNode
* @param boundExprNode
* @param basePatternVarNameNode if non-null, the parse tree node for the named variable forming the base pattern for the field binding.
*/
@Override
void findIdentifiersInFieldBindingCase(List<SourceModification> identifierList, ArrayStack<String> boundVariablesStack, ParseTreeNode fieldBindingVarAssignmentListNode, ParseTreeNode boundExprNode, ParseTreeNode basePatternVarNameNode) {
// : Renaming S to R, resolving any conflicts that R may have with local vars
ModuleName mS = renameOldName.getModuleName();
String S = renameOldName.getUnqualifiedName(), R = renameNewName.getUnqualifiedName();
// Check that S is not on the stack; if it is, then it is a local
// binding so we don't care about it and so we have nothing to do in this
// case.
if (mS.equals(getCurrentModuleName()) && boundVariablesStack.contains(S)) {
return;
}
ParseTreeNode conflictingVarNode = null;
int conflictingVarPos = -1;
boolean conflictingVarPunned = false;
// Fill pattern variables for punned fields
// Note that for an unpunned textual field name, we can still tell that this is a punned field
// since the fieldNameNode and patternVarNode have the same sourcePosition.
// We will use this fact later on if we need to un-pun the field to resolve a renaming conflict.
unpunPunnedFields(fieldBindingVarAssignmentListNode);
// Inspect variable bindings, and keep track if any may conflict with our renaming
int nVars;
if (basePatternVarNameNode != null) {
nVars = 1;
String basePatternVarName = basePatternVarNameNode.getText();
boundVariablesStack.push(basePatternVarName);
if (basePatternVarName.equals(R)) {
// This variable is a potential conflict if we find any S in the body,
// so keep it in case we need to rename it
conflictingVarNode = basePatternVarNameNode;
conflictingVarPos = boundVariablesStack.size() - 1;
}
} else {
nVars = 0;
}
for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) {
ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild();
ParseTreeNode patternVarNode = fieldNameNode.nextSibling();
switch (patternVarNode.getType())
{
case CALTreeParserTokenTypes.VAR_ID:
{
String patternVar = patternVarNode.getText();
++nVars;
boundVariablesStack.push(patternVar);
if (patternVar.equals(R)) {
// This variable is a potential conflict if we find any S in the body,
// so keep it in case we need to rename it
conflictingVarNode = patternVarNode;
conflictingVarPos = boundVariablesStack.size() - 1;
// Make a note of whether we manually unpunned this node. We check this by looking to see
// if the patternVar and fieldName share the same source position.
conflictingVarPunned = patternVarNode.getSourcePosition().equals(fieldNameNode.getSourcePosition());
}
break;
}
case CALTreeParserTokenTypes.UNDERSCORE:
break;
default:
{
patternVarNode.unexpectedParseTreeNode();
return;
}
}
}
// If S has just been defined as a case variable, will ignore any renamings
if (mS.equals(getCurrentModuleName()) && boundVariablesStack.contains(S)) {
boundVariablesStack.popN(nVars);
return;
}
// Part 1: Find replacements at lower levels
int oldIdentifierListSize = identifierList.size();
findIdentifiersInExpr(identifierList, boundVariablesStack, boundExprNode);
// Part 2: Resolve conflicts at this level
if (!(mS.equals(getCurrentModuleName()) || mS.equals(getModuleForImportedFunctionOrClassMethod(S))) || (conflictingVarNode == null) || (oldIdentifierListSize == identifierList.size())) {
// We do not try to resolve conflicts if either:
// 1. S cannot appear in unqualified form, conflicting with local variables
// 2. the list of identifiers has not changed, thus no occurrences of S
// have been found and renamed
// 3. R is not defined at our level so we have no conflicts to resolve here
boundVariablesStack.popN(nVars);
return;
}
// Will rename the local variable R by appending a suffix
int pad = 2;
String R2 = "";
// Will repeat the following section until we find a variable name which
// does not cause a conflict with others at this or inner levels
boolean stillLooking = true;
while (stillLooking) {
R2 = conflictingVarNode.getText() + pad++;
// Make sure we don't chose a name conflicting with something already defined
// in this or outer scope
while (boundVariablesStack.contains(R2)) {
R2 = conflictingVarNode.getText() + pad++;
}
// Now make sure this choice doesn't cause conflicts down the road (ie: renaming R to R2
// will not conflict R2 with local variables in inner scopes)
// Change to RenameConflict finding mode (R-R2)
RenameConflictFinder rr2ConflictFinder = new RenameConflictFinder(getCurrentModuleName(), QualifiedName.make(getCurrentModuleName(), R), QualifiedName.make(getCurrentModuleName(), R2), renameCategory);
ArrayStack<String> newBoundVariablesStack = ArrayStack.make();
List<SourceIdentifier> conflictingIdentifiers = new ArrayList<SourceIdentifier>();
rr2ConflictFinder.findIdentifiersInExpr(conflictingIdentifiers, newBoundVariablesStack, boundExprNode);
getLogger().logMessages(rr2ConflictFinder .getLogger());
// Continue looking for a new variable name until there are no conflicts
stillLooking = !conflictingIdentifiers.isEmpty();
}
// Now, R2 is a non-conflicting name, so replace all occurrences of R with R2
if (conflictingVarPunned) {
// If this was a punned field, we must now unpun it to resolve the conflict
identifierList.add(new SourceModification.ReplaceText(conflictingVarNode.getText(), conflictingVarNode.getText() + " = " + R2, conflictingVarNode.getSourcePosition()));
} else {
identifierList.add(new SourceModification.ReplaceText(conflictingVarNode.getText(), R2, conflictingVarNode.getSourcePosition()));
}
boundVariablesStack.set(conflictingVarPos, R2);
// TODO: See if this can be improved to less hackish
// Now, on the stack there may be other occurrences of R, which will not allow R-R2 renaming to
// occur (since we check local bindings). These occurrences will be renamed as we move out,
// but for now we will rename them to R2 (this does nothing since we already have R2 on the stack)
List<Integer> stackSpotsRenamed = new ArrayList<Integer>();
for (int i = 0, n = boundVariablesStack.size(); i < n; i++) {
if ((boundVariablesStack.get(i)).equals(R)) {
stackSpotsRenamed.add(Integer.valueOf(i));
boundVariablesStack.set(i, R2);
}
}
// Change mode to FindRenamings (R-R2)
// (Note: this should not go through any Part 2s, since there are no conflicts)
RenamedIdentifierFinder rr2RenamingFinder = new RenamedIdentifierFinder(QualifiedName.make(getCurrentModuleName(), R), QualifiedName.make(getCurrentModuleName(), R2), renameCategory);
rr2RenamingFinder.setCurrentModuleName(getCurrentModuleName());
rr2RenamingFinder.findIdentifiersInExpr(identifierList, boundVariablesStack, boundExprNode);
getLogger().logMessages(rr2RenamingFinder.getLogger());
// Put back Rs on the stack
for (int i = 0, n = stackSpotsRenamed.size(); i < n; i++) {
boundVariablesStack.set(i, R);
}
boundVariablesStack.popN(nVars);
}
/**
* Given an unqualified identifier, determine what module it is defined in.
* The method checks the qualification map (if it exists), then looks at "import using" clauses.
* If the identifier does not occur in either of these places, we conclude that it is from the current module.
* @param identifierName The unqualified identifier that we want to get the module for.
* @param identifierCategory The type of the identifier.
* @return The module that the entity represented by this identifier is defined in.
*/
private ModuleName getModuleForUnqualifiedIdentifier(String identifierName, SourceIdentifier.Category identifierCategory) {
// First, check the qualification map, if one exists..
if (qualificationMap != null) {
QualifiedName qualificationMapName = qualificationMap.getQualifiedName(identifierName, identifierCategory);
if (qualificationMapName != null) {
return qualificationMapName.getModuleName();
}
}
// Otherwise, check to see if the symbol has been imported with an "import using..." clause
ModuleName moduleName = getModuleForImportedIdentifier(identifierName, identifierCategory);
// If the symbol has not been imported and is not in the qualification map, then default to the current module.
return (moduleName == null) ? getCurrentModuleName() : moduleName;
}
/**
* Retrieves the module associated with the import using statement which imported the specified identifier.
* Null is returned if the identifier does not appear in any import using statement.
* @param identifierName the unqualified identifier that we want to get the module for.
* @param identifierCategory the type of the identifier.
* @return the module associated with the import using statement which imported the specified identifier, or null
* if the identifier does not appear in any import using statement.
*/
private ModuleName getModuleForImportedIdentifier(String identifierName, SourceIdentifier.Category identifierCategory) {
if (identifierCategory == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) {
return getModuleForImportedFunctionOrClassMethod(identifierName) ;
} else if (identifierCategory == SourceIdentifier.Category.DATA_CONSTRUCTOR) {
return getModuleForImportedDataConstructor(identifierName) ;
} else if (identifierCategory == SourceIdentifier.Category.TYPE_CLASS) {
return getModuleForImportedTypeClass(identifierName) ;
} else if (identifierCategory == SourceIdentifier.Category.TYPE_CONSTRUCTOR) {
return getModuleForImportedTypeConstructor(identifierName) ;
} else {
return null;
}
}
/**
* Adds a renaming (original to new name) if this node refers the SC or Class Method being renamed.
*
* Note: Local bindings are not checked because the conflict resolution algorithms ensures
* no conflicts occur (progressing outwards).
*/
@Override
void visitFunctionOrClassMethodNameNode(List<SourceModification> identifierList, ArrayStack<String> boundVariablesStack, ParseTreeNode qualifiedNode) {
qualifiedNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR);
ParseTreeNode moduleNameNode = qualifiedNode.firstChild();
visitModuleNameNode(identifierList, moduleNameNode);
ModuleName moduleName = getResolvedModuleName(moduleNameNode);
ParseTreeNode varNameNode = moduleNameNode.nextSibling();
String varName = varNameNode.getText();
if (
((moduleName == null) || (moduleName.equals(getCurrentModuleName()))) &&
boundVariablesStack.contains(varName)) {
// Variable is pattern bound (i.e. an argument variable, a lambda bound
// variable or a variable bound by a case alternative). Its module is the current module.
return;
}
if ( varName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) &&
( renameOldName.getModuleName().equals(moduleName) ||
((moduleName == null) && (renameOldName.getModuleName().equals(getModuleForUnqualifiedIdentifier(varName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD))))
)) {
identifierList.add(new SourceModification.ReplaceText(varName, renameNewName.getUnqualifiedName(), varNameNode.getSourcePosition()));
}
return;
}
/**
* For a CALDoc cross reference that is unresolved, we cannot tell what SourceIdentifier.Category it belongs to.
* For this reason, we go with a conservative approach where if the old or new name is equal to the cross reference
* then we flag it as a conflict (because we do not know how to resolve it properly).
*
* todo-jowong This is an area that can be improved upon: the proper handling of these cross references by
* subclasses of SourceIdentifierFinder as well as their clients.
*/
@Override
void visitCALDocCrossReferenceWithoutContextConsNameNode(List<SourceModification> identifierList, ParseTreeNode refNode) {
ParseTreeNode qualifiedConsNode = refNode.firstChild();
qualifiedConsNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS);
ParseTreeNode nameBeforeDotNode = qualifiedConsNode.firstChild();
nameBeforeDotNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
String rawNameBeforeDot = ModuleNameUtilities.getMaybeModuleNameStringFromParseTree(nameBeforeDotNode);
ParseTreeNode nameAfterDotNode = nameBeforeDotNode.nextSibling();
nameAfterDotNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String nameAfterDot = nameAfterDotNode.getText();
boolean foundConflict = false;
boolean isQualified = rawNameBeforeDot.length() > 0;
// Regardless of whether the reference has a dot in it or not, this reference could be
// a module reference.
// If such is the case, then in fact it would be a conflict if the reference matches either the old name, or
// if it matches the new name and the old name is visible.
if (renameCategory == SourceIdentifier.Category.MODULE_NAME) {
ModuleName oldModuleName = renameOldName.getModuleName();
ModuleName newModuleName = renameNewName.getModuleName();
boolean isOldModuleNameVisible = isModuleImported(oldModuleName) || getCurrentModuleName().equals(oldModuleName);
// NB: the entire name is the potential module name here.
// we make a shallow copy of the qualified cons node, and modify the type to be HIERARCHICAL_MODULE_NAME
ParseTreeNode moduleNameNode = new ParseTreeNode();
moduleNameNode.copyContentsFrom(qualifiedConsNode);
moduleNameNode.initialize(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, "HIERARCHICAL_MODULE_NAME");
ModuleName nameResolvedAsModuleName = getResolvedModuleName(moduleNameNode);
if (oldModuleName.equals(nameResolvedAsModuleName) ||
(newModuleName.equals(nameResolvedAsModuleName) && isOldModuleNameVisible)) {
foundConflict = true;
}
}
// Try to treat the reference as a class name, type constructor name and data constructor name.
// If renamings are identified in any of these cases, then the reference is in conflict because
// we are not sure whether any of the renamings is the "right" one due to the ambiguity of
// lacking the 'context' keyword.
final SourceIdentifier.Category[] categoriesToCheck = new SourceIdentifier.Category[] {
SourceIdentifier.Category.TYPE_CLASS,
SourceIdentifier.Category.TYPE_CONSTRUCTOR,
SourceIdentifier.Category.DATA_CONSTRUCTOR
};
ModuleName rawNameBeforeDotAsModuleName = ModuleName.make(rawNameBeforeDot);
for (final Category category : categoriesToCheck) {
if (renameCategory == category) {
ModuleName oldModuleName = renameOldName.getModuleName();
String oldUnqualifiedName = renameOldName.getUnqualifiedName();
ModuleName newModuleName = renameNewName.getModuleName();
String newUnqualifiedName = renameNewName.getUnqualifiedName();
// Sample setup:
// In module M, which contains a 'import X using {category} = Y;;' statement, and definitions V and W:
boolean conflictWithOldName;
boolean conflictWithNewName;
if (isQualified) {
ModuleName nameBeforeDotResolvedAsModuleName = getModuleNameResolver().resolve(rawNameBeforeDotAsModuleName).getResolvedModuleName();
// - {@link M.W@} conflicts with renaming M.W
// - {@link X.Y@} conflicts with renaming X.Y
// - {@link X.Z@} conflicts with renaming X.Z
conflictWithOldName = nameAfterDot.equals(oldUnqualifiedName) && nameBeforeDotResolvedAsModuleName.equals(oldModuleName);
// - {@link X.Y@} conflicts with renaming X.Z to X.Y
// - {@link X.Z@} conflicts with renaming X.Q to X.Z
conflictWithNewName = nameAfterDot.equals(newUnqualifiedName) && nameBeforeDotResolvedAsModuleName.equals(newModuleName);
} else {
ModuleName moduleForReference = getModuleForUnqualifiedIdentifier(nameAfterDot, category);
// - {@link W@} conflicts with renaming M.W
// - {@link Y@} conflicts with renaming X.Y
conflictWithOldName = nameAfterDot.equals(oldUnqualifiedName) && moduleForReference.equals(oldModuleName);
boolean isOldModuleNameVisible =
oldModuleName.equals(getCurrentModuleName()) ||
getModuleForImportedIdentifier(oldUnqualifiedName, category) != null;
// - {@link Y@} conflicts with renaming M.V to M.Y
// - {@link W@} conflicts with renaming X.Y to X.W
conflictWithNewName = nameAfterDot.equals(newUnqualifiedName) && isOldModuleNameVisible;
}
if (conflictWithOldName || conflictWithNewName) {
foundConflict = true;
}
}
}
if (foundConflict) {
if (!conflictingNameFound) {
getLogger().logMessage(new CompilerMessage(qualifiedConsNode, new MessageKind.Error.ConflictingNameInModule(getCurrentModuleName())));
conflictingNameFound = true;
}
}
}
/**
* Adds a renaming (original to new name) if this node refers to the module name being renamed.
*/
@Override
void visitModuleNameNode(List<SourceModification> identifierList, ParseTreeNode moduleNameNode) {
moduleNameNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
ModuleName moduleName = getResolvedModuleName(moduleNameNode);
if( renameOldName.getModuleName().equals(moduleName) && (renameCategory == SourceIdentifier.Category.MODULE_NAME)) {
identifierList.add(new SourceModification.ReplaceText(moduleName.toSourceText(), renameNewName.getModuleName().toSourceText(), moduleNameNode.getSourcePosition()));
}
}
/**
* Adds a renaming (original to new name) if this node refers to the type class being renamed.
*/
@Override
void visitClassNameNode(List<SourceModification> identifierList, ParseTreeNode qualifiedNode) {
qualifiedNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS);
ParseTreeNode moduleNameNode = qualifiedNode.firstChild();
visitModuleNameNode(identifierList, moduleNameNode);
ModuleName moduleName = getResolvedModuleName(moduleNameNode);
ParseTreeNode consNameNode = moduleNameNode.nextSibling();
String consName = consNameNode.getText();
if( consName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TYPE_CLASS) &&
( renameOldName.getModuleName().equals(moduleName) ||
((moduleName == null) && (renameOldName.getModuleName().equals(getModuleForUnqualifiedIdentifier(consName, SourceIdentifier.Category.TYPE_CLASS))))
)) {
identifierList.add(new SourceModification.ReplaceText(consName, renameNewName.getUnqualifiedName(), consNameNode.getSourcePosition()));
}
return;
}
/**
* Adds a renaming (original to new name) if this node refers to the type constructor being renamed.
*/
@Override
void visitTypeConsNameNode(List<SourceModification> identifierList, ParseTreeNode qualifiedNode) {
qualifiedNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS);
ParseTreeNode moduleNameNode = qualifiedNode.firstChild();
visitModuleNameNode(identifierList, moduleNameNode);
ModuleName moduleName = getResolvedModuleName(moduleNameNode);
ParseTreeNode consNameNode = moduleNameNode.nextSibling();
String consName = consNameNode.getText();
if( consName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TYPE_CONSTRUCTOR) &&
( renameOldName.getModuleName().equals(moduleName) ||
((moduleName == null) && (renameOldName.getModuleName().equals(getModuleForUnqualifiedIdentifier(consName, SourceIdentifier.Category.TYPE_CONSTRUCTOR))))
)) {
identifierList.add(new SourceModification.ReplaceText(consName, renameNewName.getUnqualifiedName(), consNameNode.getSourcePosition()));
}
return;
}
/**
* Adds a renaming (original to new name) if this node refers the data constructor being renamed.
*/
@Override
void visitDataConsNameNode(List<SourceModification> identifierList, ParseTreeNode qualifiedNode) {
qualifiedNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS);
ParseTreeNode moduleNameNode = qualifiedNode.firstChild();
visitModuleNameNode(identifierList, moduleNameNode);
ModuleName moduleName = getResolvedModuleName(moduleNameNode);
ParseTreeNode consNameNode = moduleNameNode.nextSibling();
String consName = consNameNode.getText();
if ( consName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.DATA_CONSTRUCTOR) &&
( renameOldName.getModuleName().equals(moduleName) ||
((moduleName == null) && (renameOldName.getModuleName().equals(getModuleForUnqualifiedIdentifier(consName, SourceIdentifier.Category.DATA_CONSTRUCTOR))))
)) {
identifierList.add(new SourceModification.ReplaceText(consName, renameNewName.getUnqualifiedName(), consNameNode.getSourcePosition()));
}
return;
}
/**
* Adds a renaming (original to new name) if this node refers to the class method being renamed.
*/
@Override
void visitInstanceMethodNameNode(List<SourceModification> identifierList, ParseTreeNode instanceMethodNameNode, ParseTreeNode qualifiedClassNameNode) {
qualifiedClassNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS);
ParseTreeNode moduleNameNode = qualifiedClassNameNode.firstChild();
visitModuleNameNode(identifierList, moduleNameNode);
ModuleName moduleName = getResolvedModuleName(moduleNameNode);
ParseTreeNode classNameNode = moduleNameNode.nextSibling();
String className = classNameNode.getText();
instanceMethodNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String instanceMethodName = instanceMethodNameNode.getText();
if ( instanceMethodName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) &&
( renameOldName.getModuleName().equals(moduleName) ||
((moduleName == null) && (renameOldName.getModuleName().equals(getModuleForUnqualifiedIdentifier(className, SourceIdentifier.Category.TYPE_CLASS))))
)) {
identifierList.add(new SourceModification.ReplaceText(instanceMethodName, renameNewName.getUnqualifiedName(), instanceMethodNameNode.getSourcePosition()));
}
}
/**
* Adds a renaming (original to new name) if this node defines the SC being renamed.
*/
@Override
void visitDataConsDefnNameNode(List<SourceModification> identifierList, ParseTreeNode dataConsNameNode) {
dataConsNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String dataConsName = dataConsNameNode.getText();
if (dataConsName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.DATA_CONSTRUCTOR) &&
(getCurrentModuleName().equals(renameOldName.getModuleName()))) {
identifierList.add(new SourceModification.ReplaceText(dataConsName, renameNewName.getUnqualifiedName(), dataConsNameNode.getSourcePosition()));
}
// Check for the following case (Renaming mod1.S to mod1.R):
// import mod1 using
// dataConstructor = S;
// ...
// data public SomeOtherDataType a = R a;
// This would cause a conflict between the new mod1.R and the locally defined R
if (dataConsName.equals(renameNewName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.DATA_CONSTRUCTOR)) {
ModuleName importedModule = getModuleForImportedDataConstructor(renameOldName.getUnqualifiedName());
if (renameOldName.getModuleName().equals(importedModule) && !conflictingNameFound) {
getLogger().logMessage(new CompilerMessage(dataConsNameNode, new MessageKind.Error.ConflictingNameInModule(getCurrentModuleName())));
conflictingNameFound = true;
}
}
}
/**
* Adds a renaming (original to new name) if this node defines the type class being renamed.
*/
@Override
void visitClassDefnNameNode(List<SourceModification> identifierList, ParseTreeNode classNameNode) {
classNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String className = classNameNode.getText();
if (className.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TYPE_CLASS) &&
(getCurrentModuleName().equals(renameOldName.getModuleName()))) {
identifierList.add(new SourceModification.ReplaceText(className, renameNewName.getUnqualifiedName(), classNameNode.getSourcePosition()));
}
// Check for the following case (Renaming mod1.S to mod1.R):
// import mod1 using
// typeClass = S;
// ...
// public class S a where
// ...
// This would cause a conflict between the new mod1.R and the locally defined R
if (className.equals(renameNewName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TYPE_CLASS)) {
ModuleName importedModule = getModuleForImportedTypeClass(renameOldName.getUnqualifiedName());
if (renameOldName.getModuleName().equals(importedModule) && !conflictingNameFound) {
getLogger().logMessage(new CompilerMessage(classNameNode, new MessageKind.Error.ConflictingNameInModule(getCurrentModuleName())));
conflictingNameFound = true;
}
}
}
/**
* Adds a renaming (original to new name) if this node defines the data constructor being renamed.
*/
@Override
void visitUnqualifiedDataConsNameNode(List<SourceModification> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {
unqualifiedNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String dataConsName = unqualifiedNameNode.getText();
ModuleName moduleName;
if (moduleNameNode != null) {
moduleNameNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
moduleName = getResolvedModuleName(moduleNameNode);
} else {
moduleName = getCurrentModuleName();
}
if (dataConsName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.DATA_CONSTRUCTOR) &&
(renameOldName.getModuleName().equals(moduleName))) {
identifierList.add(new SourceModification.ReplaceText(dataConsName, renameNewName.getUnqualifiedName(), unqualifiedNameNode.getSourcePosition()));
}
}
@Override
void visitUnqualifiedTypeConsNameNode(List<SourceModification> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {
unqualifiedNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String typeConsName = unqualifiedNameNode.getText();
ModuleName moduleName;
if (moduleNameNode != null) {
moduleNameNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
moduleName = getResolvedModuleName(moduleNameNode);
} else {
moduleName = getCurrentModuleName();
}
if (typeConsName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TYPE_CONSTRUCTOR) &&
(renameOldName.getModuleName().equals(moduleName))) {
identifierList.add(new SourceModification.ReplaceText(typeConsName, renameNewName.getUnqualifiedName(), unqualifiedNameNode.getSourcePosition()));
}
}
@Override
void visitUnqualifiedClassNameNode(List<SourceModification> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {
unqualifiedNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String className = unqualifiedNameNode.getText();
ModuleName moduleName;
if (moduleNameNode != null) {
moduleNameNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
moduleName = getResolvedModuleName(moduleNameNode);
} else {
moduleName = getCurrentModuleName();
}
if (className.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TYPE_CLASS) &&
(renameOldName.getModuleName().equals(moduleName))) {
identifierList.add(new SourceModification.ReplaceText(className, renameNewName.getUnqualifiedName(), unqualifiedNameNode.getSourcePosition()));
}
}
/**
* Adds a renaming (original to new name) if this node defines the type constructor being renamed.
*/
@Override
void visitTypeConsDefnNameNode(List<SourceModification> identifierList, ParseTreeNode typeConsNameNode) {
typeConsNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String typeConsName = typeConsNameNode.getText();
if (typeConsName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TYPE_CONSTRUCTOR) &&
(getCurrentModuleName().equals(renameOldName.getModuleName()))) {
identifierList.add(new SourceModification.ReplaceText(typeConsName, renameNewName.getUnqualifiedName(), typeConsNameNode.getSourcePosition()));
}
// Check for the following case (Renaming mod1.S to mod1.R):
// import mod1 using
// typeConstructor = S;
// ...
// data public R a = ...
// This would cause a conflict between the new mod1.R and the locally defined R
if (typeConsName.equals(renameNewName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TYPE_CONSTRUCTOR)) {
ModuleName importedModule = getModuleForImportedTypeConstructor(renameOldName.getUnqualifiedName());
if (renameOldName.getModuleName().equals(importedModule) && !conflictingNameFound) {
getLogger().logMessage(new CompilerMessage(typeConsNameNode, new MessageKind.Error.ConflictingNameInModule(getCurrentModuleName())));
conflictingNameFound = true;
}
}
}
/**
* Adds a renaming (original to new name) if this node defines the SC being renamed.
*/
@Override
void visitFunctionOrClassMethodDefinitionNameNode(List<SourceModification> identifierList, ParseTreeNode scNameNode) {
scNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String scName = scNameNode.getText();
if (scName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) &&
(getCurrentModuleName().equals(renameOldName.getModuleName()))) {
identifierList.add(new SourceModification.ReplaceText(scName, renameNewName.getUnqualifiedName(), scNameNode.getSourcePosition()));
}
// Check for the following case (Renaming mod1.S to mod1.R):
// import mod1 using
// function = S;
// ...
// R :: a -> a;
// This would cause a conflict between the new mod1.R and the locally defined R
if (scName.equals(renameNewName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD)) {
ModuleName importedModule = getModuleForImportedFunctionOrClassMethod(renameOldName.getUnqualifiedName());
if (renameOldName.getModuleName().equals(importedModule) && !conflictingNameFound) {
getLogger().logMessage(new CompilerMessage(scNameNode, new MessageKind.Error.ConflictingNameInModule(getCurrentModuleName())));
conflictingNameFound = true;
}
}
}
/**
* Adds a renaming (original to new name) if this node indicates an import of the SC or Class Method being renamed.
*/
@Override
void visitUnqualifiedFunctionOrClassMethodNameNode(List<SourceModification> identifierList, ParseTreeNode unqualifiedNameNode, ParseTreeNode moduleNameNode) {
unqualifiedNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String scName = unqualifiedNameNode.getText();
ModuleName moduleName;
if (moduleNameNode != null) {
moduleNameNode.verifyType(CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME, CALTreeParserTokenTypes.HIERARCHICAL_MODULE_NAME_EMPTY_QUALIFIER);
moduleName = getResolvedModuleName(moduleNameNode);
} else {
moduleName = getCurrentModuleName();
}
if (scName.equals(renameOldName.getUnqualifiedName()) && (renameCategory == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) &&
(renameOldName.getModuleName().equals(moduleName))) {
identifierList.add(new SourceModification.ReplaceText(scName, renameNewName.getUnqualifiedName(), unqualifiedNameNode.getSourcePosition()));
}
}
/**
* {@inheritDoc}
*/
@Override
void visitCALDocArgNameNode(List<SourceModification> identifierList, ArrayStack<String> boundVariablesStack, ParseTreeNode argNameNode) {}
/** The identifier list produced contains SourceModification.ReplaceText objects */
@Override
Comparator<SourceModification> getIdentifierListComparator() {
return SourceModification.compareByPosition;
}
/**
* Resolves the module name represented by the given parse tree, and but <em>does not</em> modify it in any way even if
* the resolved name is different from the original name.
*
* @param moduleNameNode the root of the parse tree representing a module name.
* @return the resolved name for the given module name. Can be null if the given parse tree represents the empty string "".
*/
private ModuleName getResolvedModuleName(ParseTreeNode moduleNameNode) {
ModuleName moduleName = ModuleNameUtilities.getModuleNameOrNullFromParseTree(moduleNameNode);
ModuleNameResolver.ResolutionResult resolution = getModuleNameResolver().resolve(moduleName);
return resolution.getResolvedModuleName();
}
@Override
void pushLocalVar(ArrayStack<String> boundVariablesStack, ParseTreeNode varNameNode, List<SourceModification> identifierList) {
boundVariablesStack.push(varNameNode.getText());
}
}