/* * 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. */ /* * OverloadingResolver.java * Created: July 20, 2001 * By: Bo Ilic */ package org.openquark.cal.compiler; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import org.openquark.cal.internal.module.Cal.Core.CAL_Prelude_internal; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.module.Cal.Core.CAL_Record; /** * This class holds the methods needed to resolve overloading in the functions defined * in a single module by * <ol> * <li> adding hidden dictionary arguments to overloaded functions * <li> supplying the extra arguments required when applying such functions * <li> adding the hidden helper dictionary functions necessary to make this all work * </ol> * * @author Bo Ilic */ final class OverloadingResolver { /** Set to true to have debug info printed while performing overload resolution. */ static final private boolean DEBUG_INFO = false; private final CALCompiler compiler; private final ModuleTypeInfo currentModuleTypeInfo; /** the number of hidden dictionary variables added to resolve overloading */ private int dictionaryVarCount; /** * The selector variable in a dictionary function such as $dictEq#Int. This will be the last argument of the * function, and it will have Int type. */ static final String DICTIONARY_FUNCTION_INDEX = "$i"; /** * OverloadingResolver constructor comment. */ OverloadingResolver(CALCompiler compiler, ModuleTypeInfo currentModuleTypeInfo) { if (compiler == null || currentModuleTypeInfo == null) { throw new NullPointerException(); } this.compiler = compiler; this.currentModuleTypeInfo = currentModuleTypeInfo; } /** * Creates the parse tree: #(ALT index @(instanceMethodName dictionaryVariables)). * This is used in generating the hidden code for entries in an instance dictionary. * Note: this version does not work for default class methods. * * @param index * @param instanceMethodName * @param dictionaryVariables * @return ParseTreeNode */ private ParseTreeNode addDictionaryMethodEntry(int index, QualifiedName instanceMethodName, ParseTreeNode[] dictionaryVariables) { //dictionaryVariables is an array of VAR nodes. We need to make into an array of QUALIFIED_VAR nodes. ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); int nVars = dictionaryVariables.length; ParseTreeNode[] arguments = new ParseTreeNode[nVars]; for (int i = 0; i < nVars; ++i) { arguments[i] = ParseTreeNode.makeQualifiedVarNode(QualifiedName.make(currentModuleName, dictionaryVariables[i].getText()), null); } return addDictionaryEntry(index, instanceMethodName, arguments); } /** * Creates the parse tree: #(ALT index @(defaultClassMethodName @(dictionaryFunctionName dictionaryVariables))). * This is used in generating the hidden code for entries in an instance dictionary where the instance does * not define an instance method, and so the default class method is used instead. * * @param index * @param defaultClassMethodName * @param dictionaryVariables * @return ParseTreeNode */ private ParseTreeNode addDictionaryMethodEntryForDefaultClassMethods(int index, QualifiedName defaultClassMethodName, String dictionaryFunctionName, ParseTreeNode[] dictionaryVariables) { ParseTreeNode altNode = new ParseTreeNode(CALTreeParserTokenTypes.ALT, "ALT"); ParseTreeNode indexNode = ParseTreeNode.makeIntLiteralNodeWithIntegerValue(index); altNode.setFirstChild(indexNode); ParseTreeNode applicationNode = new ParseTreeNode(CALTreeParserTokenTypes.APPLICATION, "@"); indexNode.setNextSibling(applicationNode); ParseTreeNode defaultClassMethodNameNode = ParseTreeNode.makeQualifiedVarNode(defaultClassMethodName, null); applicationNode.addChild(defaultClassMethodNameNode); //dictionaryVariables is an array of VAR nodes. We need to make into an array of QUALIFIED_VAR nodes. ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); int nVars = dictionaryVariables.length; ParseTreeNode[] arguments = new ParseTreeNode[nVars + 1]; arguments[0] = ParseTreeNode.makeQualifiedVarNode(currentModuleName, dictionaryFunctionName, null); for (int i = 0; i < nVars; ++i) { arguments[i + 1] = ParseTreeNode.makeQualifiedVarNode(QualifiedName.make(currentModuleName, dictionaryVariables[i].getText()), null); } ParseTreeNode applicationNode2 = new ParseTreeNode(CALTreeParserTokenTypes.APPLICATION, "@"); defaultClassMethodNameNode.setNextSibling(applicationNode2); applicationNode2.addChildren(arguments); return altNode; } private ParseTreeNode addDictionarySwitchingEntry(int index, QualifiedName ancestorDictionaryName, ParseTreeNode[] arguments) { return addDictionaryEntry(index, ancestorDictionaryName, arguments); } private ParseTreeNode addDictionaryEntry(int index, QualifiedName dictionaryEntryName, ParseTreeNode[] arguments) { ParseTreeNode altNode = new ParseTreeNode(CALTreeParserTokenTypes.ALT, "ALT"); ParseTreeNode indexNode = ParseTreeNode.makeIntLiteralNodeWithIntegerValue(index); altNode.setFirstChild(indexNode); ParseTreeNode applicationNode = new ParseTreeNode(CALTreeParserTokenTypes.APPLICATION, "@"); indexNode.setNextSibling(applicationNode); ParseTreeNode dictionaryEntryNode = ParseTreeNode.makeQualifiedVarNode(dictionaryEntryName, null); applicationNode.addChild(dictionaryEntryNode); applicationNode.addChildren(arguments); return altNode; } /** * Add hidden helper functions to the parse tree that are used when resolving overloading. * Creation date: (4/4/01 2:39:56 PM) * @param outerDefnListNode */ void addOverloadingHelperFunctions(ParseTreeNode outerDefnListNode) { outerDefnListNode.verifyType(CALTreeParserTokenTypes.OUTER_DEFN_LIST); //get the last top level node, to which we attach other top level nodes ParseTreeNode attachingNode = outerDefnListNode.lastChild(); if (attachingNode == null) { //There are no functions defined in this module, and thus no overloading to resolve. return; } if (DEBUG_INFO) { System.out.println("Adding hidden overloading helper functions.\n"); } addDictionaryFunctions(attachingNode); if (DEBUG_INFO) { System.out.println("Finished adding hidden overloading helper functions.\n"); } } /** * Add the dictionary functions. There is a dictionary for each instance for a type * constructor (that is not an instance for a type class with only 1 class method and * no parent classes) and it is defined in the module in which the instance is defined. * @param attachingNode */ private void addDictionaryFunctions(ParseTreeNode attachingNode) { for (int instanceN = 0, nInstances = currentModuleTypeInfo.getNTopLevelClassInstances(); instanceN < nInstances; ++instanceN) { ClassInstance instance = currentModuleTypeInfo.getNthTopLevelClassInstance(instanceN); //for example, Eq Int ParseTreeNode dictFunctionNode = makeDictionaryFunction(instance); if (dictFunctionNode != null) { attachingNode.setNextSibling(dictFunctionNode); attachingNode = dictFunctionNode; if (DEBUG_INFO) { System.out.println(attachingNode.toStringTree()); System.out.println(""); } } } } /** * @param instance * the class instance to make a dictionary for * @return ParseTreeNode the (hidden) function that defines the dictionary * used by clients of the class instance or null if this instance * does not require a hidden dictionary function. */ private ParseTreeNode makeDictionaryFunction(ClassInstance instance) { //For each instance type T of class A add a function $dictA#T //where if A1, A2, ..., An are superclasses (in a fixed order) of A and f1, f2, ... are instance methods that implement //the class methods of class A then //$dictA#T $i = case $i of // 0 -> $dictA1#T; // 1 -> $dictA2#T; // .. // n -> f1; // n+1 -> f2; // ... // //For example, //$dictNum#Int $i = case $i of // 0 -> $dictEq#Int; // 1 -> $dictOrd#Int; // 2 -> addInt; // 3 -> subtractInt; // 4 -> multiplyInt; // 5 -> divideInt;; //For the case of constrained instances, extra dictionary formal arguments need to be added for the constraints //and those extra arguments must be transformed appropriately when doing dictionary switching. //For example, //$dictOrd#Tuple2 $dictvarOrd#0 $dictvarOrd#1 $i = case $i of // 0 -> $dictEq#Tuple2 ($getEq#Ord $dictvarOrd#0) ($getEq#Ord $dictvarOrd#1); // 1 -> greaterThan $dictvarOrd#0 $dictvarOrd#1; // 2 -> greaterThanEquals $dictvarOrd#0 $dictvarOrd#1; // 3 -> lessThan $dictvarOrd#0 $dictvarOrd#1; // 4 -> lessThanEquals $dictvarOrd#0 $dictvarOrd#1;; TypeClass typeClass = instance.getTypeClass(); //in the case of Eq Int, this is Eq //There is an optimization in the case of type classes with 1 class method and no superclasses. //Then, //$dictClass#TypeCons = theSingleInstanceMethod //e.g. $dictOutputable#Int = outputInt; //Thus we can just inline the dictionary function, and so we don't need to generate a separate //dictionary function for the runtime. if (typeClass.internal_isSingleMethodRootClass()) { //no dictionary function is generated since it should be inlined in this case return null; } TypeExpr classInstanceType = instance.getType(); //in the case of Eq Int, this is Int final String dictionaryName = instance.getDictionaryFunctionName().getUnqualifiedName(); ParseTreeNode dictFunctionNode = new ParseTreeNode(CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN, "TOP_LEVEL_FUNCTION_DEFN"); ParseTreeNode optionalCALDocNode = new ParseTreeNode(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT, "OPTIONAL_CALDOC_COMMENT"); dictFunctionNode.setFirstChild(optionalCALDocNode); ParseTreeNode accessModifierNode = new ParseTreeNode(CALTreeParserTokenTypes.ACCESS_MODIFIER, "ACCESS_MODIFIER"); optionalCALDocNode.setNextSibling(accessModifierNode); ParseTreeNode publicNode = new ParseTreeNode(CALTreeParserTokenTypes.LITERAL_public, "public"); accessModifierNode.setFirstChild(publicNode); ParseTreeNode dictFunctionNameNode = new ParseTreeNode(CALTreeParserTokenTypes.VAR_ID, dictionaryName); accessModifierNode.setNextSibling(dictFunctionNameNode); ParseTreeNode paramListNode = new ParseTreeNode(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST, "FUNCTION_PARAM_LIST"); dictFunctionNameNode.setNextSibling(paramListNode); ParseTreeNode[] dictionaryParameters = makeDictionaryParameters(instance); paramListNode.addChildren(dictionaryParameters); //the parameter $i will always be examined by the case and so is strict ParseTreeNode index1Node = new ParseTreeNode(CALTreeParserTokenTypes.STRICT_PARAM, DICTIONARY_FUNCTION_INDEX); paramListNode.addChild(index1Node); ParseTreeNode caseNode = new ParseTreeNode(CALTreeParserTokenTypes.VIRTUAL_DATA_CONSTRUCTOR_CASE, "case"); paramListNode.setNextSibling(caseNode); ParseTreeNode index2Node = ParseTreeNode.makeQualifiedVarNode(currentModuleTypeInfo.getModuleName(), DICTIONARY_FUNCTION_INDEX, null); caseNode.setFirstChild(index2Node); ParseTreeNode altListNode = new ParseTreeNode(CALTreeParserTokenTypes.ALT_LIST, "ALT_LIST"); index2Node.setNextSibling(altListNode); //todoBI addChild is not optimal. List<TypeClass> ancestorList = typeClass.calculateAncestorClassList(); int nAncestors = ancestorList.size(); for (int i = 0; i < nAncestors; ++i) { TypeClass ancestorClass = ancestorList.get(i); ClassInstance ancestorInstance; if (instance.isUniversalRecordInstance()) { ancestorInstance = currentModuleTypeInfo.getVisibleClassInstance(new ClassInstanceIdentifier.UniversalRecordInstance(ancestorClass.getName())); } else { ancestorInstance = currentModuleTypeInfo.getVisibleClassInstance(ancestorClass, ((TypeConsApp)classInstanceType).getRoot()); } //the dictionary for the ancestor instance is defined in the module in which the ancestor instance is defined. QualifiedName ancestorDictName = ancestorInstance.getDictionaryFunctionName(); ParseTreeNode[] arguments = makeDictionarySwitchingArguments(instance, ancestorInstance); ParseTreeNode altNode = addDictionarySwitchingEntry(i, ancestorDictName, arguments); altListNode.addChild(altNode); } if (instance.getNInstanceMethods() != typeClass.getNClassMethods()) { throw new IllegalStateException("OverloadingResolver: wrong number of instance methods for instance " + instance); } for (int i = 0, nMethods = instance.getNInstanceMethods(); i < nMethods; ++i) { ParseTreeNode altNode; QualifiedName instanceMethod = instance.getInstanceMethod(i); if (instanceMethod == null) { //use the default class method, since the instance method was not defined ClassMethod classMethod = typeClass.getNthClassMethod(i); if (!classMethod.hasDefaultClassMethod()) { throw new IllegalStateException("expecting a default class method"); } QualifiedName defaultClassMethod = classMethod.getDefaultClassMethodName(); altNode = addDictionaryMethodEntryForDefaultClassMethods(nAncestors + i, defaultClassMethod, dictionaryName, dictionaryParameters); } else { altNode = addDictionaryMethodEntry(nAncestors + i, instanceMethod, dictionaryParameters); } altListNode.addChild(altNode); } return dictFunctionNode; } /** * The dictionary function for childInstance is (essentially) a case expression on an integer index with the a different * option for each class method describing the actual instance method that implements the class method, and an option for * for each ancestor class of the class that lets one obtain the dictionary function for the ancestor instance. * The ancestor instance has certain dictionary variables that it requires, and these are supplied from the dictionary * variables of the childInstance via a series of transformation functions. This is a helper method to generate those * transforming functions. * @param childInstance * @param ancestorInstance * @return ParseTreeNode[] */ private ParseTreeNode[] makeDictionarySwitchingArguments(final ClassInstance childInstance, final ClassInstance ancestorInstance) { //For example, for the (Ord a, Ord b) => Ord (Tuple2 a b) instance, whose dictionary function starts as: //$dictOrd#Tuple2 $dictvarOrd#0 $dictvarOrd#1 $i = case $i of // 0 -> $dictEq#Tuple2 ($getEq#Ord $dictvarOrd#0) ($getEq#Ord $dictvarOrd#1); //This function returns [($getEq#Ord $dictvarOrd#0), ($getEq#Ord $dictvarOrd#1)]. List<ParseTreeNode> dictionarySwitchingArgs = new ArrayList<ParseTreeNode>(); List<SortedSet<TypeClass>> childConstraints = childInstance.getDeclaredPolymorphicVarConstraints(); List<SortedSet<TypeClass>> ancestorConstraints = ancestorInstance.getDeclaredPolymorphicVarConstraints(); for (int i = 0, nAncestorConstrainedVars = ancestorConstraints.size(); i < nAncestorConstrainedVars; ++i) { //the constraints on the ith type var in the ancestor instance SortedSet<TypeClass> ancestorVarConstraints = ancestorConstraints.get(i); SortedSet<TypeClass> childVarConstraints = childConstraints.get(i); //constrainingClassForAncestor is the constraining class in the ancestor's context on the ith type variable for (final TypeClass constrainingClassForAncestor : ancestorVarConstraints) { //the constraint on the ancestor must be implied by a constraint from the child instance. //we must now find the first implying constraint, which can be assumed to exist because of //earlier static analysis. TypeClass classImplyingConstraint = getClassImplyingConstraint(childVarConstraints, constrainingClassForAncestor); //this is the dictionary variable that is defined within the dictionary for the childInstance //from which one can extract the dictionary needed to for the ancestor instance. String dictionaryVarName = getDictionaryVarName(classImplyingConstraint, i); //this is the transforming function that transforms the dictionary argument taken by the childInstance into the dictionary //argument required by the ancestor instance. final ParseTreeNode dictionarySwitchingArg; if (constrainingClassForAncestor != classImplyingConstraint) { dictionarySwitchingArg = getDictionarySwitchingNode(constrainingClassForAncestor, classImplyingConstraint, dictionaryVarName); } else { //no switching necessary, this is the "null" dictionary switch. //This can happen e.g. both the Foo and Bar constrained instances have their variables //constrained by Foo. This is rather an odd pattern though. //public class Foo a => Bar a where //instance Foo a => Bar [a] where //instance Foo a => Foo [a] where dictionarySwitchingArg = ParseTreeNode.makeQualifiedVarNode(currentModuleTypeInfo.getModuleName(), dictionaryVarName, null); } dictionarySwitchingArgs.add(dictionarySwitchingArg); } } return dictionarySwitchingArgs.toArray(new ParseTreeNode[]{}); } /** * A helper function for finding the type class in the child instance constraints that implies the constraint given by * the type class 'constrainingClassForAncestor' in the ancestor instance constraints. * @param childVarConstraints * @param constrainingClassForAncestor * @return TypeClass */ private TypeClass getClassImplyingConstraint(SortedSet<TypeClass> childVarConstraints, TypeClass constrainingClassForAncestor) { for (final TypeClass constrainingClassForChild : childVarConstraints) { if (constrainingClassForChild.isSpecializationOf(constrainingClassForAncestor)) { return constrainingClassForChild; } } return null; } /** * For a constrained instance declaration, the hidden dictionary function takes the resolving index parameter '$i', * as well as one extra parameter for each constraint in the context. * @param instance * @return ParseTreeNode[] nodes holding the names of the extra argument variables needed in the dictionary for the instance. This will be of * length 0 if the instance is not a constrained instance. */ private ParseTreeNode[] makeDictionaryParameters (ClassInstance instance) { //Given the instance declaration //"instance (C1 a1, C2 a2, ..., Cn an) => C (T a1 ... an) where ..." //where Ci = (Ci1, Ci2, .. Cij) where j is a function of i, and the ai are distinct and j_i can be 0 for an unconstrained variable, //then there will be a j1 + j2 + ... + jn dictionary variables added to resolve overloading. //j_i of the variables resolve overloading for type variable i. List<ParseTreeNode> dictionaryParams = new ArrayList<ParseTreeNode>(); List<SortedSet<TypeClass>> declaredConstraints = instance.getDeclaredPolymorphicVarConstraints(); for (int i = 0, nConstrainedVars = declaredConstraints.size(); i < nConstrainedVars; ++i) { //the constraints on the ith constrained type variable SortedSet<TypeClass> varConstraints = declaredConstraints.get(i); for (final TypeClass constrainingClass : varConstraints) { //e.g. $dictvarPrelude.Ord#0 String dictionaryVarName = getDictionaryVarName(constrainingClass, i); dictionaryParams.add(new ParseTreeNode(CALTreeParserTokenTypes.LAZY_PARAM, dictionaryVarName)); } } return dictionaryParams.toArray(new ParseTreeNode[]{}); } /** * A helper function to get the name of the variable used in dictionaries to pass along the * dictionaries specified by the class constraints. * For example, for instance (Eq a, Foo b, Zap b) => Harold (Tuple2 a b) then * for the constraining class Foo, and type variable b, of index 1, the dictionary variable name * is $dictvarPrelude.Foo#1. * @param constrainingClass * @param typeVarIndex * @return String */ private static String getDictionaryVarName(TypeClass constrainingClass, int typeVarIndex) { return "$dictvar" + constrainingClass.getName() + "#" + typeVarIndex; } /** * Returns the type class constraint on inContextTypeVar that is a specialization of * applicationTypeClass. * Creation date: (4/12/01 6:54:23 PM) * @return TypeClass * @param applicationTypeClass * @param inContextTypeVar */ static private TypeClass getFunctionTypeClass(TypeClass applicationTypeClass, TypeVar inContextTypeVar) { for (final TypeClass functionTypeClass : inContextTypeVar.getTypeClassConstraintSet()) { if (functionTypeClass.isSpecializationOf(applicationTypeClass)) { return functionTypeClass; } } return null; } /** * Returns the type class constraint on inContextRecordVar that is a specialization of * applicationTypeClass. * Creation date: (4/12/01 6:54:23 PM) * @return TypeClass * @param applicationTypeClass * @param inContextRecordVar */ static private TypeClass getFunctionTypeClass(TypeClass applicationTypeClass, RecordVar inContextRecordVar) { for (final TypeClass functionTypeClass : inContextRecordVar.getTypeClassConstraintSet()) { if (functionTypeClass.isSpecializationOf(applicationTypeClass)) { return functionTypeClass; } } return null; } /** * Fixes the definitions of the functions specified in the overloadingInfoList to * add hidden dictionary arguments, and fix up applications. * * Creation date: (7/20/01) * @param overloadingInfoList List */ void resolveOverloading(List<OverloadingInfo> overloadingInfoList) { //now add dictionary arguments to the function defined in this declaration group, and fix //up applications within the function definitions. int nOverloadingInfos = overloadingInfoList.size(); for (int i = 0; i < nOverloadingInfos; ++i) { OverloadingInfo overloadingInfo = overloadingInfoList.get(i); resolveFunctionOverloading(overloadingInfo); } if (DEBUG_INFO) { for (int i = 0; i < nOverloadingInfos; ++i) { OverloadingInfo overloadingInfo = overloadingInfoList.get(i); ParseTreeNode functionNode = overloadingInfo.getFunctionNode(); System.out.println("overloading for: " + overloadingInfo.getFunctionName()); System.out.println(functionNode.toStringTree()); System.out.println(""); } } } /** * Fixes the definition of a single function as specified by overloadingInfo to take into * account overloading introduced by the user of type classes. Warning: not all applications * occurring within the function's definition will be resolved by this function. For example, * equalsTest :: Eq a => a -> a -> Boolean; * equalsTest x y = let result = equals x y; in result; * The application at "equals" is resolved by adding arguments to "equalsTest". * * Creation date: (4/12/01 6:17:19 PM) * @param overloadingInfo information necessary to patch up the definition of a given function to resolve overloading */ private void resolveFunctionOverloading(OverloadingInfo overloadingInfo) { //Add extra dictionary arguments to the function, one for each class constraint on each type //(or record) variable. For example, if the constraints are Num a, Enum a, Ord b, and so far there were //14 hidden dictionary variables added, then there would be 3 dictionary variables added //$dictvar15Enum, $dictvar16Num and $dictvar17Ord. The indices are added to ensure no conflict //between duplicated variable names. List<String> dictVars = new ArrayList<String>(); TypeClass firstTypeClass = null; Set<PolymorphicVar> genericConstrainedPolyVars = overloadingInfo.getGenericClassConstrainedPolymorphicVars(); int nPolyVars = genericConstrainedPolyVars.size(); Map<PolymorphicVar, Map<TypeClass, String>> dictionaryVariablesMap = new HashMap<PolymorphicVar, Map<TypeClass, String>>(); //PolymorphicVar -> (TypeClass -> String) if (nPolyVars > 0) { ParseTreeNode headNode = new ParseTreeNode(); ParseTreeNode lastNode = headNode; for (final PolymorphicVar polyVar : genericConstrainedPolyVars) { SortedSet<TypeClass> typeClassConstraintSet = OverloadingResolver.getTypeClassConstraintSet(polyVar); Map<TypeClass, String> typeClassToVarNameMap = new HashMap<TypeClass, String>(); //TypeClass -> String dictionaryVariablesMap.put(polyVar, typeClassToVarNameMap); for (final TypeClass typeClass : typeClassConstraintSet) { String dictVarName = getDictionaryVarName (typeClass, dictionaryVarCount); typeClassToVarNameMap.put(typeClass, dictVarName); dictVars.add(dictVarName); if(firstTypeClass == null) firstTypeClass = typeClass; ++dictionaryVarCount; lastNode.setNextSibling(new ParseTreeNode(CALTreeParserTokenTypes.LAZY_PARAM, dictVarName)); lastNode = lastNode.nextSibling(); } } ParseTreeNode paramListNode = overloadingInfo.getFunctionArgsNode(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); ParseTreeNode firstVisibleArgNode = paramListNode.firstChild(); if (firstVisibleArgNode != null) { lastNode.setNextSibling(firstVisibleArgNode); } paramListNode.setFirstChild(headNode.nextSibling()); } overloadingInfo.setDictionaryVariablesMap(dictionaryVariablesMap); // Fix up the applications within the definition of the function for (int i = 0, nApplications = overloadingInfo.getNApplications(); i < nApplications; ++i) { ApplicationInfo apInfo = overloadingInfo.getApplication(i); if (!optimizeClassMethodOverloading(overloadingInfo, apInfo)) { if (apInfo.getAppliedFunctionalAgent().getName().equals(CAL_Record.Functions.dictionary)) { //we must mangle the dictionary function's arguments so that it is passed the dictionary if (dictVars.size() != 1 || overloadingInfo.getGenericClassConstrainedPolymorphicVars().size() != 1 || !(overloadingInfo.getGenericClassConstrainedPolymorphicVars().iterator().next() instanceof RecordVar)) { compiler.logMessage(new CompilerMessage(apInfo.getAppliedFunctionNode(), new MessageKind.Error.RecordDictionaryMisused())); } else { mangleDictionaryArgs(apInfo, dictVars.get(0), firstTypeClass); } } else resolveApplicationOverloading(apInfo, overloadingInfo); } } } /** * Class methods applied to dictionary literals can be evaluated at compile time. For example, * we can replace Prelude.add $dictNum#Int by Prelude.addInt at compile time.. * * @param overloadingInfo * @param apInfo * @return boolean true if the class method overloading was resolved at compile time by this call */ private boolean optimizeClassMethodOverloading(OverloadingInfo overloadingInfo, ApplicationInfo apInfo) { if (!apInfo.isClassMethod()) { //this optimization is only for class method calls such as 'lessThan', and not for //overloaded function calls such as 'sort'. return false; } Set<PolymorphicVar> constrainedTypeVars = apInfo.getGenericClassConstrainedPolymorphicVars(); int nConstrainedTypeVars = constrainedTypeVars.size(); if (nConstrainedTypeVars > 1) { //this can theoretically happen if a class method is overloaded in a type variable that is not //part of the class hierarchy for the type class being defined. return false; } TypeVar typeVar = (TypeVar) constrainedTypeVars.iterator().next(); //the typeVar was specialized for the context in which it is being used in the //definition of this function. TypeExpr inContextTypeExpr = typeVar.prune(); if (inContextTypeExpr instanceof TypeVar || inContextTypeExpr instanceof RecordType) { return false; } //the overloading has been resolved to a concrete type. TypeConsApp inContextTypeConsApp = (TypeConsApp)inContextTypeExpr; TypeClass typeClass = typeVar.getTypeClassConstraintSet().iterator().next(); ClassInstance classInstance = currentModuleTypeInfo.getVisibleClassInstance(typeClass, inContextTypeConsApp.getRoot()); ParseTreeNode appliedFunctionNode = apInfo.getAppliedFunctionNode(); final QualifiedName classMethodName = appliedFunctionNode.toQualifiedName(); final QualifiedName resolvingFunctionName = classInstance.getInstanceMethod(classMethodName); if (resolvingFunctionName == null) { //if a default class method is used, do not attempt to optimize //todoBI we could optimize this case more in the future return false; } //A common case is when Prelude.fromInt, Prelude.fromLong or Prelude.fromInteger (the functions used internally //when compiling integer literals) have resolved to the resolving function for //Byte, Short, Int, Long, Float, Double, Integer or Decimal //We'll explicitly perform the function evaluations here in the common cases. ParseTreeNode argNode = appliedFunctionNode.nextSibling(); if (argNode != null) { if (resolvingFunctionName.equals(CAL_Prelude.Functions.id)) { //we explicitly replace id x by x. appliedFunctionNode.copyContentsFrom(argNode); return true; } else if (argNode.getType() == CALTreeParserTokenTypes.INTEGER_LITERAL && resolvingFunctionName.getModuleName().equals(CAL_Prelude.MODULE_NAME)) { final Number numberValue = argNode.getLiteralValueForMaybeMinusIntLiteral(); if (resolvingFunctionName.equals(CAL_Prelude_internal.Functions.intToInt) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.longToInt) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.integerToInt)) { appliedFunctionNode.copyContentsFrom(argNode); appliedFunctionNode.setText("int value stored in data field"); appliedFunctionNode.setIntegerValueForMaybeMinusIntLiteral(Integer.valueOf(numberValue.intValue())); appliedFunctionNode.setType(CALTreeParserTokenTypes.INTEGER_LITERAL); return true; } else if ( resolvingFunctionName.equals(CAL_Prelude_internal.Functions.intToByte) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.longToByte) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.integerToByte)) { appliedFunctionNode.copyContentsFrom(argNode); appliedFunctionNode.setType(CALTreeParserTokenTypes.INTEGER_LITERAL); appliedFunctionNode.setText("byte value stored in data field"); appliedFunctionNode.setByteValueForMaybeMinusIntLiteral(Byte.valueOf(numberValue.byteValue())); return true; } else if ( resolvingFunctionName.equals(CAL_Prelude_internal.Functions.intToShort) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.longToShort) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.integerToShort)) { appliedFunctionNode.copyContentsFrom(argNode); appliedFunctionNode.setType(CALTreeParserTokenTypes.INTEGER_LITERAL); appliedFunctionNode.setText("short value stored in data field"); appliedFunctionNode.setShortValueForMaybeMinusIntLiteral(Short.valueOf(numberValue.shortValue())); return true; } else if ( resolvingFunctionName.equals(CAL_Prelude_internal.Functions.intToLong) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.longToLong) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.integerToLong)) { appliedFunctionNode.copyContentsFrom(argNode); appliedFunctionNode.setType(CALTreeParserTokenTypes.INTEGER_LITERAL); appliedFunctionNode.setText("long value stored in data field"); appliedFunctionNode.setLongValueForMaybeMinusIntLiteral(Long.valueOf(numberValue.longValue())); return true; } else if ( resolvingFunctionName.equals(CAL_Prelude_internal.Functions.intToInteger) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.longToInteger) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.integerToInteger)) { appliedFunctionNode.copyContentsFrom(argNode); appliedFunctionNode.setType(CALTreeParserTokenTypes.INTEGER_LITERAL); appliedFunctionNode.setText("BigInteger value stored in data field"); final BigInteger bigIntegerValue; if (numberValue instanceof BigInteger) { bigIntegerValue = (BigInteger)numberValue; } else { bigIntegerValue = BigInteger.valueOf(numberValue.longValue()); } appliedFunctionNode.setBigIntegerValueForMaybeMinusIntLiteral(bigIntegerValue); return true; } else if ( resolvingFunctionName.equals(CAL_Prelude_internal.Functions.intToFloat) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.longToFloat) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.integerToFloat)) { appliedFunctionNode.copyContentsFrom(argNode); appliedFunctionNode.setType(CALTreeParserTokenTypes.FLOAT_LITERAL); appliedFunctionNode.setText("float value stored in data field"); appliedFunctionNode.setFloatValueForFloatLiteral(new Float(numberValue.floatValue())); return true; } else if ( resolvingFunctionName.equals(CAL_Prelude_internal.Functions.intToDouble) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.longToDouble) || resolvingFunctionName.equals(CAL_Prelude_internal.Functions.integerToDouble)) { appliedFunctionNode.copyContentsFrom(argNode); appliedFunctionNode.setType(CALTreeParserTokenTypes.FLOAT_LITERAL); appliedFunctionNode.setText("double value stored in data field"); appliedFunctionNode.setDoubleValueForFloatLiteral(new Double(numberValue.doubleValue())); return true; } } //todoBI there are more cases that could be simplified here } //all we can do is replace the class method by an explicit instance method call. This //is still very good indeed! ParseTreeNode moduleNameNode = appliedFunctionNode.firstChild(); ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, resolvingFunctionName.getModuleName()); ParseTreeNode scNameNode = moduleNameNode.nextSibling(); scNameNode.setText(resolvingFunctionName.getUnqualifiedName()); if (inContextTypeConsApp.getNArgs() > 0) { //handle the "inside" constraints e.g. //equals [(1.0, 'a')] [(1.0, 'b')] //resolves to the following because of the optimization //equalsList (dictEq#Pair dictEq#Double dictEq#Char) [(1.0, 'a')] [(1.0, 'b')] //instead of //equals (dictEq#List (dictEq#Pair dictEq#Double dictEq#Char)) [(1.0, 'a')] [(1.0, 'b')] //here is how it would be resolved without optimizations ParseTreeNode overloadingArgument = getOverloadingArgument(inContextTypeExpr, typeClass, overloadingInfo, apInfo); overloadingArgument.verifyType(CALTreeParserTokenTypes.APPLICATION); overloadingArgument.firstChild().verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); //this will be of the form (APPLICATION $dictTypeClass#InContextTypeCons arg1 arg2 ... argn) //where the argi supply evidence for the overloading on the args of inContextTypeCons implied by the instance. ParseTreeNode extraDictionaryArguments = overloadingArgument.getChild(1); ParseTreeNode instanceFunctionNode = new ParseTreeNode(); instanceFunctionNode.copyContentsFrom(appliedFunctionNode); appliedFunctionNode.setType(CALTreeParserTokenTypes.APPLICATION); appliedFunctionNode.setText("@"); appliedFunctionNode.setFirstChild(instanceFunctionNode); instanceFunctionNode.setNextSibling(extraDictionaryArguments); } return true; } /** * This function mangles the arguments to the dictionary function, * so that they are transformed to the enclosing functions dictionary arg * and the index of the named method function. * @param apInfo * @param dictVar - the name of the first dictionary in the enclosing function * @param typeClass - the type class that the dictionary represents */ private void mangleDictionaryArgs(ApplicationInfo apInfo, String dictVar, TypeClass typeClass) { ParseTreeNode appliedFunctionNode = apInfo.getAppliedFunctionNode(); if (appliedFunctionNode.nextSibling().nextSibling().getType() != CALTreeParserTokenTypes.STRING_LITERAL) { //the second argument to dictionary must be a string literal compiler.logMessage(new CompilerMessage(apInfo.getAppliedFunctionNode(), new MessageKind.Error.RecordDictionaryArgumentsInvalid())); return; } //get the name of method String fname = appliedFunctionNode.nextSibling().nextSibling().getText(); //trim the quotes from the string literal if (fname.length() > 2) fname = fname.substring(1, fname.length() -1); //find the index of the typeclass function. int index = typeClass.getClassMethodIndex(fname); if (index < 0) { compiler.logMessage(new CompilerMessage(apInfo.getAppliedFunctionNode(), new MessageKind.Error.DictionaryMethodDoesNotExist(typeClass.getName().toSourceText(), fname))); return; } if (typeClass.internal_isSingleMethodRootClass()) index = -1; else { index += typeClass.calculateAncestorClassList().size(); } ParseTreeNode indexNode = ParseTreeNode.makeIntLiteralNodeWithIntegerValue(index); //mangle the parse tree so we change the args to be the dictionary function //rather than the record and the index rather //than the name of the method. appliedFunctionNode.nextSibling().nextSibling().copyContentsFrom(indexNode); appliedFunctionNode.nextSibling().getChild(1).setText(dictVar); } /** * Adds extra arguments as required to resolve overloading in a particular application of * a function. For example, if * f :: Num a => a -> Int * Then "f 2.0" would be converted to "f $dictNum#Double 2.0" and * "f x" may be converted to "f $dictvar2Num x". * * Creation date: (4/12/01 6:18:32 PM) * @param apInfo information needed to resolve one particular application of a function * @param overloadingInfo the OverloadingInfo object corresponding to the function in whose * definition the apInfo above occurs. */ private void resolveApplicationOverloading(ApplicationInfo apInfo, OverloadingInfo overloadingInfo) { final ParseTreeNode headNode = new ParseTreeNode(); final ParseTreeNode lastNode = resolveApplicationOverloadingHelper(apInfo, overloadingInfo, headNode); if (headNode == lastNode) { //no arguments need to be added. return; } //In principle, adding the extra overloading expressions is done differently depending on whether the //applied function is an operator or a textually named function. This is simply because the structure of the //parse trees are different. In the operator case, the operator is at the root of its operands. In the textually //named function case, the applied function's first argument is its next sibling. //Since at this point, all operators have been converted to textual applicative forms, we need only handle the //textual case. ParseTreeNode appliedFunctionNode = apInfo.getAppliedFunctionNode(); SourcePosition sourcePosition = appliedFunctionNode.getChild(1).getSourcePosition(); ParseTreeNode applicationNode = new ParseTreeNode(CALTreeParserTokenTypes.APPLICATION, "@", sourcePosition); if (apInfo.isClassMethod()) { //class method functions such as: //equals d = d 0; //notEquals d = d 1; //multiply d = d 9; //and in general, classMethodName d = d dictionaryIndex; //always occur in saturated form, and hence can be inlined as well as not generated as runtime entities. // //if headNode points to extra dictionary arguments d1, d2, ..., dn then replace the var f by //(@ d1 dictionaryIndexOfF d2 ... dn). //a special case is if dictionaryIndex is -1, in which case the class method function is of the form: //output d = d; // //if headNode points to extra dictionary arguments d1, d2, ..., dn then replace the var f by //(@ d1 d2 ... dn). //The situation is more complicated if a class method is overloaded, such as: //class B b where methodB :: (A a, C c) -> a -> b -> c; ... (class B has more than 1 method) //then //methodB dA dB dC = (dB 0) dA dC; //or equivalently (dropping trailing arguments) //methodB dA dB = (dB 0) dA; //0 is the dictionary index of methodB // //and if classB had only a single class method, then //methodB dA dB dC = dB dA dC; //or equivalently //methodB dA dB = dB dA; ClassMethod classMethod = (ClassMethod)apInfo.getAppliedFunctionalAgent(); final int dictionaryIndex = classMethod.internal_getDictionaryIndex(); final int classTypeVarPolymorphicIndex = classMethod.getClassTypeVarPolymorphicIndex(); //If the dictionary nodes are (d1 d2 ... dn), then move the node with index classTypeVarPolymorphicIndex to the front //for example, if classTypeVarPolymorphicIndex = 2 then //d1 d2 d3 d4 d5 .... ---> d3 d1 d2 d4 d5 ... if (classTypeVarPolymorphicIndex != 0) { ParseTreeNode previousToClassDictNode = headNode; for (int i = 0; i < classTypeVarPolymorphicIndex; ++i) { previousToClassDictNode = previousToClassDictNode.nextSibling(); } ParseTreeNode classDictNode = previousToClassDictNode.nextSibling(); previousToClassDictNode.setNextSibling(classDictNode.nextSibling()); classDictNode.setNextSibling(headNode.nextSibling()); headNode.setNextSibling(classDictNode); } ParseTreeNode firstArgNode = headNode.nextSibling(); if (dictionaryIndex != -1) { ParseTreeNode dictionaryIndexNode = ParseTreeNode.makeIntLiteralNodeWithIntegerValue(dictionaryIndex); ParseTreeNode secondArgNode = firstArgNode.nextSibling(); //could be null firstArgNode.setNextSibling(dictionaryIndexNode); dictionaryIndexNode.setNextSibling(secondArgNode); } applicationNode.setFirstChild(firstArgNode); applicationNode.setNextSibling(appliedFunctionNode.nextSibling()); appliedFunctionNode.copyContentsFrom(applicationNode); return; } //if headNode points to extra dictionary arguments d1, d2, ..., dn then replace the var f by //(@ f d1 d2 ... dn). QualifiedName functionName = appliedFunctionNode.toQualifiedName(); ParseTreeNode varNode = ParseTreeNode.makeQualifiedVarNode(functionName, sourcePosition); varNode.setNextSibling(headNode.nextSibling()); applicationNode.setFirstChild(varNode); applicationNode.setNextSibling(appliedFunctionNode.nextSibling()); appliedFunctionNode.copyContentsFrom(applicationNode); } /** * Adds extra arguments as required to resolve overloading in a particular application of * a function. The extra overloading arguments can be dictionaries, dictionary * variables or dictionary switching functions applied to dictionary variables, * (or some more complicated expression involving the above in the case of constrained instances) * * Creation date: (4/12/01) * @param apInfo * @param overloadingInfo * @param headNode add extra dictionary argument nodes as siblings to this node * @return last dictionary node added to resolve this overloading */ private ParseTreeNode resolveApplicationOverloadingHelper(ApplicationInfo apInfo, OverloadingInfo overloadingInfo, ParseTreeNode headNode) { //there is an overloading argument expression required for each constraint in the applied function's type (prior to specialization //in this context). For example, if the type is (Eq a, Foo a, Bar b, Zap b, Ord c) => ..., then 5 argument expressions are needed. Set<PolymorphicVar> genericConstrainedPolyVars = apInfo.getGenericClassConstrainedPolymorphicVars(); ParseTreeNode lastNode = headNode; for (final PolymorphicVar polyVar : genericConstrainedPolyVars) { for (final TypeClass typeClass : OverloadingResolver.getTypeClassConstraintSet(polyVar)) { //this is a ParseTreeNode for an expression that needs to be inserted as an argument at the application site to //resolve the overloading constraint imposed by typeClass-polyVar. ParseTreeNode overloadingArgumentNode; if (polyVar instanceof TypeVar) { overloadingArgumentNode = getOverloadingArgument((TypeVar)polyVar, typeClass, overloadingInfo, apInfo); } else if (polyVar instanceof RecordVar) { //the dictionary for the wrapperRecordType may be something like: //getEq#Record {field1 = dictEqInt, field2 = dictEqChar} //which is then stripped to get //{field1 = dictEqInt, field2 = dictEqChar} //the dictionary of the underlying recordVar. RecordType wrapperRecordType = new RecordType((RecordVar)polyVar, Collections.<FieldName, TypeExpr>emptyMap()); overloadingArgumentNode = getOverloadingArgument(wrapperRecordType, typeClass, overloadingInfo, apInfo).getChild(1); } else { throw new IllegalStateException(); } lastNode.setNextSibling(overloadingArgumentNode); lastNode = lastNode.nextSibling(); } } return lastNode; } /** * Converts the node @(classMethodDefault arg1 ... argn) to @(classMethodDefault @(Prelude.unsafeCoerce arg1 ... argn). * * @param applicationNode * @return the patched application node */ static private ParseTreeNode patchApplicationNode(ParseTreeNode applicationNode) { applicationNode.verifyType(CALTreeParserTokenTypes.APPLICATION); ParseTreeNode apNode = new ParseTreeNode(CALTreeParserTokenTypes.APPLICATION, "@", null); ParseTreeNode classMethodDefaultNode = applicationNode.firstChild(); apNode.setFirstChild(classMethodDefaultNode); ParseTreeNode unsafeCoerceNode = ParseTreeNode.makeQualifiedVarNode(CAL_Prelude.Functions.unsafeCoerce, null); classMethodDefaultNode.setNextSibling(unsafeCoerceNode); return apNode; } /** * Provides the expression that provides the evidence that the overloading constraint given by the pair * typeClass-(the typeVar instantiated to inContextTypeExpr) is actually satisfied. * * @param typeExpr * @param typeClass * @param overloadingInfo * @param apInfo * @return ParseTreeNode */ private ParseTreeNode getOverloadingArgument(TypeExpr typeExpr, TypeClass typeClass, OverloadingInfo overloadingInfo, ApplicationInfo apInfo) { //Here are some examples showing how resolving adds an overloading argument that is a complex expression. // //equals (1.0, 'a') (2.0, 'b') //after overloading helpers are added this is: //(equals (dictEq#Pair dictEq#Double dictEq#Char)) (1.0, 'a') (2.0, 'b') //the overloading for Eq a => a is resolved by the overloading argument (dictEq#Pair dictEq#Double dictEq#Char) // //equals [(1.0, 'a')] [(2.0, 'b')] //after overloading helpers are added this is: //(equals (dictEq#List (dictEq#Pair dictEq#Double dictEq#Char))) (1.0, 'a') (2.0, 'b') //the overloading for Eq a => a is resolved by the overloading argument (dictEq#List (dictEq#Pair dictEq#Double dictEq#Char)) // //if x and y has type Ord a => a, then //equals [(1.0, x)] [(2.0, y)] //after overloading helpers are added this is (where we pick 14 for the dictionary variable number- this will vary): //(equals (dictEq#List (dictEq#Pair dictEq#Double (getEq#Ord dictvarOrd14)))) (1.0, x) (2.0, x) //the typeVar was specialized for the context in which it is being used in the //definition of this function. TypeExpr inContextTypeExpr = typeExpr.prune(); if (inContextTypeExpr instanceof TypeConsApp) { //the overloading has been resolved to a concrete type. For example, if the type //variable a has constraints Enum a and Num a and it is instantiated to an Int //then we add $dictEnum#Int and $dictNum#Int (dictionary function valued) arguments. TypeConsApp typeConsApp = (TypeConsApp) inContextTypeExpr; ClassInstance classInstance = currentModuleTypeInfo.getVisibleClassInstance(typeClass, typeConsApp.getRoot()); //The dictionary is defined in the module in which the instance is declared. QualifiedName dictName = classInstance.getDictionaryFunctionName(); final int nArgs = typeConsApp.getNArgs(); ParseTreeNode dictionaryNode = ParseTreeNode.makeQualifiedVarNode(dictName, null); //For example, Debug.show has a single class method with a default. If the instance uses this default, //then the dictionary resolution takes on a special form. boolean isSingleMethodRootClassUsingDefaultClassMethod = typeClass.internal_isSingleMethodRootClass() && classInstance.getInstanceMethod(0) == null; //as a compile-time optimization, treat the case of 0 type arguments separately if (nArgs == 0) { if (isSingleMethodRootClassUsingDefaultClassMethod) { ParseTreeNode apNode = new ParseTreeNode(CALTreeParserTokenTypes.APPLICATION, "@", null); apNode.setFirstChild(dictionaryNode); ParseTreeNode unsafeCoerceNode = ParseTreeNode.makeQualifiedVarNode(CAL_Prelude.Functions.unsafeCoerce, null); dictionaryNode.setNextSibling(unsafeCoerceNode); return apNode; } return dictionaryNode; } //we must supply the evidence for the constraints on any of the type arguments. ParseTreeNode applicationNode = new ParseTreeNode(CALTreeParserTokenTypes.APPLICATION, "@", null); applicationNode.setFirstChild(dictionaryNode); //todoBI can make getDeclaredVarConstraints faster. List<SortedSet<TypeClass>> varConstraintsArray = classInstance.getDeclaredPolymorphicVarConstraints(); for (int i = 0; i < nArgs; ++i) { //the evidence needed for argument i depends on what constraints are imposed on argument i in the instance 'classInstance'. SortedSet<TypeClass> varConstraints = varConstraintsArray.get(i); for (final TypeClass constrainingTypeClassForArg : varConstraints) { ParseTreeNode argumentDictionaryNode = getOverloadingArgument (typeConsApp.getArg(i), constrainingTypeClassForArg, overloadingInfo, apInfo); applicationNode.addChild(argumentDictionaryNode); } } if (isSingleMethodRootClassUsingDefaultClassMethod) { //for single method root classes that are in fact using the default class method, //the dictionary node must be modified to include an ignored extra argument. e.g. //Show Coin ---> Debug.showDefault Prelude.unsafeCoerce //Show Tuple2 ---> Debug.showDefault (Prelude.unsafeCoerce arg1Dict arg2Dict) //Prelude.unsafeCoerce is just chosen as a convenient argument to be ignored. //Modify @(classMethodDefault arg1 ... argn) to @(classMethodDefault @(unsafeCoerce arg1 ... argn) return patchApplicationNode(applicationNode); } return applicationNode; } else if (inContextTypeExpr instanceof RecordType) { RecordType recordType = (RecordType) inContextTypeExpr; ClassInstance classInstance = currentModuleTypeInfo.getVisibleClassInstance(new ClassInstanceIdentifier.UniversalRecordInstance(typeClass.getName())); //The dictionary is defined in the module in which the instance is declared. QualifiedName dictName; if (classInstance != null) { dictName = classInstance.getDictionaryFunctionName(); } else { //the class instance may be null when the record is not //a member of the type class that is used to constrain the //record fields. dictName = QualifiedName.make("Unused", "$dummyDict"); } ParseTreeNode dictionaryNode = ParseTreeNode.makeQualifiedVarNode(dictName, null); boolean isSingleMethodRootClassUsingDefaultClassMethod = typeClass.internal_isSingleMethodRootClass() && (classInstance != null && classInstance.getInstanceMethod(0) == null); ParseTreeNode applicationNode = new ParseTreeNode(CALTreeParserTokenTypes.APPLICATION, "@", null); applicationNode.setFirstChild(dictionaryNode); RecordVar recordVar = recordType.getPrunedRecordVar(); Map<FieldName, TypeExpr> hasFieldsMap = recordType.getHasFieldsMap(); ParseTreeNode recordVarNode; if (!recordVar.isNoFields()) { //this is similar to the case where a type variable has been instantiated to another type variable. //the record variable must occur in the constrained polymorphic variables of the Overloading info //object or one of its ancestors. OverloadingInfo resolvingOverloadingInfo = overloadingInfo.getResolvingOverloadingInfo(recordVar); if (resolvingOverloadingInfo == null) { //ambiguous type signature in inferred type compiler.logMessage(new CompilerMessage(apInfo.getAppliedFunctionNode(), new MessageKind.Error.AmbiguousTypeSignatureInInferredType(inContextTypeExpr.toString()))); } TypeClass functionTypeClass = getFunctionTypeClass(typeClass, recordVar); String dictVarName = resolvingOverloadingInfo.getDictionaryVariableName(recordVar, functionTypeClass); if (functionTypeClass == typeClass) { //no need for dictionary switching recordVarNode = ParseTreeNode.makeQualifiedVarNode(currentModuleTypeInfo.getModuleName(), dictVarName, null); } else { //add an argument for dictionary switching recordVarNode = getDictionarySwitchingNode (typeClass, functionTypeClass, dictVarName); } if (hasFieldsMap.size() == 0) { dictionaryNode.setNextSibling(recordVarNode); //if typeClass = Eq the dictionary node will be something like: //dictEq#Record dictvarEq17 if (isSingleMethodRootClassUsingDefaultClassMethod) { return patchApplicationNode(applicationNode); } return applicationNode; } } else { recordVarNode = null; } //if typeClass is Eq the dictionary will be //dictEq#Record {dictvarEq17 | fieldName1 = EqDictionaryForFieldName1, fieldName2 = EqDictionaryForFieldName2, ...} ParseTreeNode recordConstructorNode = new ParseTreeNode(CALTreeParserTokenTypes.RECORD_CONSTRUCTOR, "RECORD_CONSTRUCTOR"); dictionaryNode.setNextSibling(recordConstructorNode); ParseTreeNode baseRecordNode = new ParseTreeNode(CALTreeParserTokenTypes.BASE_RECORD, "BASE_RECORD"); recordConstructorNode.setFirstChild(baseRecordNode); baseRecordNode.setFirstChild(recordVarNode); ParseTreeNode fieldModificationListNode = new ParseTreeNode(CALTreeParserTokenTypes.FIELD_MODIFICATION_LIST, "FIELD_MODIFICATION_LIST"); baseRecordNode.setNextSibling(fieldModificationListNode); Set<TypeClass> varConstraints; if (classInstance == null) { //here we handle the case were a record is not itself a member //of the type class constraint on its fields. varConstraints = Collections.singleton(typeClass); } else { varConstraints = classInstance.getDeclaredPolymorphicVarConstraints().get(0); } for (final Map.Entry<FieldName, TypeExpr> entry : hasFieldsMap.entrySet()) { FieldName hasField = entry.getKey(); TypeExpr hasFieldTypeExpr = entry.getValue(); ParseTreeNode fieldExtensionNode = new ParseTreeNode(CALTreeParserTokenTypes.FIELD_EXTENSION, "FIELD_EXTENSION"); ParseTreeNode hasFieldNameNode; int nodeKind; if (hasField instanceof FieldName.Textual) { nodeKind = CALTreeParserTokenTypes.VAR_ID; } else if (hasField instanceof FieldName.Ordinal){ nodeKind = CALTreeParserTokenTypes.ORDINAL_FIELD_NAME; } else { throw new IllegalStateException(); } hasFieldNameNode = new ParseTreeNode(nodeKind, hasField.getCalSourceForm()); fieldExtensionNode.setFirstChild(hasFieldNameNode); //in most cases record instances have the simple form instance C r => C {r} where ... i.e. the class constraining the fields in a //record is exactly the same as the class constraining the record. However, we do have instance ChartItem r => Chartable {r} and //there may be a potential for some other cases in the future where varConstraints has cardinality other than 1. for (final TypeClass constrainingTypeClass : varConstraints) { ParseTreeNode argumentDictionaryNode = getOverloadingArgument (hasFieldTypeExpr, constrainingTypeClass, overloadingInfo, apInfo); fieldExtensionNode.addChild(argumentDictionaryNode); } fieldModificationListNode.addChild(fieldExtensionNode); } if (isSingleMethodRootClassUsingDefaultClassMethod) { return patchApplicationNode(applicationNode); } return applicationNode; } else if (inContextTypeExpr instanceof TypeVar) { TypeVar inContextTypeVar = (TypeVar) inContextTypeExpr; //if typeVar was instantiated to another TypeVar, then this other TypeVar must //occur in the constrained type variables of the OverloadingInfo object or one of its //ancestors. Otherwise, this is an ambiguous function declaration. //A simple example of this is: //read :: Read a => String -> a; //show :: Show a => a -> String; //readShow :: String -> String; //readShow s = show (read s); //If Boolean and Int are both instances of Read and Show, then it is unclear how to //resolve the overloading. The constraining type information is lost in the type //of readShow, and it is also not supplied by the application context (from which //one can only infer that a is in the classes Read and Show). //the interesting point here is that the overloading at equals must be resolved by adding //a dictionary argument to equalsTest and not to result. This is why we must consider ancestors //as well. //equalsTest :: Eq a => a -> a -> Boolean; //equalsTest x y = // let // result = equals x y; // in // result; OverloadingInfo resolvingOverloadingInfo = overloadingInfo.getResolvingOverloadingInfo(inContextTypeVar); if (resolvingOverloadingInfo == null) { //ambiguous type signature in inferred type compiler.logMessage(new CompilerMessage(apInfo.getAppliedFunctionNode(), new MessageKind.Error.AmbiguousTypeSignatureInInferredType(inContextTypeExpr.toString()))); } TypeClass functionTypeClass = getFunctionTypeClass(typeClass, inContextTypeVar); String dictVarName = resolvingOverloadingInfo.getDictionaryVariableName(inContextTypeVar, functionTypeClass); if (functionTypeClass == typeClass) { //no need for dictionary switching return ParseTreeNode.makeQualifiedVarNode(currentModuleTypeInfo.getModuleName(), dictVarName, null); } else { //add an argument for dictionary switching return getDictionarySwitchingNode (typeClass, functionTypeClass, dictVarName); } } throw new UnsupportedOperationException("unexpected typeExpr for overload resolution"); } /** * Returns an expression that takes a dictionary variable for the descendant type class and returns a dictionary of the * ancestor type class. * @param ancestorTypeClass * @param descendantTypeClass * @param dictVarName * @return ParseTreeNode */ private ParseTreeNode getDictionarySwitchingNode(TypeClass ancestorTypeClass, TypeClass descendantTypeClass, String dictVarName) { //For each class A and subclass B there is conceptually a hidden function "$getA#B" //that gets the A dictionary from the B dictionary. This function takes an Int //and returns a dictionary (which is not a well-typed entity, but one that makes //sense to the evaluator). Suppose that A is the nth class in the traversal of //superclasses of B. Then: "$getA#B $dictvarB = $dictvarB n;" //Since the dictionary switching function's implementation is so simple, and it is only ever //used in a fully saturated form, we always inline this. Thus this function returns: //(@ dictVarName n). int index = descendantTypeClass.getAncestorClassIndex(ancestorTypeClass); if (index == -1) { throw new IllegalStateException(); } ParseTreeNode dictionaryVarNode = ParseTreeNode.makeQualifiedVarNode(currentModuleTypeInfo.getModuleName(), dictVarName, null); ParseTreeNode switchIndexNode = ParseTreeNode.makeIntLiteralNodeWithIntegerValue(index); dictionaryVarNode.setNextSibling(switchIndexNode); ParseTreeNode applicationNode = new ParseTreeNode(CALTreeParserTokenTypes.APPLICATION, "@"); applicationNode.setFirstChild(dictionaryVarNode); return applicationNode; } private static SortedSet<TypeClass> getTypeClassConstraintSet(PolymorphicVar polyVar) { //todoBI get rid of this when PolymorphicVar becomes an abstract base class of //TypeVar and RecordVar, and we can then make getTypeClassConstraintSet virtual //without making its scope public. if (polyVar instanceof TypeVar) { return ((TypeVar)polyVar).getTypeClassConstraintSet(); } else if (polyVar instanceof RecordVar){ return ((RecordVar)polyVar).getTypeClassConstraintSet(); } throw new UnsupportedOperationException(); } }