/* * 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. */ /* * ClassInstanceChecker.java * Creation date (Aug 28, 2002). * By: Bo Ilic */ package org.openquark.cal.compiler; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import org.openquark.cal.compiler.ClassInstance.InstanceStyle; import org.openquark.cal.internal.module.Cal.Core.CAL_Debug_internal; import org.openquark.cal.internal.module.Cal.Core.CAL_Prelude_internal; import org.openquark.cal.internal.module.Cal.Utilities.CAL_QuickCheck_internal; import org.openquark.cal.module.Cal.Core.CAL_Debug; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.module.Cal.Utilities.CAL_QuickCheck; /** * Checks that class instances are semantically correct and adds ClassInstance objects to the ModuleTypeInfo * for the current module. * * Also handles creation of internally defined instances for the Typeable type class * as well as for derived instances (which are defined implicitly in the deriving clauses * of data declarations). * * Creation date (Aug 28, 2002). * @author Bo Ilic */ final class ClassInstanceChecker { private final CALCompiler compiler; /** class instances for the current module are added to the ModuleTypeInfo for the current module being compiler. */ private final ModuleTypeInfo currentModuleTypeInfo; private final TypeClassChecker typeClassChecker; private final DataDeclarationChecker dataDeclarationChecker; /** used to look up the parse-tree of a class instance definition for the current module. */ private final Map<ClassInstanceIdentifier, ParseTreeNode> classInstanceMap; /** A CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN node defining a derived instance function, whose next sibling (if any) is a derived instance function.*/ private ParseTreeNode derivedInstanceFunctions; /** The generator for derived instance functions. */ private final DerivedInstanceFunctionGenerator derivedInstanceFunctionGenerator; /** * Method ClassInstanceChecker. * @param compiler * @param currentModuleTypeInfo * @param typeClassChecker * @param dataDeclarationChecker */ ClassInstanceChecker (CALCompiler compiler, ModuleTypeInfo currentModuleTypeInfo, TypeClassChecker typeClassChecker, DataDeclarationChecker dataDeclarationChecker) { if (compiler == null || currentModuleTypeInfo == null || typeClassChecker == null || dataDeclarationChecker == null) { throw new NullPointerException(); } this.compiler = compiler; this.currentModuleTypeInfo = currentModuleTypeInfo; this.typeClassChecker = typeClassChecker; this.dataDeclarationChecker = dataDeclarationChecker; this.classInstanceMap = new HashMap<ClassInstanceIdentifier, ParseTreeNode>(); this.derivedInstanceFunctionGenerator = DerivedInstanceFunctionGenerator.makeInternalUse(); } /** * Call this method prior to checking the instances declared within the current module. * * The purpose is to verify that there are no overlapping instances brought into * scope within the current module through its imports. * For example, if modules M2 and M3 import M1, and M4 imports M2 and M3, * then a C-T instance defined in both M2 and M3 will result in an error in M4 due to * overlapping instances. * * @param moduleDefnNode */ private void checkForOverlappingInstances(ParseTreeNode moduleDefnNode) { moduleDefnNode.verifyType(CALTreeParserTokenTypes.MODULE_DEFN); checkForOverlappingInstances(moduleDefnNode, currentModuleTypeInfo, new HashSet<ModuleName>(), new HashMap<ClassInstanceIdentifier, ClassInstance>()); } /** * @param moduleDefnNode * @param moduleTypeInfo * @param visitedModules Set of String objects giving the names of modules visited so far * @param instanceMap (ClassInstanceIdentifier -> ClassInstance) */ private void checkForOverlappingInstances (ParseTreeNode moduleDefnNode, ModuleTypeInfo moduleTypeInfo, Set<ModuleName> visitedModules, Map<ClassInstanceIdentifier, ClassInstance> instanceMap) { ModuleName moduleName = moduleTypeInfo.getModuleName(); if (!visitedModules.add(moduleName)) { return; } for (int i = 0, nInstances = moduleTypeInfo.getNClassInstances(); i < nInstances; ++i) { ClassInstance instance = moduleTypeInfo.getNthClassInstance(i); ClassInstanceIdentifier instanceIdentifier = instance.getIdentifier(); ClassInstance overlappingInstance = instanceMap.put(instanceIdentifier, instance); if (overlappingInstance != null) { String declName = instance.getNameWithContext(); String otherDeclName = overlappingInstance.getNameWithContext(); MessageKind message; if(declName.equals(otherDeclName)) { // ClassInstanceChecker: the instance {declName} has multiple visible definitions. // One is in module {instance.getModuleName()} and the other is in module {overlappingInstance.getModuleName()}. message = new MessageKind.Error.InstanceHasMultipleVisibleDefinitions(declName, instance.getModuleName(), overlappingInstance.getModuleName()); } else { // ClassInstanceChecker: the instance {declName} defined in module {instance.getModuleName()} overlaps with // the instance {otherDeclName} defined in module {overlappingInstance.getModuleName()}. message = new MessageKind.Error.InstancesOverlap(declName, instance.getModuleName(), otherDeclName, overlappingInstance.getModuleName()); } compiler.logMessage(new CompilerMessage(moduleDefnNode, message)); } } for (int i = 0, nImportedModules = moduleTypeInfo.getNImportedModules(); i < nImportedModules; ++i) { checkForOverlappingInstances (moduleDefnNode, moduleTypeInfo.getNthImportedModule(i), visitedModules, instanceMap); } } /** * For each type constructor T defined in the module M, such that all of its type variables have kind *, * add a built-in Prelude.Typeable M.T instance. * * It the type arity of T is > 0, this will be a constrained instance. * * For example, for Either a b, it is * * instance (Typeable a, Typeable b) => Typeable (Either a b) where * typeOf = $typeOfEither; * ; * * $typeOfEither is a hidden compiler supplied function. Its definition in CAL source formerly (prior to being built-in * and automatically supplied for each type) was: * * typeOfEither :: (Typeable a, Typeable b) => Either a b -> TypeRep; * private typeOfEither x = * let * leftType :: Either a b -> a; * leftType = undefined; * rightType :: Either a b -> b; * rightType = undefined; * in * TypeRep "Cal.Core.Prelude.Either" [typeOf (leftType x), typeOf (rightType x)]; * * The tricky part here is that the argument x cannot be touched in any way by the function typeOfEither. * * Note that the reason for the restriction on kinds is that, for example, if * data Foo a b = MakeFoo f::(a b); * then Foo has kind (* -> *) -> * -> * * and an automatic instance definition would be for * instance (Typeable a, Typeable b) => Typeable (Foo a b) where ... * but the type class Typeable has kind *, and the type variable a, of kind * -> * i.e. this instance * declaration violates static type checking. */ private void addTypeableInstances() { if (currentModuleTypeInfo.isExtension()) { //don't add instances again for an adjunct module. return; } final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); //this uses the fact that Prelude.Typeable is a public class, and all modules must import the Prelude. final TypeClass typeableTypeClass = currentModuleTypeInfo.getVisibleTypeClass(CAL_Prelude.TypeClasses.Typeable); SortedSet<TypeClass> constraintSet = TypeClass.makeNewClassConstraintSet(); constraintSet.add(typeableTypeClass); constraintSet = Collections.unmodifiableSortedSet(constraintSet); for (int i = 0, nTypeConstructors = currentModuleTypeInfo.getNTypeConstructors(); i < nTypeConstructors; ++i) { final TypeConstructor typeCons = currentModuleTypeInfo.getNthTypeConstructor(i); //we only add a Typeable instance for type constructors all of whose type arguments have arity *. if (typeCons.getKindExpr().isSimpleKindChain()) { final int arity = typeCons.getTypeArity(); final TypeExpr[] args = new TypeExpr[arity]; for (int j = 0; j < arity; ++j) { args[j] = TypeVar.makeTypeVar(null, constraintSet, false); } final TypeConsApp typeConsApp = new TypeConsApp(typeCons, args); final ClassInstance classInstance = new ClassInstance(currentModuleName, typeConsApp, typeableTypeClass, null, ClassInstance.InstanceStyle.INTERNAL); currentModuleTypeInfo.addClassInstance(classInstance); } } } /** * Check the derived instances defined in this module i.e. the instances implied by the deriving clauses * of data declarations. * * This function * a) verifies that the derived instance is valid for the particular type for type-theoretic reasons * (earlier static checks were done when the data declaration was initially checked) * b) creates the internally defined derived instance definitions * c) creates the internal helper functions which define the derived instance functions * */ private void checkDerivedInstances() throws UnableToResolveForeignEntityException { if (currentModuleTypeInfo.isExtension()) { //don't add derived instances again for an adjunct module. return; } final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); for (int i = 0, nTypeConstructors = currentModuleTypeInfo.getNTypeConstructors(); i < nTypeConstructors; ++i) { TypeConstructor typeCons = currentModuleTypeInfo.getNthTypeConstructor(i); //use private functions equalsInt, lessThanInt etc as the instance resolving functions for //the Eq, Ord and Enum instances of an enum type. This saves class file space and which could be quite bulky for //long enum types, since the Ord methods do case-of-cases and so have length proportional to the square of //the number of data constructors. It also saves on compilation time. Benchmarks show that there is no //runtime performance impact. final boolean useCompactEnumDerivedInstances = TypeExpr.isEnumType(typeCons) && !typeCons.getName().equals(CAL_Prelude.TypeConstructors.Boolean); //this flag is used to ensure that the fromInt toInt Helper functions //are added at most once for each instance type boolean addedIntHelpers = false; for (int j = 0, nDerivedInstances = typeCons.getNDerivedInstances(); j < nDerivedInstances; ++j) { QualifiedName typeClassName = typeCons.getDerivingClauseTypeClassName(j); TypeClass typeClass = currentModuleTypeInfo.getVisibleTypeClass(typeClassName); final SourceRange declarationPosition = typeCons.getDerivingClauseTypeClassPosition(j); //If the type is T a b c, then the instance type for type class C will be //(C a, C b, C c) => T a b c TypeConsApp instanceType = makeInstanceType(typeCons, typeClass); ClassInstance classInstance; //make sure the toInt fromInt helpers are added if the //class needs them if (!addedIntHelpers && (typeClassName.equals(CAL_Prelude.TypeClasses.Enum) || typeClassName.equals(CAL_Prelude.TypeClasses.IntEnum) || typeClassName.equals(CAL_QuickCheck.TypeClasses.Arbitrary))) { TypeExpr intType = compiler.getTypeChecker().getTypeConstants().getIntType(); TypeExpr fromIntHelperTypeExpr = TypeExpr.makeFunType(intType, instanceType); TypeExpr toIntHelperTypeExpr = TypeExpr.makeFunType(instanceType, intType); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeFromIntHelper(typeCons), fromIntHelperTypeExpr, declarationPosition); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeToIntHelper(typeCons), toIntHelperTypeExpr, declarationPosition); addedIntHelpers = true; } //add the definitions of the instance functions if (typeClassName.equals(CAL_Prelude.TypeClasses.Eq)) { classInstance = checkDerivedEqInstance(typeCons, instanceType, typeClass, useCompactEnumDerivedInstances, declarationPosition); } else if (typeClassName.equals(CAL_Prelude.TypeClasses.Ord)) { classInstance = checkDerivedOrdInstance(typeCons, instanceType, typeClass, useCompactEnumDerivedInstances, declarationPosition); } else if (typeClassName.equals(CAL_Prelude.TypeClasses.Bounded)) { addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeMinBoundInstanceFunction(typeCons), instanceType, declarationPosition); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeMaxBoundInstanceFunction(typeCons), instanceType, declarationPosition); classInstance = new ClassInstance(currentModuleName, instanceType, typeClass, null, ClassInstance.InstanceStyle.DERIVING); } else if (typeClassName.equals(CAL_Prelude.TypeClasses.Enum)) { TypeExpr listTypeExpr = compiler.getTypeChecker().getTypeConstants().makeListType(instanceType); TypeExpr intType = compiler.getTypeChecker().getTypeConstants().getIntType(); TypeExpr upFromTypeExpr = TypeExpr.makeFunType(instanceType, listTypeExpr); TypeExpr upFromThenTypeExpr = TypeExpr.makeFunType(instanceType, upFromTypeExpr); TypeExpr upFromThenToTypeExpr = TypeExpr.makeFunType(instanceType, upFromThenTypeExpr); TypeExpr upFromToTypeExpr = TypeExpr.makeFunType(instanceType, upFromTypeExpr); TypeExpr upFromToHelperTypeExpr = TypeExpr.makeFunType(intType, TypeExpr.makeFunType(intType, listTypeExpr)); TypeExpr upFromThenToHelperExpr = TypeExpr.makeFunType(intType, TypeExpr.makeFunType(intType, TypeExpr.makeFunType(intType, listTypeExpr))); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeUpFromToHelper(typeCons), upFromToHelperTypeExpr, declarationPosition); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeUpFromThenToHelperUp(typeCons), upFromThenToHelperExpr, declarationPosition); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeUpFromThenToHelperDown(typeCons), upFromThenToHelperExpr, declarationPosition); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeEnumUpFromThenInstanceFunction(typeCons), upFromThenTypeExpr, declarationPosition); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeEnumUpFromThenToInstanceFunction(typeCons), upFromThenToTypeExpr, declarationPosition); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeEnumUpFromToInstanceFunction(typeCons), upFromToTypeExpr, declarationPosition); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeEnumUpFromInstanceFunction(typeCons), upFromTypeExpr, declarationPosition); classInstance = new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] { ClassInstance.makeInternalInstanceMethodName(CAL_Prelude.Functions.upFrom.getUnqualifiedName(), typeCons.getName()), ClassInstance.makeInternalInstanceMethodName(CAL_Prelude.Functions.upFromThen.getUnqualifiedName(), typeCons.getName()), ClassInstance.makeInternalInstanceMethodName(CAL_Prelude.Functions.upFromTo.getUnqualifiedName(), typeCons.getName()), ClassInstance.makeInternalInstanceMethodName(CAL_Prelude.Functions.upFromThenTo.getUnqualifiedName(), typeCons.getName()) }, ClassInstance.InstanceStyle.DERIVING); } else if (typeClassName.equals(CAL_Prelude.TypeClasses.Outputable)) { classInstance = checkDerivedOutputableInstance(typeCons, instanceType, typeClass, declarationPosition); } else if (typeClassName.equals(CAL_Prelude.TypeClasses.Inputable)) { if(typeCons.getNDataConstructors() > 0) { TypeExpr jObjectTypeExpr = TypeExpr.makeNonParametricType(currentModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.JObject)); TypeExpr inputTypeExpr = TypeExpr.makeFunType(jObjectTypeExpr, instanceType); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeAlgebraicInputInstanceMethod(typeCons), inputTypeExpr, declarationPosition); classInstance = new ClassInstance(currentModuleName, instanceType, typeClass, null, ClassInstance.InstanceStyle.DERIVING); } else { //will be a foreign type (and not a built-in type or type defined by an algebraic data declaration). //Unlike any of the other derived instances, the class methods for the derived Inputable class are //added late in the ExpressionGenerator. This is because //a) there is a distinct method for each foreign object type (it must do a cast, and that cast is type specific. //b) there is no direct way to represent the cast in CAL (although derived Inputable and Outputable instances can // be used to do the job, once this feature is in place!) classInstance = new ClassInstance(currentModuleName, instanceType, typeClass, null, ClassInstance.InstanceStyle.DERIVING); } } else if (typeClassName.equals(CAL_Debug.TypeClasses.Show)) { classInstance = checkDerivedShowInstance(typeCons, instanceType, typeClass, declarationPosition); } else if (typeClassName.equals(CAL_QuickCheck.TypeClasses.Arbitrary)) { if (typeCons.getForeignTypeInfo() != null) { throw new IllegalStateException("Arbitrary instances cannot be derived for foreign types"); } //this type represents a Gen for the current instance type. TypeExpr genInstanceType = new TypeConsApp( currentModuleTypeInfo.getVisibleTypeConstructor(CAL_QuickCheck.TypeConstructors.Gen), new TypeExpr [] {instanceType}); //this type represents the type (Gen a) TypeExpr genAType = new TypeConsApp( currentModuleTypeInfo.getVisibleTypeConstructor(CAL_QuickCheck.TypeConstructors.Gen), new TypeExpr [] {TypeExpr.makeParametricType()} ); TypeExpr coarbitraryFunctionType = TypeExpr.makeFunType(instanceType, TypeExpr.makeFunType(genAType, genAType)); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeArbitraryFunction(typeCons), genInstanceType, declarationPosition); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeCoArbitraryFunction(typeCons), coarbitraryFunctionType, declarationPosition); classInstance = new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] { ClassInstance.makeInternalInstanceMethodName("arbitrary", typeCons.getName()), ClassInstance.makeInternalInstanceMethodName("coarbitrary", typeCons.getName()), CAL_QuickCheck_internal.Functions.generateInstanceDefault }, InstanceStyle.DERIVING); } else if (typeClassName.equals(CAL_Prelude.TypeClasses.IntEnum)) { // algebraic types (only enumeration types are permitted; the DerivedInstanceFunctionGenerator methods will check that) if(typeCons.getForeignTypeInfo() != null) { throw new IllegalStateException("IntEnum instances cannot be derived for foreign types"); } TypeExpr maybeTypeExpr = new TypeConsApp( currentModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.Maybe), new TypeExpr[] {instanceType}); TypeExpr intType = compiler.getTypeChecker().getTypeConstants().getIntType(); TypeExpr intToEnumTypeExpr = TypeExpr.makeFunType(intType, instanceType); TypeExpr intToEnumCheckedTypeExpr = TypeExpr.makeFunType(intType, maybeTypeExpr); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeEnumIntToEnumFunction(typeCons), intToEnumTypeExpr, declarationPosition); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeEnumIntToEnumCheckedFunction(typeCons), intToEnumCheckedTypeExpr, declarationPosition); classInstance = new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] { ClassInstance.makeInternalInstanceMethodName("intToEnum", typeCons.getName()), ClassInstance.makeInternalInstanceMethodName("intToEnumChecked", typeCons.getName()), ClassInstance.makeInternalInstanceMethodName("toIntHelper", typeCons.getName())}, InstanceStyle.DERIVING); } else { // static checks in DataDeclarationChecker will have logged an error message for classes that we cannot // derive an instance for, so this exception won't be shown to the user as an internal coding error (although it // will cause further compilation to halt with "unable to recover from previous compilation errors"). throw new IllegalStateException("Invalid deriving type class."); } currentModuleTypeInfo.addClassInstance(classInstance); } } } /** * A helper function to create the instance type for a derived instance. * If the type is T a b c, then the instance type for type class C will be * (C a, C b, C c) => T a b c * * @param typeCons * @param typeClass * @return the TypeConsApp object representing the instance type. */ static TypeConsApp makeInstanceType(TypeConstructor typeCons, TypeClass typeClass) { SortedSet<TypeClass> constraintSet = TypeClass.makeNewClassConstraintSet(); constraintSet.add(typeClass); constraintSet = Collections.unmodifiableSortedSet(constraintSet); final int arity = typeCons.getTypeArity(); final TypeExpr[] args = new TypeExpr[arity]; for (int k = 0; k < arity; ++k) { args[k] = TypeVar.makeTypeVar(null, constraintSet, false); } return new TypeConsApp(typeCons, args); } /** * A helper function to create the ClassInstance object for a derived Eq instance. * @param typeCons * @param instanceType * @param typeClass * @param useCompactEnumDerivedInstances * @param sourcePosition TODO * @return the ClassInstance object for the derived Eq instance */ private ClassInstance checkDerivedEqInstance(TypeConstructor typeCons, TypeConsApp instanceType, TypeClass typeClass, boolean useCompactEnumDerivedInstances, SourceRange sourcePosition) throws UnableToResolveForeignEntityException { if (!typeClass.getName().equals(CAL_Prelude.TypeClasses.Eq)) { throw new IllegalStateException(); } ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); ForeignTypeInfo foreignTypeInfo = typeCons.getForeignTypeInfo(); if (foreignTypeInfo != null) { // Foreign types // CAL has primitive ops for the Eq methods of each primitive Java type Class<?> foreignType = foreignTypeInfo.getForeignType(); if(foreignType.isPrimitive()) { if(foreignType == boolean.class) { // The Boolean Eq instance is derived, so its instance functions will have // standard internal names rather than standard primitive names return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] { ClassInstance.makeInternalInstanceMethodName("equals", CAL_Prelude.TypeConstructors.Boolean), ClassInstance.makeInternalInstanceMethodName("notEquals", CAL_Prelude.TypeConstructors.Boolean)}, InstanceStyle.DERIVING); } else { // The instance functions for the non-boolean primitive types are named in a standard way, // so we can generate the name of each method based on the name of the type. String foreignTypeName = foreignType.getName(); return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] { makePrimitiveInstanceMethodName("equals", foreignTypeName), makePrimitiveInstanceMethodName("notEquals", foreignTypeName)}, InstanceStyle.DERIVING); } // For foreign types that represent non-primitive Java types (reference types), // we delegate to the methods of defined for Objects. } else { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] { CAL_Prelude_internal.Functions.equalsObject, CAL_Prelude_internal.Functions.notEqualsObject}, InstanceStyle.DERIVING); } } else { // Non-foreign types if (useCompactEnumDerivedInstances){ return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[]{ CAL_Prelude_internal.Functions.equalsInt, CAL_Prelude_internal.Functions.notEqualsInt}, InstanceStyle.DERIVING); } else { TypeExpr booleanType = compiler.getTypeChecker().getTypeConstants().getBooleanType(); TypeExpr equalsTypeExpr = TypeExpr.makeFunType(instanceType, TypeExpr.makeFunType(instanceType, booleanType)); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeEqualsInstanceFunction(typeCons), equalsTypeExpr, sourcePosition); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeNotEqualsInstanceFunction(typeCons), equalsTypeExpr, sourcePosition); return new ClassInstance(currentModuleName, instanceType, typeClass, null, InstanceStyle.DERIVING); } } } /** * A helper function to create the ClassInstance object for a derived Ord instance. * @param typeCons * @param instanceType * @param typeClass * @param useCompactEnumDerivedInstances * @param position TODO * @return the ClassInstance object for the derived Ord instance */ private ClassInstance checkDerivedOrdInstance(TypeConstructor typeCons, TypeConsApp instanceType, TypeClass typeClass, boolean useCompactEnumDerivedInstances, SourceRange position) throws UnableToResolveForeignEntityException { if (!typeClass.getName().equals(CAL_Prelude.TypeClasses.Ord)) { throw new IllegalStateException(); } ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); ForeignTypeInfo foreignTypeInfo = typeCons.getForeignTypeInfo(); if (foreignTypeInfo != null) { // Foreign types // CAL has primitive ops for the Ord methods for each primitive Java type Class<?> foreignType = foreignTypeInfo.getForeignType(); if(foreignType.isPrimitive()) { if(foreignType == boolean.class) { // The Boolean Ord instance is derived, so its instance functions will have // standard internal names rather than standard primitive names return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] { ClassInstance.makeInternalInstanceMethodName("lessThan", CAL_Prelude.TypeConstructors.Boolean), ClassInstance.makeInternalInstanceMethodName("lessThanEquals", CAL_Prelude.TypeConstructors.Boolean), ClassInstance.makeInternalInstanceMethodName("greaterThanEquals", CAL_Prelude.TypeConstructors.Boolean), ClassInstance.makeInternalInstanceMethodName("greaterThan", CAL_Prelude.TypeConstructors.Boolean), ClassInstance.makeInternalInstanceMethodName("compare", CAL_Prelude.TypeConstructors.Boolean), ClassInstance.makeInternalInstanceMethodName("max", CAL_Prelude.TypeConstructors.Boolean), ClassInstance.makeInternalInstanceMethodName("min", CAL_Prelude.TypeConstructors.Boolean) }, InstanceStyle.DERIVING); } else { // The Ord instance functions for the non-boolean primitive types are named in a standard way, // so we can generate the name of each method based on the name of the type. String foreignTypeName = foreignType.getName(); return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] { makePrimitiveInstanceMethodName("lessThan", foreignTypeName), makePrimitiveInstanceMethodName("lessThanEquals", foreignTypeName), makePrimitiveInstanceMethodName("greaterThanEquals", foreignTypeName), makePrimitiveInstanceMethodName("greaterThan", foreignTypeName), makePrimitiveInstanceMethodName("compare", foreignTypeName), makePrimitiveInstanceMethodName("max", foreignTypeName), makePrimitiveInstanceMethodName("min", foreignTypeName) }, InstanceStyle.DERIVING); } // For foreign types that represent non-primitive Java types (reference types), // we delegate to the methods of the Comparable interface. } else { // Foreign reference types must implement Comparable to have derived Ord instances if(!Comparable.class.isAssignableFrom(foreignTypeInfo.getForeignType())) { throw new IllegalStateException("Foreign reference types can only derive an Ord instance if they implement Comparable"); } return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] { CAL_Prelude_internal.Functions.lessThanComparable, CAL_Prelude_internal.Functions.lessThanEqualsComparable, CAL_Prelude_internal.Functions.greaterThanEqualsComparable, CAL_Prelude_internal.Functions.greaterThanComparable, CAL_Prelude_internal.Functions.compareComparable, CAL_Prelude_internal.Functions.maxComparable, CAL_Prelude_internal.Functions.minComparable }, InstanceStyle.DERIVING); } } else { // Non-foreign types if (useCompactEnumDerivedInstances){ return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[]{ CAL_Prelude_internal.Functions.lessThanInt, CAL_Prelude_internal.Functions.lessThanEqualsInt, CAL_Prelude_internal.Functions.greaterThanEqualsInt, CAL_Prelude_internal.Functions.greaterThanInt, CAL_Prelude_internal.Functions.compareInt, CAL_Prelude_internal.Functions.maxInt, CAL_Prelude_internal.Functions.minInt}, InstanceStyle.DERIVING); } else { TypeExpr booleanType = compiler.getTypeChecker().getTypeConstants().getBooleanType(); TypeExpr lessThanTypeExpr = TypeExpr.makeFunType(instanceType, TypeExpr.makeFunType(instanceType, booleanType)); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeLessThanInstanceFunction(typeCons), lessThanTypeExpr, position); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeLessThanEqualsInstanceFunction(typeCons), lessThanTypeExpr, position); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeGreaterThanEqualsInstanceFunction(typeCons), lessThanTypeExpr, position); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeGreaterThanInstanceFunction(typeCons), lessThanTypeExpr, position); //this uses the fact that Prelude.Ordering is a public class, and all modules must import the Prelude. final TypeConstructor orderingTypeCons = currentModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.Ordering); TypeExpr compareTypeExpr = TypeExpr.makeFunType(instanceType, TypeExpr.makeFunType(instanceType, TypeExpr.makeNonParametricType(orderingTypeCons))); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeCompareInstanceFunction(typeCons), compareTypeExpr, position); TypeExpr maxTypeExpr = TypeExpr.makeFunType(instanceType, TypeExpr.makeFunType(instanceType, instanceType)); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeMaxInstanceFunction(typeCons), maxTypeExpr, position); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeMinInstanceFunction(typeCons), maxTypeExpr, position); return new ClassInstance(currentModuleName, instanceType, typeClass, null, InstanceStyle.DERIVING); } } } /** * A helper function to create the ClassInstance object for a derived Outputable instance. * @param typeCons * @param instanceType * @param typeClass * @param position TODO * @return the ClassInstance object for the derived Outputable instance */ private ClassInstance checkDerivedOutputableInstance(TypeConstructor typeCons, TypeConsApp instanceType, TypeClass typeClass, SourceRange position) throws UnableToResolveForeignEntityException { if (!typeClass.getName().equals(CAL_Prelude.TypeClasses.Outputable)) { throw new IllegalStateException(); } if (TypeConstructor.getBuiltInType(typeCons.getName()) != null) { throw new IllegalStateException("The built-in type " + typeCons.getName() + " cannot derive the Outputable type class."); } ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); ForeignTypeInfo foreignTypeInfo = typeCons.getForeignTypeInfo(); if (foreignTypeInfo != null) { // foreign types Class<?> foreignType = foreignTypeInfo.getForeignType(); if(foreignType.isPrimitive()) { if(foreignType == boolean.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Prelude_internal.Functions.outputBoolean}, InstanceStyle.DERIVING); } else if (foreignType == byte.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Prelude_internal.Functions.outputByte}, InstanceStyle.DERIVING); } else if (foreignType == char.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Prelude_internal.Functions.outputChar}, InstanceStyle.DERIVING); } else if (foreignType == short.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Prelude_internal.Functions.outputShort}, InstanceStyle.DERIVING); } else if (foreignType == int.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Prelude_internal.Functions.outputInt}, InstanceStyle.DERIVING); } else if (foreignType == long.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Prelude_internal.Functions.outputLong}, InstanceStyle.DERIVING); } else if (foreignType == float.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Prelude_internal.Functions.outputFloat}, InstanceStyle.DERIVING); } else if (foreignType == double.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Prelude_internal.Functions.outputDouble}, InstanceStyle.DERIVING); } else { throw new IllegalStateException("A type that claims to be primitive is not one of the known primitive types"); } } else { // Defer to Prelude.outputJObject for non-primitive foreign values return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Prelude_internal.Functions.outputJObject}, InstanceStyle.DERIVING); } } else { // Types declared using an algebraic data definition // The instance function will use an AlgebraicValue container object TypeExpr jObjectTypeExpr = TypeExpr.makeNonParametricType(currentModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.JObject)); TypeExpr outputTypeExpr = TypeExpr.makeFunType(instanceType, jObjectTypeExpr); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeAlgebraicOutputInstanceMethod(typeCons), outputTypeExpr, position); return new ClassInstance(currentModuleName, instanceType, typeClass, null, InstanceStyle.DERIVING); } } /** * A helper function to create the ClassInstance object for a derived Show instance. * @param typeCons * @param instanceType * @param typeClass * @param position TODO * @return the ClassInstance object for the derived Show instance */ private ClassInstance checkDerivedShowInstance(TypeConstructor typeCons, TypeConsApp instanceType, TypeClass typeClass, SourceRange position) throws UnableToResolveForeignEntityException { if (!typeClass.getName().equals(CAL_Debug.TypeClasses.Show)) { throw new IllegalStateException(); } ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); ForeignTypeInfo foreignTypeInfo = typeCons.getForeignTypeInfo(); if (foreignTypeInfo != null) { // foreign types Class<?> foreignType = foreignTypeInfo.getForeignType(); if (foreignType.isPrimitive()) { if (foreignType == boolean.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Debug_internal.Functions.showForeignBoolean}, InstanceStyle.DERIVING); } else if (foreignType == byte.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Debug_internal.Functions.showByte}, InstanceStyle.DERIVING); } else if (foreignType == char.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Debug_internal.Functions.showForeignChar}, InstanceStyle.DERIVING); } else if (foreignType == short.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Debug_internal.Functions.showShort}, InstanceStyle.DERIVING); } else if (foreignType == int.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Debug_internal.Functions.showInt}, InstanceStyle.DERIVING); } else if (foreignType == long.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Debug_internal.Functions.showLong}, InstanceStyle.DERIVING); } else if (foreignType == float.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Debug_internal.Functions.showFloat}, InstanceStyle.DERIVING); } else if (foreignType == double.class) { return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Debug_internal.Functions.showDouble}, InstanceStyle.DERIVING); } else { throw new IllegalStateException("A type that claims to be primitive is not one of the known primitive types"); } } else { // Defer to String.valueOf(Object) for non-primitive foreign values return new ClassInstance(currentModuleName, instanceType, typeClass, new QualifiedName[] {CAL_Debug_internal.Functions.showJObject}, InstanceStyle.DERIVING); } } else { // non-foreign types TypeExpr showTypeExpr = TypeExpr.makeFunType(instanceType, compiler.getTypeChecker().getTypeConstants().getStringType()); addDerivedInstanceFunction(derivedInstanceFunctionGenerator.makeShowInstanceFunction(typeCons), showTypeExpr, position); return new ClassInstance(currentModuleName, instanceType, typeClass, null, InstanceStyle.DERIVING); } } /** * A helper method to add an instance function for a derived instance, * to the environment so that that instance function can go through * further type-checking (most importantly overload resolution). * @param functionDefn * @param declaredFunctionTypeExpr may be null if no type is declared * @param sourceRange TODO */ private void addDerivedInstanceFunction(SourceModel.FunctionDefn.Algebraic functionDefn, TypeExpr declaredFunctionTypeExpr, SourceRange sourceRange) { CALTypeChecker typeChecker = compiler.getTypeChecker(); String functionName = functionDefn.getName(); ParseTreeNode functionParseTree = functionDefn.toParseTreeNode(); functionParseTree.setInternallyGenerated(sourceRange); if (derivedInstanceFunctions == null) { derivedInstanceFunctions = functionParseTree; } else { functionParseTree.setNextSibling(derivedInstanceFunctions); derivedInstanceFunctions = functionParseTree; } typeChecker.addDerivedInstanceFunction(functionName, functionParseTree, declaredFunctionTypeExpr); } /** * Adds the derived instance functions as children of outerDefnListNode so that they can be encoded * by the ExpressionGenerator into core functions * @param outerDefnListNode */ void addDerivedInstanceFunctions(ParseTreeNode outerDefnListNode) { if (derivedInstanceFunctions == null) { return; } outerDefnListNode.verifyType(CALTreeParserTokenTypes.OUTER_DEFN_LIST); ParseTreeNode attachingNode = outerDefnListNode.lastChild(); if (attachingNode == null) { outerDefnListNode.setFirstChild(derivedInstanceFunctions); } else { attachingNode.setNextSibling(derivedInstanceFunctions); } } /** * Static analysis of the class instance declarations defined within the current module. * Verification that the types of the instance methods are what they should be must be * delayed until after type checking the functions defined in this module. * * Also checks that there are no overalapping instances brought into scope in the current module. * * @param moduleLevelParseTrees */ void checkClassInstanceDefinitions(ModuleLevelParseTrees moduleLevelParseTrees) throws UnableToResolveForeignEntityException { checkForOverlappingInstances(moduleLevelParseTrees.getModuleDefnNode()); List<ParseTreeNode> instanceDefnNodes = moduleLevelParseTrees.getInstanceDefnNodes(); //add the built-in class instance declarations "instance Typeable T where ..." for each type T //where this is possible (all of the type variables must have kind *). addTypeableInstances(); checkDerivedInstances(); for (final ParseTreeNode instanceNode : instanceDefnNodes) { instanceNode.verifyType(CALTreeParserTokenTypes.INSTANCE_DEFN); ClassInstance classInstance = checkClassInstanceDefinition(instanceNode); ClassInstanceIdentifier instanceIdentifier = classInstance.getIdentifier(); classInstanceMap.put(instanceIdentifier, instanceNode); } //Now do the static analysis on the instance declarations that must wait for a //second pass over the instance declarations. //If there is a C-T instance, and C' is a superclass of C, then there is a C'-T instance in scope. //So for example, if there is an instance Ord-Foo, there must be an instance Eq-Foo in the current module //or in some module imported directly or indirectly into the current module. //Note: we only need to check immediate superclasses (i.e. parents) since the parents will check for their parents. //The constraints on the type variables in C-T must imply the constraints //on the type variables in C'-T. What this means, is that if (Cij' a) is a constraint for C'-T, then (Dij' a) //is a constraint for D-T where Dij' is Cij' or a subclass. //The reason for this is so that we can derive a dictionary for C'-T given a dictionary for C-T. for (int i = 0, nInstances = currentModuleTypeInfo.getNClassInstances(); i < nInstances; ++i) { ClassInstance classInstance = currentModuleTypeInfo.getNthClassInstance(i); if (classInstance.isUniversalRecordInstance()) { //todoBI it is not valid to skip this check. Need to do more work here in this case. //this path happens during adjunct checking. continue; } TypeClass typeClass = classInstance.getTypeClass(); TypeExpr instanceType = classInstance.getType(); List<Set<TypeClass>> childVarConstraints = classInstance.getSuperclassPolymorphicVarConstraints(); for (int j = 0, nParents = typeClass.getNParentClasses(); j < nParents; ++j) { TypeClass parentClass = typeClass.getNthParentClass(j); ClassInstanceIdentifier parentInstanceId = ClassInstanceIdentifier.make(parentClass.getName(), instanceType); ClassInstance parentInstance = currentModuleTypeInfo.getVisibleClassInstance(parentInstanceId); if (parentInstance == null) { //superclass instance declaration is missing ParseTreeNode instanceNode = classInstanceMap.get(classInstance.getIdentifier()); String requiredParentInstanceName = ClassInstance.getNameWithContext(parentClass, instanceType, ScopedEntityNamingPolicy.FULLY_QUALIFIED); compiler.logMessage(new CompilerMessage(instanceNode, new MessageKind.Error.SuperclassInstanceDeclarationMissing(requiredParentInstanceName, classInstance.getNameWithContext()))); } List<SortedSet<TypeClass>> parentVarConstraints = parentInstance.getDeclaredPolymorphicVarConstraints(); if (parentVarConstraints.size() != childVarConstraints.size()) { //this situation should be handled by earlier checking throw new IllegalArgumentException(); } for (int varN = 0; varN < parentVarConstraints.size(); ++varN) { //the constraints on the varNth type variable that are not implied by the constraints on the child instance Set<TypeClass> unsatisfiedConstraints = new HashSet<TypeClass>(parentVarConstraints.get(varN)); unsatisfiedConstraints.removeAll(childVarConstraints.get(varN)); if (!unsatisfiedConstraints.isEmpty()) { ParseTreeNode instanceNode = classInstanceMap.get(classInstance.getIdentifier()); // ClassInstanceChecker: the constraints on the instance declaration {classInstance.getNameWithContext()} must // imply the constraints on the parent instance declaration {parentInstance.getNameWithContext()}.\n In particular, // the class constraint {((TypeClass)unsatisfiedConstraints.iterator().next()).getName()} on type variable number // {varN} in the parent instance is not implied. compiler.logMessage(new CompilerMessage(instanceNode, new MessageKind.Error.ConstraintsOnInstanceDeclarationMustImplyConstraintsOnParentInstance( classInstance.getNameWithContext(), parentInstance.getNameWithContext(), unsatisfiedConstraints.iterator().next().getName().getQualifiedName(), varN))); break; } } } } } /** * Do various checks on an individual class instance definition. This includes: * <ol> * <li> check that the instance name corresponds to a type class and a type constructor e.g. (Eq Int) that * exist and are visible. * <li> check that each instance method is defined only once in the instance * <li> check that there is an instance method corresponding to every class method without a default, * and no additional instance methods. * </ol> * @param instanceNode * @return ClassInstance */ private ClassInstance checkClassInstanceDefinition(ParseTreeNode instanceNode) { instanceNode.verifyType(CALTreeParserTokenTypes.INSTANCE_DEFN); ParseTreeNode optionalCALDocNode = instanceNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode instanceNameNode = optionalCALDocNode.nextSibling(); //do most of the checking for the part of the instance declaration that occurs between the "instance" and "where" keywords. ClassInstance classInstance = resolveInstanceName (instanceNameNode); ParseTreeNode instanceMethodListNode = instanceNameNode.nextSibling(); instanceMethodListNode.verifyType(CALTreeParserTokenTypes.INSTANCE_METHOD_LIST); TypeClass typeClass = classInstance.getTypeClass(); Set<String> instanceMethodNamesSet = new HashSet<String>(); for (final ParseTreeNode instanceMethodNode : instanceMethodListNode) { instanceMethodNode.verifyType(CALTreeParserTokenTypes.INSTANCE_METHOD); ParseTreeNode optionalInstanceMethodCALDocNode = instanceMethodNode.firstChild(); optionalInstanceMethodCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode instanceMethodNameNode = optionalInstanceMethodCALDocNode.nextSibling(); instanceMethodNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String instanceMethodName = instanceMethodNameNode.getText(); if (!instanceMethodNamesSet.add(instanceMethodName)) { //each instance method can be defined only once compiler.logMessage(new CompilerMessage(instanceMethodNameNode, new MessageKind.Error.MethodDefinedMoreThanOnce(instanceMethodName, classInstance.getNameWithContext()))); continue; } ClassMethod classMethod = typeClass.getClassMethod(instanceMethodName); if (classMethod == null) { //instance method must first be declared by the type class that the instance is an instance of compiler.logMessage(new CompilerMessage(instanceMethodNameNode, new MessageKind.Error.MethodNotDeclaredByClass(instanceMethodName, typeClass.getName().getQualifiedName()))); continue; } ParseTreeNode resolvingFunctionNameNode = instanceMethodNameNode.nextSibling(); QualifiedName resolvingFunctionName = resolveResolvingFunction(resolvingFunctionNameNode); classInstance.addInstanceMethod(classMethod, resolvingFunctionName); } //check that the instance has an instance method defined for each class method in the type class that does not have //a default class method. if (typeClass.getNClassMethods() != instanceMethodNamesSet.size()) { //(String set) the class methods that are required to be implemented (because they have no defaults) but were not in this instance. Set<String> unimplementedMethodsNamesSet = new LinkedHashSet<String>(); { for (int i = 0, nClassMethods = typeClass.getNClassMethods(); i < nClassMethods; ++i) { ClassMethod classMethod = typeClass.getNthClassMethod(i); if (!classMethod.hasDefaultClassMethod()) { unimplementedMethodsNamesSet.add(classMethod.getName().getUnqualifiedName()); } } unimplementedMethodsNamesSet.removeAll(instanceMethodNamesSet); } for (final String methodName : unimplementedMethodsNamesSet) { // ClassInstanceChecker: the method {methodName} is not defined by the instance {classInstance.getNameWithContext()}. compiler.logMessage(new CompilerMessage(instanceNode, new MessageKind.Error.MethodNotDefinedByInstance(methodName, classInstance.getNameWithContext()))); } } return classInstance; } /** * Resolve an instance name such as "Num JBigInteger" to "Prelude.Num BigInteger.JBigInteger". * Other examples of instance names: "(Eq a, Eq b) => Eq (Tuple2 a b)" and (Eq a) => Eq (List a)" * In particular, the type class and type constructor defining the instance must be resolvable. * * The method returns a partially constructed ClassInstance object and adds it to the current module. * * The method also does a number of static checks on the validity of the instance name (i.e. everything * that appears between "instance" and "where". See the comments below for details. * * @param instanceNameNode * @return ClassInstance the partially constructed ClassInstance object. */ private ClassInstance resolveInstanceName(ParseTreeNode instanceNameNode) { //For the comments in this method we use the following prototype instance declaration: //"instance (C1 a1, C2 a2, ..., Cn an) => C (T b1 ... bm) where ..." //where Ci = (Ci1, Ci2, .. Cij) where j is a function of i, and the ai are distinct. //This is called a C-T instance declaration because it declares that the type T is an instance of the class C. instanceNameNode.verifyType(CALTreeParserTokenTypes.INSTANCE_NAME); ParseTreeNode contextListNode = instanceNameNode.firstChild(); contextListNode.verifyType(CALTreeParserTokenTypes.CLASS_CONTEXT_LIST, CALTreeParserTokenTypes.CLASS_CONTEXT_SINGLETON, CALTreeParserTokenTypes.CLASS_CONTEXT_NOTHING); ParseTreeNode classNameNode = contextListNode.nextSibling(); //resolve the class C i.e. make sure it is visible within the current module, and give it a fully qualified name if //it was referenced without the module qualifier. TypeClass typeClass = typeClassChecker.resolveClassName(classNameNode); ParseTreeNode instanceTypeConsNameNode = classNameNode.nextSibling(); TypeConstructor typeCons = null; //the number of type variables appearing in the CAL source (may be incorrect for the type constructor, in which case we //produce a compilation error. int nTypeVariables = -1; ParseTreeNode firstTypeVarNode = null; switch (instanceTypeConsNameNode.getType()) { case CALTreeParserTokenTypes.GENERAL_TYPE_CONSTRUCTOR: case CALTreeParserTokenTypes.UNPARENTHESIZED_TYPE_CONSTRUCTOR: { ParseTreeNode typeConsNameNode = instanceTypeConsNameNode.firstChild(); //resolve the type (constructor) T. typeCons = dataDeclarationChecker.resolveTypeConsName(typeConsNameNode); nTypeVariables = instanceTypeConsNameNode.getNumberOfChildren() - 1; firstTypeVarNode = typeConsNameNode.nextSibling(); break; } case CALTreeParserTokenTypes.FUNCTION_TYPE_CONSTRUCTOR: { typeCons = TypeConstructor.FUNCTION; nTypeVariables = 2; //guaranteed by the parser firstTypeVarNode = instanceTypeConsNameNode.firstChild(); break; } case CALTreeParserTokenTypes.UNIT_TYPE_CONSTRUCTOR: { nTypeVariables = 0; typeCons = compiler.getTypeChecker().getTypeConstants().getUnitType().rootTypeConsApp().getRoot(); firstTypeVarNode = instanceTypeConsNameNode.firstChild(); break; } case CALTreeParserTokenTypes.LIST_TYPE_CONSTRUCTOR: { typeCons = compiler.getTypeChecker().getTypeConstants().getListTypeCons(); nTypeVariables = 1; //guaranteed by the parser firstTypeVarNode = instanceTypeConsNameNode.firstChild(); break; } case CALTreeParserTokenTypes.RECORD_TYPE_CONSTRUCTOR: { //for "instance (constraints) => C {r} where ..." check that kind (C) == *. try { //after kind inference the kinds of type classes have no kind variables (they are grounded) //so they will not be mutated by the unification below. KindExpr.unifyKind(typeClass.getKindExpr(), KindExpr.STAR); } catch (TypeException typeException) { //"Invalid record instance declaration; the kind of the type class {0} (i.e. {1}) is not equal to the kind *." compiler.logMessage( new CompilerMessage( instanceTypeConsNameNode, new MessageKind.Error.RecordInstanceDeclarationKindClash( typeClass))); } String recordVarName = instanceTypeConsNameNode.firstChild().getText(); RecordType instanceRecordType = checkConstrainedRecordVar(contextListNode, recordVarName); return addCurrentClassInstance(instanceNameNode, instanceRecordType, typeClass); } default: { instanceTypeConsNameNode.unexpectedParseTreeNode(); break; } } //Type constructor instance declarations for the Typeable type class are automatically generated by the compiler //whenever possible. However, for types whose type variable arguments are not of kind *, such //an instance declaration is invalid. Thus a user could theoretically create an instance declaration //for the type, causing a security violation since the Typeable instances are used for Dynamic support. // //In addition, even for the instances where an explicit declaration would create an overlapping instance //error, this message is easier to understand. if (typeClass.getName().equals(CAL_Prelude.TypeClasses.Typeable) && instanceTypeConsNameNode.getType() != CALTreeParserTokenTypes.RECORD_TYPE_CONSTRUCTOR) { //"Explicit instance declarations for the Cal.Core.Prelude.Typeable type class are not allowed." compiler.logMessage( new CompilerMessage( classNameNode, new MessageKind.Error.ExplicitTypeableInstance())); } //check that the type (T b1 ... bm) and the typeClass C have the same kind. //First we do a simple check that m is not too big. In essence this is a kind-checking error, but we //we can give a friendlier error message than a kind-checking error in the case of over-saturation. //note that we cannot assume that the type constructor is fully saturated //i.e. this may correctly be an undersaturated application. final int maxNTypeVariables = typeCons.getTypeArity(); if (nTypeVariables > maxNTypeVariables) { // "The type constructor {0} expects at most {1} type argument(s). {2} supplied." compiler.logMessage( new CompilerMessage( instanceTypeConsNameNode, new MessageKind.Error.TypeConstructorAppliedToOverlyManyArgs(typeCons.getName(), maxNTypeVariables, nTypeVariables))); } // the kind of T b1 ... bm where m <= typeCons.getTypeArity is the last element of the kindPieces array KindExpr[] kindPieces = typeCons.getKindExpr().getKindPieces(nTypeVariables); KindExpr appliedTypeConsKind = kindPieces[nTypeVariables]; //now do the general check that kind (T b1 ... bm) == kind (type class C). try { //after kind inference the kinds of type classes and type constructors have no kind variables (they are grounded) //so they will not be mutated by the unification below. KindExpr.unifyKind(appliedTypeConsKind, typeClass.getKindExpr()); } catch (TypeException typeException) { //"Invalid instance declaration; the kind of the type class {0} (i.e. {1}) is not the same as the kind of the application of the type constructor {2} to {3} type arguments (i.e. {4})." compiler.logMessage( new CompilerMessage( instanceTypeConsNameNode, new MessageKind.Error.InstanceDeclarationKindClash( typeClass, typeCons, nTypeVariables, appliedTypeConsKind))); } //Now check that there are no repeated type variables bi and set up some data structures for later checks. //(String -> Integer) For example, for an instance for Tuple3 d a b then the map is {(d, 0), (a, 1), (b, 2)}. Map<String, Integer> varNameToArgNumberMap = new HashMap<String, Integer>(); int argNumber = 0; for (ParseTreeNode varNameNode = firstTypeVarNode; varNameNode != null; varNameNode = varNameNode.nextSibling()) { varNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String varName = varNameNode.getText(); if (varNameToArgNumberMap.put(varName, Integer.valueOf(argNumber)) != null) { //Can't have repeated type variables such as instance Eq a => Foo a a compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.RepeatedTypeVariableInInstanceDeclaration(varName))); } ++argNumber; } TypeExpr [] argTypes = checkConstrainedTypeVars(contextListNode, varNameToArgNumberMap, kindPieces); TypeConsApp instanceType = new TypeConsApp(typeCons, argTypes); return addCurrentClassInstance(instanceNameNode, instanceType, typeClass); } /** * Checks the left hand side of the "=>" in a class instance declaration for validity, and * returns and array of constrained type variables as specified by the constraints. * * For example, for constraints such as: * (Eq a, Show a, XMLSerializable b, Enum b, Read c) => ... * we must verify that all the type classes exist and are resolvable, * that the type variables (a, b, c) occur on the right hand side of the => * and then create the appropriate array of constrained type variables. * * @param contextListNode * @param varNameToArgNumberMap (String -> Integer) For example, for an instance for Tuple3 d a b then the map is {(d, 0), (a, 1), (b, 2)} * @param kindPieces for the instance declaration "instance (constraints) => T (C a0 ... am)" then the elements of this array are the kinds * of a1, a2, ..., an and (T a1 ... an). * @return TypeExpr[] will have the same size as varNameToArgNumberMap */ private TypeExpr[] checkConstrainedTypeVars(ParseTreeNode contextListNode, Map<String, Integer> varNameToArgNumberMap, KindExpr[] kindPieces) { final int nTypeVariables = varNameToArgNumberMap.size(); //Cij is the jth type class constraint on the ith type variable. //Check that each Cij resolves to a type class //Check that for each i, the Cij are distinct //Check that the type variables a1,..., an are a subset of b1, ..., bm. Note a proper subset is allowed //e.g. "instance Music (Maybe a) where..." is allowed. //constraintSetList.get(i) is the set of qualified names of all the type class constraints on type argument i. List<Set<QualifiedName>> constraintSetList = new ArrayList<Set<QualifiedName>>(nTypeVariables); for (int i = 0; i < nTypeVariables; ++i) { constraintSetList.add(new HashSet<QualifiedName>()); } for (final ParseTreeNode contextNode : contextListNode) { contextNode.verifyType(CALTreeParserTokenTypes.CLASS_CONTEXT); ParseTreeNode constraintTypeClassNameNode = contextNode.firstChild(); constraintTypeClassNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); //verify that the type class referred to actually exists, and supply its inferred module name //if it was omitted in the user's CAL code. TypeClass constraintTypeClass = typeClassChecker.resolveClassName(constraintTypeClassNameNode); ParseTreeNode varNameNode = constraintTypeClassNameNode.nextSibling(); varNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String varName = varNameNode.getText(); if (!varNameToArgNumberMap.containsKey(varName)) { //the type variables a1,..., an must be a subset of b1, ..., bm. Note a proper subset is allowed compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.TypeVariableMustOccurWithingTypeVariableArgumentsOfInstanceDeclaration(varName))); } //this context is a constraint on the type variable argument at index argN int argN = varNameToArgNumberMap.get(varName).intValue(); if (!constraintSetList.get(argN).add(constraintTypeClass.getName())) { //can't have a duplicate constraint such as (Eq a, Eq a) => Eq (List a) compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.DuplicateConstraint(constraintTypeClass.getName().getQualifiedName(), varName))); } //if the constraint is Ci aj, then kind Ci == kind aj must hold. KindExpr constraintTypeClassKind = constraintTypeClass.getKindExpr(); KindExpr typeVarKind = kindPieces[argN]; try { //check that these kinds are the same. They are both grounded (do not involve type variables) so they will not be //mutated by the unification. KindExpr.unifyKind(constraintTypeClassKind, typeVarKind); } catch (TypeException typeException) { //"Invalid instance declaration; the kind of the type variable {0} (i.e. {1}) is not the same as the kind of the constraining type class {2} (i.e. {3})." compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.InstanceDeclarationKindClashInConstraint(varName, typeVarKind, constraintTypeClass))); } } //now create the type expression (C1 a1, C2 a2, ..., Cn an) => (T b1 ... bm). TypeExpr[] argTypes = new TypeExpr[nTypeVariables]; for (int i = 0; i < nTypeVariables; ++i) { //set of TypeClass constraints on the type variable at argument i. SortedSet<TypeClass> constraintSet = TypeClass.makeNewClassConstraintSet(); Set<QualifiedName> constraintSetNames = constraintSetList.get(i); for (final QualifiedName constraintTypeClassName : constraintSetNames) { TypeClass constraintTypeClass = currentModuleTypeInfo.getVisibleTypeClass(constraintTypeClassName); if (constraintTypeClass == null) { //can't happen because of the above checks. throw new NullPointerException(); } constraintSet.add(constraintTypeClass); } argTypes[i] = TypeVar.makeTypeVar(null, constraintSet, true); } return argTypes; } /** * Checks the left hand side of the "=>" in a class instance declaration for a universal record instance for * validity, and returns and SortedSet of type class constraints on the record variable. * * For example, for constraints such as: * (Eq a, Show a, XMLSerializable b, Enum b, Read c) => ... * we must verify that all the type classes exist and are resolvable, * that the type variables (a, b, c) occur on the right hand side of the => * and then create the appropriate array of constrained type variables. * * @param contextListNode * @param recordVarName for example, for instance Eq r => Eq {r} this is "r". * @return RecordType the record type determined by the instance declaration. For example, for instance * Eq r => Eq {r}, this will be Eq r => {r}. */ private RecordType checkConstrainedRecordVar(ParseTreeNode contextListNode, String recordVarName) { //Cj is the jth type class constraint on the record variable. //Check that each Cj resolves to a type class //Check that the Cj are distinct //Check that the record variables on the lhs of context are all 'recordVarName'. //Check that the kind of Cj == * (which is the kind of the record var). //constraintSet is the set of qualified names of all the type class constraints on recordVarName. Set<QualifiedName> constraintSet = new HashSet<QualifiedName>(); for (final ParseTreeNode contextNode : contextListNode) { contextNode.verifyType(CALTreeParserTokenTypes.CLASS_CONTEXT); ParseTreeNode constraintTypeClassNameNode = contextNode.firstChild(); constraintTypeClassNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); //verify that the type class referred to actually exists, and supply its inferred module name //if it was omitted in the user's CAL code. TypeClass constraintTypeClass = typeClassChecker.resolveClassName(constraintTypeClassNameNode); ParseTreeNode varNameNode = constraintTypeClassNameNode.nextSibling(); varNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String varName = varNameNode.getText(); if (!varName.equals(recordVarName)) { //todoBI could add a more specific error message here. compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.TypeVariableMustOccurWithingTypeVariableArgumentsOfInstanceDeclaration(varName))); } if (!constraintSet.add(constraintTypeClass.getName())) { //can't have a duplicate constraint such as (Eq a, Eq a) => Eq {a} compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.DuplicateConstraint(constraintTypeClass.getName().getQualifiedName(), varName))); } //if the constraint is Ci r, then kind Ci == kind r (== *) must hold. KindExpr constraintTypeClassKind = constraintTypeClass.getKindExpr(); try { //check that these kinds are the same. They are both grounded (do not involve type variables) so they will not be //mutated by the unification. KindExpr.unifyKind(constraintTypeClassKind, KindExpr.STAR); } catch (TypeException typeException) { //"Invalid instance declaration; the kind of the type variable {0} (i.e. {1}) is not the same as the kind of the constraining type class {2} (i.e. {3})." compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.InstanceDeclarationKindClashInConstraint(varName, KindExpr.STAR, constraintTypeClass))); } } //now create the type expression (C1 r, C2 r, ..., Cn r) => {r}. //set of TypeClass constraints on the type variable at argument i. SortedSet<TypeClass> typeClassConstraintSet = TypeClass.makeNewClassConstraintSet(); for (final QualifiedName constraintTypeClassName : constraintSet) { TypeClass constraintTypeClass = currentModuleTypeInfo.getVisibleTypeClass(constraintTypeClassName); if (constraintTypeClass == null) { //can't happen because of the above checks. throw new NullPointerException(); } typeClassConstraintSet.add(constraintTypeClass); } return new RecordType( RecordVar.makeRecordVar(recordVarName, Collections.<FieldName>emptySet(), typeClassConstraintSet, true), Collections.<FieldName, TypeExpr>emptyMap()); } /** * Adds the current class instance being parsed to the ModuleTypeInfo. * Does a check that there are no overlapping instances prior to making the addition. * @param instanceNameNode * @param instanceType * @param typeClass * @return ClassInstance */ private ClassInstance addCurrentClassInstance(ParseTreeNode instanceNameNode, TypeExpr instanceType, TypeClass typeClass) { ClassInstance classInstance = new ClassInstance (currentModuleTypeInfo.getModuleName(), instanceType, typeClass, null, InstanceStyle.EXPLICIT); //There cannot be duplicate C-T instances in any class imported either directly or indirectly into the module //where this C-T instance is defined. an instance is identified by the pair (C, T). There cannot be 2 instances for the //same pair (C, T) in scope in the program. For example, instance Eq a => Ord (Tree a) and instance Ord a => Ord (Tree a) //are not both allowed. // //To elaborate, an instance, such as Ord Date defined in a module cannot be defined within any module imported, either //directly or indirectly into the module. For example, if module C imports module B and module B imports module A, //and within module A, the instance Ord Date is defined, then Ord Date, cannot be defined in module C, even though //module C doesn't import module A directly. One way to think of this is that instances have universal scope. ClassInstance otherInstance = currentModuleTypeInfo.getVisibleClassInstance(classInstance.getIdentifier()); if (otherInstance != null) { //an instance, such as Ord Date, can be defined only once within a module. //instances such as "(Eq a) => Ord Maybe a", "Ord Maybe a" and "Ord a => Ord Maybe a" //all have the same instance identifiers Ord Maybe, and are not allowed. These //are called overlapping instances. We distinguish the error messages for clarity. String declName = classInstance.getNameWithContext(); String otherDeclName = otherInstance.getNameWithContext(); MessageKind message; if (instanceType instanceof RecordType) { //universal record instance // The instance {classInstance.getNameWithContext()} overlaps with the record instance {otherInstance.getNameWithContext()} defined in module {otherInstance.getModuleName()}. message = new MessageKind.Error.RecordInstancesOverlap(classInstance.getNameWithContext(), otherInstance.getNameWithContext(), otherInstance.getModuleName()); } else { //type constructor instances if (declName.equals(otherDeclName)) { message = new MessageKind.Error.InstanceAlreadyDefined(declName, otherInstance.getModuleName()); } else { message = new MessageKind.Error.InstanceOverlapsWithInstanceInModule(declName, otherDeclName, otherInstance.getModuleName()); } } compiler.logMessage(new CompilerMessage(instanceNameNode, message)); } currentModuleTypeInfo.addClassInstance(classInstance); return classInstance; } /** * Given an instance declaration "instance C-T where m1 = f1; m2 = f2; ...;" this method * checks that the supplied instance method resolving function fi indeed exists and is visible. * Note that the resolving function cannot be a class method but must be a true function! * As a side effect, the module name is supplied if it was left out in the source. * * @param resolvingFunctionNameNode * @return QualifiedName qualified name of the instance method. */ private QualifiedName resolveResolvingFunction(ParseTreeNode resolvingFunctionNameNode) { return resolveMethodName(resolvingFunctionNameNode, compiler, currentModuleTypeInfo); } /** * A helper function used to resolve instance method resolving function names and default class method names. * This function handles the fact that at this point in type-checking the module, the functions * themselves have not been added to the ModuleTypeInfo, and so the usual methods of resolution * don't work. * * @param methodNameNode * @param compiler * @param currentModuleTypeInfo * @return QualifiedName */ static QualifiedName resolveMethodName(ParseTreeNode methodNameNode, CALCompiler compiler, ModuleTypeInfo currentModuleTypeInfo) { methodNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); ParseTreeNode moduleNameNode = methodNameNode.firstChild(); String maybeModuleName = ModuleNameUtilities.resolveMaybeModuleNameInParseTree(moduleNameNode, currentModuleTypeInfo, compiler, false); ParseTreeNode varNameNode = moduleNameNode.nextSibling(); String varName = varNameNode.getText(); ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); if (maybeModuleName.length() > 0) { ModuleName moduleName = ModuleName.make(maybeModuleName); //an explicitly qualified variable QualifiedName functionName = QualifiedName.make(moduleName, varName); //todoBI this can be improved eventually to use the currentModuleTypeInfo //we can't use currentModuleTypeInfo since functions have not been added to it yet. So we do a //direct check for the case of the current module. if (moduleName.equals(currentModuleTypeInfo.getModuleName())) { if (!compiler.getTypeChecker().isTopLevelFunctionName(varName)) { // the function {functionName} is not defined in the current module. compiler.logMessage (new CompilerMessage(methodNameNode, new MessageKind.Error.FunctionNotDefinedInCurrentModule(functionName.getQualifiedName()))); } checkResolvedClassMethodReference(compiler, methodNameNode, functionName); return functionName; } //todoBI more detailed error messages here for the various failure cases //i.e. class method, not visible, etc Function function = currentModuleTypeInfo.getVisibleFunction(functionName); if (function == null) { compiler.logMessage (new CompilerMessage(methodNameNode, new MessageKind.Error.FunctionDoesNotExist(functionName.getQualifiedName()))); } checkResolvedClassMethodReference(compiler, methodNameNode, functionName); return functionName; } if (compiler.getTypeChecker().isTopLevelFunctionName(varName)) { //the var is defined in the current module. QualifiedName functionName = QualifiedName.make(currentModuleName, varName); ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, currentModuleName); checkResolvedClassMethodReference(compiler, methodNameNode, functionName); return functionName; } //We now know that the variable can't be defined within the current module. //check if it is a "using function" and then patch up the module name. ModuleName usingModuleName = currentModuleTypeInfo.getModuleOfUsingFunctionOrClassMethod(varName); if (usingModuleName != null) { QualifiedName usingFunctionName = QualifiedName.make(usingModuleName, varName); Function function = currentModuleTypeInfo.getVisibleFunction(usingFunctionName); if (function != null) { ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, usingModuleName); checkResolvedClassMethodReference(compiler, methodNameNode, usingFunctionName); return usingFunctionName; } //we know that usingFunctionName must exist because of earlier static checks. //Since it is not a function, it must be a class method, which is an error here. compiler.logMessage (new CompilerMessage(methodNameNode, new MessageKind.Error.FunctionIsClassMethodNotResolvingFunction(usingFunctionName.getQualifiedName()))); } //We now know that the variable can't be defined within the current module and is not a "using function". //Check if it is defined in another module. //This will be an error since the user must supply a module qualification, but we //can attempt to give a good error message. List<QualifiedName> candidateEntityNames = new ArrayList<QualifiedName>(); int nImportedModules = currentModuleTypeInfo.getNImportedModules(); for (int i = 0; i < nImportedModules; ++i) { FunctionalAgent candidate = currentModuleTypeInfo.getNthImportedModule(i).getFunctionOrClassMethod(varName); if (candidate != null && currentModuleTypeInfo.isEntityVisible(candidate)) { candidateEntityNames.add(candidate.getName()); } } int numCandidates = candidateEntityNames.size(); if(numCandidates == 0) { // Attempt to use undefined identifier {varName}. compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.AttemptToUseUndefinedFunction(varName))); } else if(numCandidates == 1) { QualifiedName candidateName = candidateEntityNames.get(0); // Attempt to use undefined function {varName}. Was {candidateName} intended? compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.AttemptToUseUndefinedFunctionSuggestion(varName, candidateName))); } else { // The reference to the function {varName} is ambiguous. It could mean any of {candidateEntityNames}. compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.AmbiguousFunctionReference(varName, candidateEntityNames))); } return null; } /** * Performs late static checks on a class method reference that has already been resolved. * * Currently, this performs a deprecation check on a class method reference, logging a warning message if the method is deprecated. * @param compiler the compiler instance. * @param nameNode the parse tree node representing the reference. * @param qualifiedName the qualified name of the reference. */ private static void checkResolvedClassMethodReference(final CALCompiler compiler, final ParseTreeNode nameNode, final QualifiedName qualifiedName) { if (compiler.getDeprecationScanner().isFunctionOrClassMethodDeprecated(qualifiedName)) { compiler.logMessage(new CompilerMessage(nameNode, new MessageKind.Warning.DeprecatedClassMethod(qualifiedName))); } } /** * Given an instance declaration "instance C-T where m1 = f1; m2 = f2; ...;" this method * checks that all the instance method resolving functions f1, f2,... have * a type compatible with what is required by the C-T-m triple. For example, * the function resolving the Prelude.add class method in the Num Int instance must have type Int -> Int -> Int; * <p> * This method performs the above check for all instances within the current module. Call this method after all * the functions in the module have been type checked. */ void checkTypesOfInstanceMethodResolvingFunctions() { for (int i = 0, nClassInstances = currentModuleTypeInfo.getNClassInstances(); i < nClassInstances; ++i) { ClassInstance classInstance = currentModuleTypeInfo.getNthClassInstance(i); ParseTreeNode instanceNode = classInstanceMap.get(classInstance.getIdentifier()); if (instanceNode == null) { // This can occur if we are compiling an adjunct to a module that contains instance declarations. continue; } instanceNode.verifyType(CALTreeParserTokenTypes.INSTANCE_DEFN); ParseTreeNode instanceMethodListNode = instanceNode.getChild(2); instanceMethodListNode.verifyType(CALTreeParserTokenTypes.INSTANCE_METHOD_LIST); TypeClass typeClass = classInstance.getTypeClass(); for (final ParseTreeNode instanceMethodNode : instanceMethodListNode) { instanceMethodNode.verifyType(CALTreeParserTokenTypes.INSTANCE_METHOD); ParseTreeNode optionalInstanceMethodCALDocNode = instanceMethodNode.firstChild(); optionalInstanceMethodCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode classMethodNameNode = optionalInstanceMethodCALDocNode.nextSibling(); classMethodNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String classMethodName = classMethodNameNode.getText(); ClassMethod classMethod = typeClass.getClassMethod(classMethodName); QualifiedName resolvingFunctionName = classMethodNameNode.nextSibling().toQualifiedName(); TypeExpr instanceMethodType; { TypeExpr classMethodType = classMethod.getTypeExpr(); TypeVar classTypeVar = classMethodType.getTypeClassTypeVar(typeClass.getTypeClassTypeVarName()); classTypeVar.setInstance(classInstance.getType()); instanceMethodType = classMethodType; } TypeExpr resolvingFunctionType = compiler.getTypeChecker().getEntity(resolvingFunctionName).getTypeExpr(); if (instanceMethodType.getGenericClassConstrainedPolymorphicVars(null).size() != 0 || resolvingFunctionType.getGenericClassConstrainedPolymorphicVars(null).size() != 0) { //if either the instance method type or the resolving function type involve constrained type vars, then they //must have identically the same type. For example, for equals in the Eq-List instance, the types of the instance //method and the resolving instance must both be: Eq a => [a] -> [a] -> Boolean. if (!instanceMethodType.sameType(resolvingFunctionType)) { compiler.logMessage(new CompilerMessage(classMethodNameNode, new MessageKind.Error.ResolvingFunctionForInstanceMethodHasWrongTypeSpecific(resolvingFunctionName.getQualifiedName(), classMethodName, instanceMethodType.toString(), resolvingFunctionType.toString()))); } } else { //we allow extra flexibility here, so that e.g. withing the Eq-Integer instance, we can define fromInteger as id (rather than idInteger). if (!TypeExpr.canPatternMatch(instanceMethodType, resolvingFunctionType, currentModuleTypeInfo)) { compiler.logMessage(new CompilerMessage(classMethodNameNode, new MessageKind.Error.ResolvingFunctionForInstanceMethodHasWrongTypeGeneral(resolvingFunctionName.getQualifiedName(), classMethodName, instanceMethodType.toString(), resolvingFunctionType.toString()))); } } } } } /** * Generate the conventional name of a method for a given primitive type. * For example, makePrimitiveMethodName("equals", "int") returns Prelude.equalsInt. * This relies on the primitive methods' being in the Prelude and having been named * in this conventional fashion. * * @param methodName Name of the method to generate an instance method name for * @param primitiveTypeName Primitive Java type to generate an instance method name for * @return QualifiedName instance method name for the given method and primitive type */ private static QualifiedName makePrimitiveInstanceMethodName(String methodName, String primitiveTypeName) { if(methodName == null || primitiveTypeName == null) { throw new IllegalArgumentException("makePrimitiveInstanceMethodName must not be passed null values"); } // Trivial case: If we are given the name of a primitive type in CAL form (eg, "Int") // then we don't need to do any particular work to get things into the correct form. if(Character.isUpperCase(primitiveTypeName.charAt(0))) { return QualifiedName.make(CAL_Prelude.MODULE_NAME, methodName + primitiveTypeName); } // Non-trivial case: we need to convert the first character of the primitive type name // to upper-case. StringBuilder buffer = new StringBuilder(methodName.length() + primitiveTypeName.length()); buffer.append(methodName); buffer.append(Character.toUpperCase(primitiveTypeName.charAt(0))); buffer.append(primitiveTypeName.substring(1)); return QualifiedName.make(CAL_Prelude.MODULE_NAME, buffer.toString()); } }