/* * 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. */ /* * DataDeclarationChecker.java * Created: Jan 18, 2001 * By: Bo Ilic */ package org.openquark.cal.compiler; import java.util.ArrayList; 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.SortedMap; import org.openquark.cal.internal.compiler.ForeignEntityResolver; 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; import org.openquark.cal.util.Graph; import org.openquark.cal.util.VertexBuilder; import org.openquark.cal.util.VertexBuilderList; /** * A class used to introduce the new types and their associated data constructors as * specified via data declarations to the type checker. New types are either built-in, * introduced by foreign data declarations or introduced by data declarations. * This class also ensures that the data declarations are semantically correct (kind checking). * A new instance of this class must be instantiated for each module that needs * data declaration checking. * Creation date: (1/18/01 1:45:07 PM) * @author Bo Ilic */ final class DataDeclarationChecker { /** Set to true to have debug info printed while running the data declaration checker. */ private static final boolean DEBUG_INFO = false; private final CALCompiler compiler; /** Type constructors and data constructors for the current module are added here. */ private final ModuleTypeInfo currentModuleTypeInfo; /** * used to resolve type class names in the deriving clause of a foreign or algebraic type definition. */ private final TypeClassChecker typeClassChecker; static private final TypeConstructor.DerivingClauseInfo[] NO_DERIVING_CLAUSE = new TypeConstructor.DerivingClauseInfo[0]; /** * (QualifiedName Set) names of the type classes that are statically allowed to be in the deriving clause * of foreign data declarations. */ static private final Set<QualifiedName> possibleForeignTypeDerivingClauseNames = new HashSet<QualifiedName>(); static { possibleForeignTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Outputable); possibleForeignTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Inputable); possibleForeignTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Eq); possibleForeignTypeDerivingClauseNames.add(CAL_Debug.TypeClasses.Show); possibleForeignTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Ord); } /** * (QualifiedName Set) names of the type classes that are statically allowed to be in the deriving clause * of algebraic data declarations. Further checks are required to establish that a particular data declaration * satisfies the requirements for being a derived instance of a particular type class. * * For example, the type: * data Foo = Foo (Int -> Int); * while algebraic, cannot be made a derived Eq instance (because (Int->Int) is not an Eq instance). */ static private final Set<QualifiedName> possibleAlgebraicTypeDerivingClauseNames = new HashSet<QualifiedName>(); static { possibleAlgebraicTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Eq); possibleAlgebraicTypeDerivingClauseNames.add(CAL_Debug.TypeClasses.Show); possibleAlgebraicTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Ord); possibleAlgebraicTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Bounded); possibleAlgebraicTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Enum); possibleAlgebraicTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Outputable); possibleAlgebraicTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Inputable); possibleAlgebraicTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.IntEnum); possibleAlgebraicTypeDerivingClauseNames.add(CAL_QuickCheck.TypeClasses.Arbitrary); } /** * (QualifiedName Set) names of the type classes that are statically allowed to be in the deriving clause * of algebraic data declarations for enumeration types. */ static private final Set<QualifiedName> possibleAlgebraicEnumerationTypeDerivingClauseNames = new HashSet<QualifiedName>(); static { possibleAlgebraicEnumerationTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Bounded); possibleAlgebraicEnumerationTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.Enum); possibleAlgebraicEnumerationTypeDerivingClauseNames.add(CAL_Prelude.TypeClasses.IntEnum); possibleAlgebraicEnumerationTypeDerivingClauseNames.add(CAL_QuickCheck.TypeClasses.Arbitrary); } /** * Map (QualifiedName -> Class) * Type classes that can only be derived for foreign types * if the foreign type is either a primitive or implements a given interface. The key is * the QualifiedName of the type class, and the value is the Class object for the required * interface. */ static private final Map<QualifiedName, Class<?>> foreignTypesRequiringPrimitiveOrImplementedInterface = new HashMap<QualifiedName, Class<?>>(); static { foreignTypesRequiringPrimitiveOrImplementedInterface.put(CAL_Prelude.TypeClasses.Ord, Comparable.class); } /** * DataDeclarationChecker constructor comment. */ DataDeclarationChecker(final ModuleTypeInfo currentModuleTypeInfo, final CALCompiler compiler, final TypeClassChecker typeClassChecker) { if (currentModuleTypeInfo == null || compiler == null || typeClassChecker == null) { throw new NullPointerException(); } this.currentModuleTypeInfo = currentModuleTypeInfo; this.compiler = compiler; this.typeClassChecker = typeClassChecker; } /** * Checks a top-level type expression, which must have kind *. * @param typeVarToKindExprMap * @param typeExpr * @throws TypeException */ void kindCheckTypeExpr (final Map<TypeVar, KindExpr> typeVarToKindExprMap, final TypeExpr typeExpr) throws TypeException { final KindExpr kindExpr = analyzeTypeExpr(typeVarToKindExprMap, typeExpr); try { KindExpr.unifyKind(KindExpr.STAR, kindExpr); } catch (TypeException typeException) { throw new TypeException("The type " + typeExpr + " must have kind * but actually has kind " + kindExpr + ".", typeException); } } /** * Computes the kind of a type expression and fails in a TypeException if there is a kind error i.e. * the type expression is inconsistent with respect to kind unification constraints. * * Creation date: (1/24/01 6:55:12 PM) * @param typeVarToKindExprMap * @param typeExpr * @return KindExpr */ private KindExpr analyzeTypeExpr(final Map<TypeVar, KindExpr> typeVarToKindExprMap, TypeExpr typeExpr) throws TypeException { typeExpr = typeExpr.prune(); if (typeExpr instanceof TypeVar) { //the type variable will be uninstantiated because of the pruning if(((TypeVar)typeExpr).getInstance() != null) { throw new IllegalStateException("excepting an uninstantiated type variable"); } return typeVarToKindExprMap.get(typeExpr); } else if (typeExpr instanceof TypeConsApp) { TypeConsApp typeConsApp = (TypeConsApp) typeExpr; QualifiedName typeConsName = typeConsApp.getName(); KindExpr kindOfAppl = (currentModuleTypeInfo.getVisibleTypeConstructor(typeConsName)).getKindExpr(); for (int i = 0, nArgs = typeConsApp.getNArgs(); i < nArgs; ++i) { TypeExpr argTypeExpr = typeConsApp.getArg(i); //the kind of (typeCons a1 a2 ... ai) KindExpr kindOfArgsSoFar = kindOfAppl; KindExpr kindOfNextArg = analyzeTypeExpr(typeVarToKindExprMap, argTypeExpr); kindOfAppl = new KindExpr.KindVar(); KindExpr.unifyKind(kindOfArgsSoFar, new KindExpr.KindFunction(kindOfNextArg, kindOfAppl)); } return kindOfAppl; } else if (typeExpr instanceof TypeApp) { TypeApp typeApp = (TypeApp)typeExpr; KindExpr operatorKind = analyzeTypeExpr(typeVarToKindExprMap, typeApp.getOperatorType()); KindExpr operandKind = analyzeTypeExpr(typeVarToKindExprMap, typeApp.getOperandType()); KindExpr kindOfAppl = new KindExpr.KindVar(); KindExpr.unifyKind(operatorKind, new KindExpr.KindFunction(operandKind, kindOfAppl)); return kindOfAppl; } else if (typeExpr instanceof RecordType) { //each of the types of each of the fields in a record must have kind *. RecordType recordType = (RecordType)typeExpr; SortedMap<FieldName, TypeExpr> hasFieldsMap = recordType.getHasFieldsMap(); for (final Map.Entry<FieldName, TypeExpr> entry : hasFieldsMap.entrySet()) { FieldName fieldName = entry.getKey(); TypeExpr fieldTypeExpr = entry.getValue(); KindExpr fieldKind = analyzeTypeExpr(typeVarToKindExprMap, fieldTypeExpr); try { KindExpr.unifyKind(KindExpr.STAR, fieldKind); } catch (TypeException typeException) { throw new TypeException("The record field " + fieldName + " of type " + fieldTypeExpr + " must have kind * but actually has kind " + fieldKind + ".", typeException); } } //records types, since they always appear in fully saturated form because of their notation, must have kind *. return KindExpr.STAR; } throw new IllegalStateException(); } /** * Check that the data declarations in the module are correct, and extract the typing * information needed to type check the function definitions. * * Creation date: (1/18/01 1:46:43 PM) * @return Env the environment giving the types for all the data constructors defined in the module * @param moduleLevelParseTrees */ Env checkDataDeclarations(final ModuleLevelParseTrees moduleLevelParseTrees) { final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); final boolean isPreludeModule = currentModuleName.equals(CAL_Prelude.MODULE_NAME); //Add the built-in type constructors to the currentModuleTypeInfo. if (isPreludeModule) { TypeConstructor.addBuiltInTypes(currentModuleTypeInfo); } final List<ParseTreeNode> dataDeclarationNodes = moduleLevelParseTrees.getDataDeclarationNodes(); final List<ParseTreeNode> foreignDataDeclarationNodes = moduleLevelParseTrees.getForeignDataDeclarationNodes(); //Check that the names of all types introduced in the module are distinct. //Add the names of the foreign types to the currentModuleTypeInfo. Map<String, ParseTreeNode> typeNameToDataDeclarationNodeMap = checkNamesUsed(dataDeclarationNodes, foreignDataDeclarationNodes); Env dataConstructorEnv = null; //Each data declaration determines a set of data constructors, and the type signature of //each of these data constructors. This loop extracts this information from the data //declarations, without checking for correctness due to kind errors, which is done latter. for (final ParseTreeNode dataDeclarationNode : dataDeclarationNodes) { //Construct the type expression for the new data type. For example, in //data Tree a = Leaf a | Branch (Tree a) (Tree a) //this part constructs the type expression "Tree a". ParseTreeNode optionalCALDocNode = dataDeclarationNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); Scope typeScope = CALTypeChecker.getScopeModifier(accessModifierNode); ParseTreeNode typeNameNode = accessModifierNode.nextSibling(); String typeName = typeNameNode.getText(); ParseTreeNode typeConsParamListNode = typeNameNode.nextSibling(); typeConsParamListNode.verifyType(CALTreeParserTokenTypes.TYPE_CONS_PARAM_LIST); Map<String, TypeVar> typeVarNameToTypeVarMap = new HashMap<String, TypeVar>(); List<TypeVar> typeVarList = new ArrayList<TypeVar>(); for (final ParseTreeNode typeVarNode : typeConsParamListNode) { typeVarNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String typeVarName = typeVarNode.getText(); TypeVar typeVarType = new TypeVar(typeVarName); //Check that type variable names are not repeated within a single data declaration. //Namely, "data T a a = ..." is not allowed, and later, if we have "data T a b = ..." //then only the type variables a and b can appear on the right hand side. if (typeVarNameToTypeVarMap.containsKey(typeVarName)) { compiler.logMessage(new CompilerMessage(typeVarNode, new MessageKind.Error.RepeatedTypeVariable(typeVarName))); } typeVarNameToTypeVarMap.put(typeVarName, typeVarType); typeVarList.add(typeVarType); } TypeConstructor typeCons = currentModuleTypeInfo.getTypeConstructor(typeName); TypeExpr[] args = new TypeExpr [typeVarList.size()]; typeVarList.toArray(args); TypeExpr typeConsTypeExpr = new TypeConsApp(typeCons, args); //Now iterate over the data constructor, and obtain their types. In other words, //we now calculate the types for "Leaf" and "Branch". ParseTreeNode dataConsDefnListNode = typeConsParamListNode.nextSibling(); dataConsDefnListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN_LIST); int ordinal = 0; for (final ParseTreeNode dataConsDefnNode : dataConsDefnListNode) { //Determine the type expression of each data constructor. For example, //Leaf :: a -> Tree a dataConsDefnNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN); ParseTreeNode dataConsOptionalCALDocNode = dataConsDefnNode.firstChild(); ParseTreeNode dataConsAccessModifierNode = dataConsOptionalCALDocNode.nextSibling(); //the declared scope of a data constructor may not be its actual scope. A data constructor //cannot be more visible than its type constructor. //e.g. data private Foo = public MakeFoo; //MakeFoo has private scope. Scope declaredDataConsScope = CALTypeChecker.getScopeModifier(dataConsAccessModifierNode); Scope dataConsScope = declaredDataConsScope.min(typeScope); ParseTreeNode dataConsNameNode = dataConsAccessModifierNode.nextSibling(); String dataConsName = dataConsNameNode.getText(); ParseTreeNode dataConsArgListNode = dataConsNameNode.nextSibling(); dataConsArgListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_ARG_LIST); final int nDataConsArgs = dataConsArgListNode.getNumberOfChildren(); TypeExpr[] argTypesArray = new TypeExpr[nDataConsArgs]; boolean[] argStrictnessArray = new boolean[nDataConsArgs]; FieldName[] fieldNamesArray = new FieldName[nDataConsArgs]; Set<FieldName> fieldNamesSet = new HashSet<FieldName>(); int argN = 0; for (final ParseTreeNode dataConsArgNode : dataConsArgListNode) { dataConsArgNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAMED_ARG); // Get the name. ParseTreeNode dataConsArgNameNode = dataConsArgNode.firstChild(); FieldName fieldName = FieldName.make(dataConsArgNameNode.getText()); // Get the type node. ParseTreeNode maybePlingTypeExprNode = dataConsArgNameNode.nextSibling(); if (!fieldNamesSet.add(fieldName)) { // Repeated argument name {fieldName.getCalSourceForm()} in data constructor. compiler.logMessage(new CompilerMessage(dataConsArgNameNode, new MessageKind.Error.RepeatedFieldNameInDataConstructorDeclaration(fieldName))); } fieldNamesArray[argN] = fieldName; ParseTreeNode typeExprNode; if (maybePlingTypeExprNode.getType() == CALTreeParserTokenTypes.STRICT_ARG) { typeExprNode = maybePlingTypeExprNode.firstChild(); argStrictnessArray[argN] = true; } else { typeExprNode = maybePlingTypeExprNode; } argTypesArray[argN] = determineTypeExpr(typeExprNode, typeVarNameToTypeVarMap); ++argN; } TypeExpr dataConsTypeExpr = typeConsTypeExpr; for (int j = nDataConsArgs - 1; j >= 0; --j) { dataConsTypeExpr = TypeExpr.makeFunType(argTypesArray[j], dataConsTypeExpr); } DataConstructor dataCons = new DataConstructor(QualifiedName.make(currentModuleName, dataConsName), dataConsScope, fieldNamesArray, dataConsTypeExpr, argStrictnessArray, ordinal); dataConstructorEnv = Env.extend(dataConstructorEnv, dataCons); typeCons.addDataConstructor(dataCons); ordinal++; } } checkKinds(dataConstructorEnv, dataDeclarationNodes, typeNameToDataDeclarationNodeMap); return dataConstructorEnv; } /** * The main method for performing kind checking for all the type definitions introduced using data * declarations. * Creation date: (1/25/01 1:50:16 PM) * @param dataConstructorEnv Env environment for all the data constructors defined in the module * @param typeNameToDataDeclarationNodeMap (String->ParseTreeNode). * It is necessary to traverse the data declarations more than once for typing them. We use this map to provide quick access. * The domain of this map contain the names of all non built-in type names defined within the current module * A qualified name is not required since the module name is implicitly given. */ private void checkKinds(final Env dataConstructorEnv, final List<ParseTreeNode> dataDeclarationNodes, final Map<String, ParseTreeNode> typeNameToDataDeclarationNodeMap) { //Reorder the data declarations so that dependees are processed before dependents final Set<String> declaredTypeNamesSet = typeNameToDataDeclarationNodeMap.keySet(); final Graph<String> g = performTypeConstructorDependencyAnalysis(dataDeclarationNodes, declaredTypeNamesSet); if (DEBUG_INFO) { System.out.println("" + dataConstructorEnv); } //Kind check the type definitions. Proceed one strongly connected component at a time. for (int i = 0, nComponents = g.getNStronglyConnectedComponents(); i < nComponents; ++i) { final Graph<String>.Component component = g.getStronglyConnectedComponent(i); //Infer the values of the kind variables by inference from the type expressions used as arguments //in the definition of the type constructors. //e.g. "data T a1 ... an = MakeT1 te_1 ... te_m | MakeT2 ..." //Then we determine the kind of each of the type expressions te_1, ..., te_m involved in the //definition of the data constructor MakeT1. We then do the same for the rest of the data types //in the component. //e.g. data T a b c = Cons1 te1 te2 | Cons2 te final int componentSize = component.size(); for (int j = 0; j < componentSize; ++j) { final String typeName = component.getVertex(j).getName(); final ParseTreeNode dataDeclarationNode = typeNameToDataDeclarationNodeMap.get(typeName); dataDeclarationNode.verifyType(CALTreeParserTokenTypes.DATA_DECLARATION); final ParseTreeNode dataConsDefnListNode = dataDeclarationNode.getChild(4); dataConsDefnListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN_LIST); final Map<TypeVar, KindExpr> typeVarToKindExprMap = makeTypeVarToKindVarMap(dataConstructorEnv, dataConsDefnListNode); for (final ParseTreeNode dataConsDefnNode : dataConsDefnListNode) { dataConsDefnNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN); final String dataConsName = dataConsDefnNode.getChild(2).getText(); final FunctionalAgent dataConsEntity = dataConstructorEnv.retrieveEntity(dataConsName); final TypeExpr dataConsTypeExpr = dataConsEntity.getTypeExprExact(); final TypeExpr[] typePieces = dataConsTypeExpr.getTypePieces(); for (int k = 0, nDataConsArgs = typePieces.length - 1; k < nDataConsArgs; ++k) { try { kindCheckTypeExpr(typeVarToKindExprMap, typePieces[k]); } catch (TypeException typeException) { // DataDeclarationChecker: Kind error in the data constructor {dataConsName} for type {typeName}. compiler.logMessage( new CompilerMessage( dataConsDefnNode, new MessageKind.Error.KindErrorInDataConstructorForType(dataConsName, typeName), typeException)); } } } } //Now bind the remaining kind variables to *. This must be done before kind checking the next component. for (int j = 0; j < componentSize; ++j) { final String typeName = component.getVertex(j).getName(); final TypeConstructor typeCons = currentModuleTypeInfo.getTypeConstructor(typeName); typeCons.finishedKindChecking(); if (DEBUG_INFO) { System.out.println("Type: " + typeName + " Comp #" + i); System.out.println("Kind: " + typeCons.getKindExpr().toString()); System.out.println(""); } } } } /** * Checks that there is only one type declaration for each data type. * Note: a type may be built-in, introduced through a foreign data declaration, or introduced * through a non-foreign data declaration. * Also, checks that data constructor names are unique throughout the module. * These can be thought of as "global" symbol checks. There are other symbol checks, such that the type variables * in a given data declaration all have distinct symbols, but these are performed later. * * Also checks that the type-classes referred to in the derived clauses actually exist and satisfy some simple * static checks. * * Creation date: (1/18/01 1:48:03 PM) * @param dataDeclarationNodes * @param foreignDataDeclarationNodes * @return Map (String->ParseTreeNode) from unqualified type name to DataDeclarationNode. * It is necessary to traverse the data declarations more than once for typing them. We provide this map to enable quick access. * The domain of this map contain the names of all non built-in type names defined within the current module * A qualified name is not required since the module name is implicitly given. */ private Map<String, ParseTreeNode> checkNamesUsed(final List<ParseTreeNode> dataDeclarationNodes, final List<ParseTreeNode> foreignDataDeclarationNodes) { //the names of all the non built-in data constructors used in the module final Set<String> dataConstructorNamesSet = new HashSet<String>(); final Set<String> foreignTypeConstructorNamesSet = new HashSet<String>(); //Check to see that there is only 1 data declaration for a given data type and cache //the access to the data declaration Nodes for later use. for (final ParseTreeNode foreignDataDeclarationNode : foreignDataDeclarationNodes) { foreignDataDeclarationNode.verifyType(CALTreeParserTokenTypes.FOREIGN_DATA_DECLARATION); final ParseTreeNode optionalCALDocNode = foreignDataDeclarationNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode implementationScopeNode = optionalCALDocNode.nextSibling(); Scope implementationScope = CALTypeChecker.getScopeModifier(implementationScopeNode); final ParseTreeNode externalNameNode = implementationScopeNode.nextSibling(); externalNameNode.verifyType(CALTreeParserTokenTypes.STRING_LITERAL); final String externalName = StringEncoder.unencodeString(externalNameNode.getText ()); final ParseTreeNode accessModifierNode = externalNameNode.nextSibling(); final Scope scope = CALTypeChecker.getScopeModifier(accessModifierNode); final ParseTreeNode typeNameNode = accessModifierNode.nextSibling(); typeNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); final String typeName = typeNameNode.getText(); final boolean isBuiltInTypeName = TypeConstructor.getBuiltInType(QualifiedName.make(currentModuleTypeInfo.getModuleName(), typeName)) != null; if (isBuiltInTypeName) { //foreign type name is the same as a built-in type name compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.ForeignTypeHasSameNameAsBuiltInType(typeName))); } if (foreignTypeConstructorNamesSet.contains(typeName)) { //foreign type name is defined more than once compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.RepeatedDefinitionOfForeignType(typeName))); } final ModuleName usingModuleName = currentModuleTypeInfo.getModuleOfUsingTypeConstructor(typeName); if (usingModuleName != null) { //there is already an //import 'usingModuleName' using typeConstructor = 'typeName'; compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.ForeignFunctionNameAlreadyUsedInImportUsingDeclaration(typeName, usingModuleName))); } //Now create the type constructor entity object and add it to currentModuleTypeInfo. //todoBI we check that the Java type can be initialized at compile time. This may be //too early in certain cases, but it is handy during compiler development. May want to //later have a pragma to turn off the check until run-time. foreignTypeConstructorNamesSet.add(typeName); Class<?> foreignType = ForeignEntityResolver.getPrimitiveType(externalName); if (foreignType == null) { final ForeignEntityResolver.ResolutionResult<Class<?>> classResolution = ForeignEntityResolver.resolveClass(ForeignEntityResolver.javaSourceReferenceNameToJvmInternalName(externalName), currentModuleTypeInfo.getModule().getForeignClassLoader()); final ForeignEntityResolver.ResolutionStatus resolutionStatus = classResolution.getStatus(); foreignType = classResolution.getResolvedEntity(); if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.SUCCESS) { // the resolution was successful, so no need to report errors } else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.NO_SUCH_ENTITY) { compiler.logMessage( new CompilerMessage( typeNameNode, new MessageKind.Error.ExternalClassNotFound(externalName, typeName))); } else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.DEPENDEE_CLASS_NOT_FOUND) { // The Java class {notFoundClass} was not found. This class is required by {externalName}. compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.DependeeJavaClassNotFound(classResolution.getAssociatedMessage(), externalName))); } else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.CANNOT_LOAD_CLASS) { // The definition of Java class {externalName} could not be loaded. compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.JavaClassDefinitionCouldNotBeLoaded(externalName))); } else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.CANNOT_INITIALIZE_CLASS) { compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.ExternalClassCouldNotBeInitialized(externalName, typeName))); } else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.LINKAGE_ERROR) { // The java class {externalName} was found, but there were problems with using it. // Class: {LinkageError.class} // Message: {e.getMessage()} compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.ProblemsUsingJavaClass(externalName, (LinkageError)classResolution.getThrowable()))); } else if (resolutionStatus == ForeignEntityResolver.ResolutionStatus.NOT_ACCESSIBLE) { //"The Java type ''{0}'' is not accessible. It does not have public scope or is in an unnamed package." //the parameter {0} will be replaced by "class java.lang.Foo" or "interface java.lang.Foo" as appropriate. compiler.logMessage( new CompilerMessage( externalNameNode, new MessageKind.Error.ExternalClassNotAccessible(foreignType))); } else { // Some other unexpected status throw new IllegalStateException("Unexpected status: " + resolutionStatus); } } final QualifiedName calTypeName = QualifiedName.make(currentModuleTypeInfo.getModuleName(), typeName); final ForeignTypeInfo foreignTypeInfo = new ForeignTypeInfo(calTypeName, ForeignEntityProvider.makeStrict(foreignType), implementationScope); final ParseTreeNode derivingClauseNode = typeNameNode.nextSibling(); final TypeConstructor.DerivingClauseInfo[] derivingClauseTypeClassNames = checkDerivingClauseNames(derivingClauseNode, true, foreignType); final TypeConstructor typeCons = TypeConstructor.makeTypeConstructor(calTypeName, scope, KindExpr.STAR, foreignTypeInfo, derivingClauseTypeClassNames, null); currentModuleTypeInfo.addTypeConstructor(typeCons); } final Map<String, ParseTreeNode> typeNameToDataDeclarationNodeMap = new HashMap<String, ParseTreeNode>(); for (final ParseTreeNode dataDeclarationNode : dataDeclarationNodes) { dataDeclarationNode.verifyType(CALTreeParserTokenTypes.DATA_DECLARATION); //Collect the names of the types used in the program. final ParseTreeNode optionalCALDocNode = dataDeclarationNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); final Scope scope = CALTypeChecker.getScopeModifier(accessModifierNode); final ParseTreeNode typeNameNode = accessModifierNode.nextSibling(); typeNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); final String typeName = typeNameNode.getText(); final boolean isBuiltInTypeName = TypeConstructor.getBuiltInType(QualifiedName.make(currentModuleTypeInfo.getModuleName(), typeName)) != null; if (isBuiltInTypeName) { //type name is the same as a built-in type name compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.TypeHasSameNameAsBuiltInType(typeName))); } if (foreignTypeConstructorNamesSet.contains(typeName)) { //type name is the same as a type introduced by a foreign data declaration compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.TypeHasSameNameAsForeignType(typeName))); } if (typeNameToDataDeclarationNodeMap.containsKey(typeName)) { //type name is defined more than once compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.RepeatedDefinitionOfType(typeName))); } final ModuleName usingModuleName = currentModuleTypeInfo.getModuleOfUsingTypeConstructor(typeName); if (usingModuleName != null) { //there is already an //import 'usingModuleName' using typeConstructor = 'typeName'; compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.TypeConstructorAlreadyUsedInImportUsingDeclaration(typeName, usingModuleName))); } //add a reference to the parseTree to the map so that it can be looked up later //when typing a component typeNameToDataDeclarationNodeMap.put(typeName, dataDeclarationNode); //Data constructor names must be unique within the entire module. final ParseTreeNode dataConsDefnListNode = typeNameNode.nextSibling().nextSibling(); dataConsDefnListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN_LIST); final ParseTreeNode derivingClauseNode = dataConsDefnListNode.nextSibling(); final TypeConstructor.DerivingClauseInfo[] derivingClauseTypeClassNames = checkDerivingClauseNames(derivingClauseNode, false, null); // determine whether we need to perform additional checks to see whether // the data type being declared is an enumeration type or a non-polymorphic type. QualifiedName enumerationOnlyDerivedTypeClassName = null; for (final TypeConstructor.DerivingClauseInfo typeClass : derivingClauseTypeClassNames) { if (possibleAlgebraicEnumerationTypeDerivingClauseNames.contains(typeClass.getName())) { enumerationOnlyDerivedTypeClassName = typeClass.getName(); } } for (final ParseTreeNode dataConsDefnNode : dataConsDefnListNode) { dataConsDefnNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN); final ParseTreeNode dataConsNameNode = dataConsDefnNode.getChild(2); final String dataConsName = dataConsNameNode.getText(); if (!dataConstructorNamesSet.add(dataConsName)) { // Data constructor names must be unique within a module. compiler.logMessage(new CompilerMessage(dataConsNameNode, new MessageKind.Error.RepeatedDefinitionOfDataConstructor(dataConsName))); } final ModuleName usingModuleNameForDataConstructor = currentModuleTypeInfo.getModuleOfUsingDataConstructor(dataConsName); if (usingModuleNameForDataConstructor != null) { //there is already an //import 'usingModuleNameForDataConstructor' using dataConstructor = 'dataConsName'; compiler.logMessage(new CompilerMessage(dataConsNameNode, new MessageKind.Error.DataConstructorNameAlreadyUsedInImportUsingDeclaration(dataConsName, usingModuleNameForDataConstructor))); } if (enumerationOnlyDerivedTypeClassName != null) { final ParseTreeNode dataConsArgListNode = dataConsDefnNode.getChild(3); dataConsArgListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_ARG_LIST); if (!dataConsArgListNode.hasNoChildren()) { // the data type being declared is not an enumeration type, and therefore cannot be a derived instance of Bounded or Enum compiler.logMessage(new CompilerMessage(dataConsNameNode, new MessageKind.Error.TypeClassInDerivingClauseRequiresEnumerationType(enumerationOnlyDerivedTypeClassName))); } } } // A phantom type cannot be made a derived instance of the Prelude.Enum and Prelude.Bounded type classes. // Since derived Prelude.Enum and Prelude.Bounded instances are also required to be pure enumeration types, // (a requirement that is already checked by the code immediately above) // we can simply check to see whether the type constructor has any parameters to determine // whether the type is a phantom type. if (enumerationOnlyDerivedTypeClassName != null) { final ParseTreeNode typeConsParamListNode = typeNameNode.nextSibling(); typeConsParamListNode.verifyType(CALTreeParserTokenTypes.TYPE_CONS_PARAM_LIST); if (typeConsParamListNode.getFirstChild() != null) { // the data type being declared is a polymorphic type, and therefore cannot be a derived instance of Bounded or Enum compiler.logMessage(new CompilerMessage(typeConsParamListNode, new MessageKind.Error.TypeClassInDerivingClauseRequiresNonPolymorphicType(enumerationOnlyDerivedTypeClassName))); } } //add the type constructor to the moduleTypeInfo. Note that the kinds will need to be updated after kind checking. //Add the initial guess of the kind of each type constructor to the typeConsNameToKindMap. //e.g. for "data T a b c = ..." then T has kind k1->(k2->(k3->*)) where k1, k2, and k3 are kind //variables. The actual kinds of the kind variables are determined by kind inference final ParseTreeNode typeConsParamListNode = typeNameNode.nextSibling(); typeConsParamListNode.verifyType(CALTreeParserTokenTypes.TYPE_CONS_PARAM_LIST); KindExpr kindExpr = KindExpr.STAR; for (int j = typeConsParamListNode.getNumberOfChildren(); j > 0; --j) { kindExpr = new KindExpr.KindFunction(new KindExpr.KindVar(), kindExpr); } final TypeConstructor typeCons = TypeConstructor.makeTypeConstructor(QualifiedName.make(currentModuleTypeInfo.getModuleName(), typeName), scope, kindExpr, null, derivingClauseTypeClassNames, null); currentModuleTypeInfo.addTypeConstructor(typeCons); } return typeNameToDataDeclarationNodeMap; } /** * Checks that the deriving clause of a foreign or algebraic data declaration * a) contains only resolvable type class names * b) has no duplicate type class names * c) only makes use of supported type classes * * There are additional checks that need to be made later- this is only simple static checking. * For example: * a) certain types cannot be derived instances of Eq because their constituent types can't be e.g. data Foo a = For (a -> a); * b) cannot have overlapping instances * * @param derivingClauseNode ParseTreeNode for the deriving clause * @param isForeignType True if the deriving clause is attached to a foreign data declaration * @param foreignType Class of the foreign type (for foreign data declarations). Ignored for algebraic data declarations. * @return QualifiedName[] names of the type classes in the deriving clause in declaration order. */ private TypeConstructor.DerivingClauseInfo[] checkDerivingClauseNames(final ParseTreeNode derivingClauseNode, final boolean isForeignType, final Class<?> foreignType) { if (derivingClauseNode != null) { derivingClauseNode.verifyType(CALTreeParserTokenTypes.LITERAL_deriving); if(isForeignType && foreignType == null) { throw new IllegalArgumentException("checkDerivingClauseNames must be passed a non-null foreignType for foreign data declarations"); } //QualifiedName Set. Ordered by declaration order of the type class names in the deriving clause. final Set<TypeConstructor.DerivingClauseInfo> typeClassNamesSet = new LinkedHashSet<TypeConstructor.DerivingClauseInfo>(); for (final ParseTreeNode typeClassNameNode : derivingClauseNode) { final TypeClass typeClass = typeClassChecker.resolveClassName(typeClassNameNode); //note we check that typeClass != null in order to be able to continue compilation even if the type class name //was not successfully resolved. if (typeClass != null) { final QualifiedName typeClassName = typeClass.getName(); if (!typeClassNamesSet.add(new TypeConstructor.DerivingClauseInfo(typeClassName, typeClassNameNode.getAssemblySourceRange()))) { //Repeated type class name <name> in deriving clause. compiler.logMessage(new CompilerMessage(typeClassNameNode, new MessageKind.Error.RepeatedTypeClassNameInDerivingClause(typeClassName))); continue; } if (isForeignType && !possibleForeignTypeDerivingClauseNames.contains(typeClassName) || !isForeignType && !possibleAlgebraicTypeDerivingClauseNames.contains(typeClassName)) { // Note: for the type classes in possibleAlgebraicEnumerationTypeDerivingClauseNames, // we need to do an additional check later on to make sure that the type being // declared is indeed an enumeration type. //Unsupported type class name <name> in deriving clause. compiler.logMessage(new CompilerMessage(typeClassNameNode, new MessageKind.Error.UnsupportedTypeClassNameInDerivingClause(typeClassName))); } if(isForeignType) { // Check that foreign types inherit from prerequisite interfaces Class<?> requiredInterface = foreignTypesRequiringPrimitiveOrImplementedInterface.get(typeClassName); if(requiredInterface != null && !foreignType.isPrimitive() && !requiredInterface.isAssignableFrom(foreignType)) { // "The type class {0} in the deriving clause requires that the foreign type being imported either implement {1} or be a Java primitive" compiler.logMessage(new CompilerMessage(typeClassNameNode, new MessageKind.Error.TypeClassInDerivingClauseRequiresPrimitiveOrImplementedInterface(typeClassName, requiredInterface))); } } } } return typeClassNamesSet.toArray(NO_DERIVING_CLAUSE); } else { return NO_DERIVING_CLAUSE; } } /** * Given a type expression parse tree, and a map with all the type variables that can appear in it, * this method returns the resulting type expression. * * Creation date: (1/22/01 1:06:30 PM) * @return TypeExpr * @param parseTree * @param typeVarNameToTypeMap (String -> TypeExpr) */ private TypeExpr determineTypeExpr(final ParseTreeNode parseTree, final Map<String, TypeVar> typeVarNameToTypeMap) { //todoBI //This method is rather similar (but not identical to) TypeChecker.calculateDeclaredType //investigate how to factor better. //The main differences are that type expressions appearing within data declarations cannot //have unbound typeVars e.g. data FooBar a = MakeFooBar b gives an error since b is unbound. //Also they cannot involve record variables. switch (parseTree.getType()) { case CALTreeParserTokenTypes.FUNCTION_TYPE_CONSTRUCTOR : { ParseTreeNode domainNode = parseTree.firstChild(); TypeExpr domain = determineTypeExpr(domainNode, typeVarNameToTypeMap); TypeExpr codomain = determineTypeExpr(domainNode.nextSibling(), typeVarNameToTypeMap); return TypeExpr.makeFunType(domain, codomain); } case CALTreeParserTokenTypes.TUPLE_TYPE_CONSTRUCTOR : { if (parseTree.hasNoChildren()) { return compiler.getTypeChecker().getTypeConstants().getUnitType(); } if (parseTree.hasExactlyOneChild()) { // the type (t) is equivalent to the type t. return determineTypeExpr(parseTree.firstChild(), typeVarNameToTypeMap); } final Map<FieldName, TypeExpr> fieldToTypeMap = new HashMap<FieldName, TypeExpr>(); //FieldName -> TypeExpr int componentN = 1; for (final ParseTreeNode componentNode : parseTree) { final TypeExpr componentTypeExpr = determineTypeExpr(componentNode, typeVarNameToTypeMap); fieldToTypeMap.put(FieldName.makeOrdinalField(componentN), componentTypeExpr); ++componentN; } return new RecordType(RecordVar.NO_FIELDS, fieldToTypeMap); } case CALTreeParserTokenTypes.LIST_TYPE_CONSTRUCTOR : { final TypeExpr elementTypeExpr = determineTypeExpr(parseTree.firstChild(), typeVarNameToTypeMap); return compiler.getTypeChecker().getTypeConstants().makeListType(elementTypeExpr); } case CALTreeParserTokenTypes.TYPE_APPLICATION : { if (parseTree.hasExactlyOneChild()) { //not really an application node, but an artifact of parsing return determineTypeExpr(parseTree.firstChild(), typeVarNameToTypeMap); } //we preferentially construct a TypeConsApp over a TypeApp where both are technically //valid representations of the type. This is mainly because type inference is a little //simpler (as well as more firmly tested) in the earlier case. final ParseTreeNode firstChildNode = parseTree.firstChild(); if (firstChildNode.getType() == CALTreeParserTokenTypes.QUALIFIED_CONS) { final TypeConstructor typeCons = resolveTypeConsName(firstChildNode); //note that we cannot assume that the type constructor is fully saturated //i.e. this may correctly be an undersaturated application. //We can give a friendlier error message than a kind-checking error in the case of oversaturation. final int nArgs = parseTree.getNumberOfChildren() - 1; final int maxNArgs = typeCons.getTypeArity(); if (nArgs > maxNArgs) { // The type constructor {typeConsEntity.getName()} expects at most {maxNArgs} type argument(s). {nArgs} supplied. compiler.logMessage( new CompilerMessage( firstChildNode, new MessageKind.Error.TypeConstructorAppliedToOverlyManyArgs(typeCons.getName(), maxNArgs, nArgs))); } final TypeExpr[] args = new TypeExpr[nArgs]; int argN = 0; for (final ParseTreeNode argNode : firstChildNode.nextSiblings()) { args[argN] = determineTypeExpr(argNode, typeVarNameToTypeMap); ++argN; } return new TypeConsApp(typeCons, args); } TypeExpr partialApp = determineTypeExpr(firstChildNode, typeVarNameToTypeMap); for (final ParseTreeNode argNode : firstChildNode.nextSiblings()) { partialApp = new TypeApp(partialApp, determineTypeExpr(argNode, typeVarNameToTypeMap)); } return partialApp; } case CALTreeParserTokenTypes.QUALIFIED_CONS : { final TypeConstructor typeCons = resolveTypeConsName(parseTree); //note that we cannot assume that the type constructor is non-parametric //i.e. this may be an undersaturated application. //For example, for data T f = MakeT field :: (f List); //this case will be called for List. (The kind of T is (* -> *) -> *) return new TypeConsApp(typeCons, null); } case CALTreeParserTokenTypes.VAR_ID : { final String typeVarName = parseTree.getText(); final TypeVar typeVar = typeVarNameToTypeMap.get(typeVarName); if (typeVar == null) { compiler.logMessage(new CompilerMessage(parseTree, new MessageKind.Error.TypeVariableMustAppearOnLHSOfDataDeclaration(typeVarName))); } return typeVar; } case CALTreeParserTokenTypes.RECORD_TYPE_CONSTRUCTOR : { final ParseTreeNode recordVarNode = parseTree.firstChild(); recordVarNode.verifyType(CALTreeParserTokenTypes.RECORD_VAR); final RecordVar recordVar; final ParseTreeNode recordVarNameNode = recordVarNode.firstChild(); if (recordVarNameNode != null) { //a record-polymorphic record // Record variables, such as {recordVarNameNode.getText()} cannot appear in data declarations. compiler.logMessage(new CompilerMessage(recordVarNameNode, new MessageKind.Error.RecordVarsCannotAppearInDataDeclarations(recordVarNameNode.getText()))); return null; } else { //a non record-polymorphic record recordVar = RecordVar.NO_FIELDS; } final ParseTreeNode fieldTypeAssignmentListNode = recordVarNode.nextSibling(); fieldTypeAssignmentListNode.verifyType(CALTreeParserTokenTypes.FIELD_TYPE_ASSIGNMENT_LIST); //FieldName -> TypeExpr final Map<FieldName, TypeExpr> extensionFieldsMap = new HashMap<FieldName, TypeExpr>(); for (final ParseTreeNode fieldTypeAssignmentNode : fieldTypeAssignmentListNode) { fieldTypeAssignmentNode.verifyType(CALTreeParserTokenTypes.FIELD_TYPE_ASSIGNMENT); final ParseTreeNode fieldNameNode = fieldTypeAssignmentNode.firstChild(); final FieldName fieldName = compiler.getTypeChecker().getFieldName(fieldNameNode); final ParseTreeNode typeNode = fieldNameNode.nextSibling(); final TypeExpr type = determineTypeExpr(typeNode, typeVarNameToTypeMap); extensionFieldsMap.put(fieldName, type); } return new RecordType(recordVar, extensionFieldsMap); } default : { parseTree.unexpectedParseTreeNode(); break; } } return null; } /** * Find the types that are used within the definition of a given type in its data declaration. * * Creation date: (1/19/01 12:25:29 PM) * @param parseTree * @param typeVarNamesSet (set of Strings) the names of the type variables used in a particular data declaration * e.g. Either a b = Left a | Right b -- the type variables are a and b. * @param freeTypesSet (set of Strings) The free types encountered while traversing the parse tree of a type definition. These * are the names of the non-built-in non-foreign types defined within the current module upon which the type being examined depends. * @param declaredTypeNamesSet the set of all non-built-in type names defined within the current module. */ private void findFreeTypes(ParseTreeNode parseTree, Set<String> typeVarNamesSet, Set<String> freeTypesSet, Set<String> declaredTypeNamesSet) { switch (parseTree.getType()) { case CALTreeParserTokenTypes.FUNCTION_TYPE_CONSTRUCTOR : case CALTreeParserTokenTypes.TUPLE_TYPE_CONSTRUCTOR : case CALTreeParserTokenTypes.LIST_TYPE_CONSTRUCTOR : case CALTreeParserTokenTypes.TYPE_APPLICATION : { //We are not adding the built-in types as dependee types. This includes both the built-in types that //have identifier names, as well as those introduced using special syntax, as in this case. for (final ParseTreeNode typeExprNode : parseTree) { findFreeTypes(typeExprNode, typeVarNamesSet, freeTypesSet, declaredTypeNamesSet); } break; } case CALTreeParserTokenTypes.QUALIFIED_CONS : { QualifiedName typeName = parseTree.toQualifiedName(); String unqualifiedTypeName = typeName.getUnqualifiedName(); ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); if (typeName.getModuleName().equals(currentModuleName) && declaredTypeNamesSet.contains(unqualifiedTypeName)) { freeTypesSet.add(unqualifiedTypeName); } break; } case CALTreeParserTokenTypes.VAR_ID : { final String typeVarName = parseTree.getText(); if (!typeVarNamesSet.contains(parseTree.getText())) { // Unbound type variable {typeVarName}. compiler.logMessage(new CompilerMessage(parseTree, new MessageKind.Error.UnboundTypeVariable(typeVarName))); } break; } case CALTreeParserTokenTypes.RECORD_TYPE_CONSTRUCTOR : { final ParseTreeNode recordVarNode = parseTree.firstChild(); recordVarNode.verifyType(CALTreeParserTokenTypes.RECORD_VAR); final ParseTreeNode recordVarNameNode = recordVarNode.firstChild(); if (recordVarNameNode != null) { //a record-polymorphic record // Record variables, such as {recordVarNameNode.getText()} cannot appear in data declarations. compiler.logMessage(new CompilerMessage(recordVarNameNode, new MessageKind.Error.RecordVarsCannotAppearInDataDeclarations(recordVarNameNode.getText()))); return; } final ParseTreeNode fieldTypeAssignmentListNode = recordVarNode.nextSibling(); fieldTypeAssignmentListNode.verifyType(CALTreeParserTokenTypes.FIELD_TYPE_ASSIGNMENT_LIST); for (final ParseTreeNode fieldTypeAssignmentNode : fieldTypeAssignmentListNode) { fieldTypeAssignmentNode.verifyType(CALTreeParserTokenTypes.FIELD_TYPE_ASSIGNMENT); final ParseTreeNode fieldNameNode = fieldTypeAssignmentNode.firstChild(); final ParseTreeNode typeExprNode = fieldNameNode.nextSibling(); findFreeTypes(typeExprNode, typeVarNamesSet, freeTypesSet, declaredTypeNamesSet); } break; } default : { parseTree.unexpectedParseTreeNode(); break; } } } /** * Makes the dependency graph of the type constructors defined within the module. * * Creation date: (1/19/01 11:52:00 AM) * @param dataDeclarationNodes * @param declaredTypeNamesSet the set of all non-built-in type names defined within the current module. * @return VertexBuilderList */ private VertexBuilderList<String> makeTypeConstructorDependencyGraph(final List<ParseTreeNode> dataDeclarationNodes, final Set<String> declaredTypeNamesSet) { final VertexBuilderList<String> vertexBuilderList = new VertexBuilderList<String>(); for (final ParseTreeNode dataDeclarationNode : dataDeclarationNodes) { final String typeName = dataDeclarationNode.getChild(2).getText(); final Set<String> typeVarNamesSet = new HashSet<String>(); final ParseTreeNode typeConsParamListNode = dataDeclarationNode.getChild(3); typeConsParamListNode.verifyType(CALTreeParserTokenTypes.TYPE_CONS_PARAM_LIST); for (final ParseTreeNode typeVarNameNode : typeConsParamListNode) { //have already checked for repeated vars typeVarNamesSet.add(typeVarNameNode.getText()); } //Go through the type expression sub trees and find the free types. These are the types //upon which this type definition depends. final ParseTreeNode dataConsDefnListNode = typeConsParamListNode.nextSibling(); dataConsDefnListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN_LIST); final Set<String> freeTypesSet = new HashSet<String>(); for (final ParseTreeNode dataConsDefnNode : dataConsDefnListNode) { dataConsDefnNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN); final ParseTreeNode dataConsArgListNode = dataConsDefnNode.getChild(3); dataConsArgListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_ARG_LIST); for (final ParseTreeNode dataConsArgNode : dataConsArgListNode) { dataConsArgNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAMED_ARG); // Get the type node. final ParseTreeNode dataConsArgNameNode = dataConsArgNode.firstChild(); final ParseTreeNode maybePlingTypeExprNode = dataConsArgNameNode.nextSibling(); final ParseTreeNode typeExprNode; if (maybePlingTypeExprNode.getType() == CALTreeParserTokenTypes.STRICT_ARG) { typeExprNode = maybePlingTypeExprNode.firstChild(); } else { typeExprNode = maybePlingTypeExprNode; } findFreeTypes(typeExprNode, typeVarNamesSet, freeTypesSet, declaredTypeNamesSet); } } vertexBuilderList.add(new VertexBuilder<String>(typeName, freeTypesSet)); } return vertexBuilderList; } /** * A helper function that makes a map from the type variable to its initial kind variable kind. * Creation date: (1/26/01 10:30:08 AM) * @return Map * @param dataConstructorEnv * @param dataConsDefnListNode */ private Map<TypeVar, KindExpr> makeTypeVarToKindVarMap(final Env dataConstructorEnv, final ParseTreeNode dataConsDefnListNode) { final Map<TypeVar, KindExpr> typeVarToKindVarMap = new HashMap<TypeVar, KindExpr>(); final ParseTreeNode dataConsDefnNode = dataConsDefnListNode.firstChild(); dataConsDefnNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_DEFN); final String dataConsName = dataConsDefnNode.getChild(2).getText(); final FunctionalAgent dataConsEntity = dataConstructorEnv.retrieveEntity(dataConsName); if (dataConsEntity == null) { //we've added dataConsName to the environment, so this should never happen. throw new NullPointerException("Programming error"); } final TypeExpr dataConsTypeExpr = dataConsEntity.getTypeExprExact(); //if "data T a1 ... an = MakeT te1 te2 ... tek | ..." then the type of MakeT is of //the form "... -> T a1 ... an". In particular, the last type piece is "T a1 ... an". //The kind of T is its initial guess: k1->k2->,,,->kn->*. We create the map: //typeVarToKindExprMap = (a1,k1), (a2,k2), ... (an,kn). final TypeConsApp typeCons = (TypeConsApp) dataConsTypeExpr.getResultType(); final QualifiedName typeName = typeCons.getName(); KindExpr kindExpr = currentModuleTypeInfo.getTypeConstructor(typeName.getUnqualifiedName()).getKindExpr(); for (int i = 0, nArgs = typeCons.getNArgs(); i < nArgs; ++i) { final TypeVar typeVar = (TypeVar) typeCons.getArg(i); typeVarToKindVarMap.put(typeVar, ((KindExpr.KindFunction) kindExpr).getDomain()); kindExpr = ((KindExpr.KindFunction) kindExpr).getCodomain(); } return typeVarToKindVarMap; } /** * Perform dependency analysis to divide up the types defined in the CAL program into * a topologically ordered set of strongly connected components. This is needed because * the data declarations in a program must be processed in a particular order, and in dependency * groups, whereas they can appear in any order in the source file, and are not grouped together * in any way. * * Creation date: (1/19/01 11:47:40 AM) * @param dataDeclarationNodes * @param declaredTypeNamesSet the set of all non-built-in type names defined within the current module. * @return Graph */ private Graph<String> performTypeConstructorDependencyAnalysis(final List<ParseTreeNode> dataDeclarationNodes, final Set<String> declaredTypeNamesSet) { final VertexBuilderList<String> vertexBuilderList = makeTypeConstructorDependencyGraph(dataDeclarationNodes, declaredTypeNamesSet); // should never fail. It is a redundant check since makeTypeConstructorDependencyGraph should throw an exception otherwise. if (!vertexBuilderList.makesValidGraph()) { throw new IllegalStateException("Internal coding error during dependency analysis."); } final Graph<String> g = new Graph<String>(vertexBuilderList); vertexBuilderList.clear(); return g.calculateStronglyConnectedComponents(); } /** * A helper function that verifies that a type constructor name refers to a type constructor * that actually exists. As a side effect, the qualifiedTypeConsNode is modified to explicitly * include the module name, so that fully qualified type names can be assumed in further * analysis. * * Note: this function assumes that checkNames has already been called. * * Creation date: (7/24/01 10:47:52 AM) * @param qualifiedTypeConsNode * @return TypeConstructor the resolved type constructor, or null if a type constructor can't be resolved. */ TypeConstructor resolveTypeConsName(final ParseTreeNode qualifiedTypeConsNode) { return resolveTypeConsName(qualifiedTypeConsNode, currentModuleTypeInfo, compiler); } /** * A helper function that verifies that a type constructor name refers to a type constructor * that actually exists. As a side effect, the qualifiedTypeConsNode is modified to explicitly * include the module name, so that fully qualified type names can be assumed in further * analysis. * * Note: this function assumes that checkNames has already been called. * * @param qualifiedTypeConsNode the node containing the qualified type constructor name to be resolved. * @param currentModuleTypeInfo the type info for the current module. * @param compiler the CALCompiler instance for error reporting. * @return TypeConstructor the resolved type constructor, or null if a type constructor can't be resolved. */ static TypeConstructor resolveTypeConsName(final ParseTreeNode qualifiedTypeConsNode, final ModuleTypeInfo currentModuleTypeInfo, final CALCompiler compiler) { return resolveTypeConsName(qualifiedTypeConsNode, currentModuleTypeInfo, compiler, false); } /** * A helper function that verifies that a type constructor name refers to a type constructor * that actually exists. As a side effect, the qualifiedTypeConsNode is modified to explicitly * include the module name, so that fully qualified type names can be assumed in further * analysis. * * Note: this function assumes that checkNames has already been called. * * @param qualifiedTypeConsNode the node containing the qualified type constructor name to be resolved. * @param currentModuleTypeInfo the type info for the current module. * @param compiler the CALCompiler instance for error reporting. * @param suppressErrorMessageLogging whether to suppress the logging of error messages to the CALCompiler instance * @return TypeConstructor the resolved type constructor, or null if a type constructor can't be resolved. */ static TypeConstructor resolveTypeConsName(final ParseTreeNode qualifiedTypeConsNode, final ModuleTypeInfo currentModuleTypeInfo, final CALCompiler compiler, boolean suppressErrorMessageLogging) { qualifiedTypeConsNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); final ParseTreeNode moduleNameNode = qualifiedTypeConsNode.firstChild(); final String maybeModuleName = ModuleNameUtilities.resolveMaybeModuleNameInParseTree(moduleNameNode, currentModuleTypeInfo, compiler, suppressErrorMessageLogging); final ParseTreeNode typeNameNode = moduleNameNode.nextSibling(); final String typeName = typeNameNode.getText(); final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); if (maybeModuleName.length() > 0) { //an explicitly qualified type name final ModuleName moduleName = ModuleName.make(maybeModuleName); final QualifiedName qualifiedTypeName = QualifiedName.make(moduleName, typeName); if (moduleName.equals(currentModuleName)) { //type belongs to the current module final TypeConstructor typeCons = currentModuleTypeInfo.getTypeConstructor(typeName); if (typeCons != null) { checkResolvedTypeConsReference(compiler, typeNameNode, typeCons.getName(), currentModuleName, suppressErrorMessageLogging); //typeName is a non built-in type defined in the current module via a non-foreign data declaration //or a built-in type name or a type-name defined with a foreign data declaration return typeCons; } if (!suppressErrorMessageLogging) { // Attempt to use undefined type {qualifiedTypeName}. compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.AttemptToUseUndefinedType(qualifiedTypeName.getQualifiedName()))); } return null; } //typeName belongs to a different module final ModuleTypeInfo importedModuleTypeInfo = currentModuleTypeInfo.getImportedModule(moduleName); if (importedModuleTypeInfo == null) { // The module {moduleName} has not been imported into {currentModuleName}. if (!suppressErrorMessageLogging) { compiler.logMessage(new CompilerMessage(moduleNameNode, new MessageKind.Error.ModuleHasNotBeenImported(moduleName, currentModuleName))); } return null; } final TypeConstructor typeCons = importedModuleTypeInfo.getTypeConstructor(typeName); if (typeCons == null) { // The type {qualifiedTypeName} does not exist. if (!suppressErrorMessageLogging) { compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.TypeDoesNotExist(qualifiedTypeName.getQualifiedName()))); } return null; } else if (!currentModuleTypeInfo.isEntityVisible(typeCons)) { if (!suppressErrorMessageLogging) { // The type {qualifiedTypeName} is not visible in {currentModuleName}. //"The type {0} is not visible in module {1}." compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.TypeConstructorNotVisible(typeCons, currentModuleName))); } return null; } checkResolvedTypeConsReference(compiler, typeNameNode, typeCons.getName(), currentModuleName, suppressErrorMessageLogging); //Success- the module is indeed imported and has a visible type name of the specified name. return typeCons; } //An unqualified type name //At this point we need to decide if the type is //a. a type defined in the current module //b. a visible type defined in another module //We patch up the parse tree to supply the missing module name. final TypeConstructor typeCons = currentModuleTypeInfo.getTypeConstructor(typeName); if (typeCons != null) { checkResolvedTypeConsReference(compiler, typeNameNode, typeCons.getName(), currentModuleName, suppressErrorMessageLogging); //typeName is a non built-in type defined in the current module via a non-foreign data declaration //or a built-in type name or a type-name defined with a foreign data declaration ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, currentModuleName); return typeCons; } //We now know that the type name can't be defined within the current module. //check if it is a "using typeConstructor" and then patch up the module name. final ModuleName usingModuleName = currentModuleTypeInfo.getModuleOfUsingTypeConstructor(typeName); if (usingModuleName != null) { final QualifiedName qualifiedTypeName = QualifiedName.make(usingModuleName, typeName); checkResolvedTypeConsReference(compiler, typeNameNode, qualifiedTypeName, currentModuleName, suppressErrorMessageLogging); ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, usingModuleName); //this call is guaranteed to return a type constructor due to earlier static checks on the using clause return currentModuleTypeInfo.getVisibleTypeConstructor(qualifiedTypeName); } if (!suppressErrorMessageLogging) { //We now know that the type name can't be defined within the current module and is not a "using typeConstructor". //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. final List<QualifiedName> candidateTypeConsNames = new ArrayList<QualifiedName>(); final int nImportedModules = currentModuleTypeInfo.getNImportedModules(); for (int i = 0; i < nImportedModules; ++i) { final TypeConstructor candidate = currentModuleTypeInfo.getNthImportedModule(i).getTypeConstructor(typeName); if (candidate != null && currentModuleTypeInfo.isEntityVisible(candidate)) { candidateTypeConsNames.add(candidate.getName()); } } final int numCandidates = candidateTypeConsNames.size(); if(numCandidates == 0) { // Attempt to use undefined type {typeName}. compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.AttemptToUseUndefinedType(typeName))); } else if(numCandidates == 1) { final QualifiedName typeConsName = candidateTypeConsNames.get(0); // Attempt to use undefined type {typeName}. Was {typeCons.getName()} intended? compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.AttemptToUseUndefinedTypeSuggestion(typeName, typeConsName))); } else { // The reference to the type {typeName} is ambiguous. It could mean any of {candidateTypeConsNames.toString()}. compiler.logMessage(new CompilerMessage(typeNameNode, new MessageKind.Error.AmbiguousTypeReference(typeName, candidateTypeConsNames))); } } return null; } /** * Performs late static checks on a type constructor reference that has already been resolved. * * Currently, this performs 1) a deprecation check on a type constructor reference, logging a warning message if the type is deprecated, and * 2) a check that if the reference is to a foreign type, then the corresponding Java class should be resolvable and loadable, logging * an error message if the class cannot be loaded. * @param compiler the compiler instance. * @param nameNode the parse tree node representing the reference. * @param qualifiedName the qualified name of the reference. * @param currentModuleName the name of the current module. * @param suppressCompilerMessageLogging whether to suppress message logging. */ static void checkResolvedTypeConsReference(final CALCompiler compiler, final ParseTreeNode nameNode, final QualifiedName qualifiedName, ModuleName currentModuleName, final boolean suppressCompilerMessageLogging) { if (!suppressCompilerMessageLogging) { // 1) deprecation check on the type constructor reference, logging a warning message if the type is deprecated. if (compiler.getDeprecationScanner().isTypeDeprecated(qualifiedName)) { compiler.logMessage(new CompilerMessage(nameNode, new MessageKind.Warning.DeprecatedType(qualifiedName))); } // 2) check that if the reference is to a foreign type, then the corresponding Java class should be resolvable and loadable. // only perform the check on references to entities outside the current module if (!qualifiedName.getModuleName().equals(currentModuleName)) { // Check if the type is a foreign type final ModuleTypeInfo moduleTypeInfo = compiler.getPackager().getModuleTypeInfo(qualifiedName.getModuleName()); final TypeConstructor typeConstructor = moduleTypeInfo.getTypeConstructor(qualifiedName.getUnqualifiedName()); final ForeignTypeInfo foreignTypeInfo = typeConstructor.getForeignTypeInfo(); if (foreignTypeInfo != null) { // Force resolution to occur. try { foreignTypeInfo.getForeignType(); // return value ignored } catch (UnableToResolveForeignEntityException e) { compiler.logMessage(new CompilerMessage( nameNode, new MessageKind.Error.AttemptToUseForeignTypeThatFailedToLoad(qualifiedName.toSourceText()), e)); } } } } } }