/* * 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. */ /* * SourceDependencyFinder.java * Creation date: (Mar 24, 2005) * By: Jawright */ package org.openquark.cal.compiler; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.openquark.cal.compiler.SourceModel.Expr; import org.openquark.cal.compiler.SourceModel.FieldPattern; import org.openquark.cal.compiler.SourceModel.FunctionDefn; import org.openquark.cal.compiler.SourceModel.FunctionTypeDeclaration; import org.openquark.cal.compiler.SourceModel.Import; import org.openquark.cal.compiler.SourceModel.InstanceDefn; import org.openquark.cal.compiler.SourceModel.LocalDefn; import org.openquark.cal.compiler.SourceModel.ModuleDefn; import org.openquark.cal.compiler.SourceModel.Name; import org.openquark.cal.compiler.SourceModel.Parameter; import org.openquark.cal.compiler.SourceModel.Pattern; import org.openquark.cal.compiler.SourceModel.SourceElement; import org.openquark.cal.compiler.SourceModel.TopLevelSourceElement; import org.openquark.cal.compiler.SourceModel.TypeClassDefn; import org.openquark.cal.compiler.SourceModel.TypeExprDefn; import org.openquark.cal.compiler.SourceModel.Expr.Application; import org.openquark.cal.compiler.SourceModel.Expr.BinaryOp; import org.openquark.cal.compiler.SourceModel.Expr.DataCons; import org.openquark.cal.compiler.SourceModel.Expr.Lambda; import org.openquark.cal.compiler.SourceModel.Expr.Let; import org.openquark.cal.compiler.SourceModel.Expr.Parenthesized; import org.openquark.cal.compiler.SourceModel.Expr.SelectDataConsField; import org.openquark.cal.compiler.SourceModel.Expr.UnaryOp; import org.openquark.cal.compiler.SourceModel.Expr.Unit; import org.openquark.cal.compiler.SourceModel.Expr.Var; import org.openquark.cal.compiler.SourceModel.Expr.BinaryOp.Apply; import org.openquark.cal.compiler.SourceModel.Expr.BinaryOp.BackquotedOperator; import org.openquark.cal.compiler.SourceModel.Expr.BinaryOp.Compose; import org.openquark.cal.compiler.SourceModel.Expr.Case.Alt.UnpackDataCons; import org.openquark.cal.compiler.SourceModel.Expr.Case.Alt.UnpackListCons; import org.openquark.cal.compiler.SourceModel.Expr.Case.Alt.UnpackListNil; import org.openquark.cal.compiler.SourceModel.Expr.Case.Alt.UnpackUnit; import org.openquark.cal.compiler.SourceModel.Expr.UnaryOp.Negate; import org.openquark.cal.compiler.SourceModel.FunctionDefn.Algebraic; import org.openquark.cal.compiler.SourceModel.FunctionDefn.Foreign; import org.openquark.cal.compiler.SourceModel.FunctionDefn.Primitive; import org.openquark.cal.compiler.SourceModel.Import.UsingItem; import org.openquark.cal.compiler.SourceModel.InstanceDefn.InstanceMethod; import org.openquark.cal.compiler.SourceModel.InstanceDefn.InstanceTypeCons.TypeCons; import org.openquark.cal.compiler.SourceModel.LocalDefn.Function.Definition; import org.openquark.cal.compiler.SourceModel.TypeClassDefn.ClassMethodDefn; import org.openquark.cal.compiler.SourceModel.TypeConstructorDefn.AlgebraicType; import org.openquark.cal.compiler.SourceModel.TypeConstructorDefn.ForeignType; import org.openquark.cal.compiler.SourceModel.TypeConstructorDefn.AlgebraicType.DataConsDefn; import org.openquark.cal.filter.AcceptAllQualifiedNamesFilter; import org.openquark.cal.filter.QualifiedNameFilter; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.util.ArrayStack; import org.openquark.util.Pair; /** * Provides methods for finding various metrics associated with CAL modules. * * Currently implemented metrics: * * - Reference frequency: Each gem is given an associated reference frequency count, which * represents the number of times that the gem is referred to in the * body of other functions. The raw data collected are the number of * times each function refers to its dependees (referencees?). These * raw data data are aggregated by the ModuleSourceMetrics and * WorkspaceSourceMetricsManager classes into the workspace-level * reference frequency counts that are provided to clients. * * - Compositional frequency: Each pair of gems (A,B) is given an associated frequency count which * represents the number of times the output of B is passed directly as * an argument of A. * * - Lint warnings: The source is scanned for potential optimization issues and a list of * warning objects is returned to the client. * * - Reference and application locations: The source is scanned for references to or applications of * a specified QualifiedName, and a list of SourcePositions is * returned. * * Creation date: (Mar 24, 2005) * @author Jawright */ abstract class SourceMetricFinder { /** This class should never be instantiated */ private SourceMetricFinder() { } /** * Provides functionality that is common to most of the SourceModelTraversers in * the SourceMetricFinder class. * * Creation date: (Jun 27, 2005) * @author Jawright */ private static abstract class MetricVisitor extends BindingTrackingSourceModelTraverser<Void> { /** The name of the module being processed */ private final ModuleName currentModule; /** Information about the module being processed */ private final ModuleTypeInfo moduleTypeInfo; private MetricVisitor(ModuleTypeInfo moduleTypeInfo) { if (moduleTypeInfo == null) { throw new NullPointerException("MetricVisitor needs a non-null moduleTypeInfo to operate upon"); } this.moduleTypeInfo = moduleTypeInfo; this.currentModule = moduleTypeInfo.getModuleName(); } /** * Resolves the given module name in the context of the module being processed based on module name resolution rules * and returns the name of the module the given name resolves to. If the original name * is not unambiguously resolvable, then the original name is returned. * * @param moduleName the module name to be resolved. Cannot be null. * @return the name of the module the original name resolves to. If the original name * is not unambiguously resolvable, then the original name is returned. */ private ModuleName resolveModuleName(Name.Module moduleName) { ModuleNameResolver.ResolutionResult resolution = moduleTypeInfo.getModuleNameResolver().resolve(SourceModel.Name.Module.toModuleName(moduleName)); return resolution.getResolvedModuleName(); } /** * Check whether a variable reference counts as a reference to a dependee * @param varName The variable reference to check * @return true if varName refers to a dependee, false otherwise */ private boolean isDependeeReference(Name.Function varName) { // References to the current function don't count (ie, a function // cannot be a dependee of itself). All unqualified references to // the current function name are disqualified, since they either refer // to the current function or to a lexically-scoped variable, neither of // which counts as a dependee. String currentFunctionName = getCurrentFunction(); if(currentFunctionName != null && currentFunctionName.equals(varName.getUnqualifiedName()) && (varName.getModuleName() == null || resolveModuleName(varName.getModuleName()).equals(currentModule))) { return false; } // All other explicitly-qualified references always count as dependees // (because they are guaranteed to be toplevel functional agents) if(varName.getModuleName() != null) { return true; } // Unqualified references that match lexcially-scoped variables are not dependees if(isBound(varName.getUnqualifiedName())) { return false; } // Unqualified references that do not match lexically-scoped // variables count as dependees. return true; } /** * Looks up the named functional agent in the relevant ModuleTypeInfo object and returns it. * @param agentName The name (qualified or not) of the functional agent to look up. * @return The FunctionalAgent for the functional agent specified by entityName */ private FunctionalAgent getFunctionalAgent(Name.Qualifiable agentName) { if(!(agentName instanceof Name.DataCons || agentName instanceof Name.Function)) { return null; } // Explicitly qualified, non-imported agent if (agentName.getModuleName() != null && resolveModuleName(agentName.getModuleName()).equals(currentModule)) { return moduleTypeInfo.getFunctionalAgent(agentName.getUnqualifiedName()); } // Explicitly qualified, imported agent else if (agentName.getModuleName() != null) { ModuleTypeInfo externalModuleInfo = moduleTypeInfo.getDependeeModuleTypeInfo(resolveModuleName(agentName.getModuleName())); if(externalModuleInfo == null) { // Some sort of compilation problem return null; } return externalModuleInfo.getFunctionalAgent(agentName.getUnqualifiedName()); } // Unqualified, non-imported agent else if (agentName.getModuleName() == null && moduleTypeInfo.getFunctionalAgent(agentName.getUnqualifiedName()) != null) { return moduleTypeInfo.getFunctionalAgent(agentName.getUnqualifiedName()); } // Unqualified, imported function else { ModuleName usingModuleName = moduleTypeInfo.getModuleOfUsingFunctionOrClassMethod(agentName.getUnqualifiedName()); if (usingModuleName == null) { usingModuleName = moduleTypeInfo.getModuleOfUsingDataConstructor(agentName.getUnqualifiedName()); } ModuleTypeInfo externalModuleInfo = moduleTypeInfo.getDependeeModuleTypeInfo(usingModuleName); if(externalModuleInfo == null) { // Some sort of compilation problem return null; } return externalModuleInfo.getFunctionalAgent(agentName.getUnqualifiedName()); } } /** If the provided expression is a Var or DataCons expression, returns the name of the referenced * function or datacons. * @param expr A Var or DataCons expression * @return unqualified name of the function or datacons referred to by the expression */ private Name.Qualifiable getFunctionalAgentName(Expr expr) { if(expr instanceof Var) { return ((Var)expr).getVarName(); } else if(expr instanceof DataCons) { return ((DataCons)expr).getDataConsName(); } else { return null; } } } /** * This is an implementation of SourceModelTraverser that gathers the raw data for the reference-frequency metric. * Raw data are collected in a single traversal and stored into the associated EnvEntities. * * Creation date: (Mar 24, 2005) * @author Jawright */ private static final class ReferenceFrequencyFinder extends MetricVisitor { /** * Map from (dependee, dependent) pair to number of times dependent references dependee */ private final Map<Pair<QualifiedName, QualifiedName>, Integer> dependeeMap = new HashMap<Pair<QualifiedName, QualifiedName>, Integer>(); /** * Set of QualifiedNames from imported modules that occur in this module. */ private final Set<QualifiedName> importedNameOccurrences = new HashSet<QualifiedName>(); /** Filter for deciding whether a function is to be visited */ private final QualifiedNameFilter functionFilter; /** When true, functions that are skipped (because they match the excludeFunctionRegexp) * will have their names dumped to the system console. When false, such functions will * be skipped silently. */ private final boolean traceSkippedFunctions; /** * Construct a ReferenceFrequencyFinder * @param moduleTypeInfo ModuleTypeInfo of module to be scanned * @param functionFilter Filter for deciding which functions will be processed. Cannot be null. * @param traceSkippedFunctions When true, each skipped function will have its name dumped to the console. * When false, skipped functions will be skipped silently */ private ReferenceFrequencyFinder(ModuleTypeInfo moduleTypeInfo, QualifiedNameFilter functionFilter, boolean traceSkippedFunctions) { super(moduleTypeInfo); if (functionFilter == null) { throw new NullPointerException(); } this.functionFilter = functionFilter; this.traceSkippedFunctions = traceSkippedFunctions; } /** {@inheritDoc} */ // Don't visit functions matching the exclusion regexp @Override public Void visit_FunctionDefn_Algebraic(Algebraic algebraic, Object arg) { if(!functionFilter.acceptQualifiedName(QualifiedName.make(super.currentModule, algebraic.getName()))) { if(traceSkippedFunctions) { System.out.println("Skipping test function " + super.currentModule + "." + algebraic.getName()); } return null; } return super.visit_FunctionDefn_Algebraic(algebraic, arg); } /** {@inheritDoc} */ // A local pattern match declaration with a data cons pattern counts as a reference to the data constructor. @Override public Void visit_LocalDefn_PatternMatch_UnpackDataCons(LocalDefn.PatternMatch.UnpackDataCons unpackDataCons, Object arg) { Name.DataCons dataName = unpackDataCons.getDataConsName(); recordDependeeReference(dataName); return super.visit_LocalDefn_PatternMatch_UnpackDataCons(unpackDataCons, arg); } /** {@inheritDoc} */ // A local pattern match declaration with a Cons (:) pattern counts as a reference to Prelude.Cons. @Override public Void visit_LocalDefn_PatternMatch_UnpackListCons(LocalDefn.PatternMatch.UnpackListCons unpackListCons, Object arg) { recordDependeeReference(Name.DataCons.make(CAL_Prelude.DataConstructors.Cons)); return super.visit_LocalDefn_PatternMatch_UnpackListCons(unpackListCons, arg); } /** {@inheritDoc} */ // Using a Cons (:) pattern in a case alternative counts as a reference to Prelude.Cons. @Override public Void visit_Expr_Case_Alt_UnpackListCons(UnpackListCons cons, Object arg) { recordDependeeReference(Name.DataCons.make(CAL_Prelude.DataConstructors.Cons)); return super.visit_Expr_Case_Alt_UnpackListCons(cons, arg); } /** {@inheritDoc} */ // Using a data constructor in a case alternative pattern counts as a reference to the data constructor. @Override public Void visit_Expr_Case_Alt_UnpackDataCons(UnpackDataCons cons, Object arg) { for (int i = 0, nDataConsNames = cons.getNDataConsNames(); i < nDataConsNames; i++) { Name.DataCons dataConsName = cons.getNthDataConsName(i); recordDependeeReference(dataConsName); } return super.visit_Expr_Case_Alt_UnpackDataCons(cons, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_Case_Alt_UnpackListNil(UnpackListNil nil, Object arg) { recordDependeeReference(Name.DataCons.make(CAL_Prelude.DataConstructors.Nil)); return super.visit_Expr_Case_Alt_UnpackListNil(nil, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_Case_Alt_UnpackUnit(UnpackUnit unit, Object arg) { recordDependeeReference(Name.DataCons.make(CAL_Prelude.DataConstructors.Unit)); return super.visit_Expr_Case_Alt_UnpackUnit(unit, arg); } /** {@inheritDoc} */ // Field selection from a dataCons-valued expression counts as a reference to the data constructor. @Override public Void visit_Expr_SelectDataConsField(SelectDataConsField field, Object arg) { Name.DataCons dataName = field.getDataConsName(); recordDependeeReference(dataName); return super.visit_Expr_SelectDataConsField(field, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_DataCons(DataCons cons, Object arg) { recordDependeeReference(cons.getDataConsName()); return super.visit_Expr_DataCons(cons, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_Var(Var var, Object arg) { if(super.isDependeeReference(var.getVarName())) { recordDependeeReference(var.getVarName()); } return super.visit_Expr_Var(var, arg); } // Binary operations are translated to textual form and then // recorded as dependees /** {@inheritDoc} */ @Override protected Void visit_Expr_BinaryOp_Helper(BinaryOp binop, Object arg) { QualifiedName textualName = OperatorInfo.getTextualName(binop.getOpText()); // Some operators represent function/method applications, and some (:) represent data constructor applications if (textualName.lowercaseFirstChar()) { recordDependeeReference(SourceModel.Name.Function.make(textualName)); } else { recordDependeeReference(SourceModel.Name.DataCons.make(textualName)); } return super.visit_Expr_BinaryOp_Helper(binop, arg); } /** {@inheritDoc} */ // Unary negation is also translated to textual form and then recorded // as a dependee @Override public Void visit_Expr_UnaryOp_Negate(Negate negate, Object arg) { recordDependeeReference(SourceModel.Name.Function.make(CAL_Prelude.Functions.negate)); return super.visit_Expr_UnaryOp_Negate(negate, arg); } /** {@inheritDoc} */ // The Unit datacons () is translated to textual form and recorded as a dependee @Override public Void visit_Expr_Unit(Unit unit, Object arg) { recordDependeeReference(SourceModel.Name.DataCons.make(CAL_Prelude.DataConstructors.Unit)); return super.visit_Expr_Unit(unit, arg); } /** {@inheritDoc} */ // [] is a reference to Prelude.Nil @Override public Void visit_Expr_List(SourceModel.Expr.List list, Object arg) { if(list.getNElements() == 0) { recordDependeeReference(SourceModel.Name.DataCons.make(CAL_Prelude.DataConstructors.Nil)); } return super.visit_Expr_List(list, arg); } /** {@inheritDoc} */ @Override public Void visit_Import_UsingItem_Function(UsingItem.Function usingItemFunction, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("ReferenceFrequencyFinder.visitImportUsingItemFunction expects to be passed a module name as its arg"); } ModuleName importedModuleName = (ModuleName)arg; String[] usingNames = usingItemFunction.getUsingNames(); for (final String usingName : usingNames) { recordImportedNameOccurrence(QualifiedName.make(importedModuleName, usingName)); } return super.visit_Import_UsingItem_Function(usingItemFunction, arg); } /** {@inheritDoc} */ @Override public Void visit_Import_UsingItem_DataConstructor(UsingItem.DataConstructor usingItemDataConstructor, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("ReferenceFrequencyFinder.visitImportUsingItemDataConstructor expects to be passed a module name as its arg"); } ModuleName importedModuleName = (ModuleName)arg; String[] usingNames = usingItemDataConstructor.getUsingNames(); for (final String usingName : usingNames) { recordImportedNameOccurrence(QualifiedName.make(importedModuleName, usingName)); } return super.visit_Import_UsingItem_DataConstructor(usingItemDataConstructor, arg); } /** {@inheritDoc} */ @Override public Void visit_Import_UsingItem_TypeConstructor(UsingItem.TypeConstructor usingItemTypeConstructor, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("ReferenceFrequencyFinder.visitImportUsingItemTypeConstructor expects to be passed a module name as its arg"); } ModuleName importedModuleName = (ModuleName)arg; String[] usingNames = usingItemTypeConstructor.getUsingNames(); for (final String usingName : usingNames) { recordImportedNameOccurrence(QualifiedName.make(importedModuleName, usingName)); } return super.visit_Import_UsingItem_TypeConstructor(usingItemTypeConstructor, arg); } /** {@inheritDoc} */ @Override public Void visit_Import_UsingItem_TypeClass(UsingItem.TypeClass usingItemTypeClass, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("ReferenceFrequencyFinder.visitImportUsingItemTypeClass expects to be passed a module name as its arg"); } ModuleName importedModuleName = (ModuleName)arg; String[] usingNames = usingItemTypeClass.getUsingNames(); for (final String usingName : usingNames) { recordImportedNameOccurrence(QualifiedName.make(importedModuleName, usingName)); } return super.visit_Import_UsingItem_TypeClass(usingItemTypeClass, arg); } /** {@inheritDoc} */ @Override public Void visit_Constraint_TypeClass(SourceModel.Constraint.TypeClass typeClass, Object arg) { recordImportedNameOccurrence(typeClass.getTypeClassName()); return super.visit_Constraint_TypeClass(typeClass, arg); } /** {@inheritDoc} */ @Override public Void visit_TypeExprDefn_Function(TypeExprDefn.Function function, Object arg) { recordImportedNameOccurrence(CAL_Prelude.TypeConstructors.Function); return super.visit_TypeExprDefn_Function(function, arg); } /** {@inheritDoc} */ @Override public Void visit_TypeExprDefn_List(TypeExprDefn.List list, Object arg) { recordImportedNameOccurrence(CAL_Prelude.TypeConstructors.List); return super.visit_TypeExprDefn_List(list, arg); } /** {@inheritDoc} */ @Override public Void visit_TypeExprDefn_TypeCons(TypeExprDefn.TypeCons cons, Object arg) { recordImportedNameOccurrence(cons.getTypeConsName()); return super.visit_TypeExprDefn_TypeCons(cons, arg); } /** {@inheritDoc} */ @Override public Void visit_TypeExprDefn_Unit(TypeExprDefn.Unit unit, Object arg) { recordImportedNameOccurrence(CAL_Prelude.TypeConstructors.Unit); return super.visit_TypeExprDefn_Unit(unit, arg); } /** * Add an entry to the internal tracking set noting that the gem specified by entityName * is referred to by the current function. * @param entityName Name of the gem referred to by the current function */ private void recordDependeeReference(Name.Qualifiable entityName) { FunctionalAgent dependeeEntity = super.getFunctionalAgent(entityName); if(dependeeEntity != null) { Function currentEntity = super.moduleTypeInfo.getFunction(getCurrentFunction()); Pair<QualifiedName, QualifiedName> key = new Pair<QualifiedName, QualifiedName>(dependeeEntity.getName(), currentEntity.getName()); if(dependeeMap.get(key) != null) { Integer oldFrequency = dependeeMap.get(key); dependeeMap.put(key, Integer.valueOf(oldFrequency.intValue() + 1)); } else { dependeeMap.put(key, Integer.valueOf(1)); } } recordImportedNameOccurrence(entityName); } /** * Add an entry to the importedNameOccurrences Set, but not to the dependeeMap. * Does nothing if referenceName is from the current module * @param occurrenceName QualifiedName to add to tracking */ private void recordImportedNameOccurrence(QualifiedName occurrenceName) { if(occurrenceName == null) { return; } if(occurrenceName.getModuleName().equals(super.currentModule)) { return; } importedNameOccurrences.add(occurrenceName); } /** * Add an entry to the importedNameOccurrences Set, but not to the dependeeMap. * Does nothing if referenceName is from the current module * @param occurrenceName Name to add to tracking */ private void recordImportedNameOccurrence(Name.Qualifiable occurrenceName) { // Short-circuit return won't catch all the cases, but it'll allow us to avoid // doing a "using" lookup in some cases. The QualifiedName-accepting version that // we delegate to will catch any same-module cases that fall through this check. if(occurrenceName.getModuleName() != null && super.resolveModuleName(occurrenceName.getModuleName()).equals(super.currentModule)) { return; } QualifiedName qualifiedName = getQualifiedName(occurrenceName, super.moduleTypeInfo.getModuleNameResolver()); recordImportedNameOccurrence(qualifiedName); } /** * @return A Map from (dependee, dependent) to number of times dependent references dependee */ Map<Pair<QualifiedName, QualifiedName>, Integer> getDependeeMap() { return Collections.unmodifiableMap(dependeeMap); } /** * @return The Set of names imported from other modules that occur in this module. */ Set<QualifiedName> getImportedNameOccurrences() { return Collections.unmodifiableSet(importedNameOccurrences); } } /** * A SourceModelTraverser that scans a single module for compositional frequencies. * Compositional frequency measures how often the return value from one gem is passed * directly as an argument to another gem. * * For example, in the following module: * * module Foo; * import Prelude; * * bar = 50; * baz arg = 20 + arg; * * quux = Prelude.add bar (baz 54); * * quuux val = Prelude.add (baz 10) (baz val); * * The compositional frequency of (Prelude.add, Foo.bar) is 1 (since the output of bar is * passed directly to add once in quux). * The compositional frequency of (Prelude.add, Foo.baz) is 3 (since the output of baz is * passed directly to add once in quux and twice in quuux). * * Use this class by instantiating it bound to a module and then passing in a SourceModel * representing the same module to visitModuleDefn. After the module has been walked, calling * getCompositionalFrequencyMap will return a Map from gem pair to compositional frequency of the * pair. * * The module to walk and all the modules that it imports is assumed to have already been * successfully compiled. * * This class has no external side effects. It is mutable (its state is different after it has * walked a module). * * Creation date: (Jun 27, 2005) * @author Jawright */ private static final class CompositionalFrequencyFinder extends MetricVisitor { /** * Map from (consumer, producer) Pair to compositional frequency of the pair. */ private final Map<Pair<QualifiedName, QualifiedName>, Integer> compositionalFrequencyMap = new HashMap<Pair<QualifiedName, QualifiedName>, Integer>(); /** Filter for deciding whether a function is to be visited */ private final QualifiedNameFilter functionFilter; /** When true, functions that are skipped (because they match the excludeFunctionRegexp) * will have their names dumped to the system console. When false, such functions will * be skipped silently. */ private final boolean traceSkippedFunctions; private static final QualifiedName COMPOSE = CAL_Prelude.Functions.compose; private static final QualifiedName APPLY = CAL_Prelude.Functions.apply; /** * Construct a CompositionalFrequencyFinder for a specific module. * @param moduleTypeInfo ModuleTypeInfo for the module to scan for compositional relationships * @param functionFilter Filter for deciding which functions will be processed. Cannot be null. * @param traceSkippedFunctions When true, each skipped function will have its name dumped to the console. * When false, skipped functions will be skipped silently */ private CompositionalFrequencyFinder(ModuleTypeInfo moduleTypeInfo, QualifiedNameFilter functionFilter, boolean traceSkippedFunctions) { super(moduleTypeInfo); if (functionFilter == null) { throw new NullPointerException(); } this.functionFilter = functionFilter; this.traceSkippedFunctions = traceSkippedFunctions; } /** * @return Map from (consumer, producer) to the compositional frequency of the pair. */ Map<Pair<QualifiedName, QualifiedName>, Integer> getCompositionalFrequencyMap() { return Collections.unmodifiableMap(compositionalFrequencyMap); } /** {@inheritDoc} */ // Don't visit functions matching the exclusion regexp @Override public Void visit_FunctionDefn_Algebraic(Algebraic algebraic, Object arg) { if(!functionFilter.acceptQualifiedName(QualifiedName.make(super.currentModule, algebraic.getName()))) { if(traceSkippedFunctions) { System.out.println("Skipping test function " + super.currentModule + "." + algebraic.getName()); } return null; } return super.visit_FunctionDefn_Algebraic(algebraic, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_Application(Application application, Object arg) { Expr consumerExpr = application.getNthExpression(0); Name.Qualifiable consumerName = getTopLevelConsumerName(consumerExpr); FunctionalAgent consumerEntity = null; if(consumerName != null && consumerExpr instanceof Var) { consumerEntity = super.getFunctionalAgent(consumerName); } // Process the standard case where an expression with a statically-identifiable consumer name // accepts some arguments if(consumerName != null) { for(int i = 1; i < application.getNExpressions(); i++) { processArgumentExpression(consumerName, i, application.getNthExpression(i)); } } // Special cases for apply and compose calls if(consumerEntity != null) { // `apply arg1 arg2` is effectively `arg1 arg2` if(consumerEntity.getName().equals(APPLY)) { Expr effectiveConsumer = application.getNthExpression(1); Name.Qualifiable effectiveConsumerName = getTopLevelConsumerName(effectiveConsumer); if(effectiveConsumerName != null) { processArgumentExpression(effectiveConsumerName, 1, application.getNthExpression(2)); } } // `compose arg1 arg2 arg3 ... argn` is effectively `arg1 (arg2 arg3 ... argn)` if(consumerEntity.getName().equals(COMPOSE)) { Expr effectiveConsumer1 = application.getNthExpression(1); Name.Qualifiable effectiveConsumer1Name = getTopLevelConsumerName(effectiveConsumer1); Expr effectiveConsumer2 = application.getNthExpression(2); Name.Qualifiable effectiveConsumer2Name = getTopLevelConsumerName(effectiveConsumer2); if(effectiveConsumer1Name != null) { processArgumentExpression(effectiveConsumer1Name, 1, effectiveConsumer2); } if(effectiveConsumer2Name != null) { for(int i = 3; i < application.getNExpressions(); i++) { processArgumentExpression(effectiveConsumer2Name, i, application.getNthExpression(i)); } } } } return super.visit_Expr_Application(application, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_BinaryOp_BackquotedOperator_Var(BackquotedOperator.Var backquotedOperator, Object arg) { Expr consumerExpr = backquotedOperator.getOperatorVarExpr(); Name.Qualifiable consumerName = getTopLevelConsumerName(consumerExpr); processArgumentExpression(consumerName, 1, backquotedOperator.getLeftExpr()); processArgumentExpression(consumerName, 2, backquotedOperator.getRightExpr()); return super.visit_Expr_BinaryOp_BackquotedOperator_Var(backquotedOperator, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_BinaryOp_BackquotedOperator_DataCons(BackquotedOperator.DataCons backquotedOperator, Object arg) { Expr consumerExpr = backquotedOperator.getOperatorDataConsExpr(); Name.Qualifiable consumerName = getTopLevelConsumerName(consumerExpr); processArgumentExpression(consumerName, 1, backquotedOperator.getLeftExpr()); processArgumentExpression(consumerName, 2, backquotedOperator.getRightExpr()); return super.visit_Expr_BinaryOp_BackquotedOperator_DataCons(backquotedOperator, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_BinaryOp_Apply(Apply apply, Object arg) { Name.Qualifiable consumerName = getTopLevelConsumerName(apply.getLeftExpr()); // `leftArg $ rightArg` is effectively `leftArg rightArg` if(consumerName != null) { processArgumentExpression(consumerName, 1, apply.getRightExpr()); } return super.visit_Expr_BinaryOp_Apply(apply, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_BinaryOp_Compose(Compose compose, Object arg) { Name.Qualifiable consumerName = getTopLevelConsumerName(compose.getLeftExpr()); if(consumerName != null) { processArgumentExpression(consumerName, 1, compose.getRightExpr()); } return super.visit_Expr_BinaryOp_Compose(compose, arg); } /** * Returns the name of the consumer represented by an expression if it is possible to * statically determine this. So, for DataCons and Var expressions, it just returns * the name (if it isn't the name of a bound local variable). These represent the base * cases. * * For compose expressions, it is sometimes also possible to statically determine * the name of a consumer that will pop out of the expression. * * @param arg An expression which may represent a consumer * @return Name of the consumer represented by the expression if statically determinable, * or null otherwise. */ private Name.Qualifiable getTopLevelConsumerName(Expr arg) { Expr potentialConsumer; //we ignore paren expressions and just consider their contents if (arg instanceof Parenthesized) { potentialConsumer = ((Parenthesized)arg).getExpression(); } else { potentialConsumer = arg; } // Standard `foo x` case if(potentialConsumer instanceof Var) { Name.Function consumerName = ((Var)potentialConsumer).getVarName(); if(super.isDependeeReference(consumerName)) { return consumerName; } else { return null; } // Standard `Just x` case } else if(potentialConsumer instanceof DataCons) { return ((DataCons)potentialConsumer).getDataConsName(); // Special case for expressions of the form `(compose foo bar) baz` } else if(potentialConsumer instanceof Application) { Application app = (Application)potentialConsumer; Name.Qualifiable consumerName = getTopLevelConsumerName(app.getNthExpression(0)); if(consumerName != null) { FunctionalAgent consumer = super.getFunctionalAgent(consumerName); if(consumer.getName().equals(COMPOSE) && app.getNExpressions() == 3) { return getTopLevelConsumerName(app.getNthExpression(2)); } } // Special case for expressions of the form `(foo # bar) baz` } else if(potentialConsumer instanceof BinaryOp.Compose) { BinaryOp.Compose composeOp = (BinaryOp.Compose)potentialConsumer; return getTopLevelConsumerName(composeOp.getRightExpr()); } return null; } /** * Record that the functional agent specified by consumerName consumes the expression * argumentExpression as its argumentNumberth parameter. If either consumerName or * argumentExpression refers to a local function definition, then no action is taken. * @param consumerName * @param argumentNumber * @param argument */ private void processArgumentExpression(Name.Qualifiable consumerName, int argumentNumber, Expr argument) { FunctionalAgent producer = null; FunctionalAgent consumer = super.getFunctionalAgent(consumerName); Expr argumentExpression; //we ignore paren expressions and just consider their contents if (argument instanceof Parenthesized) { argumentExpression = ((Parenthesized)argument).getExpression(); } else { argumentExpression = argument; } if(argumentExpression instanceof Var) { Var var = (Var)argumentExpression; if (super.isDependeeReference(var.getVarName())) { producer = super.getFunctionalAgent(var.getVarName()); } } else if (argumentExpression instanceof DataCons) { DataCons cons = (DataCons)argumentExpression; producer = super.getFunctionalAgent(cons.getDataConsName()); } else if (argumentExpression instanceof Application) { Application app = (Application)argumentExpression; processArgumentExpression(consumerName, argumentNumber, app.getNthExpression(0)); return; } else if (argumentExpression instanceof BinaryOp.Apply) { BinaryOp.Apply app = (BinaryOp.Apply)argumentExpression; processArgumentExpression(consumerName, argumentNumber, app.getLeftExpr()); return; } else if (argumentExpression instanceof BinaryOp.Compose) { BinaryOp.Compose composeOp = (BinaryOp.Compose)argumentExpression; processArgumentExpression(consumerName, argumentNumber, composeOp.getLeftExpr()); return; } if(producer != null && consumer != null) { recordComposition(consumer.getName(), producer.getName()); } } private void recordComposition(QualifiedName consumer, QualifiedName producer) { Pair<QualifiedName, QualifiedName> key = new Pair<QualifiedName, QualifiedName>(consumer, producer); Integer frequency = compositionalFrequencyMap.get(key); if(frequency != null) { compositionalFrequencyMap.put(key, Integer.valueOf(frequency.intValue() + 1)); } else { compositionalFrequencyMap.put(key, Integer.valueOf(1)); } } } /** * Represents a warning about a piece of code flagged by the lint process. * This is an immutable class. * * Creation date: (Jul 8, 2005) * @author Jawright */ static final class LintWarning { /** Typesafe enum representing the type of the warning */ static final class WarningType { /** Name of this warning type */ private final String name; /** Private constructor for a warning type */ private WarningType(String name) { this.name = name; } /** {@inheritDoc} */ @Override public String toString() { return name; } static final WarningType REDUNDANT_LAMBDA = new WarningType("Possibly redundant lambda"); static final WarningType UNPLINGED_PRIMITIVE_ARG = new WarningType("Unplinged primitive lexical argument"); static final WarningType UNUSED_PRIVATE_FUNCTION = new WarningType("Unreferenced private function"); static final WarningType MISMATCHED_ALIAS_PLINGS = new WarningType("Alias function's arguments do not have same strictness as aliased function/data constructor"); static final WarningType UNREFERENCED_LET_VARIABLE = new WarningType("Unreferenced let variable"); } /** Construct a new LintWarning * * @param warningType Type of problem that we are warning about * @param flaggedElement Object representing the SourceElement that caused the warning. Normally this * this will just be the SourceElement, but occasionally we may want to custom-print * the flagged element (eg, for brevity), so a String may also be passed in. * @param sourceRange SourceRange of the element that caused the warning * @param moduleName Name of the module that the source element causing the warning occurs in * @param functionName Name of the top-level function that the source element that caused the warning occurs in. */ private LintWarning(WarningType warningType, Object flaggedElement, SourceRange sourceRange, ModuleName moduleName, String functionName) { if(warningType == null) { throw new IllegalArgumentException("LintWarning: warningType must not be null"); } if(flaggedElement == null) { throw new IllegalArgumentException("LintWarning: flaggedElement must not be null"); } if(functionName == null) { throw new IllegalArgumentException("LintWarning: functionName must not be null"); } if(moduleName == null) { throw new IllegalArgumentException("LintWarning: moduleName must not be null"); } // (no check for sourcePosition because it is allowed to be null) this.warningType = warningType; this.flaggedElement = flaggedElement; this.functionName = QualifiedName.make(moduleName, functionName); this.sourceRange = sourceRange; } /** The type of warning */ private final WarningType warningType; /** A printable representation of the flagged expression. This is usually just the * SourceElement that caused the warning, but it will occasionally be a String representing some * subset of the element (e.g., the lefthand side of an algebraic function definition). */ private final Object flaggedElement; /** The position range of the flagged expression */ private final SourceRange sourceRange; /** The name of the top-level function in which the error occurs */ private final QualifiedName functionName; /** * @return a string representation of the source element that caused the warning */ String getFlaggedElement() { return flaggedElement.toString(); } /** * @return the top-level function in which the flagged expression occurs */ QualifiedName getFunctionName() { return functionName; } /** * @return the start position of the flagged expression, if available, or null if not available */ SourcePosition getSourcePosition() { if(sourceRange != null) { return sourceRange.getStartSourcePosition(); } else { return null; } } /** * @return the location of the flagged expression, if available, or null if not available */ SourceRange getSourceRange() { return sourceRange; } /** * @return the type of the warning */ WarningType getWarningType() { return warningType; } /** {@inheritDoc} */ @Override public String toString() { if(sourceRange != null) { return warningType + " in " + functionName + " " + sourceRange + ": " + flaggedElement; } else { return warningType + " in " + functionName + ": " + flaggedElement; } } } /** * A SourceModelTraverser that scans a single module for various style problems that * are statically detectable. Currently scans for the following problems: * * - Redundant lambdas * - Unreferenced private functions * - Lexical arguments of primitive types that are unplinged * - Alias functions whose arguments are plinged incompatibly from the aliased function * - Let variables that are never referenced. * * Warnings are accumulated as a list of LintWarnings, which can be retrieved using the * getWarningList method. * * The module to walk and all the modules that it imports is assumed to have already been * successfully compiled. * * The let-variable check relies on the assumption that the component SourceElements of the * SourceModel that we walk are not shared (ie, it assumes that the graph is a tree). * This assumption can be enforced by copying the incoming tree using the SourceModelCopier; * we don't perform that (expensive) step currently because the SourceModelBuilder does * generate trees, and those are the only models that we're currently checking. * * * Creation date: (Jul 4, 2005) * @author Jawright */ private static class LintWalker extends MetricVisitor { /** List of warnings found so far */ private final List<LintWarning> warningList = new ArrayList<LintWarning>(); /** Filter for deciding which functions will be processed. */ private final QualifiedNameFilter functionFilter; /** Set of functions in the module with at least one reference to them within the module. */ private final Set<QualifiedName> referencedFunctions = new HashSet<QualifiedName>(); /** Map from unqualified function or datacons name to array of parameter strictness */ private final Map<String, boolean[]> parameterStrictnessMap = new HashMap<String, boolean[]>(); /** Set of SourceElements of bound names whose names have been referenced */ private final Set<SourceModel.SourceElement> referencedBoundNames = new HashSet<SourceModel.SourceElement>(); /** When true, dump the names of skipped functions to the console */ private final boolean traceSkippedFunctions; /** When true, return warnings about unplinged function arguments with primitive types. */ private final boolean includeUnplingedPrimitiveArgs; /** When true, return warnings about potentially redundant lambdas */ private final boolean includeRedundantLambdas; /** When true, return warnings about unreferenced private functions */ private final boolean includeUnusedPrivates; /** When true, return warnings about alias functions whose argument strictness does not exactly match that of the wrapped function. */ private final boolean includeMismatchedAliasPlings; /** When true, return warnings about unused let variables */ private final boolean includeUnreferencedLetVariables; /** * Construct a LintWalker * @param moduleTypeInfo moduleTypeInfo for the module that will be walked * @param functionFilter Filter for deciding which functions will be processed. Cannot be null. * @param traceSkippedFunctions If true, all functions that are skipped will have their names * dumped to the console. * @param includeUnplingedPrimitiveArgs * @param includeRedundantLambdas * @param includeUnusedPrivates * @param includeMismatchedAliasPlings * @param includeUnreferencedLetVariables */ private LintWalker(ModuleTypeInfo moduleTypeInfo, QualifiedNameFilter functionFilter, boolean traceSkippedFunctions, boolean includeUnplingedPrimitiveArgs, boolean includeRedundantLambdas, boolean includeUnusedPrivates, boolean includeMismatchedAliasPlings, boolean includeUnreferencedLetVariables) { super(moduleTypeInfo); if (functionFilter == null) { throw new NullPointerException(); } this.functionFilter = functionFilter; this.traceSkippedFunctions = traceSkippedFunctions; this.includeUnplingedPrimitiveArgs = includeUnplingedPrimitiveArgs; this.includeRedundantLambdas = includeRedundantLambdas; this.includeUnusedPrivates = includeUnusedPrivates; this.includeMismatchedAliasPlings = includeMismatchedAliasPlings; this.includeUnreferencedLetVariables = includeUnreferencedLetVariables; if(includeUnusedPrivates) { computeReferencedFunctions(); } } /** get an expressions without parens */ private Expr getExprWithoutParen(Expr expr) { if (expr instanceof Parenthesized) { return ((Parenthesized)expr).getExpression(); } else { return expr; } } /** {@inheritDoc} */ @Override public Void visit_ModuleDefn(ModuleDefn defn, Object arg) { // We do a pretraversal to find out about parameter strictness if(includeMismatchedAliasPlings) { ParameterStrictnessWalker strictnessWalker = new ParameterStrictnessWalker(); strictnessWalker.visit_ModuleDefn(defn, null); parameterStrictnessMap.putAll(strictnessWalker.getStrictnessMap()); } return super.visit_ModuleDefn(defn, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_Lambda(Lambda lambda, Object arg) { boolean isRedundant = false; Expr lambdaExp = getExprWithoutParen(lambda.getDefiningExpr()); if(lambdaExp instanceof Application) { Application definingExpr = (Application)lambdaExp; isRedundant = (definingExpr.getNExpressions() >= lambda.getNParameters() + 1); // Walk backwards along the parameters so that we catch cases like // (\x -> map add x) int i = lambda.getNParameters() - 1; int j = definingExpr.getNExpressions() - 1; for(;isRedundant && i >= 0 && j >= 1;i--, j--) { if(!isParameterVar(lambda.getNthParameter(i), definingExpr.getNthExpression(j))) { isRedundant = false; } } } else if(lambdaExp instanceof BinaryOp) { BinaryOp definingExpr = (BinaryOp)lambdaExp; if(lambda.getNParameters() == 2 && isParameterVar(lambda.getNthParameter(0), definingExpr.getLeftExpr()) && isParameterVar(lambda.getNthParameter(1), definingExpr.getRightExpr())) { isRedundant = true; } else if(lambda.getNParameters() == 1 && isParameterVar(lambda.getNthParameter(0), definingExpr.getRightExpr()) && !containsParameterReference(lambda.getNthParameter(0), definingExpr.getLeftExpr())) { isRedundant = true; } } else if(lambdaExp instanceof UnaryOp) { UnaryOp definingExpr = (UnaryOp)lambdaExp; if(lambda.getNParameters() == 1 && isParameterVar(lambda.getNthParameter(0), definingExpr.getExpr())) { isRedundant = true; } } if(isRedundant && includeRedundantLambdas) { warningList.add(new LintWarning(LintWarning.WarningType.REDUNDANT_LAMBDA, lambda, lambda.getSourceRange(), super.currentModule, getCurrentFunction())); } return super.visit_Expr_Lambda(lambda, arg); } /** {@inheritDoc} */ @Override public Void visit_FunctionDefn_Algebraic(Algebraic algebraic, Object arg) { String functionName = algebraic.getName(); Function functionEntity = super.moduleTypeInfo.getFunction(functionName); // Don't process functions that match the exclusion expression if(!functionFilter.acceptQualifiedName(QualifiedName.make(super.currentModule, algebraic.getName()))) { if(traceSkippedFunctions) { System.out.println("Skipping test function " + super.currentModule + "." + algebraic.getName()); } return null; } if(includeUnplingedPrimitiveArgs) { checkForUnplingedPrimitives(algebraic, functionName, functionEntity); } if(includeUnusedPrivates) { checkForUnusedPrivates(algebraic, functionName, functionEntity); } if(includeMismatchedAliasPlings) { checkForMismatchedAliasPlings(algebraic, functionName, functionEntity); } return super.visit_FunctionDefn_Algebraic(algebraic, arg); } /** {@inheritDoc} */ @Override public Void visit_FunctionDefn_Foreign(FunctionDefn.Foreign foreign, Object arg) { String functionName = foreign.getName(); Function functionEntity = super.moduleTypeInfo.getFunction(functionName); // Don't process functions that match the exclusion expression if(!functionFilter.acceptQualifiedName(QualifiedName.make(super.currentModule, foreign.getName()))) { if(traceSkippedFunctions) { System.out.println("Skipping test function " + super.currentModule + "." + foreign.getName()); } return null; } if(includeUnusedPrivates) { checkForUnusedPrivates(foreign, functionName, functionEntity); } return super.visit_FunctionDefn_Foreign(foreign, arg); } /** {@inheritDoc} */ /* Unused let-variable checks * * We first call the super-method, which will visit each subexpression * of the let-expression with bindings fully set up. Our override of * visitFunctionName adds the referenced SourceElement to the set * referencedBoundNames each time it encounters a reference to a bound * name during this traversal of the subexpressions. * * So, when the super-method returns, we have a set containing the * SourceElement of each bound name that was referenced in the * subexpressions. We iterate over each of our local definitions; * if the local definition is an element of the referencedBoundNames * set, we remove it (since it scopes out after we return). * If the local defninition is NOT contained in referencedBoundNames, * we spit out a warning about an unreferenced let variable. */ @Override public Void visit_Expr_Let(Let let, Object arg) { final Void ret = super.visit_Expr_Let(let, arg); final ModuleName currentModule = super.currentModule; /** * Handles the identification of unused let variables. * @author Joseph Wong */ class UnusedLetVariablesCollector extends SourceModelTraverser<Void, Void> { @Override public Void visit_LocalDefn_Function_Definition(final Definition function, final Void arg) { if(referencedBoundNames.contains(function)) { referencedBoundNames.remove(function); } else if(includeUnreferencedLetVariables) { warningList.add(new LintWarning(LintWarning.WarningType.UNREFERENCED_LET_VARIABLE, function.getName(), function.getSourceRange(), currentModule, getCurrentFunction())); } return null; } @Override public Void visit_Pattern_Var(final Pattern.Var var, final Void arg) { if(referencedBoundNames.contains(var)) { referencedBoundNames.remove(var); } else if(includeUnreferencedLetVariables) { warningList.add(new LintWarning(LintWarning.WarningType.UNREFERENCED_LET_VARIABLE, var.getName(), var.getSourceRange(), currentModule, getCurrentFunction())); } return null; } @Override public Void visit_FieldPattern(final FieldPattern fieldPattern, final Void arg) { // Handle punning if (fieldPattern.getPattern() == null) { // punning. // Textual field names become Vars of the same name. // Ordinal field names become wildcards ("_"). final FieldName fieldName = fieldPattern.getFieldName().getName(); if (fieldName instanceof FieldName.Textual) { if(referencedBoundNames.contains(fieldPattern)) { referencedBoundNames.remove(fieldPattern); } else if(includeUnreferencedLetVariables) { warningList.add(new LintWarning(LintWarning.WarningType.UNREFERENCED_LET_VARIABLE, fieldName.getCalSourceForm(), fieldPattern.getSourceRange(), currentModule, getCurrentFunction())); } } } // call the superclass impl to reach the pattern and visit it (if it is non-null) return super.visit_FieldPattern(fieldPattern, arg); } @Override public Void visit_LocalDefn_PatternMatch_UnpackDataCons(final LocalDefn.PatternMatch.UnpackDataCons unpackDataCons, final Void arg) { // visit only the patterns unpackDataCons.getArgBindings().accept(this, arg); return null; } @Override public Void visit_LocalDefn_PatternMatch_UnpackListCons(final LocalDefn.PatternMatch.UnpackListCons unpackListCons, final Void arg) { // visit only the patterns unpackListCons.getHeadPattern().accept(this, arg); unpackListCons.getTailPattern().accept(this, arg); return null; } @Override public Void visit_LocalDefn_PatternMatch_UnpackRecord(final LocalDefn.PatternMatch.UnpackRecord unpackRecord, final Void arg) { // visit only the field patterns (and not the base record pattern - since we do not support them in local pattern match decl) final int nFieldPatterns = unpackRecord.getNFieldPatterns(); for (int i = 0; i < nFieldPatterns; i++) { unpackRecord.getNthFieldPattern(i).accept(this, arg); } return null; } @Override public Void visit_LocalDefn_PatternMatch_UnpackTuple(final LocalDefn.PatternMatch.UnpackTuple unpackTuple, final Void arg) { // visit only the patterns final int nPatterns = unpackTuple.getNPatterns(); for (int i = 0; i < nPatterns; i++) { unpackTuple.getNthPattern(i).accept(this, arg); } return null; } } // Run the collector through the defintions final UnusedLetVariablesCollector unusedLetVariablesCollector = new UnusedLetVariablesCollector(); for(int i = 0; i < let.getNLocalDefinitions(); i++) { let.getNthLocalDefinition(i).accept(unusedLetVariablesCollector, null); } return ret; } /** {@inheritDoc} */ @Override public Void visit_Name_Function(Name.Function function, Object arg) { if(!function.isQualified()) { SourceModel.SourceElement boundElement = getBoundElement(function.getUnqualifiedName()); if(boundElement != null) { referencedBoundNames.add(boundElement); } } return super.visit_Name_Function(function, arg); } /** * Check whether a function is private and unreferenced. If it is, add an appropriate * warning to the list. * * @param functionDefn SourceModel of the function to check * @param functionName name of the function to check * @param functionEntity Function corresponding to the function to check */ private void checkForUnusedPrivates(FunctionDefn functionDefn, String functionName, Function functionEntity) { if(functionEntity.getScope() == Scope.PRIVATE && !referencedFunctions.contains(functionEntity.getName())) { warningList.add(new LintWarning(LintWarning.WarningType.UNUSED_PRIVATE_FUNCTION, leftHandString(functionDefn), functionDefn.getNameSourceRange(), super.currentModule, functionName)); } } /** * Check whether a function has any lexical arguments of primitive type that are not plinged. * If it does, then add an approriate LintWarning to the list. * * @param algebraic SourceModel of the function to check * @param functionName name of the function to check * @param functionEntity Function corresponding to the function to check */ private void checkForUnplingedPrimitives(Algebraic algebraic, String functionName, Function functionEntity) { TypeExpr[] arguments = functionEntity.getTypeExpr().getTypePieces(); for(int i = 0; i < arguments.length - 1 && i < algebraic.getNParameters(); i++) { Parameter parameter = algebraic.getNthParameter(i); if(!parameter.isStrict() && isPrimitiveType(arguments[i])) { warningList.add(new LintWarning(LintWarning.WarningType.UNPLINGED_PRIMITIVE_ARG, parameter, parameter.getSourceRangeOfNameNotIncludingPotentialPling(), super.currentModule, functionName)); } } } /** * Check whether a function is an alias function whose plings do not match those of the aliased function * @param algebraic SourceModel of the function to check * @param functionName name of the function to check * @param functionEntity Function corresponding to the function to check */ private void checkForMismatchedAliasPlings(Algebraic algebraic, String functionName, Function functionEntity) { Expr definingExpr = algebraic.getDefiningExpr(); Expr aliasedEntityExpr = null; boolean isPotentialAlias = false; if(definingExpr instanceof Application) { Application application = (Application)definingExpr; aliasedEntityExpr = application.getNthExpression(0); // An alias is an application of a function or data constructor to exactly the same // arguments in exactly the same order. if((aliasedEntityExpr instanceof DataCons || aliasedEntityExpr instanceof Var) && application.getNExpressions() == algebraic.getNParameters() + 1) { isPotentialAlias = true; for(int i = 1; isPotentialAlias && i < application.getNExpressions(); i++) { if(!isParameterVar(algebraic.getNthParameter(i - 1), application.getNthExpression(i))) { isPotentialAlias = false; } } } } if(isPotentialAlias) { String aliasedEntityName = null; if(super.getFunctionalAgentName(aliasedEntityExpr) != null) { aliasedEntityName = super.getFunctionalAgentName(aliasedEntityExpr).getUnqualifiedName(); } final boolean[] aliasedEntityStrictness = parameterStrictnessMap.get(aliasedEntityName); // If we can't find the wrapped entity, we can hardly check its argument strictness if(aliasedEntityStrictness == null) { return; } // Alias functions and wrapped functions must have precisely the same arity as each other. if( aliasedEntityStrictness.length != algebraic.getNParameters()) { return; } // Alias functions and aliased functions must have precisely the same // plinging UP TO the last plinged argument of the alias function. // ie, it's okay for the aliased function to have extra plings, so long // as they are to the right of the alias function's last pling. int lastPlingOnAlias = algebraic.getNParameters() - 1; for(; lastPlingOnAlias >= 0; lastPlingOnAlias--) { if(algebraic.getNthParameter(lastPlingOnAlias).isStrict()) { break; } } for (int i = 0; i <= lastPlingOnAlias; i++) { if (aliasedEntityStrictness[i] != algebraic.getNthParameter(i).isStrict()) { warningList.add(new LintWarning(LintWarning.WarningType.MISMATCHED_ALIAS_PLINGS, algebraic, algebraic.getNameSourceRange(), super.currentModule, algebraic.getName())); return; } } } } /** * @param type TypeExpr to check * @return true if type is a primitive type suitable for unboxing */ private static boolean isPrimitiveType(TypeExpr type) { return type.isNonParametricType(CAL_Prelude.TypeConstructors.Boolean) || isForeignPrimitiveDataType(type) || isEnumDataType(type); } /** * Returns true if type is an enumeration type that is suitable for unboxing. * This function is intended to mirror the results of SCJavaDefn.isEnumDataType * (which is not visible in the compiler package). * * @param type TypeExpr to check * @return true if type is an enumeration data type */ private static boolean isEnumDataType(TypeExpr type) { TypeConsApp typeConsApp = type.rootTypeConsApp(); if(typeConsApp == null) { return false; } return TypeExpr.isEnumType(typeConsApp.getRoot()); } /** * @param type TypeExpr to check * @return true if type is a foreign type that represents a primitive, * or false otherwise. */ private static boolean isForeignPrimitiveDataType(TypeExpr type) { TypeConsApp typeConsApp = type.rootTypeConsApp(); if(typeConsApp == null) { return false; } ForeignTypeInfo foreignTypeInfo = typeConsApp.getForeignTypeInfo(); if(foreignTypeInfo == null) { return false; } try { return foreignTypeInfo.getForeignType().isPrimitive(); } catch (UnableToResolveForeignEntityException e) { // The foreign Java type cannot be resolved, so we print out the error message, and return the default value (false) System.out.println(e.getCompilerMessage().toString()); return false; } } /** * @param parameter A parameter * @param expression Expression to check * @return true if expression is a Var that refers to Parameter (assuming that there are * no intermediate same-name bindings between parameter's scope and expression's). */ private boolean isParameterVar(Parameter parameter, Expr expression) { if(expression instanceof Var) { Var var = (Var)expression; return (var.getVarName().getUnqualifiedName().equals(parameter.getName()) && (var.getVarName().getModuleName() == null || super.resolveModuleName(var.getVarName().getModuleName()).equals(super.currentModule))); } return false; } /** * @param functionDefn An algebraic function definition * @return A string representing the left-hand side of the definition */ private String leftHandString(FunctionDefn functionDefn) { if (functionDefn instanceof Algebraic) { Algebraic algebraic = (Algebraic)functionDefn; StringBuilder buffer = new StringBuilder(algebraic.getScope().toString()); buffer.append(' '); buffer.append(algebraic.getName()); for(int i = 0; i < algebraic.getNParameters(); i++) { buffer.append(' '); buffer.append(algebraic.getNthParameter(i)); } buffer.append(" = ..."); return buffer.toString(); } else if (functionDefn instanceof Foreign) { Foreign foreign = (Foreign)functionDefn; final StringBuilder buffer = new StringBuilder(); buffer.append("foreign unsafe import jvm "); buffer.append(StringEncoder.encodeString(foreign.getExternalName())); if (foreign.isScopeExplicitlySpecified()) { buffer.append(' ').append(foreign.getScope().toString()); } buffer.append(' ').append(foreign.getName()).append(" :: "); buffer.append("..."); return buffer.toString(); } else { return functionDefn.toString(); } } /** * Helper class for discovering whether an expression contains a reference * to a parameter. The parameter is assumed to be in scope at the level * of expression (although expression itself may contain lets or whatnot * that mask the target parameter). * * Creation date: (Jul 5, 2005) * @author Jawright */ private class ParameterReferenceFinder extends MetricVisitor { /** True if we encounter a variable referring to targetParameter */ private boolean foundReference = false; /** The parameter that we are trying to find references to */ private final Parameter targetParameter; /** * Construct a ParameterReferenceFinder * @param targetParameter Parameter to search for */ ParameterReferenceFinder(Parameter targetParameter, ModuleTypeInfo moduleTypeInfo) { super(moduleTypeInfo); this.targetParameter = targetParameter; } /** * @return true if a variable referring to the targetParameter was found */ boolean getFoundReference() { return foundReference; } @Override public Void visit_Expr_Var(Var var, Object arg) { if(!isBound(targetParameter.getName()) && isParameterVar(targetParameter, var)) { foundReference = true; } return super.visit_Expr_Var(var, arg); } } /** * Helper class for accumulating the strictness of each parameter of each top-level function in a module. * * Creation date: (Jul 14, 2005) * @author Jawright */ private class ParameterStrictnessWalker extends SourceModelTraverser<Void, Void> { /** Map from unqualified functional agent to its strictness array (array of isStrict flags for each parameter) */ private final Map<String, boolean[]> strictnessMap = new HashMap<String, boolean[]>(); /** @return Map from unqualified functional agent to its strictness array (array of isStrict flags for each parameter) */ Map<String, boolean[]> getStrictnessMap() { return Collections.unmodifiableMap(strictnessMap); } /** {@inheritDoc} */ @Override public Void visit_FunctionDefn_Algebraic(Algebraic algebraic, Void arg) { boolean[] parameterStrictness = new boolean[algebraic.getNParameters()]; for(int i = 0; i < algebraic.getNParameters(); i++) { parameterStrictness[i] = algebraic.getNthParameter(i).isStrict(); } strictnessMap.put(algebraic.getName(), parameterStrictness); return super.visit_FunctionDefn_Algebraic(algebraic, arg); } /** {@inheritDoc} */ @Override public Void visit_FunctionDefn_Foreign(Foreign foreign, Void arg) { int arity = getArity(foreign.getDeclaredType().getTypeExprDefn()); boolean[] parameterStrictness = new boolean[arity]; for(int i = 0; i < arity; i++) { parameterStrictness[i] = true; // Foreign functions are fully strict } strictnessMap.put(foreign.getName(), parameterStrictness); return super.visit_FunctionDefn_Foreign(foreign, arg); } @Override public Void visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(DataConsDefn defn, Void arg) { boolean[] parameterStrictness = new boolean[defn.getNTypeArgs()]; for(int i = 0; i < defn.getNTypeArgs(); i++) { parameterStrictness[i] = defn.getNthTypeArg(i).isStrict(); } strictnessMap.put(defn.getDataConsName(), parameterStrictness); return super.visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(defn, arg); } /** * Returns the arity of the function represented by typeExprDefn. e.g., a function of * type (Foo -> Bar -> Baz) has arity of 2. * @param typeExprDefn A TypeExprDefn * @return int the arity of typeExprDefn */ private int getArity(TypeExprDefn typeExprDefn) { if(typeExprDefn instanceof TypeExprDefn.Function) { TypeExprDefn.Function functionType = (TypeExprDefn.Function)typeExprDefn; return 1 + getArity(functionType.getCodomain()); } else { return 0; } } } /** * Checks whether expression contains a reference to the specified parameter. Parameter * is assumed to be in scope at the level of expression. Subexpressions that * make bindings that hide parameter will be handled correctly. * @param parameter The parameter to check for references to * @param expression The expression to check * @return true if expression contains references to parameter */ private boolean containsParameterReference(final Parameter parameter, Expr expression) { ParameterReferenceFinder parameterReferenceFinder = new ParameterReferenceFinder(parameter, super.moduleTypeInfo); expression.accept(parameterReferenceFinder, ArrayStack.make()); return parameterReferenceFinder.getFoundReference(); } /** * Fill the referencedFunctions Set with every function in the current module * that has been called from within the current module. * */ private void computeReferencedFunctions() { // First find all of the private functions that get referenced as default methods of some type class for(int i = 0; i < super.moduleTypeInfo.getNTypeClasses(); i++) { TypeClass typeClass = super.moduleTypeInfo.getNthTypeClass(i); for(int j = 0; j < typeClass.getNClassMethods(); j++) { ClassMethod classMethod = typeClass.getNthClassMethod(j); final QualifiedName defaultClassMethodName = classMethod.getDefaultClassMethodName(); if (defaultClassMethodName != null) { referencedFunctions.add(defaultClassMethodName); } } } // Then find all of the private functions that get referenced as methods of some instance for(int i = 0; i < super.moduleTypeInfo.getNClassInstances(); i++) { ClassInstance instance = super.moduleTypeInfo.getNthClassInstance(i); for(int j = 0; j < instance.getNInstanceMethods(); j++) { QualifiedName methodName = instance.getInstanceMethod(j); if (methodName != null) { //will be null in the case when an instance does not define the class method i.e. the intent is to use //the default class method referencedFunctions.add(methodName); } } } // Now walk over all of the functions in the module checking who references whom for(int i = 0; i < super.moduleTypeInfo.getNFunctions(); i++) { Function function = super.moduleTypeInfo.getNthFunction(i); for (final QualifiedName dependeeName : function.getDependeeToFrequencyMap().keySet()) { if(dependeeName.getModuleName().equals(super.currentModule)) { referencedFunctions.add(dependeeName); } } } } /** * @return The list of warnings found by this traversal */ List<LintWarning> getWarningList() { return Collections.unmodifiableList(warningList); } } /** Typesafe enum representing the possible types of search */ static final class SearchType { /** Name of this search type */ private final String name; /** Private constructor for a warning type */ private SearchType(String name) { this.name = name; } /** * {@inheritDoc} */ @Override public String toString() { return name; } static final SearchType ALL = new SearchType("Find all occurrences of an entity"); static final SearchType REFERENCES = new SearchType("Find references in expressions to a gem"); static final SearchType DEFINITION = new SearchType("Find all top-level definitions of a type or gem"); static final SearchType INSTANCES = new SearchType("Find instances of a type class"); static final SearchType CLASSES = new SearchType("Find all classes that a type is an instance of"); static final SearchType POSITION = new SearchType("Find the entity defined at the given location"); static final SearchType REFERENCES_CONSTRUCTIONS = new SearchType("Find all constructions of the given data type."); static final SearchType SOURCE_TEXT = new SearchType("Find the source text for the entity defined at the given location"); } /** * A SourceModelTraverser used to perform searches on a particular QualifiedName. Behaviour * is defined for each type in the SearchType enumeration. * * Creation date: (Jul 28, 2005) * @author Jawright */ private static final class SearchWalker extends MetricVisitor { /** List of QualifiedNames of the entities to search for. Will be null if the search type is POSITION. */ private final List<? extends org.openquark.cal.compiler.Name> targetNames; /** Type of search to perform */ private final SearchType searchType; /** List of locations that have matched the search criteria */ private final List<SearchResult.Precise> searchResults = new ArrayList<SearchResult.Precise>(); /** * The position of the symbol to search for. Only set if the searchType is POSITION. */ private final SourcePosition searchPosition; /** * Construct a new SearchWalker * @param moduleTypeInfo ModuleTypeInfo of module to search. It is assumed to have been compiled already. * @param targetNames List of QualifiedName of the entities to search for * @param searchType Type of search to perform (i.e., for references, for applications, etc.) * @param searchPosition The position of the symbol to search for. This will only be set if the searchType is position. */ private SearchWalker(ModuleTypeInfo moduleTypeInfo, List<? extends org.openquark.cal.compiler.Name> targetNames, SearchType searchType, SourcePosition searchPosition) { super(moduleTypeInfo); if(targetNames == null && searchType != SearchType.POSITION) { throw new IllegalArgumentException("SearchWalker must be given a non-null list of targetNames"); } if(searchType == SearchType.POSITION && searchPosition == null){ throw new IllegalArgumentException("SearchWalker must be given a non-null sourcePosition if the search type is by position"); } this.targetNames = targetNames; this.searchType = searchType; this.searchPosition = searchPosition; } @Override public Void visit_Name_Module(Name.Module moduleName, Object arg) { final SourceRange sourceRange = moduleName.getSourceRange(); if (searchType == SearchType.POSITION){ if (sourceRange.containsPositionInclusive(searchPosition)){ addSearchResult(sourceRange, super.resolveModuleName(moduleName), SourceIdentifier.Category.MODULE_NAME); } } else{ final ModuleName candidateName = SourceModel.Name.Module.toModuleName(moduleName); for (final org.openquark.cal.compiler.Name targetName : targetNames) { if(targetName.equals(candidateName)) { addSearchResult(sourceRange, candidateName, SourceIdentifier.Category.MODULE_NAME); } } } return super.visit_Name_Module(moduleName, arg); } /** (@inheritDoc} */ @Override public Void visit_Import(Import importStmt, Object arg) { final SourceRange sourceRange = importStmt.getImportedModuleName().getSourceRange(); if (searchType == SearchType.POSITION){ if (sourceRange.containsPositionInclusive(searchPosition)){ addSearchResult(sourceRange, SourceModel.Name.Module.toModuleName(importStmt.getImportedModuleName()), SourceIdentifier.Category.MODULE_NAME); } } else{ final ModuleName candidateName = SourceModel.Name.Module.toModuleName(importStmt.getImportedModuleName()); for (final org.openquark.cal.compiler.Name targetName : targetNames) { if(targetName.equals(candidateName)) { addSearchResult(sourceRange, candidateName, SourceIdentifier.Category.MODULE_NAME); } } } return super.visit_Import(importStmt, arg); } /** {@inheritDoc} */ @Override public Void visit_Import_UsingItem_Function(UsingItem.Function usingItemFunction, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("SearchWalker.visitImportUsingItemFunction expects to be passed a module name as its arg"); } ModuleName importedModuleName = (ModuleName)arg; String[] usingNames = usingItemFunction.getUsingNames(); SourceRange[] usingNameSourceRanges = usingItemFunction.getUsingNameSourceRanges(); if(searchType == SearchType.ALL || searchType == SearchType.POSITION) { for(int i = 0, nNames = usingNames.length; i < nNames; i++) { checkReference(QualifiedName.make(importedModuleName, usingNames[i]), usingNameSourceRanges[i], SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } } return super.visit_Import_UsingItem_Function(usingItemFunction, arg); } /** {@inheritDoc} */ @Override public Void visit_Import_UsingItem_DataConstructor(UsingItem.DataConstructor usingItemDataConstructor, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("SearchWalker.visitImportUsingItemDataConstructor expects to be passed a module name as its arg"); } ModuleName importedModuleName = (ModuleName)arg; String[] usingNames = usingItemDataConstructor.getUsingNames(); SourceRange[] usingNameSourceRanges = usingItemDataConstructor.getUsingNameSourceRanges(); if(searchType == SearchType.ALL || searchType == SearchType.POSITION) { for(int i = 0, nNames = usingNames.length; i < nNames; i++) { checkReference(QualifiedName.make(importedModuleName, usingNames[i]), usingNameSourceRanges[i], SourceIdentifier.Category.DATA_CONSTRUCTOR); } } return super.visit_Import_UsingItem_DataConstructor(usingItemDataConstructor, arg); } /** {@inheritDoc} */ @Override public Void visit_Import_UsingItem_TypeConstructor(UsingItem.TypeConstructor usingItemTypeConstructor, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("SearchWalker.visitImportUsingItemTypeConstructor expects to be passed a module name as its arg"); } ModuleName importedModuleName = (ModuleName)arg; String[] usingNames = usingItemTypeConstructor.getUsingNames(); SourceRange[] usingNameSourceRanges = usingItemTypeConstructor.getUsingNameSourceRanges(); if(searchType == SearchType.ALL || searchType == SearchType.POSITION) { for(int i = 0, nNames = usingNames.length; i < nNames; i++) { checkReference(QualifiedName.make(importedModuleName, usingNames[i]), usingNameSourceRanges[i], SourceIdentifier.Category.TYPE_CONSTRUCTOR); } } return super.visit_Import_UsingItem_TypeConstructor(usingItemTypeConstructor, arg); } /** {@inheritDoc} */ @Override public Void visit_Import_UsingItem_TypeClass(UsingItem.TypeClass usingItemTypeClass, Object arg) { if(arg == null || !(arg instanceof ModuleName)) { throw new IllegalArgumentException("SearchWalker.visitImportUsingItemTypeClass expects to be passed a module name as its arg"); } ModuleName importedModuleName = (ModuleName)arg; String[] usingNames = usingItemTypeClass.getUsingNames(); SourceRange[] usingNameSourceRanges = usingItemTypeClass.getUsingNameSourceRanges(); if(searchType == SearchType.ALL || searchType == SearchType.POSITION) { for(int i = 0, nNames = usingNames.length; i < nNames; i++) { checkReference(QualifiedName.make(importedModuleName, usingNames[i]), usingNameSourceRanges[i], SourceIdentifier.Category.TYPE_CLASS); } } return super.visit_Import_UsingItem_TypeClass(usingItemTypeClass, arg); } /** {@inheritDoc} */ @Override public Void visit_FunctionDefn_Algebraic(Algebraic algebraic, Object arg) { if (searchType == SearchType.POSITION){ if (algebraic.getNameSourceRange().containsPositionInclusive(searchPosition)){ addSearchResult(algebraic.getNameSourceRange(), toQualifiedName(algebraic.getName()), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } } else{ for (final org.openquark.cal.compiler.Name next : targetNames) { if (next instanceof QualifiedName){ QualifiedName targetName = (QualifiedName) next; if ( algebraic.getName().equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())){ if( searchType == SearchType.DEFINITION || searchType == SearchType.ALL || searchType == SearchType.POSITION ){ addSearchResult(algebraic.getNameSourceRange(), targetName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } else if (searchType == SearchType.SOURCE_TEXT){ addSearchResult(algebraic.getSourceRange(), targetName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } } } } } return super.visit_FunctionDefn_Algebraic(algebraic, arg); } /** {@inheritDoc} */ @Override public Void visit_FunctionDefn_Primitive(Primitive primitive, Object arg) { if (searchType == SearchType.POSITION){ if (primitive.getNameSourceRange().containsPositionInclusive(searchPosition)){ addSearchResult(primitive.getNameSourceRange(), toQualifiedName(primitive.getName()), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } } else{ for (final org.openquark.cal.compiler.Name next : targetNames) { if (next instanceof QualifiedName){ QualifiedName targetName = (QualifiedName) next; if (primitive.getName().equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())) { if(searchType == SearchType.DEFINITION || searchType == SearchType.ALL || searchType == SearchType.POSITION){ addSearchResult(primitive.getNameSourceRange(), targetName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } else if (searchType == SearchType.SOURCE_TEXT){ addSearchResult(primitive.getSourceRangeExcludingCaldoc(), targetName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } } } } } return super.visit_FunctionDefn_Primitive(primitive, arg); } /** {@inheritDoc} */ @Override public Void visit_InstanceDefn_InstanceTypeCons_Function(InstanceDefn.InstanceTypeCons.Function function, Object arg) { if((searchType == SearchType.CLASSES || searchType == SearchType.ALL || searchType == SearchType.POSITION)) { checkReference(CAL_Prelude.TypeConstructors.Function, function.getOperatorSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } return super.visit_InstanceDefn_InstanceTypeCons_Function(function, arg); } /** {@inheritDoc} */ @Override public Void visit_InstanceDefn_InstanceTypeCons_List(InstanceDefn.InstanceTypeCons.List list, Object arg) { if((searchType == SearchType.CLASSES || searchType == SearchType.ALL || searchType == SearchType.POSITION)) { checkReference(CAL_Prelude.TypeConstructors.List, list.getSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } return super.visit_InstanceDefn_InstanceTypeCons_List(list, arg); } /** {@inheritDoc} */ @Override public Void visit_InstanceDefn_InstanceTypeCons_TypeCons(TypeCons cons, Object arg) { if((searchType == SearchType.CLASSES || searchType == SearchType.ALL || searchType == SearchType.POSITION)) { checkReference(cons.getTypeConsName(), cons.getSourceRangeOfName(), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } return super.visit_InstanceDefn_InstanceTypeCons_TypeCons(cons, arg); } /** {@inheritDoc} */ @Override public Void visit_InstanceDefn_InstanceTypeCons_Unit(InstanceDefn.InstanceTypeCons.Unit unit, Object arg) { if((searchType == SearchType.CLASSES || searchType == SearchType.ALL || searchType == SearchType.POSITION)) { checkReference(CAL_Prelude.TypeConstructors.Unit, unit.getSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } return super.visit_InstanceDefn_InstanceTypeCons_Unit(unit, arg); } /** {@inheritDoc} */ @Override public Void visit_InstanceDefn(InstanceDefn defn, Object arg) { // Override default visitation order to ensure that constraints // are visited before the typeclass or typecons. This ensures // that results will be found in textual order. if (defn.getCALDocComment() != null) { defn.getCALDocComment().accept(this, arg); } final int nConstraints = defn.getNConstraints(); for (int i = 0; i < nConstraints; i++) { defn.getNthConstraint(i).accept(this, arg); } if(searchType == SearchType.INSTANCES || searchType == SearchType.ALL || searchType == SearchType.POSITION) { Name.TypeClass typeClassName = defn.getTypeClassName(); checkReference(typeClassName, typeClassName.getSourceRange(), SourceIdentifier.Category.TYPE_CLASS); } defn.getTypeClassName().accept(this, arg); defn.getInstanceTypeCons().accept(this, arg); final int nInstanceMethods = defn.getNInstanceMethods(); for (int i = 0; i < nInstanceMethods; i++) { defn.getNthInstanceMethod(i).accept(this, arg); } return null; } /** {@inheritDoc} */ @Override public Void visit_InstanceDefn_InstanceMethod(InstanceMethod method, Object arg) { if(searchType == SearchType.REFERENCES || searchType == SearchType.ALL || searchType == SearchType.POSITION) { checkReference(method.getResolvingFunctionName(), method.getSourceRangeOfResolvingFunctionName(), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } return super.visit_InstanceDefn_InstanceMethod(method, arg); } /** {@inheritDoc} */ @Override public Void visit_TypeClassDefn(TypeClassDefn defn, Object arg) { if (searchType == SearchType.POSITION){ if (defn.getSourceRangeOfName().containsPositionInclusive(searchPosition)){ addSearchResult(defn.getSourceRangeOfName(), toQualifiedName(defn.getTypeClassName()), SourceIdentifier.Category.TYPE_CLASS); } } else{ for (final org.openquark.cal.compiler.Name next : targetNames) { if (next instanceof QualifiedName){ QualifiedName targetName = (QualifiedName) next; if (defn.getTypeClassName().equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())){ if (searchType == SearchType.DEFINITION || searchType == SearchType.ALL || searchType == SearchType.POSITION){ addSearchResult(defn.getSourceRangeOfName(), targetName, SourceIdentifier.Category.TYPE_CLASS); } else if (searchType == SearchType.SOURCE_TEXT){ addSearchResult(defn.getSourceRangeOfDefn(), targetName, SourceIdentifier.Category.TYPE_CLASS); } } } } } return super.visit_TypeClassDefn(defn, arg); } /** {@inheritDoc} */ @Override public Void visit_TypeClassDefn_ClassMethodDefn(ClassMethodDefn defn, Object arg) { if (searchType == SearchType.POSITION){ if (defn.getSourceRangeOfName() != null){ if (defn.getSourceRangeOfName().containsPositionInclusive(searchPosition)){ addSearchResult(defn.getSourceRangeOfName(), toQualifiedName(defn.getMethodName()), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } } if (defn.getDefaultClassMethodName() != null){ if (defn.getDefaultClassMethodName().getSourceRange().containsPositionInclusive(searchPosition)){ addSearchResult(defn.getDefaultClassMethodName().getSourceRange(), toQualifiedName(defn.getDefaultClassMethodName()), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } } } else{ for (final org.openquark.cal.compiler.Name next : targetNames) { if (next instanceof QualifiedName){ QualifiedName targetName = (QualifiedName) next; if (defn.getMethodName().equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())) { if(searchType == SearchType.DEFINITION || searchType == SearchType.ALL || searchType == SearchType.POSITION){ addSearchResult(defn.getSourceRangeOfName(), targetName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } else if (searchType == SearchType.SOURCE_TEXT){ addSearchResult(defn.getSourceRangeOfClassDefn(), targetName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } } } } if (searchType == SearchType.ALL || searchType == SearchType.POSITION) { Name.Function defaultClassMethodName = defn.getDefaultClassMethodName(); if (defaultClassMethodName != null) { checkReference(defaultClassMethodName, defaultClassMethodName.getSourceRange(), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } } } return super.visit_TypeClassDefn_ClassMethodDefn(defn, arg); } /** {@inheritDoc} */ @Override public Void visit_FunctionTypeDeclaraction(FunctionTypeDeclaration declaration, Object arg) { if(searchType == SearchType.ALL || searchType == SearchType.POSITION) { checkReference(QualifiedName.make(super.currentModule, declaration.getFunctionName()), declaration.getSourceRangeOfName(), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } return super.visit_FunctionTypeDeclaraction(declaration, arg); } /** {@inheritDoc} */ @Override public Void visit_Constraint_TypeClass(SourceModel.Constraint.TypeClass typeClass, Object arg) { if(searchType == SearchType.ALL || searchType == SearchType.POSITION) { checkReference(typeClass.getTypeClassName(), typeClass.getTypeClassName().getSourceRange(), SourceIdentifier.Category.TYPE_CLASS); } return super.visit_Constraint_TypeClass(typeClass, arg); } /** {@inheritDoc} */ @Override public Void visit_TypeExprDefn_Function(TypeExprDefn.Function function, Object arg) { // Force an in-order traversal rather than relying on the super method to // make the recursive calls. Doing an in-order traversal will ensure that the // the results are returned in source-order without needing to be explicitly sorted. function.getDomain().accept(this, arg); if(searchType == SearchType.ALL || searchType == SearchType.POSITION) { checkReference(CAL_Prelude.TypeConstructors.Function, function.getOperatorSourceRange(), null); } function.getCodomain().accept(this, arg); return null; } /** {@inheritDoc} */ @Override public Void visit_TypeExprDefn_List(TypeExprDefn.List list, Object arg) { if(searchType == SearchType.ALL || searchType == SearchType.POSITION) { checkReference(CAL_Prelude.TypeConstructors.List, list.getSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } return super.visit_TypeExprDefn_List(list, arg); } /** {@inheritDoc} */ @Override public Void visit_TypeExprDefn_TypeCons(TypeExprDefn.TypeCons cons, Object arg) { if(searchType == SearchType.ALL || searchType == SearchType.POSITION) { checkReference(cons.getTypeConsName(), cons.getSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } return super.visit_TypeExprDefn_TypeCons(cons, arg); } /** {@inheritDoc} */ @Override public Void visit_TypeExprDefn_Unit(TypeExprDefn.Unit unit, Object arg) { if(searchType == SearchType.ALL || searchType == SearchType.POSITION) { checkReference(CAL_Prelude.TypeConstructors.Unit, unit.getSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } return super.visit_TypeExprDefn_Unit(unit, arg); } private QualifiedName toQualifiedName(String unqualifiedName){ return QualifiedName.make(super.currentModule, unqualifiedName); } private QualifiedName toQualifiedName(Name.Function name){ if (name.getModuleName() == null){ return toQualifiedName(name.getUnqualifiedName()); } else{ return QualifiedName.make(super.resolveModuleName(name.getModuleName()), name.getUnqualifiedName()); } } /** {@inheritDoc} */ @Override public Void visit_TypeConstructorDefn_AlgebraicType(AlgebraicType type, Object arg) { if (searchType == SearchType.POSITION){ if(type.getSourceRangeOfName().containsPositionInclusive(searchPosition)){ addSearchResult(type.getSourceRangeOfName(), toQualifiedName(type.getTypeConsName()), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } Name.TypeClass[] derivedTypeClasses = type.getDerivingClauseTypeClassNames(); for (final SourceModel.Name.TypeClass typeClassName : derivedTypeClasses) { checkReference(typeClassName, typeClassName.getSourceRange(), SourceIdentifier.Category.TYPE_CLASS); } } else{ for (final org.openquark.cal.compiler.Name next : targetNames) { if (next instanceof QualifiedName){ QualifiedName targetName = (QualifiedName) next; if((searchType == SearchType.DEFINITION || searchType == SearchType.ALL) && type.getTypeConsName().equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())) { addSearchResult(type.getSourceRangeOfName(), targetName, SourceIdentifier.Category.TYPE_CONSTRUCTOR); } if((searchType == SearchType.SOURCE_TEXT) && type.getTypeConsName().equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())) { addSearchResult(type.getSourceRangeOfDefn(), targetName, SourceIdentifier.Category.TYPE_CONSTRUCTOR); } if(searchType == SearchType.INSTANCES || searchType == SearchType.ALL) { Name.TypeClass[] derivedTypeClasses = type.getDerivingClauseTypeClassNames(); for (final Name.TypeClass typeClassName : derivedTypeClasses) { checkReference(typeClassName, typeClassName.getSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } } if((searchType == SearchType.CLASSES || searchType == SearchType.ALL) && type.getTypeConsName().equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())) { Name.TypeClass[] derivedTypeClasses = type.getDerivingClauseTypeClassNames(); for (final Name.TypeClass derivedTypeClass : derivedTypeClasses) { addSearchResult(derivedTypeClass.getSourceRange(), targetName, SourceIdentifier.Category.TYPE_CONSTRUCTOR); } } } } } return super.visit_TypeConstructorDefn_AlgebraicType(type, arg); } /** {@inheritDoc} */ @Override public Void visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(DataConsDefn defn, Object arg) { if (searchType == SearchType.POSITION){ if (defn.getSourceRangeOfName().containsPositionInclusive(searchPosition)){ addSearchResult(defn.getSourceRangeOfName(), toQualifiedName(defn.getDataConsName()), SourceIdentifier.Category.DATA_CONSTRUCTOR); } } else{ for (final org.openquark.cal.compiler.Name next : targetNames) { if (next instanceof QualifiedName){ QualifiedName targetName = (QualifiedName) next; if (defn.getDataConsName().equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())){ if(searchType == SearchType.DEFINITION || searchType == SearchType.ALL || searchType == SearchType.POSITION) { addSearchResult(defn.getSourceRangeOfName(), targetName, SourceIdentifier.Category.DATA_CONSTRUCTOR); } else if (searchType == SearchType.SOURCE_TEXT){ addSearchResult(defn.getSourceRangeOfDefn(), targetName, SourceIdentifier.Category.DATA_CONSTRUCTOR); } } } } } return super.visit_TypeConstructorDefn_AlgebraicType_DataConsDefn(defn, arg); } /** {@inheritDoc} */ @Override public Void visit_FunctionDefn_Foreign(Foreign foreign, Object arg) { if (searchType == SearchType.POSITION){ // TODO in the future have the goto definition find the definition in the java code. // That would be cool. if (foreign.getNameSourceRange().containsPositionInclusive(searchPosition)){ addSearchResult(foreign.getNameSourceRange(), toQualifiedName(foreign.getName()), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } if (foreign.getExternalNameSourceRange().containsPositionInclusive(searchPosition)){ addSearchResult(foreign.getSourceRange(), toQualifiedName(foreign.getName()), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD, true); } } else{ for (final org.openquark.cal.compiler.Name next : targetNames) { if (next instanceof QualifiedName){ QualifiedName targetName = (QualifiedName) next; if (foreign.getName().equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())) { if((searchType == SearchType.DEFINITION || searchType == SearchType.ALL || searchType == SearchType.POSITION)) { addSearchResult(foreign.getNameSourceRange(), targetName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } else if (searchType == SearchType.SOURCE_TEXT){ addSearchResult(foreign.getSourceRangeExcludingCaldoc(), targetName, SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } } } } } return super.visit_FunctionDefn_Foreign(foreign, arg); } /** {@inheritDoc} */ @Override public Void visit_TypeConstructorDefn_ForeignType(ForeignType type, Object arg) { if (searchType == SearchType.POSITION){ if (type.getSourceRangeOfName().containsPositionInclusive(searchPosition)){ addSearchResult(type.getSourceRangeOfName(), toQualifiedName(type.getTypeConsName()), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } if (type.getExternalNameSourceRange().containsPositionInclusive(searchPosition)){ addSearchResult(type.getSourceRangeOfName(), toQualifiedName(type.getTypeConsName()), SourceIdentifier.Category.TYPE_CONSTRUCTOR, true); } // TODO in the future have the goto definition find the definition in the java code. // That would be cool. for(int i = 0; i < type.getNDerivingClauseTypeClassNames(); i++) { Name.TypeClass typeClassName = type.getDerivingClauseTypeClassName(i); checkReference(typeClassName, typeClassName.getSourceRange(), SourceIdentifier.Category.TYPE_CLASS); } } else{ for (final org.openquark.cal.compiler.Name next : targetNames) { if (next instanceof QualifiedName){ QualifiedName targetName = (QualifiedName) next; if((searchType == SearchType.DEFINITION || searchType == SearchType.ALL || searchType == SearchType.POSITION) && type.getTypeConsName().equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())) { addSearchResult(type.getSourceRangeOfName(), targetName, SourceIdentifier.Category.TYPE_CONSTRUCTOR); } if((searchType == SearchType.SOURCE_TEXT) && type.getTypeConsName().equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())) { addSearchResult(type.getSourceRangeOfDefn(), targetName, SourceIdentifier.Category.TYPE_CONSTRUCTOR); } if(searchType == SearchType.INSTANCES || searchType == SearchType.ALL || searchType == SearchType.POSITION) { for(int i = 0; i < type.getNDerivingClauseTypeClassNames(); i++) { Name.TypeClass typeClassName = type.getDerivingClauseTypeClassName(i); checkReference(typeClassName, typeClassName.getSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } } if((searchType == SearchType.CLASSES || searchType == SearchType.ALL || searchType == SearchType.POSITION) && targetName.getUnqualifiedName().equals(type.getTypeConsName()) && targetName.getModuleName().equals(super.currentModule)) { for(int i = 0; i < type.getNDerivingClauseTypeClassNames(); i++) { final Name.TypeClass typeClass = type.getDerivingClauseTypeClassName(i); checkReference(typeClass, typeClass.getSourceRange(), SourceIdentifier.Category.TYPE_CONSTRUCTOR); } } } } } return super.visit_TypeConstructorDefn_ForeignType(type, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_DataCons(DataCons cons, Object arg) { // No work to do here unless we're looking for references if(searchType != SearchType.REFERENCES && searchType != SearchType.REFERENCES_CONSTRUCTIONS && searchType != SearchType.ALL && searchType != SearchType.POSITION) { return super.visit_Expr_DataCons(cons, arg); } Name.Qualifiable candidateName = cons.getDataConsName(); // If this is a reference to one of the targetNames, add it to the list checkReference(candidateName, cons.getSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR); return super.visit_Expr_DataCons(cons, arg); } /** {@inheritDoc} */ @Override public Void visit_LocalDefn_PatternMatch_UnpackDataCons(LocalDefn.PatternMatch.UnpackDataCons unpackDataCons, Object arg) { // Process the pattern-bound variables first processLocalPatternMatchDeclarationPattern(unpackDataCons.getArgBindings()); // No further work to do here unless we're looking for references if(searchType != SearchType.REFERENCES && searchType != SearchType.ALL && searchType != SearchType.POSITION) { return super.visit_LocalDefn_PatternMatch_UnpackDataCons(unpackDataCons, arg); } Name.Qualifiable candidateName = unpackDataCons.getDataConsName(); // If this is a reference to one of the targetNames, add it to the list checkReference(candidateName, candidateName.getSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR); return super.visit_LocalDefn_PatternMatch_UnpackDataCons(unpackDataCons, arg); } /** {@inheritDoc} */ @Override public Void visit_LocalDefn_PatternMatch_UnpackListCons(LocalDefn.PatternMatch.UnpackListCons unpackListCons, Object arg) { // Process the pattern-bound variables first processLocalPatternMatchDeclarationPattern(unpackListCons.getHeadPattern()); processLocalPatternMatchDeclarationPattern(unpackListCons.getTailPattern()); // No further work to do here unless we're looking for references if(searchType != SearchType.REFERENCES && searchType != SearchType.ALL && searchType != SearchType.POSITION) { return super.visit_LocalDefn_PatternMatch_UnpackListCons(unpackListCons, arg); } checkReference(CAL_Prelude.DataConstructors.Cons, unpackListCons.getOperatorSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR); return super.visit_LocalDefn_PatternMatch_UnpackListCons(unpackListCons, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_Case_Alt_UnpackDataCons(UnpackDataCons cons, Object arg) { // No work to do here unless we're looking for references if(searchType != SearchType.REFERENCES && searchType != SearchType.ALL && searchType != SearchType.POSITION) { return super.visit_Expr_Case_Alt_UnpackDataCons(cons, arg); } for(int i = 0; i < cons.getNDataConsNames(); i++) { Name.Qualifiable candidateName = cons.getNthDataConsName(i); // If this is a reference to one of the targetNames, add it to the list checkReference(candidateName, candidateName.getSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR); } return super.visit_Expr_Case_Alt_UnpackDataCons(cons, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_Case_Alt_UnpackListNil(UnpackListNil nil, Object arg) { // No work to do here unless we're looking for references if(searchType != SearchType.REFERENCES && searchType != SearchType.ALL && searchType != SearchType.POSITION) { return super.visit_Expr_Case_Alt_UnpackListNil(nil, arg); } checkReference(CAL_Prelude.DataConstructors.Nil, nil.getSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR); return super.visit_Expr_Case_Alt_UnpackListNil(nil, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_Case_Alt_UnpackListCons(UnpackListCons cons, Object arg) { // No work to do here unless we're looking for references if(searchType != SearchType.REFERENCES && searchType != SearchType.ALL && searchType != SearchType.POSITION) { return super.visit_Expr_Case_Alt_UnpackListCons(cons, arg); } checkReference(CAL_Prelude.DataConstructors.Cons, cons.getOperatorSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR); return super.visit_Expr_Case_Alt_UnpackListCons(cons, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_Case_Alt_UnpackUnit(UnpackUnit unit, Object arg) { // No work to do here unless we're looking for references if(searchType != SearchType.REFERENCES && searchType != SearchType.ALL && searchType != SearchType.POSITION) { return super.visit_Expr_Case_Alt_UnpackUnit(unit, arg); } checkReference(CAL_Prelude.DataConstructors.Unit, unit.getSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR); return super.visit_Expr_Case_Alt_UnpackUnit(unit, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_SelectDataConsField(SelectDataConsField field, Object arg) { // No work to do here unless we're looking for references if(searchType != SearchType.REFERENCES && searchType != SearchType.ALL && searchType != SearchType.POSITION) { return super.visit_Expr_SelectDataConsField(field, arg); } Name.Qualifiable candidateName = field.getDataConsName(); // If this is a reference to targetName, add it to the list checkReference(candidateName, field.getSourceRangeOfName(), SourceIdentifier.Category.DATA_CONSTRUCTOR); return super.visit_Expr_SelectDataConsField(field, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_Var(Var var, Object arg) { // No work to do here unless we're looking for references if(searchType != SearchType.REFERENCES && searchType != SearchType.REFERENCES_CONSTRUCTIONS && searchType != SearchType.ALL && searchType != SearchType.POSITION) { return super.visit_Expr_Var(var, arg); } Name.Qualifiable candidateName = var.getVarName(); // figure out the category of this symbol SourceIdentifier.Category category = SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD; if (candidateName.getModuleName() == null || SourceModel.Name.Module.toModuleName(candidateName.getModuleName()).equals(getModuleName())){ if (getBoundLocalFunctionIdentifier(candidateName.getUnqualifiedName()) != null){ category = SourceIdentifier.Category.LOCAL_VARIABLE; } } // If this is a reference to targetName, add it to the list checkReference(candidateName, var.getSourceRange(), category); return super.visit_Expr_Var(var, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_BinaryOp_BackquotedOperator_DataCons(org.openquark.cal.compiler.SourceModel.Expr.BinaryOp.BackquotedOperator.DataCons backquotedOperator, Object arg) { // Force an in-order traversal rather than relying on the super method to // make the recursive calls. Doing an in-order traversal will ensure that the // the results are returned in source-order without needing to be explicitly sorted. backquotedOperator.getLeftExpr().accept(this, arg); backquotedOperator.getOperatorDataConsExpr().accept(this, arg); backquotedOperator.getRightExpr().accept(this, arg); return null; } /** {@inheritDoc} */ @Override public Void visit_Expr_BinaryOp_BackquotedOperator_Var(org.openquark.cal.compiler.SourceModel.Expr.BinaryOp.BackquotedOperator.Var backquotedOperator, Object arg) { // Force an in-order traversal rather than relying on the super method to // make the recursive calls. Doing an in-order traversal will ensure that the // the results are returned in source-order without needing to be explicitly sorted. backquotedOperator.getLeftExpr().accept(this, arg); backquotedOperator.getOperatorVarExpr().accept(this, arg); backquotedOperator.getRightExpr().accept(this, arg); return null; } /** {@inheritDoc} */ @Override protected Void visit_Expr_BinaryOp_Helper(BinaryOp binop, Object arg) { // Force an in-order traversal rather than relying on the super method to // make the recursive calls. Doing an in-order traversal will ensure that the // the results are returned in source-order without needing to be explicitly sorted. binop.getLeftExpr().accept(this, arg); if(searchType == SearchType.REFERENCES || searchType == SearchType.REFERENCES_CONSTRUCTIONS || searchType == SearchType.ALL || searchType == SearchType.POSITION) { checkReference(OperatorInfo.getTextualName(binop.getOpText()), binop.getOperatorSourceRange(), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } binop.getRightExpr().accept(this, arg); return null; } /** {@inheritDoc} */ @Override public Void visit_Expr_List(org.openquark.cal.compiler.SourceModel.Expr.List list, Object arg) { if(searchType == SearchType.REFERENCES || searchType == SearchType.REFERENCES_CONSTRUCTIONS || searchType == SearchType.ALL || searchType == SearchType.POSITION) { // All empty list literals reference the Prelude.Nil datacons if(list.getNElements() == 0) { checkReference(CAL_Prelude.DataConstructors.Nil, list.getSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR); } } return super.visit_Expr_List(list, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_UnaryOp_Negate(Negate negate, Object arg) { if(searchType == SearchType.REFERENCES || searchType == SearchType.REFERENCES_CONSTRUCTIONS || searchType == SearchType.ALL || searchType == SearchType.POSITION) { checkReference(CAL_Prelude.Functions.negate, negate.getSourceRange(), SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD); } return super.visit_Expr_UnaryOp_Negate(negate, arg); } /** {@inheritDoc} */ @Override public Void visit_Expr_Unit(Unit unit, Object arg) { if(searchType == SearchType.REFERENCES || searchType == SearchType.REFERENCES_CONSTRUCTIONS || searchType == SearchType.ALL || searchType == SearchType.POSITION) { checkReference(CAL_Prelude.DataConstructors.Unit, unit.getSourceRange(), SourceIdentifier.Category.DATA_CONSTRUCTOR); } return super.visit_Expr_Unit(unit, arg); } /** * Check whether candidateName refers to any of the targetNames. * If it does, then add the specified source position to sourcePositions. * @param candidateName Name to check * @param sourceRange SourceRange to record if candidateName is a match */ private void checkReference(QualifiedName candidateName, SourceRange sourceRange, SourceIdentifier.Category category) { // Skip bound names if(candidateName.getModuleName().equals(super.currentModule) && isBound(candidateName.getUnqualifiedName())) { return; } if (searchType == SearchType.POSITION){ if (sourceRange.containsPositionInclusive(searchPosition)){ addSearchResult(sourceRange, candidateName, category); return; } } else{ for (final org.openquark.cal.compiler.Name next : targetNames) { if (next instanceof QualifiedName){ QualifiedName targetName = (QualifiedName) next; if(targetName.equals(candidateName)) { addSearchResult(sourceRange, candidateName, category); return; } } } } } /** * {@inheritDoc} */ @Override public Void visit_Parameter(Parameter parameter, Object arg) { if (searchType == SearchType.POSITION){ if (parameter.getSourceRangeOfNameNotIncludingPotentialPling() != null){ if (parameter.getSourceRangeOfNameNotIncludingPotentialPling().containsPositionInclusive(searchPosition)){ final LocalFunctionIdentifier lfi = getBoundLocalFunctionIdentifier(parameter.getName()); if (lfi != null){ final QualifiedName resolvedCandidateName = QualifiedName.makeFromCompoundName(lfi.toString()); addSearchResult(parameter.getSourceRangeOfNameNotIncludingPotentialPling(), resolvedCandidateName, SourceIdentifier.Category.LOCAL_VARIABLE_DEFINITION); } } } } return super.visit_Parameter(parameter, arg); } /** * {@inheritDoc} */ @Override public Void visit_LocalDefn_Function_TypeDeclaration( LocalDefn.Function.TypeDeclaration declaration, Object arg) { if (searchType == SearchType.POSITION){ if (declaration.getNameSourceRange() != null){ if (declaration.getNameSourceRange().containsPositionInclusive(searchPosition)){ final LocalFunctionIdentifier lfi = getBoundLocalFunctionIdentifier(declaration.getName()); if (lfi != null){ final QualifiedName resolvedCandidateName = QualifiedName.makeFromCompoundName(lfi.toString()); addSearchResult(declaration.getNameSourceRange(), resolvedCandidateName, SourceIdentifier.Category.LOCAL_VARIABLE_DEFINITION); } } } } return super.visit_LocalDefn_Function_TypeDeclaration(declaration, arg); } /** * {@inheritDoc} */ @Override public Void visit_LocalDefn_PatternMatch_UnpackRecord(final LocalDefn.PatternMatch.UnpackRecord unpackRecord, final Object arg) { // Process the pattern-bound variables first final int nFieldPatterns = unpackRecord.getNFieldPatterns(); for (int i = 0; i < nFieldPatterns; i++) { processLocalPatternMatchDeclarationPattern(unpackRecord.getNthFieldPattern(i)); } return super.visit_LocalDefn_PatternMatch_UnpackRecord(unpackRecord, arg); } /** * {@inheritDoc} */ @Override public Void visit_LocalDefn_PatternMatch_UnpackTuple(final LocalDefn.PatternMatch.UnpackTuple unpackTuple, final Object arg) { // Process the pattern-bound variables first final int nPatterns = unpackTuple.getNPatterns(); for (int i = 0; i < nPatterns; i++) { processLocalPatternMatchDeclarationPattern(unpackTuple.getNthPattern(i)); } return super.visit_LocalDefn_PatternMatch_UnpackTuple(unpackTuple, arg); } /** * Processes a Pattern, a FieldPattern or an ArgBindings element contained in a local * pattern match declaration for the pattern-bound variables defined therein. * @param element the element to process. */ private void processLocalPatternMatchDeclarationPattern(final SourceModel.SourceElement element) { /** * A helper class for processing locally bound names for the purpose of identifying search hits and location of * local definitions. * @author Joseph Wong */ class LocalNamesProcessor extends SourceModelTraverser<Void, Void> { /** * {@inheritDoc} */ @Override public Void visit_Pattern_Var(final Pattern.Var var, final Void arg) { processLocallyBoundName(var.getName(), var.getSourceRange()); return null; } /** * {@inheritDoc} */ @Override public Void visit_FieldPattern(final FieldPattern fieldPattern, final Void arg) { // Handle punning if (fieldPattern.getPattern() == null) { // punning. // Textual field names become Vars of the same name. // Ordinal field names become wildcards ("_"). final FieldName fieldName = fieldPattern.getFieldName().getName(); if (fieldName instanceof FieldName.Textual) { processLocallyBoundName(fieldName.getCalSourceForm(), fieldPattern.getFieldName().getSourceRange()); } } // call the superclass impl to reach the pattern and visit it (if it is non-null) return super.visit_FieldPattern(fieldPattern, arg); } } // run the LocalNamesProcessor on the element element.accept(new LocalNamesProcessor(), null); } /** * {@inheritDoc} */ @Override public Void visit_LocalDefn_Function_Definition(final LocalDefn.Function.Definition function, final Object arg) { // Process the local function binding first processLocallyBoundName(function.getName(), function.getNameSourceRange()); return super.visit_LocalDefn_Function_Definition(function, arg); } /** * Processes a locally defined name (either a local function definition, or a local pattern-bound variable). * @param name the locally defined name. * @param nameSourceRange the source range of the name. Can be null. */ private void processLocallyBoundName(final String name, final SourceRange nameSourceRange) { //todo-jowong rewrite the position search as a separate walker to handle searching through all identifiers if (searchType == SearchType.POSITION){ if (nameSourceRange != null){ if (nameSourceRange.containsPositionInclusive(searchPosition)){ final LocalFunctionIdentifier lfi = getBoundLocalFunctionIdentifier(name); if (lfi != null){ final QualifiedName resolvedCandidateName = QualifiedName.makeFromCompoundName(lfi.toString()); addSearchResult(nameSourceRange, resolvedCandidateName, SourceIdentifier.Category.LOCAL_VARIABLE_DEFINITION); } } } } else{ for (final org.openquark.cal.compiler.Name next : targetNames) { if (next instanceof QualifiedName){ QualifiedName targetName = (QualifiedName) next; final LocalFunctionIdentifier lfi = getBoundLocalFunctionIdentifier(name); if (lfi != null){ final QualifiedName resolvedCandidateName = QualifiedName.makeFromCompoundName(lfi.toString()); if((searchType == SearchType.DEFINITION || searchType == SearchType.ALL || searchType == SearchType.POSITION) && resolvedCandidateName.getUnqualifiedName().equals(targetName.getUnqualifiedName()) && resolvedCandidateName.getModuleName().equals(targetName.getModuleName())) { addSearchResult(nameSourceRange, targetName, SourceIdentifier.Category.LOCAL_VARIABLE_DEFINITION); } else if((searchType == SearchType.SOURCE_TEXT) && resolvedCandidateName.getUnqualifiedName().equals(targetName.getUnqualifiedName()) && resolvedCandidateName.getModuleName().equals(targetName.getModuleName())) { SourceModel.SourceElement se = getBoundElement(name); if (se != null){ addSearchResult(se.getSourceRange(), targetName, SourceIdentifier.Category.LOCAL_VARIABLE_DEFINITION); } } } else if((searchType == SearchType.DEFINITION || searchType == SearchType.ALL || searchType == SearchType.POSITION) && name.equals(targetName.getUnqualifiedName()) && super.currentModule.equals(targetName.getModuleName())) { addSearchResult(nameSourceRange, targetName, SourceIdentifier.Category.LOCAL_VARIABLE_DEFINITION); } } } } } /** * Check whether candidateName refers to any of the targetNames. * If it does, then add the specified source position to sourcePositions. * @param candidateName Name to check * * @param sourceRange SourceRange to add to searchResults if candidateName is a match */ private void checkReference(Name.Qualifiable candidateName, SourceRange sourceRange, SourceIdentifier.Category category) { // Null names can sometimes get passed in, but they are obviously not a match if(candidateName == null) { return; } if (searchType == SearchType.POSITION){ if (sourceRange.containsPositionInclusive(searchPosition)){ if (candidateName.getModuleName() == null) { // We have an unqualified name. If it is not currently bound, then // look up the FunctionalAgent and compare the QualifiedNames. if(!isBound(candidateName.getUnqualifiedName())) { ScopedEntity candidateEntity = getScopedEntityFromUnboundCandidateName(candidateName); if(candidateEntity != null) { addSearchResult(sourceRange, candidateEntity.getName(), category); } } else{ LocalFunctionIdentifier lfi = getBoundLocalFunctionIdentifier(candidateName.getUnqualifiedName()); if (lfi != null){ final QualifiedName resolvedCandidateName = QualifiedName.makeFromCompoundName(lfi.toString()); addSearchResult(sourceRange, resolvedCandidateName, category); } } } else { final QualifiedName resolvedCandidateName = QualifiedName.make(super.resolveModuleName(candidateName.getModuleName()), candidateName.getUnqualifiedName()); addSearchResult(sourceRange, resolvedCandidateName, category); } } } else { for (final org.openquark.cal.compiler.Name next : targetNames) { if (next instanceof QualifiedName){ QualifiedName targetName = (QualifiedName) next; // If the gem being checked has a different unqualified name, we can do a trivial reject if(!candidateName.getUnqualifiedName().equals(targetName.getUnqualifiedName())) { continue; } // If the gem being applied is fully-qualified, then we can do a simple check without looking up the FunctionalAgent if(candidateName.getModuleName() != null) { if(super.resolveModuleName(candidateName.getModuleName()).equals(targetName.getModuleName())) { addSearchResult(sourceRange, targetName, category); } continue; } // Otherwise, we have an unqualified name. If it is not currently bound, then // look up the FunctionalAgent and compare the QualifiedNames. if (!isBound(candidateName.getUnqualifiedName())) { ScopedEntity candidateEntity = getScopedEntityFromUnboundCandidateName(candidateName); if(candidateEntity == null) { continue; } if(candidateEntity.getName().equals(targetName)) { addSearchResult(sourceRange, targetName, category); } } } } } } /** * Fetches the named entity (function/data cons/method/type/class). * @param candidateName the name of the entity. * @return the entity named, or null if no such entity exists. */ private ScopedEntity getScopedEntityFromUnboundCandidateName(Name.Qualifiable candidateName) { String unqualifiedCandidate = candidateName.getUnqualifiedName(); ScopedEntity candidateEntity = super.getFunctionalAgent(candidateName); // Wasn't a functional agent. Maybe a type. if(candidateEntity == null && candidateName instanceof Name.TypeCons) { candidateEntity = super.moduleTypeInfo.getTypeConstructor(unqualifiedCandidate); if(candidateEntity == null) { ModuleName dependeeModuleName = super.moduleTypeInfo.getModuleOfUsingTypeConstructor(unqualifiedCandidate); ModuleTypeInfo dependeeModuleTypeInfo = super.moduleTypeInfo.getDependeeModuleTypeInfo(dependeeModuleName); if(dependeeModuleTypeInfo != null) { candidateEntity = dependeeModuleTypeInfo.getTypeConstructor(unqualifiedCandidate); } } } // Maybe a type class. if(candidateEntity == null && candidateName instanceof Name.TypeClass) { candidateEntity = super.moduleTypeInfo.getTypeClass(unqualifiedCandidate); if(candidateEntity == null) { ModuleName dependeeModuleName = super.moduleTypeInfo.getModuleOfUsingTypeClass(unqualifiedCandidate); ModuleTypeInfo dependeeModuleTypeInfo = super.moduleTypeInfo.getDependeeModuleTypeInfo(dependeeModuleName); if(dependeeModuleTypeInfo != null) { candidateEntity = dependeeModuleTypeInfo.getTypeClass(unqualifiedCandidate); } } } return candidateEntity; } /** * Adds the specified sourceRange to searchResults if it contains a source name, * or (if it does not) a SourceRange augmented by the name of the current module. * @param sourceRange SourceRange to add (possibly in augmented form) * @param name QualifiedName that was found */ private void addSearchResult(SourceRange sourceRange, org.openquark.cal.compiler.Name name, SourceIdentifier.Category category, boolean refersToJavaSource) { if(sourceRange == null || sourceRange.getSourceName() != null) { // TODO this is really wrong - SearchResult.Precise REQUIRES a non-null sourceRange searchResults.add(new SearchResult.Precise(sourceRange, name, category, refersToJavaSource)); } else { SourcePosition startPosition = new SourcePosition(sourceRange.getStartLine(), sourceRange.getStartColumn(), super.currentModule); SourcePosition endPosition = new SourcePosition(sourceRange.getEndLine(), sourceRange.getEndColumn(), super.currentModule); searchResults.add(new SearchResult.Precise(new SourceRange(startPosition, endPosition), name, category, refersToJavaSource)); } } /** * Adds the specified sourceRange to searchResults if it contains a source name, * or (if it does not) a SourceRange augmented by the name of the current module. * @param sourceRange SourceRange to add (possibly in augmented form) * @param name QualifiedName that was found */ private void addSearchResult(SourceRange sourceRange, org.openquark.cal.compiler.Name name, SourceIdentifier.Category category) { addSearchResult(sourceRange, name, category, false); } /** * @return List of all the search hits */ List<SearchResult.Precise> getSearchResults() { return Collections.unmodifiableList(searchResults); } } /** * Performs the compile-time collection of source metric data and updates the appropriate * compiler structures with the raw data from the results. Collects data for: * * - Reference frequency metric. Collects information on which top-level gems are referenced * by each top-level function in the module. In this example function, foo has one reference * to baz and two to quux, but none to bar: * foo x = * let * bar y = quux y; * in * quux (bar (baz x)); * * - Module references: Collects a list of all QualifiedNames from other modules that this * module refers to. * * Note that this method assumes that the specified module and all it imported modules have all been compiled * already and that ModuleTypeInfo objects are therefore available for all of them. * * This method call MODIFIES the raw data sets of the specified module's FunctionEntities as a side effect, * so it should only be called as a part of the compilation process. * * @param moduleDefn SourceModel of the module to process * @param moduleTypeInfo The typeinfo object for the module specified by moduleSourceDefn */ static void updateRawMetricData(SourceModel.ModuleDefn moduleDefn, ModuleTypeInfo moduleTypeInfo) { // Compute the set of references from this module ReferenceFrequencyFinder finder = new ReferenceFrequencyFinder(moduleTypeInfo, new AcceptAllQualifiedNamesFilter(), false); finder.visit_ModuleDefn(moduleDefn, null); Map<Pair<QualifiedName, QualifiedName>, Integer> dependeeMap = finder.getDependeeMap(); // Add each reference to the appropriate Function for (final Map.Entry<Pair<QualifiedName, QualifiedName>, Integer> entry : dependeeMap.entrySet()) { Pair<QualifiedName, QualifiedName> referencePair = entry.getKey(); Integer frequency = entry.getValue(); QualifiedName dependeeName = referencePair.fst(); QualifiedName dependentName = referencePair.snd(); // Every dependent was found in this scan, so it must be a member of this module Function dependentEntity = moduleTypeInfo.getFunction(dependentName.getUnqualifiedName()); dependentEntity.addDependee(dependeeName, frequency.intValue()); } moduleTypeInfo.setImportedNameOccurrences(finder.getImportedNameOccurrences()); } /** * Finds all the pairs of (referred_to_gem, referred_by_function) in the provided module and returns * them in a Set of Pairs of QualifiedNames. This is the raw data used to calculate the reference * frequency heuristic. * * This function has no external side effects. * * @param moduleDefn SourceModel of the module to process * @param moduleTypeInfo ModuleTypeInfo object corresponding to the module specified by moduleSourceDefn. * @param functionFilter Filter for deciding which functions will be processed. Cannot be null. * @param traceSkippedFunctions When true, each skipped function will have its name dumped to the console. * When false, skipped functions will be skipped silently * @return List of (dependee, dependent) pairs (one for each reference). */ static Map<Pair<QualifiedName, QualifiedName>, Integer> computeReferenceFrequencies(SourceModel.ModuleDefn moduleDefn, ModuleTypeInfo moduleTypeInfo, QualifiedNameFilter functionFilter, boolean traceSkippedFunctions) { ReferenceFrequencyFinder finder = new ReferenceFrequencyFinder(moduleTypeInfo, functionFilter, traceSkippedFunctions); finder.visit_ModuleDefn(moduleDefn, null); return finder.getDependeeMap(); } /** * Scans the provided module for instances where the output of one gem is used * directly as the input of another. Each (consumer, producer) pair is associated with a count * of the number of times that the output of producer is used as input to consumer. This metric * is called the "compositional frequency" of (consumer, producer). * * For example, in the following module: * * module Foo; * import Prelude; * * bar = 50; * baz arg = 20 + arg; * * quux = Prelude.add bar (baz 54); * * quuux val = Prelude.add (baz 10) (baz val); * * The compositional frequency of (Prelude.add, Foo.bar) is 1 (since the output of bar is * passed directly to add once in quux). * The compositional frequency of (Prelude.add, Foo.baz) is 3 (since the output of baz is * passed directly to add once in quux and twice in quuux). * * @param moduleDefn A SourceModel of a module to scan * @param moduleTypeInfo The ModuleTypeInfo object corresponding to the moduleDefn's module * @param functionFilter Filter for deciding which functions will be included in the frequency counts. Cannot be null. * @param traceSkippedFunctions When true, each skipped function will have its name dumped to the console. * When false, skipped functions will be skipped silently * @return A map from (consumer, producer) to * the number of times the output of producer is used as the input to consumer. */ static Map<Pair<QualifiedName, QualifiedName>, Integer> computeCompositionalFrequencies(SourceModel.ModuleDefn moduleDefn, ModuleTypeInfo moduleTypeInfo, QualifiedNameFilter functionFilter, boolean traceSkippedFunctions) { CompositionalFrequencyFinder finder = new CompositionalFrequencyFinder(moduleTypeInfo, functionFilter, traceSkippedFunctions); finder.visit_ModuleDefn(moduleDefn, null); return finder.getCompositionalFrequencyMap(); } /** * Scans the provided module for questionable expressions (currently only finds redundant lambdas). * Dumps a warning to the console for each flagged expression. * * @param moduleDefn A SourceModel of a module to scan * @param moduleTypeInfo The ModuleTypeInfo object corresponding to the moduleDefn's module * @param functionFilter Filter for deciding which functions will be processed. Cannot be null. * @param traceSkippedFunctions When true, each skipped function will have its name dumped to the console. * When false, skipped functions will be skipped silently * @param includeUnplingedPrimitives When true, check for unplinged arguments of primitive type * @param includeRedundantLambdas When true, check for possibly-redundant lambdas * @param includeUnusedPrivates When true, check for unused private top-level functions. * @param includeMismatchedAliasPlings When true, check for alias functions whose plinging does not * match that of the underlying data constructor / foreign function exactly. * @param includeUnreferencedLetVariables When true, check for let variables that are not referenced * @return List of warnings found for this module */ static List<LintWarning> computeLintWarnings(SourceModel.ModuleDefn moduleDefn, ModuleTypeInfo moduleTypeInfo, QualifiedNameFilter functionFilter, boolean traceSkippedFunctions, boolean includeUnplingedPrimitives, boolean includeRedundantLambdas, boolean includeUnusedPrivates, boolean includeMismatchedAliasPlings, boolean includeUnreferencedLetVariables) { LintWalker walker = new LintWalker(moduleTypeInfo, functionFilter, traceSkippedFunctions, includeUnplingedPrimitives, includeRedundantLambdas, includeUnusedPrivates, includeMismatchedAliasPlings, includeUnreferencedLetVariables); walker.visit_ModuleDefn(moduleDefn, null); return walker.getWarningList(); } /** * Searches a module for references, definitions, or instances, and returns a list of SearchResults * representing the hits. * * @param moduleDefn SourceModel.ModuleDefn of the module to search * @param moduleTypeInfo ModuleTypeInfo corresponding to the moduleDefn's module * @param searchType SearchType type of search to perform * @param targetGemNames List of QualifiedNames of the entities to search for * @return List of SourcePositions of the hits */ static List<SearchResult.Precise> performSearch(SourceModel.ModuleDefn moduleDefn, ModuleTypeInfo moduleTypeInfo, SearchType searchType, List<? extends org.openquark.cal.compiler.Name> targetGemNames) { SearchWalker searchWalker = new SearchWalker(moduleTypeInfo, targetGemNames, searchType, null); searchWalker.visit_ModuleDefn(moduleDefn, null); return searchWalker.getSearchResults(); } /** * Searches a module for a SourcePosition, and returns a list of SearchResults * representing the hits. * * @param moduleDefn SourceModel.ModuleDefn of the module to search * @param moduleTypeInfo ModuleTypeInfo corresponding to the moduleDefn's module * @return List of SourcePositions of the hits */ static List<SearchResult.Precise> findSymbolAt(SourceModel.ModuleDefn moduleDefn, ModuleTypeInfo moduleTypeInfo, SourcePosition position) { SearchWalker searchWalker = new SearchWalker(moduleTypeInfo, null, SearchType.POSITION, position); searchWalker.visit_ModuleDefn(moduleDefn, null); return searchWalker.getSearchResults(); } /** * @param defn the top level element to get the name source range of. * @return the source range of the name of the given top level element. If not * available the source range of the entire element is returned. Otherwise null * is returned. */ private static SourceRange getScopedEntityNameSourceRange(TopLevelSourceElement defn){ SourceRange result = null; if (defn instanceof SourceModel.FunctionDefn){ SourceModel.FunctionDefn functionDefn = (FunctionDefn) defn; result = functionDefn.getNameSourceRange(); } else if (defn instanceof SourceModel.TypeConstructorDefn){ SourceModel.TypeConstructorDefn typeConstructorDefn = (SourceModel.TypeConstructorDefn) defn; result = typeConstructorDefn.getSourceRangeOfName(); } else if (defn instanceof SourceModel.InstanceDefn){ SourceModel.InstanceDefn instanceDefn = (SourceModel.InstanceDefn) defn; result = instanceDefn.getSourceRangeOfName(); } else if (defn instanceof SourceModel.TypeClassDefn){ SourceModel.TypeClassDefn typeClassDefn = (SourceModel.TypeClassDefn) defn; result = typeClassDefn.getSourceRangeOfName(); } if (result == null){ return defn.getSourceRangeOfDefn(); } else{ return result; } } /** * Get the next top level element definition that is defined after the given position. * @param moduleDefn the source model to search * @param position the position to look after * @return the next top level element defined after the given position. This may be null. */ static SourceRange findNextTopLevelElement(SourceModel.ModuleDefn moduleDefn, SourcePosition position){ final int nTopLevelDefns = moduleDefn.getNTopLevelDefns(); SourceRange next = null; for (int i = 0; i < nTopLevelDefns; i++) { TopLevelSourceElement nthTopLevelDefn = moduleDefn.getNthTopLevelDefn(i); // skip the type declarations if (nthTopLevelDefn instanceof SourceModel.FunctionTypeDeclaration){ continue; } SourceRange testSourcePosition = getScopedEntityNameSourceRange(nthTopLevelDefn); if (testSourcePosition.getStartSourcePosition().isAfter(position)){ if (next == null){ next = testSourcePosition; } else if (testSourcePosition.getStartSourcePosition().isBefore(next.getStartSourcePosition())){ next = testSourcePosition; } } } if (next != null){ return next; } else{ return null; } } /** * Get the previous top level element definition that is defined before the given position. * @param moduleDefn the source model to search * @param position the position to look after * @return the previous top level element defined before the given position. This may be null. */ static SourceRange findPreviousTopLevelElement(SourceModel.ModuleDefn moduleDefn, SourcePosition position){ final int nTopLevelDefns = moduleDefn.getNTopLevelDefns(); SourceRange next = null; for (int i = 0; i < nTopLevelDefns; i++) { TopLevelSourceElement nthTopLevelDefn = moduleDefn.getNthTopLevelDefn(i); // skip the type declarations if (nthTopLevelDefn instanceof SourceModel.FunctionTypeDeclaration){ continue; } SourceRange testSourcePosition = getScopedEntityNameSourceRange(nthTopLevelDefn); if (testSourcePosition.getStartSourcePosition().isBefore(position)){ if (next == null){ next = testSourcePosition; } else if (testSourcePosition.getStartSourcePosition().isAfter(next.getStartSourcePosition())){ next = testSourcePosition; } } } if (next != null){ return next; } else{ return null; } } /** * Finds the inner most definition containing the given position. This will return * null if there is no inner definition containing the given position. * @return the innermost definition containing the given position or null if there is no such definition. */ private static Pair<SourceElement, SourceRange> findInnermostDef(LocalDefn.Function.Definition def, SourcePosition position){ SourceRange next = null; SourceElement element = null; if (def.getDefiningExpr() instanceof SourceModel.Expr.Let){ SourceModel.Expr.Let let = (SourceModel.Expr.Let) def.getDefiningExpr(); for(LocalDefn localDefn : let.getLocalDefinitions()){ SourceRange testSourcePosition = localDefn.getSourceRange(); if (testSourcePosition.containsPosition(position)){ if (next == null || next.contains(testSourcePosition)){ next = testSourcePosition; element = localDefn; if (localDefn instanceof LocalDefn.Function){ SourceModel.LocalDefn.Function localFunction = (SourceModel.LocalDefn.Function) localDefn; // if there is a type declaration then use that to put the // cal doc in front of for(LocalDefn localTypeDefn : let.getLocalDefinitions()){ if (localTypeDefn instanceof SourceModel.LocalDefn.Function.TypeDeclaration){ SourceModel.LocalDefn.Function.TypeDeclaration type = (SourceModel.LocalDefn.Function.TypeDeclaration) localTypeDefn; if (type.getName().equals(localFunction.getName())){ element = type; break; } } } } if (localDefn instanceof LocalDefn.Function.Definition){ LocalDefn.Function.Definition def2 = (Definition) localDefn; Pair<SourceElement, SourceRange> innerMatch = findInnermostDef(def2, position); if (innerMatch != null){ return innerMatch; } } return new Pair<SourceElement, SourceRange>(element, next); } } } } return null; } /** * Get the source element definition that is at the given position. * @param moduleDefn the source model to search * @param position the position to look after * @return A pair where the first element is the source element that * contains the given position and the second element is the * source range of the element. This may be null. */ static Pair<SourceElement, SourceRange> findContainingSourceElement(SourceModel.ModuleDefn moduleDefn, SourcePosition position){ final int nTopLevelDefns = moduleDefn.getNTopLevelDefns(); SourceRange next = null; SourceElement element = null; for (int i = 0; i < nTopLevelDefns; i++) { TopLevelSourceElement nthTopLevelDefn = moduleDefn.getNthTopLevelDefn(i); // Check for matching data constructor if (nthTopLevelDefn instanceof SourceModel.TypeConstructorDefn.AlgebraicType){ SourceModel.TypeConstructorDefn.AlgebraicType algebraicType = (AlgebraicType) nthTopLevelDefn; for(DataConsDefn dataConsDefn : algebraicType.getDataConstructors()){ SourceRange testSourcePosition = dataConsDefn.getSourceRangeOfDefn(); if (testSourcePosition.containsPosition(position)){ if (next == null || next.contains(testSourcePosition)){ next = testSourcePosition; element = dataConsDefn; return new Pair<SourceElement, SourceRange>(element, element.getSourceRange()); } } } } // check for matching class method else if (nthTopLevelDefn instanceof SourceModel.TypeClassDefn){ SourceModel.TypeClassDefn typeClassDefn = (SourceModel.TypeClassDefn) nthTopLevelDefn; for(ClassMethodDefn classMethodDefn : typeClassDefn.getClassMethodDefns()){ SourceRange testSourcePosition = classMethodDefn.getSourceRangeOfClassDefn(); if (testSourcePosition.containsPosition(position)){ if (next == null || next.contains(testSourcePosition)){ next = testSourcePosition; element = classMethodDefn; return new Pair<SourceElement, SourceRange>(element, element.getSourceRange()); } } } } // check for let expressions else if (nthTopLevelDefn instanceof SourceModel.FunctionDefn.Algebraic){ SourceModel.FunctionDefn.Algebraic algebraic = (SourceModel.FunctionDefn.Algebraic) nthTopLevelDefn; if (algebraic.getDefiningExpr() instanceof SourceModel.Expr.Let){ SourceModel.Expr.Let let = (SourceModel.Expr.Let) algebraic.getDefiningExpr(); for(LocalDefn localDefn : let.getLocalDefinitions()){ SourceRange testSourcePosition = localDefn.getSourceRange(); if (testSourcePosition.containsPosition(position)){ if (next == null || next.contains(testSourcePosition)){ next = testSourcePosition; element = localDefn; if (localDefn instanceof LocalDefn.Function){ SourceModel.LocalDefn.Function localFunction = (SourceModel.LocalDefn.Function) localDefn; // if there is a type declaration then use that to put the // cal doc in front of for(LocalDefn localTypeDefn : let.getLocalDefinitions()){ if (localTypeDefn instanceof SourceModel.LocalDefn.Function.TypeDeclaration){ SourceModel.LocalDefn.Function.TypeDeclaration type = (SourceModel.LocalDefn.Function.TypeDeclaration) localTypeDefn; if (type.getName().equals(localFunction.getName())){ element = type; } } } } if (localDefn instanceof LocalDefn.Function.Definition){ LocalDefn.Function.Definition def = (Definition) localDefn; Pair<SourceElement, SourceRange> innerMatch = findInnermostDef(def, position); if (innerMatch != null){ element = innerMatch.fst(); next = innerMatch.snd(); } } } } } if (element != null){ break; } } } // check the current element SourceRange testSourcePosition = nthTopLevelDefn.getSourceRangeOfDefn(); if (testSourcePosition.containsPosition(position)){ if (next == null || next.contains(testSourcePosition)){ next = testSourcePosition; element = nthTopLevelDefn; } } } if (element != null){ // Find type declaration if any if (element instanceof SourceModel.FunctionDefn){ SourceModel.FunctionDefn functionDefn = (FunctionDefn) element; String name = functionDefn.getName(); for (int i = 0; i < nTopLevelDefns; i++) { TopLevelSourceElement nthTopLevelDefn = moduleDefn.getNthTopLevelDefn(i); if (nthTopLevelDefn instanceof FunctionTypeDeclaration){ FunctionTypeDeclaration type = (FunctionTypeDeclaration) nthTopLevelDefn; if (type.getFunctionName().equals(name)){ element = type; break; } } } } return new Pair<SourceElement, SourceRange>(element, element.getSourceRange()); } else{ if (moduleDefn.getSourceRange().containsPosition(position)){ return new Pair<SourceElement, SourceRange>(moduleDefn, moduleDefn.getSourceRange()); } return null; } } }