/*
* 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.
*/
/*
* TypeDeclarationInserter.java
* Creation date: (Feb 20, 2006)
* By: James Wright
*/
package org.openquark.cal.compiler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.openquark.cal.compiler.SourceModel.FunctionDefn;
import org.openquark.cal.compiler.SourceModel.FunctionTypeDeclaration;
import org.openquark.cal.compiler.SourceModel.LocalDefn;
import org.openquark.cal.compiler.SourceModel.SourceElement;
import org.openquark.cal.compiler.SourceModel.TypeSignature;
import org.openquark.cal.compiler.SourceModel.Expr.Let;
import org.openquark.cal.util.ArrayStack;
/**
* The insert-type-declarations refactoring adds explicit type declarations for top-level
* algebraic function declarations and local function declarations that lack them.
*
* @author James Wright
*/
final class TypeDeclarationInserter {
private TypeDeclarationInserter() {
}
/**
* Helper class for collecting information about a module from its SourceModel.
* We run the summarizer over a ModuleDefn to collect information on which local
* functions need type declarations, and at which SourcePositions.
*
* @author James Wright
*/
private static final class Summarizer extends BindingTrackingSourceModelTraverser<Void> {
/** Set (String) of toplevel algebraic functions that are annotated */
private final Set<String> annotatedAlgebraicFunctions = new HashSet<String>();
/** Map (String -> FunctionDefn.Algebraic) from toplevel function name to the corresponding SourceModel element */
private final Map<String, FunctionDefn.Algebraic> algebraicFunctions = new HashMap<String, FunctionDefn.Algebraic>();
/**
* LinkedHashMap (LocalFunctionIdentifier -> LocalDefn) from local function identifier to the corresponding SourceModel element
* for either a function definition or a pattern match declaration. May be a many-to-one mapping for pattern match declarations
* with more than one pattern-bound variable.
*
* This is ordered by the original source order of the definitions.
*/
private final LinkedHashMap<LocalFunctionIdentifier, LocalDefn> localDefns = new LinkedHashMap<LocalFunctionIdentifier, LocalDefn>();
/**
* Set (LocalFunctionIdentifier) of the names of local functions/pattern-bound variables whose type declaratiosn
* would not need a leading newline.
* The first local function defined in a let expression is such a definition.
*/
private final Set<LocalFunctionIdentifier> typeDeclsNotNeedingLeadingNewline = new HashSet<LocalFunctionIdentifier>();
/** Set (LocalFunctionIdentifier) of identifiers of local functions that are annotated */
private final Set<LocalFunctionIdentifier> annotatedLocalFunctions = new HashSet<LocalFunctionIdentifier>();
/**
* Handles the adding of bindings for local definitions (both local functions and local pattern match declarations).
* This is done by walking the local definitions and adding a binding for each local function / pattern-bound variable
* encountered.
*
* @author Joseph Wong
*/
private final class LocallyDefinedNamesCollector extends IdentifierResolver.LocalBindingsProcessor<LocalDefn, Void> {
/**
* Keeps track of whether the inserted type declaration will look good without a leading newline.
*/
private boolean typeDeclDoesNotNeedLeadingNewline;
/**
* The LocalFunctionIdentifierGenerator to use for generating identifers for function names/pattern-bound variables that are encountered.
*/
// @implementation the correctness of this code depends on the fact that the synthetic definition
// for a pattern match declaration comes in *last*, after all the pattern-bound variables have been desugared into
// function definitions (because of the nature of the LocalFunctionIdentifierGenerator where each generated identifier
// contains the value of an internal counter - a pair of identifiers match only if that counter value also match)
private final LocalFunctionIdentifierGenerator localFunctionIdentifierGenerator;
/**
* Constructs an instance of this class.
* @param typeDeclDoesNotNeedLeadingNewline whether the first type declaration inserted will look good without a leading newline.
*/
LocallyDefinedNamesCollector(final boolean typeDeclDoesNotNeedLeadingNewline) {
this.typeDeclDoesNotNeedLeadingNewline = typeDeclDoesNotNeedLeadingNewline;
this.localFunctionIdentifierGenerator = getLocalFunctionNameGenerator();
}
/**
* Checks whether the type declaration for the given local function needs a leading newline.
* @param localFunctionIdentifier the identfier for a local function.
*/
private void checkLeadingPosition(final LocalFunctionIdentifier localFunctionIdentifier) {
if (typeDeclDoesNotNeedLeadingNewline) {
typeDeclsNotNeedingLeadingNewline.add(localFunctionIdentifier);
}
// subsequent type decls (for multi-variable patterns) do not need leading newlines...
typeDeclDoesNotNeedLeadingNewline = true;
}
/**
* {@inheritDoc}
*/
@Override
void processLocalDefinitionBinding(final String name, final SourceModel.SourceElement localDefinition, final LocalDefn arg) {
final LocalFunctionIdentifier localFunctionIdentifier = localFunctionIdentifierGenerator.generateLocalFunctionIdentifier(getModuleName(), name);
final LocalDefn localDefn = arg;
localDefns.put(localFunctionIdentifier, localDefn);
checkLeadingPosition(localFunctionIdentifier);
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_LocalDefn_Function_Definition(final LocalDefn.Function.Definition function, final LocalDefn arg) {
return super.visit_LocalDefn_Function_Definition(function, function);
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_LocalDefn_PatternMatch_UnpackDataCons(final LocalDefn.PatternMatch.UnpackDataCons unpackDataCons, final LocalDefn arg) {
return super.visit_LocalDefn_PatternMatch_UnpackDataCons(unpackDataCons, unpackDataCons);
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_LocalDefn_PatternMatch_UnpackListCons(final LocalDefn.PatternMatch.UnpackListCons unpackListCons, final LocalDefn arg) {
return super.visit_LocalDefn_PatternMatch_UnpackListCons(unpackListCons, unpackListCons);
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_LocalDefn_PatternMatch_UnpackRecord(final LocalDefn.PatternMatch.UnpackRecord unpackRecord, final LocalDefn arg) {
return super.visit_LocalDefn_PatternMatch_UnpackRecord(unpackRecord, unpackRecord);
}
/**
* {@inheritDoc}
*/
@Override
public Void visit_LocalDefn_PatternMatch_UnpackTuple(final LocalDefn.PatternMatch.UnpackTuple unpackTuple, final LocalDefn arg) {
return super.visit_LocalDefn_PatternMatch_UnpackTuple(unpackTuple, unpackTuple);
}
}
/** @return List (FunctionDefn.Algebraic) of toplevel functions that don't have type declarations. */
private List<FunctionDefn.Algebraic> getUnannotatedFunctions() {
Set<String> unannotatedFunctionNames = new HashSet<String>(algebraicFunctions.keySet());
unannotatedFunctionNames.removeAll(annotatedAlgebraicFunctions);
List<FunctionDefn.Algebraic> unannotatedFunctions = new ArrayList<FunctionDefn.Algebraic>();
for (final String functionName : unannotatedFunctionNames) {
FunctionDefn.Algebraic unannotatedFunctionObj = algebraicFunctions.get(functionName);
unannotatedFunctions.add(unannotatedFunctionObj);
}
return unannotatedFunctions;
}
/**
* @return LinkedHashSet (LocalFunctionIdentifier) of LocalFunctionIdentifiers of local functions that don't have type declarations.
*/
private LinkedHashSet<LocalFunctionIdentifier> getUnannotatedLocalFunctions() {
LinkedHashSet<LocalFunctionIdentifier> unannotatedLocalFunctionIdentifiers = new LinkedHashSet<LocalFunctionIdentifier>(localDefns.keySet());
unannotatedLocalFunctionIdentifiers.removeAll(annotatedLocalFunctions);
return unannotatedLocalFunctionIdentifiers;
}
/**
* @return LocalDefn for the local function or pattern match declaration named by localFunctionIdentifier.
* @param localFunctionIdentifier
*/
private LocalDefn getLocalDefn(final LocalFunctionIdentifier localFunctionIdentifier) {
return localDefns.get(localFunctionIdentifier);
}
/**
* @param localFunctionIdentifier
* @return true if the local function named by localFunctionIdentifier does not need a leading newline
* before its type declaration. For example, the first definition in
* a let expression (including type declarations) is such a function.
*
* For example, in
*
* let
* foo :: Int;
* foo = 10;
* in ...
*
* foo is /not/ a leading local function (because it is preceded by its type declaration), whereas
* in
*
* let
* foo = 20;
* foo :: Int;
* in ...
*
* foo /is/ a leading local function, because its definition occurs first in the let expression.
*/
private boolean doesTypeDeclNeedLeadingNewline(LocalFunctionIdentifier localFunctionIdentifier) {
return typeDeclsNotNeedingLeadingNewline.contains(localFunctionIdentifier);
}
/** {@inheritDoc} */
@Override
public Void visit_FunctionDefn_Algebraic(FunctionDefn.Algebraic algebraic, Object arg) {
algebraicFunctions.put(algebraic.getName(), algebraic);
return super.visit_FunctionDefn_Algebraic(algebraic, arg);
}
/** {@inheritDoc} */
@Override
public Void visit_FunctionTypeDeclaraction(FunctionTypeDeclaration declaration, Object arg) {
annotatedAlgebraicFunctions.add(declaration.getFunctionName());
return super.visit_FunctionTypeDeclaraction(declaration, arg);
}
/** {@inheritDoc} */
@Override
public Void visit_Expr_Let(Let let, Object arg) {
// We record the name of the leading local function definition if and
// only if it is a function definition (since we don't add anything before
// function type declarations).
for (int i = 0, n = let.getNLocalDefinitions(); i < n; i++) {
final LocallyDefinedNamesCollector collector = new LocallyDefinedNamesCollector(i == 0);
let.getNthLocalDefinition(i).accept(collector, null);
}
return super.visit_Expr_Let(let, arg);
}
/** {@inheritDoc} */
@Override
public Void visit_LocalDefn_Function_Definition(LocalDefn.Function.Definition function, Object arg) {
Void ret = super.visit_LocalDefn_Function_Definition(function, arg);
LocalFunctionIdentifier identifier = getBoundLocalFunctionIdentifier(function.getName());
localDefns.put(identifier, function);
return ret;
}
@Override
public Void visit_LocalDefn_Function_TypeDeclaration(LocalDefn.Function.TypeDeclaration declaration, Object arg) {
LocalFunctionIdentifier identifier = getBoundLocalFunctionIdentifier(declaration.getName());
annotatedLocalFunctions.add(identifier);
return super.visit_LocalDefn_Function_TypeDeclaration(declaration, arg);
}
}
/**
* Class that contains statistics about a TypeDeclarationInserter refactoring
* of a CAL module.
*
* @author James Wright
*/
static final class RefactoringStatistics {
/**
* Number of type declarations that included at least one type class constraint
* that were added by the refactoring to top-level functions.
*/
private int topLevelTypeDeclarationsAddedWithClassConstraints;
/**
* Number of type declarations that included no type class constraints
* that were added by the refactoring to top-level functions.
*/
private int topLevelTypeDeclarationsAddedWithoutClassConstraints;
/**
* Number of type declarations that included at least one type class constraint
* that were added by the refactoring to local functions.
*/
private int localTypeDeclarationsAddedWithClassConstraints;
/**
* Number of type declarations that included no type class constraints
* that were added by the refactoring to local functions.
*/
private int localTypeDeclarationsAddedWithoutClassConstraints;
/**
* Number of local functions that did not have type declarations where
* type declarations were not added because their types had no syntactic representation
* (eg, types that include non-generic type variables).
*/
private int localTypeDeclarationsNotAdded;
/** Number of import declarations that were added by the refactoring. */
private int importsAdded;
/**
* Number of type declarations that are not added due to the fact that the module names appearing
* therein would require new import statements that may potential introduce module name resolution conflicts.
*/
private int typeDeclarationsNotAddedDueToPotentialImportConflict;
/**
* The set of module names that are not added as imports due to the fact that they may introduce module name
* resolution conflicts.
*/
private final SortedSet<ModuleName> importsNotAddedDueToPotentialConfict = new TreeSet<ModuleName>();
/**
* Increment the appropriate counter for top-level function type declarations added
* depending upon the value of withClassConstraint.
* @param withClassConstraint True if the type expr being added includes at least one type class constraint
*/
private void recordAddedToplevelTypeDeclaration(boolean withClassConstraint) {
if(withClassConstraint) {
topLevelTypeDeclarationsAddedWithClassConstraints++;
} else {
topLevelTypeDeclarationsAddedWithoutClassConstraints++;
}
}
/**
* Increment the appropriate counter for local function type declarations added
* depending upon the value of withClassConstraint.
* @param withClassConstraint True if the type expr being added includes at least one type class constraint
*/
private void recordAddedLocalTypeDeclaration(boolean withClassConstraint) {
if(withClassConstraint) {
localTypeDeclarationsAddedWithClassConstraints++;
} else {
localTypeDeclarationsAddedWithoutClassConstraints++;
}
}
/**
* Increment the counter for import declarations added
* @param nModules Number of modules added
*/
private void recordImportInsertions(int nModules) {
importsAdded += nModules;
}
/**
* Increment the counter for local functions that could not have a type declaration added.
*/
private void recordLocalTypeDeclarationNotAdded() {
localTypeDeclarationsNotAdded++;
}
/**
* Increment the counter for the number of type declarations that are not added due to the fact that the module names
* appearing therein would require new import statements that may potential introduce module name resolution conflicts.
* The module names are also recorded.
*
* @param importsNotAdded
* the set of module names that are not added as imports due to the fact that they may introduce
* module name resolution conflicts.
*/
private void recordTypeDeclarationNotAddedDueToPotentialImportConflict(Set<ModuleName> importsNotAdded) {
typeDeclarationsNotAddedDueToPotentialImportConflict++;
importsNotAddedDueToPotentialConfict.addAll(importsNotAdded);
}
/**
* @return Number of type declarations that included at least one type class constraint
* that were added by the refactoring to top-level functions.
*/
int getTopLevelTypeDeclarationsAddedWithClassConstraints() {
return topLevelTypeDeclarationsAddedWithClassConstraints;
}
/**
* @return Number of type declarations that included no type class constraints
* that were added by the refactoring to top-level functions.
*/
int getTopLevelTypeDeclarationsAddedWithoutClassConstraints() {
return topLevelTypeDeclarationsAddedWithoutClassConstraints;
}
/**
* @return Number of type declarations that included at least one type class constraint
* that were added by the refactoring to local functions.
*/
int getLocalTypeDeclarationsAddedWithClassConstraints() {
return localTypeDeclarationsAddedWithClassConstraints;
}
/**
* @return Number of type declarations that included no type class constraints
* that were added by the refactoring to local functions.
*/
int getLocalTypeDeclarationsAddedWithoutClassConstraints() {
return localTypeDeclarationsAddedWithoutClassConstraints;
}
/**
* @return Number of local functions that did not have type declarations where
* type declarations were not added because their types had no syntactic representation
* (eg, types that include non-generic type variables).
*/
int getLocalTypeDeclarationsNotAdded() {
return localTypeDeclarationsNotAdded;
}
/**
* @return Number of import declarations that were added by the refactoring.
*/
int getImportsAdded() {
return importsAdded;
}
/**
* @return Number of type declarations that are not added due to the fact that the module names appearing
* therein would require new import statements that may potential introduce module name resolution conflicts.
*/
int getTypeDeclarationsNotAddedDueToPotentialImportConflict() {
return typeDeclarationsNotAddedDueToPotentialImportConflict;
}
/**
* @return The set of module names that are not added as imports due to the fact that they may introduce module name
* resolution conflicts.
*/
SortedSet<ModuleName> getImportsNotAddedDueToPotentialConfict() {
return Collections.unmodifiableSortedSet(importsNotAddedDueToPotentialConfict);
}
}
/**
*
* @param sourceText
* @param messageLogger
* @param sourceRange The source range to perform the action over. This maybe null to apply to the whole file.
* @return A SourceModifier containing the necessary SourceModifications to perform the Insert Type Declarations refactoring
* on sourceText.
*/
static SourceModifier getSourceModifier(ModuleContainer moduleContainer, ModuleName moduleName, SourceRange sourceRange, String sourceText, CompilerMessageLogger messageLogger, RefactoringStatistics refactoringStatistics) {
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;
}
int nErrorsBefore = messageLogger.getNErrors();
SourceModel.ModuleDefn moduleDefn = SourceModelUtilities.TextParsing.parseModuleDefnIntoSourceModel(sourceText, messageLogger);
if(moduleDefn == null || messageLogger.getNErrors() > nErrorsBefore) {
// We can't proceed if the module is unparseable
return sourceModifier;
}
if(!moduleName.equals(SourceModel.Name.Module.toModuleName(moduleDefn.getModuleName()))) {
throw new IllegalArgumentException("moduleName must correspond to the module name in sourceText");
}
ModuleTypeInfo moduleTypeInfo = moduleContainer.getModuleTypeInfo(moduleName);
if(moduleTypeInfo == null) {
messageLogger.logMessage(new CompilerMessage(new MessageKind.Fatal.ModuleNotInWorkspace(moduleName)));
return sourceModifier;
}
Set<ModuleName> unimportedModules = new HashSet<ModuleName>();
Summarizer visitor = new Summarizer();
visitor.visit_ModuleDefn(moduleDefn, ArrayStack.make());
List<FunctionDefn.Algebraic> unannotatedFunctions = visitor.getUnannotatedFunctions();
ScopedEntityNamingPolicy namingPolicy = new ScopedEntityNamingPolicy.UnqualifiedIfUsingOrSameModule(moduleTypeInfo);
// Add SourceModifications for unannotated toplevel functions
for (final FunctionDefn.Algebraic function : unannotatedFunctions) {
if (sourceRange != null){
final SourceRange functionSourceRange = function.getSourceRange();
if (functionSourceRange != null){
if (!sourceRange.overlaps(functionSourceRange)){
continue;
}
}
}
TypeExpr typeExpr = computeTypeExpr(moduleTypeInfo, function, messageLogger);
if(typeExpr == null) {
continue;
}
Set<ModuleName> importsThatProduceConflicts = new HashSet<ModuleName>();
boolean noConflicts = updateWithUnimportedModules(unimportedModules, importsThatProduceConflicts, moduleTypeInfo, typeExpr);
if (noConflicts) {
TypeSignature typeSignature = typeExpr.toSourceModel(null, namingPolicy);
FunctionTypeDeclaration typeDecl = FunctionTypeDeclaration.make(function.getName(), typeSignature);
SourcePosition insertionPosition = function.getSourceRangeExcludingCaldoc().getStartSourcePosition();
String insertionText = makeIndentedSourceElementText(typeDecl, insertionPosition, sourceText, false);
sourceModifier.addSourceModification(new SourceModification.InsertText(insertionText, insertionPosition));
if(refactoringStatistics != null) {
refactoringStatistics.recordAddedToplevelTypeDeclaration(containsClassConstraints(typeExpr));
}
} else {
if(refactoringStatistics != null) {
refactoringStatistics.recordTypeDeclarationNotAddedDueToPotentialImportConflict(importsThatProduceConflicts);
}
}
}
// Add SourceModifications for unannotated local functions
LinkedHashSet<LocalFunctionIdentifier> unannotatedLocalFunctions = visitor.getUnannotatedLocalFunctions();
for (final LocalFunctionIdentifier identifier : unannotatedLocalFunctions) {
TypeExpr typeExpr = computeLocalTypeExpr(moduleTypeInfo, identifier);
if(typeExpr == null) {
if(refactoringStatistics != null) {
refactoringStatistics.recordLocalTypeDeclarationNotAdded();
}
continue;
}
LocalDefn localDefn = visitor.getLocalDefn(identifier);
if (sourceRange != null){
final SourceRange functionSourceRange = localDefn.getSourceRange();
if (functionSourceRange != null){
if (!sourceRange.overlaps(functionSourceRange)){
continue;
}
}
}
Set<ModuleName> importsThatProduceConflicts = new HashSet<ModuleName>();
boolean noConflicts = updateWithUnimportedModules(unimportedModules, importsThatProduceConflicts, moduleTypeInfo, typeExpr);
if (noConflicts) {
TypeSignature typeSignature = typeExpr.toSourceModel(null, namingPolicy);
LocalDefn.Function.TypeDeclaration typeDecl = LocalDefn.Function.TypeDeclaration.make(identifier.getLocalFunctionName(), typeSignature);
boolean noLeadingNewline = visitor.doesTypeDeclNeedLeadingNewline(identifier);
final SourcePosition insertionPosition;
if (localDefn instanceof LocalDefn.Function.Definition) {
insertionPosition = ((LocalDefn.Function.Definition)localDefn).getSourceRangeExcludingCaldoc().getStartSourcePosition();
} else {
// in this case the localDefn should be a LocalDefn.PatternMatch
insertionPosition = localDefn.getSourceRange().getStartSourcePosition();
}
String insertionText = makeIndentedSourceElementText(typeDecl, insertionPosition, sourceText, noLeadingNewline);
sourceModifier.addSourceModification(new SourceModification.InsertText(insertionText, insertionPosition));
if(refactoringStatistics != null) {
refactoringStatistics.recordAddedLocalTypeDeclaration(containsClassConstraints(typeExpr));
}
} else {
if(refactoringStatistics != null) {
refactoringStatistics.recordTypeDeclarationNotAddedDueToPotentialImportConflict(importsThatProduceConflicts);
}
}
}
// Add a SourceModification for importing any unimported modules whose types we want to
// reference.
SourceModification importInsertion = computeImportInsertion(unimportedModules, moduleDefn, sourceText);
if(importInsertion != null) {
sourceModifier.addSourceModification(importInsertion);
if(refactoringStatistics != null) {
refactoringStatistics.recordImportInsertions(unimportedModules.size());
}
}
return sourceModifier;
}
/**
*
* @param sourceElement
* @param sourcePosition
* @param sourceText
* @param noLeadingNewline
* @return representation of sourceElement followed by a line indented to the column of sourcePosition
*/
private static String makeIndentedSourceElementText(SourceElement sourceElement, SourcePosition sourcePosition, String sourceText, boolean noLeadingNewline) {
int column = sourcePosition.getColumn();
StringBuilder buffer = new StringBuilder();
if(!noLeadingNewline && !isPriorLineBlank(sourcePosition, sourceText)) {
buffer.append('\n');
appendNBlanks(buffer, column - 1);
}
sourceElement.toSourceText(buffer);
buffer.append('\n');
appendNBlanks(buffer, column - 1);
return buffer.toString();
}
/**
* Add to the unimportedModules set the names of modules that are not currently imported, but which are the
* home modules of elements of typeExpr. eg, if typeExpr is "Prelude.Int -> Color.JColor" but we don't
* currently import Color, then "Color" will be added to unimportedModules.
* <p>
* If a module that needs to be added as an import will cause new ambiguities in resolving partially qualified module names,
* the module is not added, and false is returned.
*
* @param unimportedModules Set (ModuleName) of module names to add to
* @param importsThatProduceConflicts Set (ModuleName) of module names that will (potentially) produce conflicts - to add to
* @param moduleTypeInfo ModuleTypeInfo of the module to perform the check in the context of. ie, when we
* say above "not currently imported", we mean "not currently imported into the module represented by
* moduleTypeInfo".
* @param typeExpr TypeExpr to check
* @return false if one or more modules that need to be imported cannot be safely imported due to potential ambiguities; true otherwise.
*/
private static boolean updateWithUnimportedModules(Set<ModuleName> unimportedModules, Set<ModuleName> importsThatProduceConflicts, ModuleTypeInfo moduleTypeInfo, TypeExpr typeExpr) {
ArrayStack<TypeExpr> typeExprStack = ArrayStack.make();
typeExprStack.add(typeExpr);
boolean hasConflictingImports = false;
ModuleName moduleName = moduleTypeInfo.getModuleName();
while(typeExprStack.size() > 0) {
TypeExpr currentExpr = typeExprStack.pop();
TypeConsApp rootTypeConsApp = currentExpr.rootTypeConsApp();
if(rootTypeConsApp != null) {
ModuleName typeConsModuleName = rootTypeConsApp.getName().getModuleName();
if(moduleName.equals(typeConsModuleName) == false &&
moduleTypeInfo.getImportedModule(typeConsModuleName) == null) {
if (moduleTypeInfo.getModuleNameResolver().willAdditionalModuleImportProduceConflict(typeConsModuleName)) {
importsThatProduceConflicts.add(typeConsModuleName);
hasConflictingImports = true;
} else {
unimportedModules.add(typeConsModuleName);
}
}
}
addChildTypeExprsToStack(currentExpr, typeExprStack);
}
return !hasConflictingImports;
}
/**
* Check that every type and class referenced in a type expression is visible in the module
* represented by ModuleTypeInfo.
* @param moduleTypeInfo ModuleTypeInfo of module to check in the context of
* @param typeExpr TypeExpr to check
* @return true if every type and class referenced in typeExpr is visible in moduleTypeInfo's module,
* or false if the typeExpr contains some non-public (and non-protected for friend modules)
* type or class.
*/
private static boolean isEntireTypeExprVisibleInModule(ModuleTypeInfo moduleTypeInfo, TypeExpr typeExpr) {
ArrayStack<TypeExpr> typeExprStack = ArrayStack.make();
typeExprStack.add(typeExpr);
// Check type constructors
while(typeExprStack.size() > 0) {
TypeExpr currentExpr = typeExprStack.pop();
TypeConsApp rootTypeConsApp = currentExpr.rootTypeConsApp();
if(rootTypeConsApp != null) {
if(moduleTypeInfo.getVisibleTypeConstructor(rootTypeConsApp.getName()) == null) {
return false;
}
}
addChildTypeExprsToStack(currentExpr, typeExprStack);
}
// Check variable constraints
for (final PolymorphicVar polymorphicVar : typeExpr.getConstrainedPolymorphicVars()) {
if(polymorphicVar instanceof TypeVar) {
TypeVar typeVar = (TypeVar)polymorphicVar;
for (final TypeClass typeClass : typeVar.getTypeClassConstraintSet()) {
if(moduleTypeInfo.getVisibleTypeClass(typeClass.getName()) == null) {
return false;
}
}
} else if(polymorphicVar instanceof RecordVar) {
RecordVar recordVar = (RecordVar)polymorphicVar;
for (final TypeClass typeClass : recordVar.getTypeClassConstraintSet()) {
if(moduleTypeInfo.getVisibleTypeClass(typeClass.getName()) == null) {
return false;
}
}
}
}
return true;
}
/**
* Compute a single SourceModification to insert import declarations for all the modules of unimportedModules.
* @param unimportedModules Set (ModuleName) of module names to include in the inserted imports
* @param moduleDefn ModuleDefn of the module to insert the imports into
* @param sourceText String source of the module to insert the imports into
* @return SourceModification that adds imports for all of the modules in unimportedModules.
*/
private static SourceModification computeImportInsertion(Set<ModuleName> unimportedModules, SourceModel.ModuleDefn moduleDefn, String sourceText) {
// If there are no modules that need to be imported, then we don't have anything to calculate
if(unimportedModules.size() == 0) {
return null;
}
// Compute the block of text that we want to insert
StringBuilder insertionTextBuffer = new StringBuilder();
for (final ModuleName moduleName : unimportedModules) {
SourceModel.Import importDecl = SourceModel.Import.make(moduleName);
importDecl.toSourceText(insertionTextBuffer);
insertionTextBuffer.append('\n');
}
// Compute the insertion point
SourcePosition lastImportEndPosition = new SourcePosition(1, 1);
for(int i = 0; i < moduleDefn.getNImportedModules(); i++) {
SourceModel.Import importDecl = moduleDefn.getNthImportedModule(i);
SourceRange sourceRange = importDecl.getSourceRange();
if(sourceRange == null) {
continue;
}
SourcePosition endPosition = sourceRange.getEndSourcePosition();
if(SourcePosition.compareByPosition.compare(endPosition, lastImportEndPosition) > 0) {
lastImportEndPosition = endPosition;
}
}
// We want to insert at the beginning of the line following the final existing import.
int lastImportEndLine = lastImportEndPosition.getLine();
SourcePosition insertionPosition = new SourcePosition(lastImportEndLine + 1, 1);
String insertionText = insertionTextBuffer.toString();
// There is always the "pathological case with the whole module on one line" to consider, so
// verify that insertionPosition actually exists. If it doesn't we'll fail over to starting
// from the very end of the final import, starting with a newline. This is less ideal, because
// end-of-line comments associated with the final import will get moved, which is why we don't
// do it for non-pathological cases.
int newlineCount = 0;
int newlineIndex = sourceText.indexOf('\n');
while(newlineIndex != -1 && newlineCount < lastImportEndLine) {
newlineCount++;
newlineIndex = sourceText.indexOf('\n', newlineIndex + 1);
}
if(newlineCount < lastImportEndLine) {
insertionPosition = lastImportEndPosition;
insertionText = '\n' + insertionText;
}
return new SourceModification.InsertText(insertionText, insertionPosition);
}
/**
* Adds the components of typeExpr to stack.
* @param typeExpr
* @param stack
*/
private static void addChildTypeExprsToStack(TypeExpr typeExpr, ArrayStack<TypeExpr> stack) {
if (typeExpr instanceof RecordType) {
RecordType recordType = (RecordType)typeExpr;
for (final TypeExpr fieldTypeExpr : recordType.getHasFieldsMap().values()) {
stack.add(fieldTypeExpr);
}
} else if (typeExpr instanceof TypeConsApp) {
TypeConsApp typeConsApp = (TypeConsApp)typeExpr;
for(int i = 0; i < typeConsApp.getNArgs(); i++) {
stack.add(typeConsApp.getArg(i));
}
} else if (typeExpr instanceof TypeApp) {
TypeApp typeApp = (TypeApp)typeExpr;
stack.add(typeApp.getOperatorType());
stack.add(typeApp.getOperandType());
} else if (typeExpr instanceof TypeVar) {
return;
} else {
throw new IllegalStateException("unhandled TypeExpr subtype");
}
}
/**
* @param sourcePosition position of the line just after the line to check
* @param sourceText String that sourcePosition points into
* @return true if the previous line is "effectively" blank (ie, it might contain a comment)
*/
private static boolean isPriorLineBlank(SourcePosition sourcePosition, String sourceText) {
int line = sourcePosition.getLine();
// If there's no previous line, it can't be blank
if(line <= 1) {
return false;
}
SourcePosition currentLineStartPosition = new SourcePosition(line, 1);
SourcePosition priorLineStartPosition = new SourcePosition(line - 1, 1);
int priorLineStartIndex = priorLineStartPosition.getPosition(sourceText);
int currentLineStartIndex = currentLineStartPosition.getPosition(sourceText, priorLineStartPosition, priorLineStartIndex);
String priorLine = sourceText.substring(priorLineStartIndex, currentLineStartIndex);
// The heuristic: If the line starts with a single-line comment or ends with a multi-line close-comment
// delimiter, then it is effectively blank.
return priorLine.matches("(\\s*//.*\\s*)|(.*\\*/\\s*)|(\\s*)");
}
/**
* Append numBlanks blanks to buffer
* @param buffer StringBuilder
* @param numBlanks int
*/
private static void appendNBlanks(StringBuilder buffer, int numBlanks) {
for(int i = 0; i < numBlanks; i++) {
buffer.append(' ');
}
}
/**
* Returns the type of the local function specified by localFunctionIdentifier if it is possible to insert
* an explicit declaration of that type, or null if it isn't (due to eg. non-generic variable issues).
* @param moduleTypeInfo
* @param localFunctionIdentifier
* @return TypeExpr for use in an explicit type declaration if possible, or null otherwise
*/
private static TypeExpr computeLocalTypeExpr(ModuleTypeInfo moduleTypeInfo, LocalFunctionIdentifier localFunctionIdentifier) {
Function toplevelFunction = moduleTypeInfo.getFunction(localFunctionIdentifier.getToplevelFunctionName().getUnqualifiedName());
if (toplevelFunction == null){
return null;
}
Function localFunction = toplevelFunction.getLocalFunction(localFunctionIdentifier);
if(localFunction == null) {
return null;
}
// If the local function's type contains uninstantiated nongeneric variables,
// then it's of no use to us.
if(localFunction.typeContainsUninstantiatedNonGenerics()) {
return null;
}
TypeExpr typeExpr = localFunction.getTypeExpr();
if(typeExpr == null) {
return null;
}
// A type declaration won't compile unless every part of the type expression is visible in the current module.
if(!isEntireTypeExprVisibleInModule(moduleTypeInfo, typeExpr)) {
return null;
}
return typeExpr;
}
/**
* Returns the type of the toplevel function specified by function if it is possible to insert
* an explicit declaration of that type, or null if it isn't (due to eg. type scoping issues).
* @param moduleTypeInfo ModuleTypeInfo for the function's module
* @param function SourceModel of the function
* @param messageLogger Logger to log failures to
* @return TypeExpr for the specified function (may be null)
*/
private static TypeExpr computeTypeExpr(ModuleTypeInfo moduleTypeInfo, FunctionDefn.Algebraic function, CompilerMessageLogger messageLogger) {
String functionName = function.getName();
Function functionEntity = moduleTypeInfo.getFunction(functionName);
if(functionEntity == null) {
return null;
}
TypeExpr functionTypeExpr = functionEntity.getTypeExpr();
// A type declaration won't compile unless every part of the type expression is visible in the current module.
if(!isEntireTypeExprVisibleInModule(moduleTypeInfo, functionTypeExpr)) {
return null;
}
return functionTypeExpr;
}
/**
* @param typeExpr A type expression to check
* @return true if typeExpr contains any type or record variables with class
* constraints, or false otherwise.
*/
private static boolean containsClassConstraints(TypeExpr typeExpr) {
Set<PolymorphicVar> polymorphicVars = typeExpr.getConstrainedPolymorphicVars();
for (final PolymorphicVar var : polymorphicVars) {
if(var instanceof RecordVar) {
RecordVar recordVar = (RecordVar)var;
if(recordVar.noClassConstraints() == false) {
return true;
}
} else if(var instanceof TypeVar) {
TypeVar typeVar = (TypeVar)var;
if(typeVar.noClassConstraints() == false) {
return true;
}
}
}
return false;
}
}