Package org.openquark.cal.compiler

Source Code of org.openquark.cal.compiler.TypeDeclarationInserter$RefactoringStatistics

/*
* 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;
    }
}
TOP

Related Classes of org.openquark.cal.compiler.TypeDeclarationInserter$RefactoringStatistics

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.