/* * 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. */ /* * CALTypeChecker.java (originally TypeChecker.java) * Created: July 17, 2000 * By: Bo Ilic */ package org.openquark.cal.compiler; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.logging.Level; import org.openquark.cal.compiler.CALSourceGenerator.CheckGraphSource; import org.openquark.cal.compiler.CompilerMessage.AbortCompilation; import org.openquark.cal.compiler.Expression.ErrorInfo; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.util.Graph; import org.openquark.util.Pair; /** * The main class for type checking modules in a CAL program. * This class checks top-level function definitions as well as top-level function type declarations. * * <p> * Various subtasks in type checking are delegated to other classes. For example: * data declarations, for the creation of new types, are delegated to the DataDeclarationChecker * lambda, case and local function lifting is delegated to LambdaLifter * adding extra dictionary arguments to resolve overloading is done in the OverloadingResolver * ... * * <p> * Creation date: (July 17, 2000) * @author Bo Ilic */ final class CALTypeChecker { /** Set to true to have debug info printed while running the type checker. */ private static final boolean DEBUG_INFO = false; /** The environment built-up during type checking for function (both top-level and local) definitions. */ private Env functionEnv; /** * (String -> TypeExpr) map of the (unique-transformed) names of all lower-case starting args within the definition of * a top-level (non-foreign, non-primitive) function to their type. */ private final Map<String, TypeExpr> functionBoundNamesToTypeMap; /** * (String -> NonGenericVars) map from unique-transformed local function name to the NonGenericVars * for the scope of the local function's declaration. It is entirely possible that all * of these type variables will have been instantiated by the time type checking is complete. * * Note that not every unique-transformed bound name has an entry in this map; only unique names that * correspond to local function definitions. */ private final Map<String, NonGenericVars> localFunctionBoundNamesToNonGenericVarsMap; /** * (String -> LocalFunctionIdentifier) map from unique-transformed local function name to the * corresponding LocalFunctionIdentifier. This map is populated quite late in the process of checking * a module. */ private final Map<String, LocalFunctionIdentifier> localFunctionBoundNamesToLocalFunctionIdentifiersMap; /** * (String -> Function) map from unique-transformed local function name to the Function for * that local function. * * Note that not every unique-transformed bound name has an entry in this map; only unique names that * correspond to local function definitions. */ private final Map<String, Function> localFunctionBoundNamesToFunctionEntityMap; /** * true if a warning should be given when a let variable is created with an overloaded polymorphic type. * An example is "let x = 1; in ..." then x has type Num a => a, and in particular, will be lambda lifted and * treated as a function by the runtime, even though it has the appearance of a local constant. */ private static final boolean WARNING_FOR_OVERLOADED_POLYMORPHIC_LET_VARS = false; /** * (String -> ParseTreeNode) map from let variables (i.e. 0-arguments supplied in the CAL source, so not a local function) * to their defining ParseTreeNode. Provided that the type of the let-variable is not an overloaded polymorphic type, then * these variables will not be lambda lifted. */ private final Map<String, ParseTreeNode> letVarNameToParseTreeMap; /** * (String Set) names of the local variables that the compiler knows to be evaluated to weak-head normal form. * These are unique-transformed so they will have a $ in them. * * <p> * These variables are: * <ol> * <li> strict function bound argument names (i.e. corresponding to plinged function arguments). * <li> variables introduced by a case-pattern where the variable is guaranteed to be evaluated because of the * strictness annotations on the corresponding data constructor argument. * <li> let variables of the form * x = expression known by the compiler to be evaluated to weak head normal form. * Some important cases of this are where the expression is: * <ul> * <li> a top level applications of Prelude.eager * <li> literals (string, integers, double, lists, tuples, non-extension records) * <li> aliases for top-level functions and data constructors * <li> aliases for evaluated local variables * <li> certain special data constructor field selections of the form (evaluated-expression).MyDataConstructor.myStrictField) * </ul> * </ol> */ private final Set<String> evaluatedLocalVariables; /** * The environment for data constructors. This is made up of the built-in data constructors, as * well as those introduced using data declarations. It does not include the data constructors * that are created using special notations (Char, Int, Double, List, Tuples and the trivial type). */ private Env dataConstructorEnv; /** The ModuleTypeInfo of the module currently being type checked. */ private ModuleTypeInfo currentModuleTypeInfo; /** Determines the types of all data constructors, both built-in and those introduced via data declarations. */ private DataDeclarationChecker dataDeclarationChecker; /** Used for type checking type class declarations within a module. */ private TypeClassChecker typeClassChecker; /** * (String->ParseTreeNode). A map from a function name to a Node in the program parse tree where the function * is defined. This is needed since type checking does not proceed in textual order of the source but * rather, according to the order given by dependency analysis. * The domain of this map contain the names of all non built-in top-level functions defined within * the current module (and thus a qualified name is not required since the module name is * implicitly given). */ private final Map<String, ParseTreeNode> functionNameToDefinitionNodeMap; private final CALCompiler compiler; /** (String). The names of all foreign functions declared in the current module. */ private Set<String> foreignFunctionNamesSet; /** * (String Set). The names of all built-in primitive functions declared in the current module. */ private Set<String> primitiveFunctionNamesSet; /** * (String->TypeExpr). A map from a top-level function's name to its type expression as expressed in a type declaration. * The domain of this map contain the names of all (non primitive) functions defined within * the current module that have been given explicit type declarations. */ private final Map<String, TypeExpr> functionNameToDeclaredTypeMap; /** * (OverloadingInfo). Holds onto all the OverloadingInfo objects created while type checking a top level * component. Overload resolution can only occur after this is completed. For * example, if f is a top level function and g and h are defined at the same nesting * level within f, then this list will include OverloadingInfo for g and h. */ private final List<OverloadingInfo> overloadingInfoList; /** * (ParseTreeNode). Holds onto all the parse tree nodes that are calls to assert and * undefined. This list is used to updated the parse tree by inlining assert and * undefined calls and add the source position info to the error call node. */ private final List<ParseTreeNode> errorCallList; /** the level in the CAL source of a definition. This is increased by a let definition. */ private int nestingLevel; /** * holds onto the OverloadingInfo object corresponding to the function that is currently being type * checked. This is changed within the body of a local function to be the OverloadingInfo object * of the local function. */ private OverloadingInfo currentOverloadingInfo; /** * (String Set) The names of all non built-in top-level functions defined within the current module. */ private final Set<String> topLevelFunctionNameSet; private final TypeConstants typeConstants; /** * A class used to return info about an adjunct * after it has been type checked. * @author RCypher */ static final class AdjunctInfo { /** Name of the target function in the adjunct. */ private final String targetName; /** The type of the target function. */ private final TypeExpr targetType; /** True if there is a type declaration for the target function. */ private final boolean targetExplicitlyTyped; public AdjunctInfo (final String targetName, final TypeExpr targetType, final boolean targetExplicitlyTyped) { this.targetName = targetName; this.targetType = targetType; this.targetExplicitlyTyped = targetExplicitlyTyped; } public String getTargetName() { return targetName; } public TypeExpr getTargetType () { return targetType; } public boolean isTargetExplicitlyTyped() { return targetExplicitlyTyped; } } /** * A simple container class to hold the results of data constructor pattern analysis returned * by the various analyzeDataConstructorPatternXXXHelper() methods. * @author Edward Lam */ private static final class DataConstructorPatternAnalysisResults { private final Env extendedEnv; private final NonGenericVars extendedNonGenericVars; DataConstructorPatternAnalysisResults(final Env extendedEnv, final NonGenericVars extendedNonGenericVars) { this.extendedEnv = extendedEnv; this.extendedNonGenericVars = extendedNonGenericVars; } public Env getExtendedEnv() { return this.extendedEnv; } public NonGenericVars getExtendedNonGenericVars() { return this.extendedNonGenericVars; } } /** * Useful TypeExpr constants for use during compilation. * These are used for typing literal constructs in the language such as Char and String literals. * Note: the literals are lazily initialized since the types themselves are not-built in, so we need * to have processed type declarations first or calling the methods below will fail in an exception. * @author Bo Ilic */ final class TypeConstants { /** constant for the Prelude.Boolean type. */ private TypeExpr BOOLEAN_TYPE; /** constant for the Prelude.Char type. */ private TypeExpr CHAR_TYPE; /** constant for the Prelude.Int type. */ private TypeExpr INT_TYPE; /** constant for the Prelude.Double type. */ private TypeExpr DOUBLE_TYPE; /** constant for the Prelude.String type. */ private TypeExpr STRING_TYPE; /** constant for the Prelude.Unit type, also known as (). */ private TypeExpr UNIT_TYPE; /** constant for the type Prelude.Boolean -> Prelude.Boolean -> Prelude.Boolean, i.e. the type of the && and || operators. */ private TypeExpr AND_OR_TYPE; /** for the Prelude.List type */ private TypeConstructor LIST_TYPE_CONS; private TypeConstants() {} /** * @return the type Prelude.Boolean. */ TypeExpr getBooleanType() { if (BOOLEAN_TYPE == null) { BOOLEAN_TYPE = TypeExpr.makeNonParametricType(currentModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.Boolean)); if (BOOLEAN_TYPE == null) { throw new IllegalStateException("The " + CAL_Prelude.TypeConstructors.Boolean.getQualifiedName() + " type is not defined."); } } return BOOLEAN_TYPE; } /** * @return the type Prelude.Char. */ TypeExpr getCharType() { if (CHAR_TYPE == null) { CHAR_TYPE = TypeExpr.makeNonParametricType(currentModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.Char)); if (CHAR_TYPE == null) { throw new IllegalStateException("The " + CAL_Prelude.TypeConstructors.Char.getQualifiedName() + " type is not defined."); } } return CHAR_TYPE; } /** * @return the type Prelude.Int. */ TypeExpr getIntType() { if (INT_TYPE == null) { INT_TYPE = TypeExpr.makeNonParametricType(currentModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.Int)); if (INT_TYPE == null) { throw new IllegalStateException("The " + CAL_Prelude.TypeConstructors.Int.getQualifiedName() + " type is not defined."); } } return INT_TYPE; } /** * @return the type Prelude.Double. */ TypeExpr getDoubleType() { if (DOUBLE_TYPE == null) { DOUBLE_TYPE = TypeExpr.makeNonParametricType(currentModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.Double)); if (DOUBLE_TYPE == null) { throw new IllegalStateException("The " + CAL_Prelude.TypeConstructors.Double.getQualifiedName() + " type is not defined."); } } return DOUBLE_TYPE; } /** * @return the type Prelude.String. */ TypeExpr getStringType() { if (STRING_TYPE == null) { STRING_TYPE = TypeExpr.makeNonParametricType(currentModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.String)); if (STRING_TYPE == null) { throw new IllegalStateException("The " + CAL_Prelude.TypeConstructors.String + " type is not defined."); } } return STRING_TYPE; } /** * @return the type Prelude.Unit, also known as (). */ TypeExpr getUnitType() { if (UNIT_TYPE == null) { UNIT_TYPE = TypeExpr.makeNonParametricType(currentModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.Unit)); if (UNIT_TYPE == null) { throw new IllegalStateException("The " + CAL_Prelude.TypeConstructors.Unit + " type is not defined."); } } return UNIT_TYPE; } /** * @return constant for the type Prelude.Boolean -> Prelude.Boolean -> Prelude.Boolean, i.e. the type of the && and || operators. */ TypeExpr getAndOrType() { if (AND_OR_TYPE == null) { TypeExpr booleanType = getBooleanType(); AND_OR_TYPE = TypeExpr.makeFunType (booleanType, TypeExpr.makeFunType(booleanType, booleanType)); } return AND_OR_TYPE; } TypeConstructor getListTypeCons() { if (LIST_TYPE_CONS == null) { LIST_TYPE_CONS = currentModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.List); if (LIST_TYPE_CONS == null) { throw new IllegalStateException(CAL_Prelude.TypeConstructors.List.getQualifiedName() + " type not available."); } } return LIST_TYPE_CONS; } /** * Create a type expression for a list. * * @param elementTypeExpr the type of the element * @return TypeExpr the type expression for the list with given element type */ TypeExpr makeListType(final TypeExpr elementTypeExpr) { return new TypeConsApp(getListTypeCons(), new TypeExpr [] {elementTypeExpr}); } /** * Returns the TypeExpr for list append operator (:). Namely, a -> [a] -> [a]. * Creation date: (3/9/01 3:27:27 PM) * @return TypeExpr */ TypeExpr makeConsType() { TypeExpr alpha = new TypeVar(); TypeExpr listType = makeListType(alpha); return TypeExpr.makeFunType(alpha, TypeExpr.makeFunType(listType, listType)); } } /** * Insert the method's description here. * Creation date: (8/31/00 11:47:59 AM) * @param compiler */ CALTypeChecker(final CALCompiler compiler) { if (compiler == null) { throw new NullPointerException(); } this.compiler = compiler; functionBoundNamesToTypeMap = new HashMap<String, TypeExpr>(); localFunctionBoundNamesToNonGenericVarsMap = new HashMap<String, NonGenericVars>(); localFunctionBoundNamesToLocalFunctionIdentifiersMap = new HashMap<String, LocalFunctionIdentifier>(); localFunctionBoundNamesToFunctionEntityMap = new HashMap<String, Function>(); evaluatedLocalVariables = new HashSet<String>(); letVarNameToParseTreeMap = new HashMap<String, ParseTreeNode>(); functionNameToDefinitionNodeMap = new HashMap<String, ParseTreeNode>(); functionNameToDeclaredTypeMap = new HashMap<String, TypeExpr>(); overloadingInfoList = new ArrayList<OverloadingInfo>(); errorCallList = new ArrayList<ParseTreeNode>(); nestingLevel = 0; topLevelFunctionNameSet = new HashSet<String>(); typeConstants = new TypeConstants(); } /** * DO NOT MAKE THIS PUBLIC. * * @return Useful TypeExpr constants for use during compilation for typing literal constructs in the language * such as Char and String literals. Note: the literals are lazily initialized since the types themselves * are not-built in, so we need to have processed type declarations before these will not fail in exceptions. */ TypeConstants getTypeConstants() { return typeConstants; } /** * There are 2 types of case expressions, those unpacking data constructors, and those unpacking record values. * This distinguishes between them to faciliate compilation analysis. * @param caseNode * @return boolean */ static boolean isRecordCase(final ParseTreeNode caseNode) { caseNode.verifyType(CALTreeParserTokenTypes.LITERAL_case); final ParseTreeNode altListNode = caseNode.getChild(1); altListNode.verifyType(CALTreeParserTokenTypes.ALT_LIST); final ParseTreeNode altNode = altListNode.firstChild(); altNode.verifyType(CALTreeParserTokenTypes.ALT); return altNode.firstChild().getType() == CALTreeParserTokenTypes.RECORD_PATTERN; } /** * Determines the type of a record-case expression. This is a case-expression whose single pattern matches * for a value of a record type. * @param functionEnv * @param nonGenericVars * @param recordCaseNode * @return TypeExpr */ private TypeExpr analyzeRecordCase(final Env functionEnv, final NonGenericVars nonGenericVars, final ParseTreeNode recordCaseNode) { recordCaseNode.verifyType(CALTreeParserTokenTypes.VIRTUAL_RECORD_CASE); final ParseTreeNode conditionNode = recordCaseNode.firstChild(); final TypeExpr conditionType = analyzeExpr(functionEnv, nonGenericVars, conditionNode); final ParseTreeNode altListNode = conditionNode.nextSibling(); altListNode.verifyType(CALTreeParserTokenTypes.ALT_LIST); if (!altListNode.hasExactlyOneChild()) { //record-case patterns have only 1 alternative. This should be caught earlier in static analysis. throw new IllegalArgumentException(); } final ParseTreeNode altNode = altListNode.firstChild(); altNode.verifyType(CALTreeParserTokenTypes.ALT); final ParseTreeNode patternNode = altNode.firstChild(); patternNode.verifyType(CALTreeParserTokenTypes.RECORD_PATTERN); //Add the pattern variables to the environment for when we type the expression to evaluate //as a consequence of pattern matching i.e. the expression to the right of the "->". //For example, if the pattern is {r | field1 = x, field2 = y, field3 = z}, //The environment is extended by r, x, y and z, and the non-generic vars is extended by s, a, b, c. //where s is then specialized to (u\field1, u\field2, u\field3) => {u} Env extendedEnv = functionEnv; NonGenericVars extendedNonGenericVars = nonGenericVars; final ParseTreeNode baseRecordPatternNode = patternNode.firstChild(); baseRecordPatternNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD_PATTERN); final ParseTreeNode baseRecordPatternVarNode = baseRecordPatternNode.firstChild(); final String baseRecordVarName; final TypeExpr baseRecordType; if (baseRecordPatternVarNode != null) { //a record-polymorphic pattern baseRecordType = new TypeVar(); switch (baseRecordPatternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: { baseRecordVarName = baseRecordPatternVarNode.getText(); extendedEnv = extendedEnv.extend(new Function(QualifiedName.make(currentModuleTypeInfo.getModuleName(), baseRecordVarName), Scope.PRIVATE, null, baseRecordType, FunctionalAgent.Form.PATTERNVAR, nestingLevel)); functionBoundNamesToTypeMap.put(baseRecordVarName, baseRecordType); break; } case CALTreeParserTokenTypes.UNDERSCORE: break; default: { baseRecordPatternVarNode.unexpectedParseTreeNode(); return null; } } extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, (TypeVar)baseRecordType); } else { //a non-record-polymorphic pattern baseRecordType = TypeExpr.EMPTY_RECORD; } final ParseTreeNode fieldBindingVarAssignmentListNode = baseRecordPatternNode.nextSibling(); fieldBindingVarAssignmentListNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST); final Map<FieldName, TypeExpr> extensionFieldsMap = new HashMap<FieldName, TypeExpr>(); for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) { fieldBindingVarAssignmentNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT); final ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild(); final FieldName fieldName = getFieldName(fieldNameNode); final TypeVar patternVarType = new TypeVar(); final ParseTreeNode patternVarNode = fieldNameNode.nextSibling(); switch (patternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID : { final String patternVarName = patternVarNode.getText(); extendedEnv = extendedEnv.extend(new Function(QualifiedName.make(currentModuleTypeInfo.getModuleName(), patternVarName), Scope.PRIVATE, null, patternVarType, FunctionalAgent.Form.PATTERNVAR, nestingLevel)); functionBoundNamesToTypeMap.put(patternVarName, patternVarType); break; } case CALTreeParserTokenTypes.UNDERSCORE : break; default : { patternVarNode.unexpectedParseTreeNode(); return null; } } extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, patternVarType); extensionFieldsMap.put(fieldName, patternVarType); } //this is the type expression determined by the record pattern. For example, if the pattern is: //{r | field1 = x, field2 = y, field3 = z}, this is just: (r\field1, r\field2, r\field3) => {r | field1 :: a, field2 :: b, field3 :: c} final TypeExpr recordPatternTypeExpr; try { recordPatternTypeExpr = RecordType.recordExtension(baseRecordType, extensionFieldsMap); } catch (TypeException te) { //this should never fail since static analysis should have caught failure cases earlier compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Fatal.UnexpectedUnificationFailure())); return null; } try { TypeExpr.unifyType(conditionType, recordPatternTypeExpr, currentModuleTypeInfo); } catch (TypeException te) { //the case pattern and the case condition must unify to the same type compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.CasePatternAndCaseConditionMustHaveSameType(), te)); } //now type the expression after the ->, which determines the type returned by the case. Use the extended environments. final ParseTreeNode exprNode = patternNode.nextSibling(); final TypeExpr caseTypeExpr = analyzeExpr(extendedEnv, extendedNonGenericVars, exprNode); return caseTypeExpr; } /** * Determines the type of a record constructor. This can either be a record literal, or a record modification (i.e. * includes a list of field extensions and field value updates). * * e.g. * {name = "Anton", age = 2.5} * {r | name := "Anton", #1 = True, #2 = "abc", age := 3.0} * * @param functionEnv * @param nonGenericVars * @param recordConstructorNode * @return TypeExpr */ private TypeExpr analyzeRecordConstructor(final Env functionEnv, final NonGenericVars nonGenericVars, final ParseTreeNode recordConstructorNode) { recordConstructorNode.verifyType(CALTreeParserTokenTypes.RECORD_CONSTRUCTOR); final ParseTreeNode baseRecordNode = recordConstructorNode.firstChild(); baseRecordNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD); final ParseTreeNode baseRecordExprNode = baseRecordNode.firstChild(); final TypeExpr baseRecordType; if (baseRecordExprNode != null) { baseRecordType = analyzeExpr(functionEnv, nonGenericVars, baseRecordExprNode); } else { baseRecordType = TypeExpr.EMPTY_RECORD; } //baseRecordType, but updated for the fields whose types are updated via the := operator. TypeExpr updatedBaseRecordType = baseRecordType; final ParseTreeNode fieldModificationListNode = baseRecordNode.nextSibling(); fieldModificationListNode.verifyType(CALTreeParserTokenTypes.FIELD_MODIFICATION_LIST); //FieldName -> TypeExpr final Map<FieldName, TypeExpr> extensionFieldsMap = new HashMap<FieldName, TypeExpr>(); for (final ParseTreeNode fieldModificationNode : fieldModificationListNode) { fieldModificationNode.verifyType(CALTreeParserTokenTypes.FIELD_EXTENSION, CALTreeParserTokenTypes.FIELD_VALUE_UPDATE); final ParseTreeNode fieldNameNode = fieldModificationNode.firstChild(); final FieldName fieldName = getFieldName(fieldNameNode); final ParseTreeNode fieldValueExprNode = fieldNameNode.nextSibling(); final TypeExpr fieldTypeExpr = analyzeExpr(functionEnv, nonGenericVars, fieldValueExprNode); switch (fieldModificationNode.getType()) { case CALTreeParserTokenTypes.FIELD_EXTENSION: { extensionFieldsMap.put(fieldName, fieldTypeExpr); break; } case CALTreeParserTokenTypes.FIELD_VALUE_UPDATE: { //updatedBaseRecordType must unify with r\fieldName => {r | fieldName :: a} //i.e. it must be or specialize to a record type having a field named 'fieldName'. //the type of fieldName will then be fieldTypeExpr i.e. the type of the updated field comes from //the updating expression, and does not need to be compatible with the type of the value being //replaced. final TypeExpr lacksUpdateFieldType = new TypeVar(); //this is initially the type {r | fieldName :: a}. final TypeExpr hasUpdateFieldType; { final Map<FieldName, TypeExpr> singleFieldMap = new HashMap<FieldName, TypeExpr>(); singleFieldMap.put(fieldName, new TypeVar()); try { hasUpdateFieldType = RecordType.recordExtension(lacksUpdateFieldType, singleFieldMap); } catch (TypeException te) { //this should never fail //todoBI remove this error message since it is not a true user error. compiler.logMessage(new CompilerMessage(fieldModificationNode, new MessageKind.Fatal.UnexpectedUnificationFailure())); return null; } } try { //the only way this can fail is if updatedBaseRecordType cannot unify with a free record type that has a field //named 'fieldName'. TypeExpr.unifyType(updatedBaseRecordType, hasUpdateFieldType, currentModuleTypeInfo); } catch (TypeException te) { //"Type error. Invalid field value update for field {0}." compiler.logMessage(new CompilerMessage(fieldNameNode, new MessageKind.Error.InvalidRecordFieldValueUpdate(fieldName), te)); } try { final Map<FieldName, TypeExpr> singleFieldMap = new HashMap<FieldName, TypeExpr>(); singleFieldMap.put(fieldName, fieldTypeExpr); updatedBaseRecordType = RecordType.recordExtension(lacksUpdateFieldType, singleFieldMap); } catch (TypeException te) { //this should never fail //todoBI remove this error message since it is not a true user error. compiler.logMessage(new CompilerMessage(fieldModificationNode, new MessageKind.Fatal.UnexpectedUnificationFailure())); return null; } break; } default: { fieldModificationNode.unexpectedParseTreeNode(); break; } } } final TypeExpr recordType; try { recordType = RecordType.recordExtension(updatedBaseRecordType, extensionFieldsMap); } catch (TypeException te) { // Type error. Invalid record extension. compiler.logMessage(new CompilerMessage(baseRecordNode, new MessageKind.Error.InvalidRecordExtension(), te)); return null; } return recordType; } /** * Determines the type of a case expression (that switches on data constructors). * In particular, case expressions on record values and tuples are not handled here. * Also note that data constructor field selection syntax is not handled here e.g. expr.Just.value. * * Creation date: (9/13/00 10:44:54 AM) * @return TypeExpr * @param functionEnv * @param nonGenericVars * @param caseNode */ private TypeExpr analyzeDataConstructorCase(final Env functionEnv, final NonGenericVars nonGenericVars, final ParseTreeNode caseNode) { caseNode.verifyType(CALTreeParserTokenTypes.VIRTUAL_DATA_CONSTRUCTOR_CASE); final ParseTreeNode conditionNode = caseNode.firstChild(); final TypeExpr conditionType = analyzeExpr(functionEnv, nonGenericVars, conditionNode); final ParseTreeNode altListNode = conditionNode.nextSibling(); altListNode.verifyType(CALTreeParserTokenTypes.ALT_LIST); final TypeExpr unifiedCaseTypeExpr = new TypeVar(); for (final ParseTreeNode altNode : altListNode) { altNode.verifyType(CALTreeParserTokenTypes.ALT); final ParseTreeNode patternNode = altNode.firstChild(); final DataConstructorPatternAnalysisResults patternAnalysisResults; switch (patternNode.getType()) { case CALTreeParserTokenTypes.PATTERN_CONSTRUCTOR : { final ParseTreeNode dcNameListNode = patternNode.firstChild(); dcNameListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_LIST, CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_SINGLETON); final ParseTreeNode dcArgBindingsNode = dcNameListNode.nextSibling(); switch (dcArgBindingsNode.getType()) { case CALTreeParserTokenTypes.PATTERN_VAR_LIST: { //positional based extraction, possibly with multiple data constructors //e.g. (MyDataCons1 | MyDataCons2) field1 field2 field3 -> expr patternAnalysisResults = analyzeDataConstructorPatternWithVarListHelper( functionEnv, nonGenericVars, conditionType, dcNameListNode, patternNode); break; } case CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST: { //field-name based extraction, possibly with multiple data constructors //e.g. (MyDataCons1 | MyDataCons2) {field1, field2, field3} -> expr dcArgBindingsNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST); patternAnalysisResults = analyzeDataConstructorPatternWithFieldBindingsHelper( functionEnv, nonGenericVars, conditionType, dcNameListNode, patternNode); break; } default: { dcArgBindingsNode.unexpectedParseTreeNode(); return null; } } break; } case CALTreeParserTokenTypes.INT_PATTERN : { //matches to Int or (Int | Int | Int | ...) final TypeExpr consTypeExpr = typeConstants.getIntType(); patternAnalysisResults = analyzeDataConstructorNonPatternConstructorHelper( functionEnv, nonGenericVars, consTypeExpr, conditionType, null, patternNode); break; } case CALTreeParserTokenTypes.CHAR_PATTERN : { //matches to Char or (Char | Char | Char | ...) final TypeExpr consTypeExpr = typeConstants.getCharType(); patternAnalysisResults = analyzeDataConstructorNonPatternConstructorHelper( functionEnv, nonGenericVars, consTypeExpr, conditionType, null, patternNode); break; } case CALTreeParserTokenTypes.LIST_CONSTRUCTOR : { //matches to the null list data constructor [] final TypeExpr consTypeExpr = typeConstants.makeListType(new TypeVar()); patternAnalysisResults = analyzeDataConstructorNonPatternConstructorHelper( functionEnv, nonGenericVars, consTypeExpr, conditionType, null, patternNode); break; } case CALTreeParserTokenTypes.COLON : { //matches to a : as final TypeExpr consTypeExpr = typeConstants.makeConsType(); patternAnalysisResults = analyzeDataConstructorNonPatternConstructorHelper( functionEnv, nonGenericVars, consTypeExpr, conditionType, patternNode, patternNode); break; } case CALTreeParserTokenTypes.UNDERSCORE: { //matches to any constructor final TypeExpr consTypeExpr = new TypeVar(); patternAnalysisResults = analyzeDataConstructorNonPatternConstructorHelper( functionEnv, nonGenericVars, consTypeExpr, conditionType, null, patternNode); break; } case CALTreeParserTokenTypes.VIRTUAL_UNIT_DATA_CONSTRUCTOR: { //matches to the Unit data constructor () final TypeExpr consTypeExpr = typeConstants.getUnitType(); patternAnalysisResults = analyzeDataConstructorNonPatternConstructorHelper( functionEnv, nonGenericVars, consTypeExpr, conditionType, null, patternNode); break; } case CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR : default : { patternNode.unexpectedParseTreeNode(); return null; } } final Env extendedEnv = patternAnalysisResults.getExtendedEnv(); final NonGenericVars extendedNonGenericVars = patternAnalysisResults.getExtendedNonGenericVars(); // Finally, type the expression to evaluate as a consequence of pattern matching. // The unification ensures that the collection of all these expressions will have // a compatible type specialization. // c1 ps1 -> e1 // c2 ps2 -> e2 // c3 ps3 -> e3 // Then the type of the case is Unify (Unify (e1, e2), e3). final ParseTreeNode exprNode = patternNode.nextSibling(); final TypeExpr caseTypeExpr = analyzeExpr(extendedEnv, extendedNonGenericVars, exprNode); try { TypeExpr.unifyType(unifiedCaseTypeExpr, caseTypeExpr, currentModuleTypeInfo); } catch (TypeException te) { //the types of all the case branches must be compatible compiler.logMessage(new CompilerMessage(exprNode, new MessageKind.Error.TypesOfAllCaseBranchesMustBeCompatible(), te)); } } return unifiedCaseTypeExpr.prune(); } /** * Get the extended env and non generic vars resulting from analysis of the given case alt pattern. * The alt pattern should not be for a pattern constructor or group pattern constructor. * @param functionEnv * @param nonGenericVars * @param consTypeExpr the most general type of the data constructor * @param conditionType the type of the expression on which the case is predicated. * @param patternVarListNode if non-null, the parent node of the pattern vars (in a generalized var list) for the case alt. * @param patternNode the node on which to report errors for a data constructor. * This should have pattern var nodes as its children. * @return the extended env and non-generic vars resulting from analysis of the pattern. */ private DataConstructorPatternAnalysisResults analyzeDataConstructorNonPatternConstructorHelper ( final Env functionEnv, final NonGenericVars nonGenericVars, final TypeExpr consTypeExpr, final TypeExpr conditionType, final ParseTreeNode patternVarListNode, final ParseTreeNode patternNode) { // Add the pattern variables to the environment for when we type the expression to evaluate // as a consequence of pattern matching. Also, we must unify the type of the application // (constructor p1 p2 ... pn) with the type of the condition expression. For example, if // x : xs then we can type x :: a and xs :: b, then determine further restrictions // via unification with the type of the condition expression. Env extendedEnv = functionEnv; NonGenericVars extendedNonGenericVars = nonGenericVars; TypeExpr typeOfAppl = consTypeExpr; // start with the type of the case pattern if (patternVarListNode != null) { for (final ParseTreeNode patternVarNode : patternVarListNode) { // The type of the current pattern var. TypeVar typeOfNextArg = new TypeVar(); switch (patternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: { final String varName = patternVarNode.getText(); extendedEnv = extendedEnv.extend(new Function(QualifiedName.make(currentModuleTypeInfo.getModuleName(), varName), Scope.PRIVATE, null, typeOfNextArg, FunctionalAgent.Form.PATTERNVAR, nestingLevel)); extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, typeOfNextArg); functionBoundNamesToTypeMap.put(varName, typeOfNextArg); break; } case CALTreeParserTokenTypes.UNDERSCORE: { //todoBI is it necessary to add the typeVar to the non-generics for wildcards? extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, typeOfNextArg); break; } default: { patternVarNode.unexpectedParseTreeNode(); break; } } // the type of (constructor p1 p2 ... pi) TypeExpr typeOfArgsSoFar = typeOfAppl; typeOfAppl = new TypeVar(); try { // typeOfNextArg is now an unspecialized typeVar in the extendedNonGenericVars (and possibly the env). // Perform unification to specialize it and typeOfAppl to the correct type. TypeExpr.unifyType(typeOfArgsSoFar, TypeExpr.makeFunType(typeOfNextArg, typeOfAppl), currentModuleTypeInfo); } catch (TypeException te) { //this should never happen since we should have earlier checked that the number of args //supplied to the data constructor is correct. compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.DataConstructorArgumentsDoNotMatchDataConstructor(), te)); } } } try { TypeExpr.unifyType(conditionType, typeOfAppl, currentModuleTypeInfo); } catch (TypeException te) { //the case pattern and the case condition must have the same type compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.CasePatternAndCaseConditionMustHaveSameType(), te)); } return new DataConstructorPatternAnalysisResults(extendedEnv, extendedNonGenericVars); } /** * Helper for positional based extraction, possibly with multiple data constructors * e.g. (MyDataCons1 | MyDataCons2) field1 field2 field3 -> expr * * Get the extended env and non generic vars resulting from analysis of the * given data constructor pattern group alt with var list. * @param functionEnv * @param nonGenericVars * @param conditionType the type of the expression on which the case is predicated. * @param dcNameListNode the parse tree node for the data constructor name(s) in the pattern. * @param patternNode the node on which to report errors for a data constructor. * @return the extended env and non-generic vars resulting from analysis of the pattern, which should * not be a group pattern. */ private DataConstructorPatternAnalysisResults analyzeDataConstructorPatternWithVarListHelper( final Env functionEnv, final NonGenericVars nonGenericVars, final TypeExpr conditionType, final ParseTreeNode dcNameListNode, final ParseTreeNode patternNode) { dcNameListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_LIST, CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_SINGLETON); final ParseTreeNode dcArgBindingsNode = dcNameListNode.nextSibling(); dcArgBindingsNode.verifyType(CALTreeParserTokenTypes.PATTERN_VAR_LIST); // (Set of DataConstructor objects). Data constructors referenced in this pattern. final Set<DataConstructor> dataConstructors = getPatternDataConstructors(dcNameListNode); Env extendedEnv = functionEnv; NonGenericVars extendedNonGenericVars = nonGenericVars; // (List of TypeVar) the type var at index i will be the type of the ith arg, if not an underscore (in the cal code). // If the ith pattern var is an underscore, the ith item will be null. final List<TypeVar> argTypesList = new ArrayList<TypeVar>(); // (TypeVar->String) map from type var for an arg to the name of the arg. final Map<TypeVar, String> argTypeToNameMap = new HashMap<TypeVar, String>(); int fieldIndex = 0; for (final ParseTreeNode patternVarNode : dcArgBindingsNode) { switch (patternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: { // The type of the nth pattern var. TypeVar typeOfNextArg = new TypeVar(); argTypesList.add(typeOfNextArg); final String varName = patternVarNode.getText(); argTypeToNameMap.put(typeOfNextArg, varName); //if the fields at index fieldIndex are strict for all data constructors then add the pattern variable to //the set of evaluatedLocalVariables. if (isStrictForAllDataConstructors(fieldIndex, dataConstructors)) { evaluatedLocalVariables.add(varName); } extendedEnv = extendedEnv.extend(new Function(QualifiedName.make(currentModuleTypeInfo.getModuleName(), varName), Scope.PRIVATE, null, typeOfNextArg, FunctionalAgent.Form.PATTERNVAR, nestingLevel)); extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, typeOfNextArg); functionBoundNamesToTypeMap.put(varName, typeOfNextArg); break; } case CALTreeParserTokenTypes.UNDERSCORE: { // Check whether the underscore was converted from an unused var_id. final String unusedVarName = patternVarNode.getUnusedVarNameForWildcardPatternVar(); if (unusedVarName != null) { // Add to the list and map, but not to the extended env and non-generic vars. TypeVar typeOfNextArg = new TypeVar(); argTypesList.add(typeOfNextArg); argTypeToNameMap.put(typeOfNextArg, unusedVarName); } else { argTypesList.add(null); } break; } default: { patternVarNode.unexpectedParseTreeNode(); break; } } ++fieldIndex; } final int nPatternArgs = dcArgBindingsNode.getNumberOfChildren(); // Iterate over the data constructors named in the pattern. for (final DataConstructor dataConsEntity : dataConstructors) { // Get the entity's type. // For example, if consName = "Just" then the type is "a -> Maybe a" and this is a constructor taking 1 argument. final TypeExpr consTypeExpr = dataConsEntity.getTypeExpr(); // Check that the number of variables expected by the data constructor corresponds to the number actually supplied in the pattern. final int nConsArgs = consTypeExpr.getArity(); if (nPatternArgs != nConsArgs) { //this is logged as a compiler message earlier during static analysis. throw new IllegalStateException("unexpected number of pattern arguments"); } // start with the type of the case pattern TypeExpr typeOfAppl = consTypeExpr; // Iterate over the type vars in the list of arg types. for (TypeVar typeOfNextArg : argTypesList) { // Check for an underscore. if (typeOfNextArg == null) { typeOfNextArg = new TypeVar(); //todoBI is it necessary to add the typeVar to the non-generics for wildcards? extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, typeOfNextArg); } // the type of (constructor p1 p2 ... pi) TypeExpr typeOfArgsSoFar = typeOfAppl; typeOfAppl = new TypeVar(); try { // typeOfNextArg is now a typeVar in the extendedNonGenericVars (and possibly the env). // Perform unification to (possibly further) specialize it and typeOfAppl to the correct type. TypeExpr.unifyType(typeOfArgsSoFar, TypeExpr.makeFunType(typeOfNextArg, typeOfAppl), currentModuleTypeInfo); } catch (TypeException te) { // We must be in a pattern group where the type of an argument in one dc // doesn't unify with the corresponding arg in another dc. // This should only happen for args with names, since args with underscores unify with a fresh type var, // which should always succeed. final String argName = argTypeToNameMap.get(typeOfNextArg); final String displayName = FreeVariableFinder.getDisplayName(argName); compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.DataConstructorPatternGroupArgumentNotTypeable(displayName), te)); } } try { TypeExpr.unifyType(conditionType, typeOfAppl, currentModuleTypeInfo); } catch (TypeException te) { //the case pattern and the case condition must have the same type compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.CasePatternAndCaseConditionMustHaveSameType(), te)); } } return new DataConstructorPatternAnalysisResults(extendedEnv, extendedNonGenericVars); } /** * Collect the data constructors declared in a pattern (either using positional or field-name based extraction) * into a set. For example, for either of: * * (MyDataCons1 | MyDataCons2) {field1, field2 field3} -> expr * (MyDataCons1 | MyDataCons2) field1 field2 field3 -> expr * The set would be {MyDataCons1, MyDataCons2}. * * @param dcNameListNode * @return Set of DataConstructor objects. Ordered by declaration order in the pattern. */ private Set<DataConstructor> getPatternDataConstructors(final ParseTreeNode dcNameListNode) { dcNameListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_LIST, CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_SINGLETON); // (Set of DataConstructor objects). Data constructors referenced in this pattern. final Set<DataConstructor> dataConstructors = new LinkedHashSet<DataConstructor>(); for (final ParseTreeNode dcNameNode : dcNameListNode) { dcNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); // Get the dc entity. final DataConstructor dataConstructor = retrieveQualifiedDataConstructor(dcNameNode); if (dataConstructor == null) { //a message is logged in retrieveQualifiedDataConstructor, //continue to the next data constructor to try to catch additional errors. (since otherwise //a null pointer exception will shortly occur continue; } dataConstructors.add(dataConstructor); } return dataConstructors; } /** * Returns true if 'fieldName' is a strict data constructor field for all the data constructors in the 'dataConstructors' set. * * @param fieldName each data constructor is assumed to have fieldName as a field. * @param dataConstructors Set of DataConstructor objects * @return true if fieldName is a strict data constructor field for all the data constructors in the dataConstructors set. */ private boolean isStrictForAllDataConstructors(final FieldName fieldName, final Set<DataConstructor> dataConstructors) { for (final DataConstructor dataConstructor : dataConstructors) { int index = dataConstructor.getFieldIndex(fieldName); if (index == -1) { throw new IllegalArgumentException(); } if (!dataConstructor.isArgStrict(index)) { return false; } } return true; } /** * Returns true if 'fieldIndex' is the index of a strict data constructor field for all the data constructors in the 'dataConstructors' set. * * @param fieldIndex each data constructor is assumed to have a field at the given fieldIndex. * @param dataConstructors Set of DataConstructor objects * @return true if fieldName is a strict data constructor field for all the data constructors in the dataConstructors set. */ private static boolean isStrictForAllDataConstructors(final int fieldIndex, final Set<DataConstructor> dataConstructors) { for (final DataConstructor dataConstructor : dataConstructors) { if (!dataConstructor.isArgStrict(fieldIndex)) { return false; } } return true; } /** * Helper for field-name based extraction, possibly with multiple data constructors * e.g. (MyDataCons1 | MyDataCons2) {field1, field2, field3} -> expr * * et the extended env and non generic vars resulting from analysis of the * given data constructor pattern group alt with var list. * @param functionEnv * @param nonGenericVars * @param conditionType the type of the expression on which the case is predicated. * @param dcNameListNode (List of ParseTreeNode) the parse tree nodes for the data constructor name(s) in the pattern. * @param patternNode the node on which to report errors for a data constructor. * @return the extended env and non-generic vars resulting from analysis of the pattern, which should * not be a group pattern. */ private DataConstructorPatternAnalysisResults analyzeDataConstructorPatternWithFieldBindingsHelper( final Env functionEnv, final NonGenericVars nonGenericVars, final TypeExpr conditionType, final ParseTreeNode dcNameListNode, final ParseTreeNode patternNode) { dcNameListNode.verifyType(CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_LIST, CALTreeParserTokenTypes.DATA_CONSTRUCTOR_NAME_SINGLETON); final ParseTreeNode dcArgBindingsNode = dcNameListNode.nextSibling(); dcArgBindingsNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST); // (Set of DataConstructor objects). Data constructors referenced in this pattern. final Set<DataConstructor> dataConstructors = getPatternDataConstructors(dcNameListNode); Env extendedEnv = functionEnv; NonGenericVars extendedNonGenericVars = nonGenericVars; // (FieldName->TypeVar) map from field name to the type var which will hold the type for that field. final Map<FieldName, TypeVar> fieldNameStringToArgTypeMap = new HashMap<FieldName, TypeVar>(); // Populate the map and extend the env and the non-generic vars. for (final ParseTreeNode fieldBindingNode : dcArgBindingsNode) { fieldBindingNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT); final ParseTreeNode fieldNameNode = fieldBindingNode.firstChild(); final ParseTreeNode patternVarNode = fieldNameNode.nextSibling(); // Add to field names. final FieldName fieldName = FieldName.make(fieldNameNode.getText()); //note that by earlier static analysis we know that fieldName *is* a field for all the data constructors // The type of the arg for this field. TypeVar typeOfFieldArg = new TypeVar(); fieldNameStringToArgTypeMap.put(fieldName, typeOfFieldArg); // Add to the extended env and non-generic vars if not unused. if (patternVarNode.getType() != CALTreeParserTokenTypes.UNDERSCORE) { patternVarNode.verifyType(CALTreeParserTokenTypes.VAR_ID); String varName = patternVarNode.getText(); //if fieldName is strict for each data constructor in the pattern, then add the pattern variable to //the set of evaluatedLocalVariables. if (isStrictForAllDataConstructors(fieldName, dataConstructors)) { evaluatedLocalVariables.add(varName); } extendedEnv = extendedEnv.extend(new Function(QualifiedName.make(currentModuleTypeInfo.getModuleName(), varName), Scope.PRIVATE, null, typeOfFieldArg, FunctionalAgent.Form.PATTERNVAR, nestingLevel)); extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, typeOfFieldArg); functionBoundNamesToTypeMap.put(varName, typeOfFieldArg); } } // Iterate over the data constructors named in the list. for (final DataConstructor dataConstructor : dataConstructors) { // Get the entity's type. // For example, if consName = "Just" then the type is "a -> Maybe a" and this is a constructor taking 1 argument. final TypeExpr consTypeExpr = dataConstructor.getTypeExpr(); // start with the type of the case pattern TypeExpr typeOfAppl = consTypeExpr; // Iterate over the fields in the data constructor. for (int i = 0, arity = dataConstructor.getArity(); i < arity; i++) { final FieldName fieldName = dataConstructor.getNthFieldName(i); // get the binding for the field name if any. TypeVar typeOfNextArg = fieldNameStringToArgTypeMap.get(fieldName); if (typeOfNextArg == null) { // No binding for this field. typeOfNextArg = new TypeVar(); //todoBI is it necessary to add the typeVar to the non-generics for wildcards? extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, typeOfNextArg); } // the type of (constructor p1 p2 ... pi) TypeExpr typeOfArgsSoFar = typeOfAppl; typeOfAppl = new TypeVar(); try { // typeOfNextArg is now a typeVar in the extendedNonGenericVars (and possibly the env). // Perform unification to (possibly further) specialize it and typeOfAppl to the correct type. TypeExpr.unifyType(typeOfArgsSoFar, TypeExpr.makeFunType(typeOfNextArg, typeOfAppl), currentModuleTypeInfo); } catch (TypeException te) { // We must be in a pattern group where the type of an argument in one dc // doesn't unify with the corresponding arg in another dc. compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.DataConstructorPatternGroupArgumentNotTypeable(fieldName.getCalSourceForm()), te)); } } try { TypeExpr.unifyType(conditionType, typeOfAppl, currentModuleTypeInfo); } catch (TypeException te) { //the case pattern and the case condition must have the same type compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.CasePatternAndCaseConditionMustHaveSameType(), te)); } } return new DataConstructorPatternAnalysisResults(extendedEnv, extendedNonGenericVars); } /** * Determines the type of a case expression whose pattern is a tuple-style record (e.g. (), (x, y), (x, y, z) ...). * @param functionEnv * @param nonGenericVars * @param caseNode * @return TypeExpr */ private TypeExpr analyzeTupleCase(final Env functionEnv, final NonGenericVars nonGenericVars, final ParseTreeNode caseNode) { caseNode.verifyType(CALTreeParserTokenTypes.VIRTUAL_TUPLE_CASE); final ParseTreeNode conditionNode = caseNode.firstChild(); final TypeExpr conditionType = analyzeExpr(functionEnv, nonGenericVars, conditionNode); final ParseTreeNode altListNode = conditionNode.nextSibling(); altListNode.verifyType(CALTreeParserTokenTypes.ALT_LIST); if (!altListNode.hasExactlyOneChild()) { //tuple-case patterns have only 1 alternative. This should be caught earlier in static analysis. throw new IllegalArgumentException(); } final ParseTreeNode altNode = altListNode.firstChild(); altNode.verifyType(CALTreeParserTokenTypes.ALT); final ParseTreeNode patternNode = altNode.firstChild(); patternNode.verifyType(CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR); // Add the pattern variables to the environment for when we type the expression to evaluate // as a consequence of pattern matching. Also, we must unify the type of the application // (constructor p1 p2 ... pn) with the type of the condition expression. For example, if // x : xs then we can type x :: a and xs :: b, then determine further restrictions // via unification with the type of the condition expression. Env extendedEnv = functionEnv; NonGenericVars extendedNonGenericVars = nonGenericVars; int componentN = 1; final Map<FieldName, TypeExpr> fieldNamesToTypeMap = new HashMap<FieldName, TypeExpr>(); for (final ParseTreeNode patternVarNode : patternNode) { final TypeVar typeVar; switch (patternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: { final String varName = patternVarNode.getText(); typeVar = new TypeVar(); extendedEnv = extendedEnv.extend(new Function(QualifiedName.make(currentModuleTypeInfo.getModuleName(), varName), Scope.PRIVATE, null, typeVar, FunctionalAgent.Form.PATTERNVAR, nestingLevel)); extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, typeVar); functionBoundNamesToTypeMap.put(varName, typeVar); break; } case CALTreeParserTokenTypes.UNDERSCORE: { typeVar = new TypeVar(); //todoBI is it necessary to add the typeVar to the non-generics for wildcards? extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, typeVar); break; } default: { patternVarNode.unexpectedParseTreeNode(); typeVar = null; break; } } fieldNamesToTypeMap.put(FieldName.makeOrdinalField(componentN), typeVar); ++componentN; } final TypeExpr tuplePatternTypeExpr = new RecordType(RecordVar.NO_FIELDS, fieldNamesToTypeMap); try { TypeExpr.unifyType(conditionType, tuplePatternTypeExpr, currentModuleTypeInfo); } catch (TypeException te) { //the case pattern and the case condition must have the same type compiler.logMessage(new CompilerMessage(patternNode, new MessageKind.Error.CasePatternAndCaseConditionMustHaveSameType(), te)); } //now type the expression after the ->, which determines the type returned by the case. Use the extended environments. final ParseTreeNode exprNode = patternNode.nextSibling(); final TypeExpr caseTypeExpr = analyzeExpr(extendedEnv, extendedNonGenericVars, exprNode); return caseTypeExpr; } /** * Determines the type of a data constructor field selection such as expr.Just.value. * * @param functionEnv * @param nonGenericVars * @param selectNode * @return TypeExpr may be null if the type could not be determined (although in that case an error will be logged). */ private TypeExpr analyzeDataConstructorFieldSelection(final Env functionEnv, final NonGenericVars nonGenericVars, final ParseTreeNode selectNode) { selectNode.verifyType(CALTreeParserTokenTypes.SELECT_DATA_CONSTRUCTOR_FIELD); final ParseTreeNode conditionNode = selectNode.firstChild(); final TypeExpr conditionType = analyzeExpr(functionEnv, nonGenericVars, conditionNode); final ParseTreeNode dcNameNode = conditionNode.nextSibling(); dcNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); final DataConstructor dataConstructor = retrieveQualifiedDataConstructor(dcNameNode); if (dataConstructor == null) { //a message was logged in retrieveQualifiedDataConstructor return null; } final ParseTreeNode fieldNameNode = dcNameNode.nextSibling(); fieldNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.ORDINAL_FIELD_NAME); final FieldName fieldName = getFieldName(fieldNameNode); // During the first pass of free variable finding, the select node should have had its children augmented with the // qualified var for the alt expression. final ParseTreeNode qualifiedVarNode = fieldNameNode.nextSibling(); qualifiedVarNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); // Add corresponding pattern variables to the environment for when we type the expression to // evaluate as a consequence of pattern matching. Also, we must unify the type of the application // (constructor p1 p2 ... pn) with the type of the condition expression. For example, if // x : xs then we can type x :: a and xs :: b, then determine further restrictions // via unification with the type of the condition expression. Env extendedEnv = functionEnv; NonGenericVars extendedNonGenericVars = nonGenericVars; // Start out with the cons type expr. //As an example, if consName = "Just" then the type is "a -> Maybe a" and this is a constructor taking 1 argument. TypeExpr typeOfAppl = dataConstructor.getTypeExpr(); for (int i = 0, arity = dataConstructor.getArity(); i < arity; i++) { final FieldName nthFieldName = dataConstructor.getNthFieldName(i); TypeVar typeVar = new TypeVar(); if (nthFieldName.equals(fieldName)) { // a var. // Get the name of the pattern var. // This will not be the same as the field name for ordinal-based fields, since ordinals are not valid identifiers. final String varName = qualifiedVarNode.getChild(1).getText(); extendedEnv = extendedEnv.extend(new Function(QualifiedName.make(currentModuleTypeInfo.getModuleName(), varName), Scope.PRIVATE, null, typeVar, FunctionalAgent.Form.PATTERNVAR, nestingLevel)); extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, typeVar); functionBoundNamesToTypeMap.put(varName, typeVar); } else { // an underscore. //todoBI is it necessary to add the typeVar to the non-generics for wildcards? extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, typeVar); } // the type of (constructor p1 p2 ... pi) TypeExpr typeOfArgsSoFar = typeOfAppl; TypeExpr typeOfNextArg = typeVar; typeOfAppl = new TypeVar(); try { TypeExpr.unifyType(typeOfArgsSoFar, TypeExpr.makeFunType(typeOfNextArg, typeOfAppl), currentModuleTypeInfo); } catch (TypeException te) { //this should never happen since we should have earlier checked that the number of args //supplied to the data constructor is correct. compiler.logMessage(new CompilerMessage(fieldNameNode, new MessageKind.Fatal.UnexpectedUnificationFailure(), te)); } } try { TypeExpr.unifyType(conditionType, typeOfAppl, currentModuleTypeInfo); } catch (TypeException te) { //the expression and the data constructor must have the same type compiler.logMessage(new CompilerMessage(dcNameNode, new MessageKind.Error.ExpressionDoesNotMatchDataConstructorType(), te)); } // Finally, return the type of the expression being evaluated. // This is the implicit qualified var which would appear on the rhs of the corresponding case. // eg. foo.DCName.argName converts to case foo of DCName {argName} -> argName // then the "argName" on the rhs of the arrow is typed. return analyzeExpr(extendedEnv, extendedNonGenericVars, qualifiedVarNode); } /** * Determining the type of expressions. * * Creation date: (9/1/00 9:05:15 AM) * @return TypeExpr the type of the expression. May be null if the type could not be determined, although in that case * an error message will be logged. * @param functionEnv environment to use for typing the expression * @param nonGenericVars the list of variables to treat as non-generic during the typing * @param parseTree an expression parse tree */ private TypeExpr analyzeExpr(final Env functionEnv, final NonGenericVars nonGenericVars, final ParseTreeNode parseTree) { final int nodeType = parseTree.getType(); switch (nodeType) { case CALTreeParserTokenTypes.VIRTUAL_LET_NONREC: case CALTreeParserTokenTypes.VIRTUAL_LET_REC: return analyzeLet(functionEnv, nonGenericVars, parseTree); case CALTreeParserTokenTypes.LAMBDA_DEFN : { final ParseTreeNode paramListNode = parseTree.firstChild(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); return analyzeLambda(functionEnv, nonGenericVars, paramListNode); } case CALTreeParserTokenTypes.LITERAL_if : { final ParseTreeNode conditionNode = parseTree.firstChild(); final TypeExpr conditionTypeExpr = analyzeExpr(functionEnv, nonGenericVars, conditionNode); try { TypeExpr.unifyType(typeConstants.getBooleanType(), conditionTypeExpr, currentModuleTypeInfo); } catch (TypeException te) { //the condition part of an if-then-else must be a Boolean compiler.logMessage(new CompilerMessage(parseTree, new MessageKind.Error.ConditionPartOfIfThenElseMustBeBoolean(), te)); } final ParseTreeNode ifTrueNode = conditionNode.nextSibling(); final TypeExpr ifTrueTypeExpr = analyzeExpr(functionEnv, nonGenericVars, ifTrueNode); final ParseTreeNode ifFalseNode = ifTrueNode.nextSibling(); final TypeExpr ifFalseTypeExpr = analyzeExpr(functionEnv, nonGenericVars, ifFalseNode); try { TypeExpr.unifyType(ifTrueTypeExpr, ifFalseTypeExpr, currentModuleTypeInfo); } catch (TypeException te) { //the type of the 'then' and 'else' parts of an if-then-else must match compiler.logMessage(new CompilerMessage(parseTree, new MessageKind.Error.TypeOfThenAndElsePartsMustMatch(), te)); } return ifTrueTypeExpr; } case CALTreeParserTokenTypes.VIRTUAL_DATA_CONSTRUCTOR_CASE : { errorCallList.add(parseTree); final TypeExpr typeExpr = analyzeDataConstructorCase(functionEnv, nonGenericVars, parseTree); // save the type so later on I can know the type of the case expression. parseTree.setTypeExprForCaseExpr(typeExpr); return typeExpr; } case CALTreeParserTokenTypes.VIRTUAL_TUPLE_CASE: { errorCallList.add(parseTree); final TypeExpr typeExpr = analyzeTupleCase(functionEnv, nonGenericVars, parseTree); // save the type so later on I can know the type of the case expression. parseTree.setTypeExprForCaseExpr(typeExpr); return typeExpr; } case CALTreeParserTokenTypes.VIRTUAL_RECORD_CASE : { errorCallList.add(parseTree); final TypeExpr typeExpr = analyzeRecordCase(functionEnv, nonGenericVars, parseTree); // save the type so later on I can know the type of the case expression. parseTree.setTypeExprForCaseExpr(typeExpr); return typeExpr; } case CALTreeParserTokenTypes.BARBAR: case CALTreeParserTokenTypes.AMPERSANDAMPERSAND: case CALTreeParserTokenTypes.PLUSPLUS: case CALTreeParserTokenTypes.EQUALSEQUALS: case CALTreeParserTokenTypes.NOT_EQUALS: case CALTreeParserTokenTypes.GREATER_THAN_OR_EQUALS: case CALTreeParserTokenTypes.GREATER_THAN: case CALTreeParserTokenTypes.LESS_THAN: case CALTreeParserTokenTypes.LESS_THAN_OR_EQUALS: case CALTreeParserTokenTypes.PLUS: case CALTreeParserTokenTypes.MINUS: case CALTreeParserTokenTypes.ASTERISK: case CALTreeParserTokenTypes.SOLIDUS: case CALTreeParserTokenTypes.PERCENT: case CALTreeParserTokenTypes.COLON: case CALTreeParserTokenTypes.UNARY_MINUS: case CALTreeParserTokenTypes.POUND: return analyzeOperator(functionEnv, nonGenericVars, parseTree); case CALTreeParserTokenTypes.DOLLAR: { // Turn the node representing the $ operator into an application node parseTree.setType(CALTreeParserTokenTypes.APPLICATION); parseTree.setText("@operator"); return analyzeApplication(functionEnv, nonGenericVars, parseTree); } case CALTreeParserTokenTypes.BACKQUOTE : { // Skip the back-quoted node parseTree.setType(CALTreeParserTokenTypes.APPLICATION); parseTree.setText("@operator"); parseTree.setFirstChild(parseTree.firstChild().firstChild()); return analyzeApplication(functionEnv, nonGenericVars, parseTree); } case CALTreeParserTokenTypes.APPLICATION : return analyzeApplication(functionEnv, nonGenericVars, parseTree); //variables and functions case CALTreeParserTokenTypes.QUALIFIED_VAR : { // Keep track of which nodes have calls to error in order // to add source position information. { final ModuleName moduleName = ModuleNameUtilities.getModuleNameFromParseTree(parseTree.getChild(0)); final ParseTreeNode name = parseTree.getChild(1); if (moduleName.equals(CAL_Prelude.MODULE_NAME)){ if (name.getText().equals(CAL_Prelude.Functions.error.getUnqualifiedName())){ errorCallList.add(parseTree); } } } final FunctionalAgent entity = retrieveQualifiedVar(functionEnv, parseTree, true); if (entity == null) { //logging already done in retreiveQualifiedVar return null; } final TypeExpr typeExpr = entity.getTypeExpr(nonGenericVars); final FunctionalAgent.Form form = entity.getForm(); //exclude pattern bound variables from overload resolution if (form != FunctionalAgent.Form.PATTERNVAR) { //functions that are in the process of being type checked are treated differently from functions //which are already type checked. Note: what we mean by "type checked" is that the body of all functions //in the same declaration group have been type checked. In other words, the function is free to be //generalized when used in the "in" part (if a local function) or in another component (if a top-level function). final boolean isPolymorphic = entity.isTypeCheckingDone(); final ApplicationInfo apInfo = new ApplicationInfo(entity, parseTree, typeExpr, nonGenericVars, isPolymorphic); currentOverloadingInfo.addApplication(apInfo); } return typeExpr; } //data constructors case CALTreeParserTokenTypes.QUALIFIED_CONS : { final DataConstructor dataCons = retrieveQualifiedDataConstructor(parseTree); if (dataCons == null) { //error logged in retrieveQualifiedDataConstructor return null; } return dataCons.getTypeExpr(); } // literals case CALTreeParserTokenTypes.CHAR_LITERAL : return typeConstants.getCharType(); case CALTreeParserTokenTypes.INTEGER_LITERAL : { //the integer literal n is replaced by the application: //Prelude.fromInt n //if n is representable as an Int //Otherwise it is replaced by //Prelude.fromLong n //if n is representable as a Long //Otherwise it is replaced by //Prelude.fromInteger n // //In all cases, the application is marked for overload resolution final String integerAsString = parseTree.getText(); BigInteger integerLiteral = null; try { integerLiteral = new BigInteger(parseTree.getText()); } catch (NumberFormatException e) { // We failed to parse the INTEGER_LITERAL as an integer! compiler.logMessage(new CompilerMessage(parseTree, new MessageKind.Error.UnableToParseToIntegerLiteral(integerAsString))); } final SourcePosition sourcePosition = parseTree.getSourcePosition(); final QualifiedName fromLiteralFunction; final ParseTreeNode integerLiteralNode = new ParseTreeNode(CALTreeParserTokenTypes.INTEGER_LITERAL, integerAsString, sourcePosition); final int intLiteral; final long longLiteral; if (integerLiteral.compareTo(BigInteger.valueOf(intLiteral = integerLiteral.intValue())) == 0) { //the literal can be represented as an int fromLiteralFunction = CAL_Prelude.Functions.fromInt; integerLiteralNode.setIntegerValueForMaybeMinusIntLiteral(Integer.valueOf(intLiteral)); } else if (integerLiteral.compareTo(BigInteger.valueOf(longLiteral = integerLiteral.longValue())) == 0) { //the literal cannot be represented as an int, but can be represented as a long fromLiteralFunction = CAL_Prelude.Functions.fromLong; integerLiteralNode.setLongValueForMaybeMinusIntLiteral(Long.valueOf(longLiteral)); } else { //the literal can't be represented as an int or long- fall through to BigInteger which can represent them all. fromLiteralFunction = CAL_Prelude.Functions.fromInteger; integerLiteralNode.setBigIntegerValueForMaybeMinusIntLiteral(integerLiteral); } final ParseTreeNode fromLiteralNode = ParseTreeNode.makeQualifiedVarNode(fromLiteralFunction, sourcePosition); fromLiteralNode.setNextSibling(integerLiteralNode); final ParseTreeNode applicationNode = new ParseTreeNode(CALTreeParserTokenTypes.APPLICATION, "@", sourcePosition); applicationNode.setFirstChild(fromLiteralNode); applicationNode.setNextSibling(parseTree.nextSibling()); parseTree.copyContentsFrom(applicationNode); final ClassMethod classMethod = currentModuleTypeInfo.getVisibleClassMethod(fromLiteralFunction); final TypeExpr fromLiteralType = classMethod.getTypeExpr(); final TypeExpr typeOfResult = fromLiteralType.getResultType(); final boolean isPolymorphic = true; final ApplicationInfo apInfo = new ApplicationInfo(classMethod, fromLiteralNode, fromLiteralType, null, isPolymorphic); currentOverloadingInfo.addApplication(apInfo); return typeOfResult; } case CALTreeParserTokenTypes.FLOAT_LITERAL : return typeConstants.getDoubleType(); case CALTreeParserTokenTypes.STRING_LITERAL : return typeConstants.getStringType(); case CALTreeParserTokenTypes.LIST_CONSTRUCTOR : { //Lists could be simply reduced to applications e.g. treat [1,2,3] as Cons 1 (Cons 2 (Cons 3 Nil)) //However, it is clearer and more efficient to treat them as a special case. final TypeExpr unifiedElementType = new TypeVar(); for (final ParseTreeNode elementNode : parseTree) { final TypeExpr elementType = analyzeExpr(functionEnv, nonGenericVars, elementNode); try { TypeExpr.unifyType(unifiedElementType, elementType, currentModuleTypeInfo); } catch (TypeException te) { //all elements of a list must have compatible types. compiler.logMessage(new CompilerMessage(elementNode, new MessageKind.Error.AllListElementsMustHaveCompatibleTypes(), te)); } } return typeConstants.makeListType(unifiedElementType.prune()); } case CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR : { if (parseTree.hasNoChildren()) { //the Unit type, also known as (). return typeConstants.getUnitType(); } if (parseTree.hasExactlyOneChild()) { //a parenthesized expression return analyzeExpr(functionEnv, nonGenericVars, parseTree.firstChild()); } //Tuples //The 3-tuple (a, b, c) is treated like {#1 = a, #2 = b, #3 = c} //FieldName -> TypeExpr final Map<FieldName, TypeExpr> extensionFieldsMap = new HashMap<FieldName, TypeExpr>(); int componentN = 1; for (final ParseTreeNode componentNode : parseTree) { final TypeExpr valueExpr = analyzeExpr(functionEnv, nonGenericVars, componentNode); extensionFieldsMap.put(FieldName.makeOrdinalField(componentN), valueExpr); ++componentN; } return new RecordType(RecordVar.NO_FIELDS, extensionFieldsMap); } case CALTreeParserTokenTypes.RECORD_CONSTRUCTOR : { return analyzeRecordConstructor(functionEnv, nonGenericVars, parseTree); } case CALTreeParserTokenTypes.SELECT_DATA_CONSTRUCTOR_FIELD : { errorCallList.add(parseTree); return analyzeDataConstructorFieldSelection(functionEnv, nonGenericVars, parseTree); } case CALTreeParserTokenTypes.SELECT_RECORD_FIELD: { final ParseTreeNode exprNode = parseTree.firstChild(); final TypeExpr exprTypeExpr = analyzeExpr(functionEnv, nonGenericVars, exprNode); final ParseTreeNode fieldNameNode = exprNode.nextSibling(); final FieldName fieldName = getFieldName(fieldNameNode); //make the record type r\fieldName => {r | fieldName :: a}. //exprTypeExpr must unify with this. final Set<FieldName> lacksFieldsSet = new HashSet<FieldName>(); lacksFieldsSet.add(fieldName); final RecordVar baseRecordVar = RecordVar.makeRecordVar(null, lacksFieldsSet, TypeClass.NO_CLASS_CONSTRAINTS, false); final Map<FieldName, TypeExpr> hasFieldsMap = new HashMap<FieldName, TypeExpr>(); hasFieldsMap.put(fieldName, TypeExpr.makeParametricType()); final RecordType recordType = new RecordType(baseRecordVar, hasFieldsMap); try { TypeExpr.unifyType(exprTypeExpr, recordType, currentModuleTypeInfo); } catch (TypeException te) { if (parseTree.getIsSyntheticVarOrRecordFieldSelection()) { // Type error. Invalid record field {fieldName} in local pattern match decl. compiler.logMessage(new CompilerMessage(fieldNameNode, new MessageKind.Error.InvalidRecordFieldInLocalPatternMatchDecl(fieldName, exprTypeExpr))); } else { // Type error. Invalid record selection for field {fieldName}. compiler.logMessage(new CompilerMessage(fieldNameNode, new MessageKind.Error.InvalidRecordSelectionForField(fieldName), te)); } } return recordType.getHasFieldType(fieldName).prune(); } case CALTreeParserTokenTypes.EXPRESSION_TYPE_SIGNATURE: { final ParseTreeNode exprNode = parseTree.firstChild(); final TypeExpr inferredTypeExpr = analyzeExpr(functionEnv, nonGenericVars, exprNode); //copy for error reporting purposes in case pattern matching fails. final TypeExpr copyOfInferredTypeExpr = inferredTypeExpr.copyTypeExpr(); final ParseTreeNode signatureNode = exprNode.nextSibling(); final TypeExpr declaredTypeExpr = calculateTypeFromSignature(signatureNode, null); try { TypeExpr.patternMatch(declaredTypeExpr, inferredTypeExpr, nonGenericVars, currentModuleTypeInfo); } catch (TypeException te) { //the declared type of the expression is not compatible with its inferred type. //for example, this would happen for something like // 'a' :: Double compiler.logMessage(new CompilerMessage(parseTree, new MessageKind.Error.DeclaredTypeOfExpressionNotCompatibleWithInferredType(copyOfInferredTypeExpr.toString()), te)); } return inferredTypeExpr; } default : { parseTree.unexpectedParseTreeNode(); break; } } return null; } /** * Determining the type of application expressions. * * @param functionEnv environment to use for typing the expression * @param nonGenericVars the list of variables to treat as non-generic during the typing * @param parseTree an expression parse tree * @return TypeExpr the type of the expression */ private TypeExpr analyzeApplication(final Env functionEnv, final NonGenericVars nonGenericVars, final ParseTreeNode parseTree) { // Look for calls to error/assert and undefined and flag them. The source position only // applies to fully saturated calls. // we know from the grammar that there is at least one child final ParseTreeNode firstChildNode = parseTree.firstChild(); if (firstChildNode.getType() == CALTokenTypes.QUALIFIED_VAR){ // look for error/trace/assert/undefined call final ParseTreeNode module = firstChildNode.firstChild(); final ModuleName moduleName = ModuleNameUtilities.getModuleNameFromParseTree(module); if (moduleName.equals(CAL_Prelude.MODULE_NAME)){ final ParseTreeNode name = module.nextSibling(); final String varName = name.getText(); if (varName.equals(CAL_Prelude.Functions.assert_.getUnqualifiedName()) && parseTree.getNumberOfChildren() == 2){ errorCallList.add( parseTree ); } else if (varName.equals(CAL_Prelude.Functions.undefined.getUnqualifiedName())){ errorCallList.add( parseTree ); } } } TypeExpr typeOfAppl = analyzeExpr(functionEnv, nonGenericVars, firstChildNode); for (final ParseTreeNode nextArgNode : firstChildNode.nextSiblings()) { // the type of (a1 a2 ... ai) final TypeExpr typeOfArgsSoFar = typeOfAppl; final TypeExpr typeOfNextArg = analyzeExpr(functionEnv, nonGenericVars, nextArgNode); typeOfAppl = new TypeVar(); try { TypeExpr.unifyType(typeOfArgsSoFar, TypeExpr.makeFunType(typeOfNextArg, typeOfAppl), currentModuleTypeInfo); } catch (TypeException te) { //the type of the application so far is not compatible with its next argument compiler.logMessage(new CompilerMessage(nextArgNode, new MessageKind.Error.TypeErrorDuringApplication(), te)); } } return typeOfAppl; } /** * A helper function to construct FieldName values from their ParseTreeNode form. * @param fieldNameNode * @return FieldName either a FieldName.Ordinal or a FieldName.Textual for the 2 different * kinds of field names */ FieldName getFieldName(final ParseTreeNode fieldNameNode) { switch (fieldNameNode.getType()) { case CALTreeParserTokenTypes.VAR_ID: { return FieldName.makeTextualField(fieldNameNode.getText()); } case CALTreeParserTokenTypes.ORDINAL_FIELD_NAME: { final int ordinal; try { ordinal = Integer.parseInt(fieldNameNode.getText().substring(1)); } catch (NumberFormatException nfe) { // Ordinal field name {fieldNameNode.getText()} is out of range. compiler.logMessage(new CompilerMessage(fieldNameNode, new MessageKind.Error.OrdinalFieldNameOutOfRange(fieldNameNode.getText()), nfe)); return null; } return FieldName.makeOrdinalField(ordinal); } default: { fieldNameNode.unexpectedParseTreeNode(); return null; } } } /** * Method analyzeOperator. * @param functionEnv * @param nonGenericVars * @param parseTree * @return TypeExpr */ private TypeExpr analyzeOperator(final Env functionEnv, final NonGenericVars nonGenericVars, final ParseTreeNode parseTree) { //Note operators can't be hidden by scoping. So that && always refers to Prelude.and and not //some local function named "and". Thus, we can't look up the type directly in the environment... final FunctionalAgent envEntity; final String operatorName = parseTree.getText(); final int nodeKind = parseTree.getType(); boolean unaryMinus = false; switch (nodeKind) { case CALTreeParserTokenTypes.BARBAR : case CALTreeParserTokenTypes.AMPERSANDAMPERSAND : { envEntity = Function.makeTopLevelFunction(OperatorInfo.getTextualName(operatorName), typeConstants.getAndOrType(), Scope.PUBLIC); break; } case CALTreeParserTokenTypes.POUND: { envEntity = Function.makeTopLevelFunction(OperatorInfo.getTextualName(operatorName), TypeExpr.makeComposeType(), Scope.PUBLIC); break; } case CALTreeParserTokenTypes.PLUSPLUS: case CALTreeParserTokenTypes.EQUALSEQUALS: case CALTreeParserTokenTypes.NOT_EQUALS: case CALTreeParserTokenTypes.GREATER_THAN_OR_EQUALS: case CALTreeParserTokenTypes.GREATER_THAN: case CALTreeParserTokenTypes.LESS_THAN: case CALTreeParserTokenTypes.LESS_THAN_OR_EQUALS: case CALTreeParserTokenTypes.PLUS: case CALTreeParserTokenTypes.MINUS: case CALTreeParserTokenTypes.ASTERISK: case CALTreeParserTokenTypes.SOLIDUS: case CALTreeParserTokenTypes.PERCENT: { //These tokens are a short-hand for class methods envEntity = currentModuleTypeInfo.getVisibleClassMethod(OperatorInfo.getTextualName(operatorName)); break; } case CALTreeParserTokenTypes.UNARY_MINUS: { envEntity = currentModuleTypeInfo.getVisibleClassMethod(CAL_Prelude.Functions.negate); unaryMinus = true; break; } case CALTreeParserTokenTypes.COLON : { envEntity = currentModuleTypeInfo.getVisibleDataConstructor(OperatorInfo.getTextualName(operatorName)); break; } default : { parseTree.unexpectedParseTreeNode(); return null; } } final TypeExpr typeOfOp = envEntity.getTypeExpr(); //convert the parse tree so that it doesn't use operators (for example, + is converted to Prelude.add) //we do not do this in earlier passes (such as the FreeVariableFinder) in order to give better error messages. final ParseTreeNode arg1Node = parseTree.firstChild(); final QualifiedName textualName = unaryMinus? CAL_Prelude.Functions.negate : OperatorInfo.getTextualName(operatorName); final ParseTreeNode functionalAgentNode; if (nodeKind == CALTreeParserTokenTypes.COLON) { functionalAgentNode = ParseTreeNode.makeQualifiedConsNode(textualName, parseTree.getSourcePosition()); } else { functionalAgentNode = ParseTreeNode.makeQualifiedVarNode(textualName, parseTree.getSourcePosition()); } parseTree.setType(CALTreeParserTokenTypes.APPLICATION); parseTree.setText("@operator"); parseTree.setFirstChild(functionalAgentNode); functionalAgentNode.setNextSibling(arg1Node); //(&&), (||), (:), (#) do not need overload resolution, since their type signatures do not //depend on constrained type variables. switch (nodeKind) { case CALTreeParserTokenTypes.BARBAR : case CALTreeParserTokenTypes.AMPERSANDAMPERSAND : case CALTreeParserTokenTypes.COLON : case CALTreeParserTokenTypes.POUND: break; default: { final boolean isPolymorphic = true; final ApplicationInfo apInfo = new ApplicationInfo(envEntity, functionalAgentNode, typeOfOp, null, isPolymorphic); currentOverloadingInfo.addApplication(apInfo); break; } } final TypeExpr typeOfArg1 = analyzeExpr(functionEnv, nonGenericVars, arg1Node); final TypeExpr typeOfOpAppliedToArg1 = new TypeVar(); try { TypeExpr.unifyType(typeOfOp, TypeExpr.makeFunType(typeOfArg1, typeOfOpAppliedToArg1), currentModuleTypeInfo); } catch (TypeException te) { //type clash between the operator and its first argument compiler.logMessage(new CompilerMessage(functionalAgentNode, new MessageKind.Error.TypeErrorApplyingOperatorToFirstArgument(operatorName), te)); } if (unaryMinus) { return typeOfOpAppliedToArg1; } final ParseTreeNode arg2Node = arg1Node.nextSibling(); final TypeExpr typeOfArg2 = analyzeExpr(functionEnv, nonGenericVars, arg2Node); final TypeExpr typeOfResult = new TypeVar(); try { TypeExpr.unifyType(typeOfOpAppliedToArg1, TypeExpr.makeFunType(typeOfArg2, typeOfResult), currentModuleTypeInfo); } catch (TypeException te) { //type clash between the operator and its second argument compiler.logMessage(new CompilerMessage(functionalAgentNode, new MessageKind.Error.TypeErrorApplyingOperatorToSecondArgument(operatorName), te)); } return typeOfResult; } /** * Determines the type of a lambda expression. * * Creation date: (9/1/00 9:05:15 AM) * @return TypeExpr the type of the lambda expression * @param functionEnv environment to use for typing the function * @param nonGenericVars the list of variables to treat as non-generic during the typing * @param paramListNode the parse tree for the lambda expression */ private TypeExpr analyzeLambda(final Env functionEnv, final NonGenericVars nonGenericVars, final ParseTreeNode paramListNode) { paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); final List<TypeVar> typesOfArgs = new ArrayList<TypeVar>(); Env extendedFunctionEnv = functionEnv; NonGenericVars extendedNonGenericVars = nonGenericVars; for (final ParseTreeNode varNode : paramListNode) { varNode.verifyType(CALTreeParserTokenTypes.LAZY_PARAM, CALTreeParserTokenTypes.STRICT_PARAM); final String varName = varNode.getText(); if (varNode.getType() == CALTreeParserTokenTypes.STRICT_PARAM) { evaluatedLocalVariables.add(varName); } final TypeVar newTypeVar = new TypeVar(); typesOfArgs.add(newTypeVar); extendedFunctionEnv = extendedFunctionEnv.extend(new Function(QualifiedName.make(currentModuleTypeInfo.getModuleName(), varName), Scope.PRIVATE, null, newTypeVar, FunctionalAgent.Form.PATTERNVAR, nestingLevel)); extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, newTypeVar); functionBoundNamesToTypeMap.put(varName, newTypeVar); } final ParseTreeNode exprNode = paramListNode.nextSibling(); final TypeExpr typeOfBody = analyzeExpr(extendedFunctionEnv, extendedNonGenericVars, exprNode); TypeExpr typeOfLambda = typeOfBody; for (int i = typesOfArgs.size() - 1; i >= 0; --i) { typeOfLambda = TypeExpr.makeFunType(typesOfArgs.get(i), typeOfLambda); } // save the type so later on I can know the type of the lambda expression. paramListNode.setTypeExprForFunctionParamList(typeOfLambda); return typeOfLambda; } /** * A helper function to determine the declared types in a let block. * @param defnListNode * @return Map (String -> TypeExpr) local function name to its declared type. */ private Map<String, TypeExpr> calculateDeclaredTypesMap(final ParseTreeNode defnListNode) { defnListNode.verifyType(CALTreeParserTokenTypes.LET_DEFN_LIST); final Map<String, TypeExpr> declaredTypesMap = new HashMap<String, TypeExpr>(); for (final ParseTreeNode defnNode : defnListNode) { if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN_TYPE_DECLARATION) { final ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode typeDeclNode = optionalCALDocNode.nextSibling(); typeDeclNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); final String functionName = typeDeclNode.firstChild().getText(); final TypeExpr declaredType = calculateDeclaredType(typeDeclNode); declaredTypesMap.put(functionName, declaredType); } } return declaredTypesMap; } /** * Attempts to match the inferred type for a local function with the type determined by the local type declaration. * * @param defnNode parse tree of the local function definition * @param declaredTypesMap * @param inferredTypeExpr (in/out) the inferred type for the local function, which when this method returns successfully, * will be pattern matched against the declared type. * @param nonGenericVars */ private void localPatternMatch(final ParseTreeNode defnNode, final Map<String, TypeExpr> declaredTypesMap, final TypeExpr inferredTypeExpr, final NonGenericVars nonGenericVars) { defnNode.verifyType(CALTreeParserTokenTypes.LET_DEFN); final ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode localFunctionNameNode = optionalCALDocNode.nextSibling(); final String functionName = localFunctionNameNode.getText(); final TypeExpr originalDeclaredTypeExpr = declaredTypesMap.get(functionName); if (originalDeclaredTypeExpr != null) { final TypeExpr declaredTypeExpr = originalDeclaredTypeExpr.copyTypeExpr(); // we use a copy of the declared type, so as to keep the original one pristine for later checking try { TypeExpr.patternMatch(declaredTypeExpr, inferredTypeExpr, nonGenericVars, currentModuleTypeInfo); } catch (TypeException te) { //the declared type of the local function is not compatible with its inferred type. compiler.logMessage(new CompilerMessage(defnNode, new MessageKind.Error.DeclaredTypeOfLocalFunctionNotCompatibleWithInferredType(FreeVariableFinder.getDisplayName(functionName), inferredTypeExpr.toString()), te)); } } } /** * Determines the type of a let expression. Note this is the same as "letrec" in the Core functional * language in that the functions defined within the let can be mutually recursive. * * Creation date: (9/5/00 1:26:53 PM) * @return TypeExpr * @param functionEnv * @param nonGenericVars * @param letNode */ private TypeExpr analyzeLet(final Env functionEnv, final NonGenericVars nonGenericVars, final ParseTreeNode letNode) { letNode.verifyType(CALTreeParserTokenTypes.VIRTUAL_LET_NONREC, CALTreeParserTokenTypes.VIRTUAL_LET_REC); ++nestingLevel; final ParseTreeNode defnListNode = letNode.firstChild(); defnListNode.verifyType(CALTreeParserTokenTypes.LET_DEFN_LIST); Env extendedEnv = functionEnv; NonGenericVars extendedNonGenericVars = nonGenericVars; final int baseSize = overloadingInfoList.size(); int componentSizeCounter = 0; //if the let expression is (let f1 x1 = e1; f2 x2 = e2; ... fn xn = en in e), then add f1, f2, ..., fn to the //extended environment prior to type checking e1, e2, ..., en. for (final ParseTreeNode defnNode : defnListNode) { if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) { componentSizeCounter++; final ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode localFunctionNameNode = optionalCALDocNode.nextSibling(); localFunctionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String functionName = localFunctionNameNode.getText(); final ParseTreeNode paramListNode = localFunctionNameNode.nextSibling(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); //special check for lexical 0-argument let declarations. if (paramListNode.hasNoChildren()) { //we check for the special let variables of the form //x = expression known to be evaluated to weak-head normal form //some important cases are: //x = Prelude.eager expr; //x = Prelude.eager $ expr; //x = "abc"; //literal String //and mark the variable x as being "evaluated". //This is so that the lambda lifter can add these as strict arguments to lifted functions //when actually doing the lifting. //note that we will also be marking as evaluated let variables that are not truly variables //because they depend on an overloaded type signature e.g. //x :: Num a => a; //x = 1; //However, this doesn't really matter since the lambda lifter will not add these as extra arguments //to a lifted function, but rather lift them in their own right. //note that we exclude 0-arity functions (not data constructors) such as //x = undefined //since evaluating these may intentionally terminate in a run-time error. final ParseTreeNode exprNode = paramListNode.nextSibling(); if (isEvaluatedExpr(functionEnv, exprNode)) { evaluatedLocalVariables.add(functionName); } } final List<String> namedArgumentsList = new ArrayList<String>(); for (final ParseTreeNode varNode : paramListNode) { varNode.verifyType(CALTreeParserTokenTypes.LAZY_PARAM, CALTreeParserTokenTypes.STRICT_PARAM); final String varName = FreeVariableFinder.getDisplayName(varNode.getText()); namedArgumentsList.add(varName); } final String[] namedArguments = namedArgumentsList.toArray(new String[0]); final TypeVar newTypeVar = new TypeVar(); final Function function = new Function( QualifiedName.make(currentModuleTypeInfo.getModuleName(), functionName), Scope.PRIVATE, namedArguments, newTypeVar, FunctionalAgent.Form.FUNCTION, nestingLevel); extendedEnv = extendedEnv.extend(function); localFunctionBoundNamesToFunctionEntityMap.put(functionName, function); extendedNonGenericVars = NonGenericVars.extend(extendedNonGenericVars, newTypeVar); functionBoundNamesToTypeMap.put(functionName, newTypeVar); localFunctionBoundNamesToNonGenericVarsMap.put(functionName, nonGenericVars); overloadingInfoList.add(new OverloadingInfo(function, defnNode, newTypeVar, currentOverloadingInfo)); } } final int componentSize = componentSizeCounter; final Map<String, TypeExpr> declaredTypesMap = calculateDeclaredTypesMap(defnListNode); int componentN = 0; //now type check the e1, e2, ..., en. // We store a map of the LET_DEFN to their type expressions, to be checked against the corresponding type declaration // in the second phase. // // We need to do the checking in two phases because when the inferred type and its corresponding declared type // are unified in the first phase, the inferred type may not be the ultimate specific type of the function (as it // may be further restricted by the types of subsequent functions in the component) and thus not all type errors are // detectable then. The second phase, with its second loop, is able to handle the situation as the inferred types should all // have been restricted to their final form. // // An example of such a component is: // // let // gt :: (Prelude.Num a) => a -> a -> Boolean; // gt x y = // let // foo = gt3 0 1; // in // x > y; // // gt2 :: Int -> Int -> Boolean; // gt2 = gt; // // gt3 = gt2; // in // gt // // Here, the local definition of foo in gt constrains gt to have a type of Int -> Int -> Boolean (since it uses gt3 // which is aliased to gt2, which in turn is aliased to gt, but constraining both arguments to type Int). Thus the // type declaration for gt is too general, but cannot be detected in the first phase as gt2 has not been type checked // (and more importantly has had its inferred type unified with its type declaration). // final IdentityHashMap<ParseTreeNode, TypeExpr> localFunctionTypes = new IdentityHashMap<ParseTreeNode, TypeExpr>(); for (final ParseTreeNode defnNode : defnListNode) { if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) { final ParseTreeNode optionalCALDocNode = defnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode localFunctionNameNode = optionalCALDocNode.nextSibling(); localFunctionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String functionName = localFunctionNameNode.getText(); final ParseTreeNode paramListNode = localFunctionNameNode.nextSibling(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); currentOverloadingInfo = overloadingInfoList.get(baseSize + componentN); final TypeExpr localFunctionType = extendedEnv.retrieveEntity(functionName).getTypeExpr(extendedNonGenericVars); final TypeExpr exprType = analyzeLambda(extendedEnv, extendedNonGenericVars, paramListNode); try { TypeExpr.unifyType(localFunctionType, exprType, currentModuleTypeInfo); } catch (TypeException te) { if (defnNode.getIsDesugaredPatternMatchForLetDefn()) { if (FreeVariableFinder.isInternalSyntheticVariable(functionName)) { //type of the desugared definition of a pattern match decl is not compatible with the type of some of its pattern-bound variables //for example this happens when typing: //foo19 = let {ax, b2} = (b2, ax); in (ax, b2); compiler.logMessage(new CompilerMessage(defnNode, new MessageKind.Error.TypeOfDesugaredDefnOfLocalPatternMatchDeclNotCompatibleWithPatternBoundVars(), te)); } else { //type of the local pattern-bound variable is not compatible with the defining expression of the local pattern match decl //for example, this happens when typing g in: //test = let f = 1.0 + g; (g,h) = ((f, 2.0), "foo"); in True; compiler.logMessage(new CompilerMessage(defnNode, new MessageKind.Error.TypeOfPatternBoundVariableNotCompatibleWithLocalPatternMatchDecl(FreeVariableFinder.getDisplayName(functionName)), te)); } } else { //type of the local function is not compatible with its defining expression //for example, this happens when typing g in: //test = let f = 1.0 + g; g = (f, 2.0); in True; compiler.logMessage(new CompilerMessage(defnNode, new MessageKind.Error.TypeOfLocalFunctionNotCompatibleWithDefiningExpression(FreeVariableFinder.getDisplayName(functionName)), te)); } } // Even though we are not doing the checking of the type declarations in this phase, we still need // to unified the inferred type with the type declaration, since it may constrain the function to a // more specific type than the one inferred. // //Another interesting point is that we use nonGenericVars instead of extendedNonGenericVars. The problem //type variables are those that come from the enclosing scope. //can't pattern match a nongeneric type variable to a generic type variable. //For example, for the function: //g y = let f x = x + y; in f y; //we can't add a local type declaration f :: Num a => a -> a; //because that would imply that a is a generic polymorphic type variable, //and f could be applied polymorphically in the "in" part. In fact, //f's actual type is constrained by each application in the "in" part. localPatternMatch(defnNode, declaredTypesMap, localFunctionType, nonGenericVars); // Add the type expression as one to be checked in the second phase. localFunctionTypes.put(defnNode, localFunctionType); // If the local function is generated by desugaring a local record/tuple pattern match declaration, // then we will need to perform additional checks on the type of the defining expression because implicit // in the declaration of the record/tuple pattern is a constraint on the exact fields that must be in the record type. // Retrieve the SortedSet of FieldNames corresponding to non-polymorphic record patterns appearing in the local pattern match declaration final SortedSet<FieldName> declaredFieldsInNonPolymorphicRecordPatternMatchForLetDefn = defnNode.getDeclaredFieldsInNonPolymorphicRecordPatternMatchForLetDefn(); if (declaredFieldsInNonPolymorphicRecordPatternMatchForLetDefn != null) { // We unify the type of the expression with the type implied by the non-polymorphic record pattern final Map<FieldName, TypeExpr> fieldNamesToTypeMap = new HashMap<FieldName, TypeExpr>(); for (final FieldName fieldName : declaredFieldsInNonPolymorphicRecordPatternMatchForLetDefn) { fieldNamesToTypeMap.put(fieldName, new TypeVar()); } final TypeExpr nonPolymorphicRecordPatternTypeExpr = new RecordType(RecordVar.NO_FIELDS, fieldNamesToTypeMap); try { TypeExpr.unifyType(exprType, nonPolymorphicRecordPatternTypeExpr, currentModuleTypeInfo); } catch (TypeException te) { //the local pattern match pattern and the defining expression must have the same type reportErrorOnTypeMismatchForNonPolymorphicLocalRecordPatternMatchDecl(defnNode, exprType, declaredFieldsInNonPolymorphicRecordPatternMatchForLetDefn); } } else { // Retrieve the SortedSet of FieldNames corresponding to *polymorphic* record patterns appearing in the local pattern match declaration final SortedSet<FieldName> declaredFieldsInPolymorphicRecordPatternMatchForLetDefn = defnNode.getDeclaredFieldsInPolymorphicRecordPatternMatchForLetDefn(); if (declaredFieldsInPolymorphicRecordPatternMatchForLetDefn != null) { // We unify the type of the expression with the type implied by the polymorphic record pattern final Map<FieldName, TypeExpr> fieldNamesToTypeMap = new HashMap<FieldName, TypeExpr>(); for (final FieldName fieldName : declaredFieldsInPolymorphicRecordPatternMatchForLetDefn) { fieldNamesToTypeMap.put(fieldName, new TypeVar()); } try { final TypeExpr polymorphicRecordPatternTypeExpr = RecordType.recordExtension(new TypeVar(), fieldNamesToTypeMap); TypeExpr.unifyType(exprType, polymorphicRecordPatternTypeExpr, currentModuleTypeInfo); } catch (TypeException te) { //the local pattern match pattern and the defining expression must have the same type reportErrorOnTypeMismatchForPolymorphicLocalRecordPatternMatchDecl(defnNode, exprType, declaredFieldsInPolymorphicRecordPatternMatchForLetDefn); } } } // Store the localFunctionType in the OPTIONAL_CALDOC_COMMENT node // for use by the CALDocChecker in verifying the associated CALDoc comment's @arg blocks. // Note that we count on localFunctionType itself to be modified in the remainder of the // type checking process. In particular, the value localFunctionType.getNApplications() // at this point may not reflect the actual number of arguments accepted by the local function. // For example: // // fact x = // let // /** // * @arg y the arg. // */ // f = fact; // in // if (x :: Prelude.Int) == 0 then 1 else x * f (x - 1); // // The value of localFunctionType for the local function 'f' at this point is simply 'a', // and not the final Int -> Int. optionalCALDocNode.setFunctionTypeForLocalFunctionCALDocComment(localFunctionType); if (WARNING_FOR_OVERLOADED_POLYMORPHIC_LET_VARS && paramListNode.hasNoChildren() && !localFunctionType.getGenericClassConstrainedPolymorphicVars(nonGenericVars).isEmpty() && !localFunctionNameNode.getIsSyntheticVarOrRecordFieldSelection()) { //the 2nd condition says that we have a local variable declaration rather than a local function declaration. //the 3rd condition says that the type of the local variable is a polymorphic overloaded type. //in particular, this local variable actually is a local function in the runtime because overloading must //be resolved. For example, //let x = 1; in ... //then x has polymorphic type Num a => a //the 4th condition says that the local variable is not synthetically added by the compiler //which is the case when a local pattern match decl is desugared, e.g. // let (x, y)=(1, 2); in "foo" //is desugared to: // let x=$pattern_x_y.#1; y=$pattern_x_y.#2; $pattern_x_y=(1, 2); in "foo" //then the last desugared definition is a synthetic local variable declaration letVarNameToParseTreeMap.put(functionName, localFunctionNameNode); } ++componentN; } } // Now we perform the second phase of the type checking of the local functions: checking that the type declarations // do unify with the inferred types of the local functions. if (componentSize > 1) { // optimization: we do not need to do the second pattern match when the componentSize is 1 // because the type unification in the first phase is sufficient for checking the declared type // against the (final) inferred type. for (final ParseTreeNode defnNode : defnListNode) { if (defnNode.getType() == CALTreeParserTokenTypes.LET_DEFN) { final TypeExpr localFunctionType = localFunctionTypes.get(defnNode); //verify that the inferred type of the local function in fact meets the assertion of the declared type (if any) //the inferred type may be specialized here as well. // //Another interesting point is that we use nonGenericVars instead of extendedNonGenericVars. The problem //type variables are those that come from the enclosing scope. //can't pattern match a nongeneric type variable to a generic type variable. //For example, for the function: //g y = let f x = x + y; in f y; //we can't add a local type declaration f :: Num a => a -> a; //because that would imply that a is a generic polymorphic type variable, //and f could be applied polymorphically in the "in" part. In fact, //f's actual type is constrained by each application in the "in" part. localPatternMatch(defnNode, declaredTypesMap, localFunctionType, nonGenericVars); } } } //the types of the functions in this component (declaration group) are now free to be generalized. extendedEnv.finishedTypeChecking(componentSize); --nestingLevel; currentOverloadingInfo = currentOverloadingInfo.getParent(); //we can now determine what dictionary variables need to be added to each local function //these correspond to the type variables in the type of the signature that are *generic* constrained type variables. //non-generic constrained type variables will be resolved in an enclosing scope. for (int i = 0; i < componentSize; ++i) { OverloadingInfo oi = overloadingInfoList.get(baseSize + i); oi.finishedTypeCheckingFunction(nonGenericVars); } final ParseTreeNode exprNode = defnListNode.nextSibling(); // The let local function or CAFs are nonGeneric only during their declaration, // and not for the "in" part. This is why we use nonGenericVars here and not // extendedNonGenericVars below. return analyzeExpr(extendedEnv, nonGenericVars, exprNode); } /** * Reports an error for a type mismatch of a local non-polymorphic record (or tuple) pattern match declaration * and its defining expression. * @param defnNode the node to report the error on. * @param exprType the type of the defining expression. * @param declaredFieldsInNonPolymorphicRecordPatternMatchForLetDefn the declared fields of the non-polymorphic record (or tuple) pattern. */ private void reportErrorOnTypeMismatchForNonPolymorphicLocalRecordPatternMatchDecl( ParseTreeNode defnNode, final TypeExpr exprType, final SortedSet<FieldName> declaredFieldsInNonPolymorphicRecordPatternMatchForLetDefn) { // we figure out whether the declared fields form a tuple type or not, and produce // the appropriate error int tupleDimension = 0; for (final FieldName fieldName : declaredFieldsInNonPolymorphicRecordPatternMatchForLetDefn) { if (fieldName instanceof FieldName.Ordinal) { if (((FieldName.Ordinal)fieldName).getOrdinal() == tupleDimension + 1) { tupleDimension++; } else { // includes non-contiguous ordinal fields - not a tuple tupleDimension = -1; break; } } else { // includes non-ordinal fields - not a tuple tupleDimension = -1; break; } } if (tupleDimension < 2) { // just the field #1 - not a tuple tupleDimension = -1; } // report the appropriate error if (tupleDimension == -1) { compiler.logMessage(new CompilerMessage(defnNode, new MessageKind.Error.LocalPatternMatchDeclMustHaveFields(exprType, declaredFieldsInNonPolymorphicRecordPatternMatchForLetDefn))); } else { compiler.logMessage(new CompilerMessage(defnNode, new MessageKind.Error.LocalPatternMatchDeclMustHaveTupleDimension(exprType, tupleDimension))); } } /** * Reports an error for a type mismatch of a local polymorphic record (or tuple) pattern match declaration * and its defining expression. * @param defnNode the node to report the error on. * @param exprType the type of the defining expression. * @param declaredFieldsInPolymorphicRecordPatternMatchForLetDefn the declared fields of the non-polymorphic record (or tuple) pattern. */ private void reportErrorOnTypeMismatchForPolymorphicLocalRecordPatternMatchDecl( ParseTreeNode defnNode, final TypeExpr exprType, final SortedSet<FieldName> declaredFieldsInPolymorphicRecordPatternMatchForLetDefn) { compiler.logMessage(new CompilerMessage(defnNode, new MessageKind.Error.LocalPatternMatchDeclMustAtLeastHaveFields(exprType, declaredFieldsInPolymorphicRecordPatternMatchForLetDefn))); } /** * Identifies calls to Prelude.eager. Also handles some degenerate situations which are * parser and analysis artifacts that semantically are equivalent to a reference to Prelude.eager. * a) parenthesized calls to Prelude.eager such as (((Prelude.eager))) * b) zero-argument applications of Prelude.eager such as (APPLICATION_NODE^ Prelude.eager) * * @param exprNode * @return true if the node is a call to Prelude.eager. */ private static boolean isEagerFunctionNode (final ParseTreeNode exprNode) { switch (exprNode.getType()) { case CALTreeParserTokenTypes.QUALIFIED_VAR : { final ParseTreeNode moduleNode = exprNode.firstChild(); final ParseTreeNode varNode = moduleNode.nextSibling(); return varNode.getText().equals(CAL_Prelude.Functions.eager.getUnqualifiedName()) && ModuleNameUtilities.getModuleNameFromParseTree(moduleNode).equals(CAL_Prelude.MODULE_NAME); } case CALTreeParserTokenTypes.APPLICATION : case CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR: { return exprNode.hasExactlyOneChild() && isEagerFunctionNode(exprNode.firstChild()); } default: { return false; } } } /** * A helper function which returns true if the compiler can guarantee that the expression is already * evaluated to weak-head normal form. Note that it is possible to do more aggressive check here but * we limit ourselves to simple cases. * * The main application of this function is when analyzing let variables of the form * x = expr; * then if expr is know to be evaluated, we can mark x as an evaluated variable. Then if x is used * in a local function, it can be added as a plinged variable to that local function. This is a run-time * optimization. * * Some of the main cases that are handled: * -top level applications of Prelude.eager * -literals (string, integers, double, lists, tuples, non-extension records) * -aliases for positive arity top-level functions and (zero of positive arity) data constructors * -aliases for evaluated local variables * -certain special data constructor field selections of the form (evaluated-expression).MyDataConstructor.myStrictField) * * @param functionEnv the environment to look up local symbols from * @param exprNode * @return true if the expression is guaranteed to be evaluated to weak-head normal form. */ private boolean isEvaluatedExpr(final Env functionEnv, final ParseTreeNode exprNode) { final int nodeType = exprNode.getType(); switch (nodeType) { case CALTreeParserTokenTypes.VIRTUAL_LET_NONREC: case CALTreeParserTokenTypes.VIRTUAL_LET_REC: { //a let or let rec is evaluated to weak-head normal form if its in part is. final ParseTreeNode defnListNode = exprNode.firstChild(); defnListNode.verifyType(CALTreeParserTokenTypes.LET_DEFN_LIST); final ParseTreeNode inExprNode = defnListNode.nextSibling(); return isEvaluatedExpr(functionEnv, inExprNode); } case CALTreeParserTokenTypes.LAMBDA_DEFN : case CALTreeParserTokenTypes.LITERAL_if : case CALTreeParserTokenTypes.VIRTUAL_DATA_CONSTRUCTOR_CASE : case CALTreeParserTokenTypes.VIRTUAL_TUPLE_CASE: case CALTreeParserTokenTypes.VIRTUAL_RECORD_CASE : case CALTreeParserTokenTypes.BARBAR : case CALTreeParserTokenTypes.AMPERSANDAMPERSAND : case CALTreeParserTokenTypes.PLUSPLUS : case CALTreeParserTokenTypes.EQUALSEQUALS : case CALTreeParserTokenTypes.NOT_EQUALS : case CALTreeParserTokenTypes.GREATER_THAN_OR_EQUALS : case CALTreeParserTokenTypes.GREATER_THAN : case CALTreeParserTokenTypes.LESS_THAN : case CALTreeParserTokenTypes.LESS_THAN_OR_EQUALS : case CALTreeParserTokenTypes.PLUS : case CALTreeParserTokenTypes.MINUS : case CALTreeParserTokenTypes.ASTERISK : case CALTreeParserTokenTypes.SOLIDUS : case CALTreeParserTokenTypes.PERCENT: case CALTreeParserTokenTypes.UNARY_MINUS: case CALTreeParserTokenTypes.POUND: return false; case CALTreeParserTokenTypes.COLON : { //Prelude.Cons fully saturated is in weak-head normal form since both arguments are non-strict. return true; } case CALTreeParserTokenTypes.DOLLAR: { return isEvaluatedExpr(functionEnv, exprNode.firstChild()); } case CALTreeParserTokenTypes.BACKQUOTE : return false; case CALTreeParserTokenTypes.APPLICATION : { //true if we have an application of Prelude.eager to an expr. final ParseTreeNode argNode = exprNode.firstChild(); if (argNode.nextSibling() == null) { return isEvaluatedExpr(functionEnv, argNode); } return isEagerFunctionNode(argNode); } //variables and functions case CALTreeParserTokenTypes.QUALIFIED_VAR : { final FunctionalAgent entity = retrieveQualifiedVar(functionEnv, exprNode, false); if (entity == null) { //we don't log errors for entites not found. This is because we don't //build a deep environment so that code such as //let // x = let y = 2.0; in y; //in expr //will not be able to resolve the local symbol y. return false; } if (entity.getNestingLevel() == 0 && entity.getForm() != FunctionalAgent.Form.PATTERNVAR) { //references to top level functions are in weak head normal form. //note that arguments of top level functions have nesting level 0, so we must exclude pattern variables as well. //we want to exclude 0-arity functions since these may intentionally evaluate to a run-time error. //x = Prelude.undefined; return entity.getTypeExpr().getArity() > 0; } //aliases of evaluated variables are evaluted. return isEvaluatedLocalVariable(entity.getName().getUnqualifiedName()); } //data constructors case CALTreeParserTokenTypes.QUALIFIED_CONS : { final DataConstructor dataCons = retrieveQualifiedDataConstructor(exprNode); if (dataCons == null) { //error logged in retrieveQualifiedDataConstructor return false; } //data constructors applied to 0 arguments are already in WHNF. return true; } // literals case CALTreeParserTokenTypes.CHAR_LITERAL : case CALTreeParserTokenTypes.INTEGER_LITERAL : case CALTreeParserTokenTypes.FLOAT_LITERAL : case CALTreeParserTokenTypes.STRING_LITERAL : case CALTreeParserTokenTypes.LIST_CONSTRUCTOR : { //literals are already in weak-head normal form. //the integer literal case is a bit different since it may in fact be an overloaded value of type Num a. //but we won't be calling this function in that case... return true; } case CALTreeParserTokenTypes.TUPLE_CONSTRUCTOR : { if (exprNode.hasExactlyOneChild()) { //a parenthesized expression return isEvaluatedExpr(functionEnv, exprNode.firstChild()); } //literal tuples are in weak-head normal form return true; } case CALTreeParserTokenTypes.RECORD_CONSTRUCTOR : { final ParseTreeNode baseRecordNode = exprNode.firstChild(); baseRecordNode.verifyType(CALTreeParserTokenTypes.BASE_RECORD); final ParseTreeNode baseRecordExprNode = baseRecordNode.firstChild(); //a record which is not a record-extension i.e. a record literal is evaluated to weak-head normal form. return baseRecordExprNode == null; } case CALTreeParserTokenTypes.SELECT_DATA_CONSTRUCTOR_FIELD : { //handle the special pattern //(evalauted expr).MyDataConstructor.myStrictField final ParseTreeNode conditionNode = exprNode.firstChild(); final ParseTreeNode dcNameNode = conditionNode.nextSibling(); dcNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); final DataConstructor dataConstructor = retrieveQualifiedDataConstructor(dcNameNode); if (dataConstructor == null) { //a message was logged in retrieveQualifiedDataConstructor return false; } final ParseTreeNode fieldNameNode = dcNameNode.nextSibling(); fieldNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.ORDINAL_FIELD_NAME); final FieldName fieldName = getFieldName(fieldNameNode); if (!dataConstructor.isArgStrict(dataConstructor.getFieldIndex(fieldName))) { //if the field is not a strict field of the data constructor, then it is not evaluated. return false; } return isEvaluatedExpr(functionEnv, conditionNode); } case CALTreeParserTokenTypes.SELECT_RECORD_FIELD: return false; case CALTreeParserTokenTypes.EXPRESSION_TYPE_SIGNATURE: { //an expression type-signature is evaluated to weak-head normal form if its expression is. return isEvaluatedExpr(functionEnv, exprNode.firstChild()); } default : { exprNode.unexpectedParseTreeNode(); return false; } } } /** * Determines the type of a top-level function. * * Creation date: (9/1/00 9:05:15 AM) * @return TypeExpr the type of the function * @param functionEnv environment to use for typing the function * @param nonGenericVars the list of variables to treat as non-generic during the typing * @param parseTree the parse tree for the function */ private TypeExpr analyzeTopLevelFunction(final Env functionEnv, final NonGenericVars nonGenericVars, final ParseTreeNode parseTree) { parseTree.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN); final ParseTreeNode optionalCALDocNode = parseTree.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); accessModifierNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); final ParseTreeNode functionNameNode = accessModifierNode.nextSibling(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String functionName = functionNameNode.getText(); final ParseTreeNode paramListNode = functionNameNode.nextSibling(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); // f x1 x2 .. xn = e // is typed as if it were a lambda of the form // f = \x1 -> (\x2 -> ... (e)...) // in other words, x1,...xn are added to the environment and the list of nonGenericVars while // typing e. final TypeExpr lambdaTypeExpr = analyzeLambda(functionEnv, nonGenericVars, paramListNode); final TypeExpr functionTypeExpr = functionEnv.retrieveEntity(functionName).getTypeExpr(nonGenericVars); //The simplest example where this final unification is necessary is: //g f x = g f (f x); //Then the type of g while typing the body of the lambda (i.e. functionType) is (a->b)->b->c //whilst lambdaType is (a->b)->a->c. So unification gives the right answer of (a->a)->a->c. try { TypeExpr.unifyType(functionTypeExpr, lambdaTypeExpr, currentModuleTypeInfo); } catch (TypeException te) { //type of the function is not compatible with its defining expression //for example, this happens when typing //cat = isEmpty [cat + 1.0]; compiler.logMessage(new CompilerMessage(parseTree, new MessageKind.Error.TypeOfFunctionNotCompatibleWithDefiningExpression(functionName), te)); } return lambdaTypeExpr; } /** * Adds the built-in primitive functions to the environment. * @param primitiveFunctionDeclarationNodes */ private void calculatePrimitiveFunctionTypes(final List<ParseTreeNode> primitiveFunctionDeclarationNodes) { for (final ParseTreeNode primitiveFunctionNode : primitiveFunctionDeclarationNodes) { primitiveFunctionNode.verifyType(CALTreeParserTokenTypes.PRIMITIVE_FUNCTION_DECLARATION); final ParseTreeNode optionalCALDocNode = primitiveFunctionNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); final Scope scope = CALTypeChecker.getScopeModifier(accessModifierNode); final ParseTreeNode typeDeclarationNode = accessModifierNode.nextSibling(); final TypeExpr typeExpr = calculateDeclaredType(typeDeclarationNode); final ParseTreeNode functionNameNode = typeDeclarationNode.firstChild(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); final String functionName = functionNameNode.getText(); final Function function = Function.makeTopLevelFunction(QualifiedName.make(currentModuleName, functionName), typeExpr, scope); function.setAsPrimitive(); //entity.setTypeCheckingDone(); functionEnv = Env.extend(functionEnv, function); } } /** * Adds the foreign function declarations to the environment. * Creation date: (April 29, 2002) * @param foreignFunctionDefnNodes */ private void calculateForeignFunctionTypes(final List<ParseTreeNode> foreignFunctionDefnNodes) throws UnableToResolveForeignEntityException { final ForeignFunctionChecker foreignFunctionChecker = new ForeignFunctionChecker (compiler, currentModuleTypeInfo); functionEnv = foreignFunctionChecker.calculateForeignFunctionTypes(functionEnv, foreignFunctionDefnNodes); } /** * Determines the typeExpression defined by a parseTree describing a declaration. * Creation date: (12/8/00 10:46:25 AM) * @return TypeExpr * @param parseTree * @param typeVarNameToTypeMap (String -> TypeExpr) this function populates this map, which should be empty in the initial call * (unless some types are supplied by an enclosing context, as in the case of a class method and the type class type variable). * @param recordVarNameToRecordVarMap (String -> RecordVar) this function populates this map, which should be empty in the initial call. */ private TypeExpr calculateDeclaredTypeExpr( final ParseTreeNode parseTree, final Map<String, TypeVar> typeVarNameToTypeMap, final Map<String, RecordVar> recordVarNameToRecordVarMap) { switch (parseTree.getType()) { case CALTreeParserTokenTypes.FUNCTION_TYPE_CONSTRUCTOR : { final ParseTreeNode domainNode = parseTree.firstChild(); final TypeExpr domain = calculateDeclaredTypeExpr(domainNode, typeVarNameToTypeMap, recordVarNameToRecordVarMap); final TypeExpr codomain = calculateDeclaredTypeExpr(domainNode.nextSibling(), typeVarNameToTypeMap, recordVarNameToRecordVarMap); return TypeExpr.makeFunType(domain, codomain); } case CALTreeParserTokenTypes.LIST_TYPE_CONSTRUCTOR : { final TypeExpr elementTypeExpr = calculateDeclaredTypeExpr(parseTree.firstChild(), typeVarNameToTypeMap, recordVarNameToRecordVarMap); return typeConstants.makeListType(elementTypeExpr); } case CALTreeParserTokenTypes.TYPE_APPLICATION : { if (parseTree.hasExactlyOneChild()) { //not really an application node, but an artifact of parsing return calculateDeclaredTypeExpr(parseTree.firstChild(), typeVarNameToTypeMap, recordVarNameToRecordVarMap); } //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) { TypeConstructor typeCons = dataDeclarationChecker.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] = calculateDeclaredTypeExpr(argNode, typeVarNameToTypeMap, recordVarNameToRecordVarMap); ++argN; } return new TypeConsApp(typeCons, args); } TypeExpr partialApp = calculateDeclaredTypeExpr(firstChildNode, typeVarNameToTypeMap, recordVarNameToRecordVarMap); for (final ParseTreeNode argNode : firstChildNode.nextSiblings()) { partialApp = new TypeApp(partialApp, calculateDeclaredTypeExpr(argNode, typeVarNameToTypeMap, recordVarNameToRecordVarMap)); } return partialApp; } case CALTreeParserTokenTypes.QUALIFIED_CONS : { final TypeConstructor typeCons = dataDeclarationChecker.resolveTypeConsName(parseTree); //note that we cannot assume that the type constructor is non-parametric //i.e. this may be an understaturated application. return new TypeConsApp(typeCons, null); } case CALTreeParserTokenTypes.VAR_ID : { final String typeVarName = parseTree.getText(); if (typeVarNameToTypeMap.containsKey(typeVarName)) { return typeVarNameToTypeMap.get(typeVarName); } final TypeVar typeVar = new TypeVar(typeVarName); typeVarNameToTypeMap.put(typeVarName, typeVar); return typeVar; } case CALTreeParserTokenTypes.TUPLE_TYPE_CONSTRUCTOR : { if (parseTree.hasNoChildren()) { return typeConstants.getUnitType(); } if (parseTree.hasExactlyOneChild()) { // the type (t) is equivalent to the type t. return calculateDeclaredTypeExpr(parseTree.firstChild(), typeVarNameToTypeMap, recordVarNameToRecordVarMap); } final Map<FieldName, TypeExpr> fieldToTypeMap = new HashMap<FieldName, TypeExpr>(); int componentN = 1; for (final ParseTreeNode componentNode : parseTree) { final TypeExpr componentTypeExpr = calculateDeclaredTypeExpr(componentNode, typeVarNameToTypeMap, recordVarNameToRecordVarMap); fieldToTypeMap.put(FieldName.makeOrdinalField(componentN), componentTypeExpr); ++componentN; } return new RecordType(RecordVar.NO_FIELDS, fieldToTypeMap); } 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 String recordVarName = recordVarNameNode.getText(); if (recordVarNameToRecordVarMap.containsKey(recordVarName)) { recordVar = recordVarNameToRecordVarMap.get(recordVarName); } else { //create an unconstrained recordVar i.e. {r} recordVar = RecordVar.makePolymorphicRecordVar(recordVarName); recordVarNameToRecordVarMap.put(recordVarName, recordVar); } } 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 = getFieldName(fieldNameNode); final ParseTreeNode typeNode = fieldNameNode.nextSibling(); final TypeExpr type = calculateDeclaredTypeExpr(typeNode, typeVarNameToTypeMap, recordVarNameToRecordVarMap); extensionFieldsMap.put(fieldName, type); } final RecordVar prunedRecordVar = recordVar.prune(); if (!prunedRecordVar.isNoFields() && !prunedRecordVar.getLacksFieldsSet().containsAll(extensionFieldsMap.keySet())) { //Can't have extensions such as {r | field1 :: Int}. r must have a field1 lacks constraint. final Set<FieldName> missingLacksConstraints = new HashSet<FieldName>(extensionFieldsMap.keySet()); missingLacksConstraints.removeAll(prunedRecordVar.getLacksFieldsSet()); // TypeChecker: the extension fields {missingLacksConstraints} must be lacks constraints on the record variable. compiler.logMessage(new CompilerMessage(parseTree, new MessageKind.Error.ExtensionFieldsLacksConstraintsOnRecordVariable(missingLacksConstraints.toString()))); return null; } return new RecordType(recordVar, extensionFieldsMap); } default : { parseTree.unexpectedParseTreeNode(); break; } } return null; } /** * Calculates the context determined by a given type declaration. * The context specifies * <ol> * <li> the type variables that are qualified by type class constraints. * <li> the record variables that have lacks fields constraints. * </ol> * * <p> * In a context, we must ensure that the type class constraints imposed by each type class on a particular type * variable are compatible. For example, (Functor a, Eq a) => ... should give a compilation error since the kind of * Functor is * -> * and the kind of Eq is *. * * @param contextListNode * @param typeVarNameToTypeMap (String -> TypeVar) map of the names of the type variables that appear in the type context to their TypeVar values. * This map is empty on entry, and is populated by this method. * @param typeVarNamesSet (String Set) the names of the type variables that occur in the type expression (not including the context) * @param recordVarNameToRecordVarMap (String -> RecordVar) map of the names of the record variables that appear in the type context to their RecordVar values. * This map is empty on entry, and is populated by this method. * @param recordVarNamesSet (String Set) the names of the record variables that occur in the type expression (not including the context) * @param classTypeVarNamesSet (Set of Strings) Empty except in the case of class methods. These are the additional * type variables scoped over this type arising from the class declaration. Note that the set has one element for * a single parameter type class. */ private void calculateDeclaredTypeContext( final ParseTreeNode contextListNode, final Map<String, TypeVar> typeVarNameToTypeMap, final Set<String> typeVarNamesSet, final Map<String, RecordVar> recordVarNameToRecordVarMap, final Set<String> recordVarNamesSet, final Set<String> classTypeVarNamesSet) { contextListNode.verifyType(CALTreeParserTokenTypes.TYPE_CONTEXT_LIST, CALTreeParserTokenTypes.TYPE_CONTEXT_NOTHING, CALTreeParserTokenTypes.TYPE_CONTEXT_SINGLETON ); //String -> (TypeClass SortedSet) final Map<String, SortedSet<TypeClass>> typeVarNameToConstraintSet = new HashMap<String, SortedSet<TypeClass>>(); //String -> (TypeClass SortedSet) final Map<String, SortedSet<TypeClass>> recordVarNameToConstraintSet = new HashMap<String, SortedSet<TypeClass>>(); //String -> (FieldName Set) final Map<String, Set<FieldName>> recordVarNameToLacksSet = new HashMap<String, Set<FieldName>>(); //String -> TypeClass //map from a type var name to the lexically first class in the context that uses that type var. Used for error messages. //for example, for (Fuctor f, Eq a, Ord b, Foo a, Foo b, Monad f) this is [(a, Eq), (b, Ord), (f, Functor)] final Map<String, TypeClass> typeVarNameToFirstClassConstraint = new HashMap<String, TypeClass>(); for (final ParseTreeNode contextNode : contextListNode) { switch (contextNode.getType()) { case CALTreeParserTokenTypes.CLASS_CONTEXT : { final ParseTreeNode typeClassNameNode = contextNode.firstChild(); typeClassNameNode.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. final TypeClass typeClass = typeClassChecker.resolveClassName(typeClassNameNode); final ParseTreeNode varNameNode = typeClassNameNode.nextSibling(); varNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); //could be a record var or a type var final String varName = varNameNode.getText(); if (classTypeVarNamesSet.contains(varName)) { //"The class type variable {0} cannot be used in a class method context." compiler.logMessage (new CompilerMessage(varNameNode, new MessageKind.Error.ClassTypeVarInMethodContext(varName))); } if (typeVarNamesSet.contains(varName)) { SortedSet<TypeClass> constraintSet = typeVarNameToConstraintSet.get(varName); if (constraintSet == null) { constraintSet = TypeClass.makeNewClassConstraintSet(); constraintSet.add(typeClass); typeVarNameToConstraintSet.put(varName, constraintSet); //the kind of the type var must be the same as the kind of any of its constraining type classes, //in particular, that of the first. Log the first type class to report a potential kinding error //with later constraints typeVarNameToFirstClassConstraint.put(varName, typeClass); } else { if (!constraintSet.add(typeClass)) { //a repeated constraint such as //(Eq a, Eq a) => a -> a //is allowed in Hugs, but is not allowed in CAL to be consistent with other restrictions //on repeated declarations and definitions. compiler.logMessage(new CompilerMessage(typeClassNameNode, new MessageKind.Error.RepeatedClassConstraintOnTypeVariable(typeClass.getName(), varName))); } //check that the type class kinds for a given type variable all unify final TypeClass firstTypeClass = typeVarNameToFirstClassConstraint.get(varName); try { KindExpr.unifyKind(firstTypeClass.getKindExpr(), typeClass.getKindExpr()); } catch (TypeException typeException) { //"The kinds of all classes constraining the type variable '{0}' must be the same. Class {1} has kind {2} while class {3} has kind {4}." compiler.logMessage(new CompilerMessage(contextNode, new MessageKind.Error.KindErrorInClassConstraints( varName, firstTypeClass, typeClass))); } } } else if (recordVarNamesSet.contains(varName)) { SortedSet<TypeClass> constraintSet = recordVarNameToConstraintSet.get(varName); if (constraintSet == null) { constraintSet = TypeClass.makeNewClassConstraintSet(); constraintSet.add(typeClass); recordVarNameToConstraintSet.put(varName, constraintSet); } else { if (!constraintSet.add(typeClass)) { //a repeated constraint such as //(Eq a, Eq a) => {a} //is allowed in Hugs, but is not allowed in CAL to be consistent with other restrictions //on repeated declarations and definitions. compiler.logMessage( new CompilerMessage( typeClassNameNode, new MessageKind.Error.RepeatedClassConstraintOnRecordVariable(typeClass.getName().getQualifiedName(), varName))); } } } else { //The purpose is to signal declarations like //(Eq a) => Int //in which a constraint appears on an unused variable. This is a compilation error because it is not possible //to resolve the overloading when this method is then used in an expression, so it would result in an error. //Thus, we might as well give the error as soon as possible to avoid having people mess up. //varName could have been intended to be a record variable or a row variable. We can't tell. //TypeChecker: variable {varName} is not used in the type signature. compiler.logMessage(new CompilerMessage(varNameNode, new MessageKind.Error.VariableNotUsedInTypeSignature(varName))); } break; } case CALTreeParserTokenTypes.LACKS_FIELD_CONTEXT: { final ParseTreeNode recordVarNameNode = contextNode.firstChild(); recordVarNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String recordVarName = recordVarNameNode.getText(); //this error message is strictly speaking not needed since the following error will be triggered. However, it is //added for clarity sake. if (classTypeVarNamesSet.contains(recordVarName)) { //"The class type variable {0} cannot be used in a class method context." compiler.logMessage (new CompilerMessage(recordVarNameNode, new MessageKind.Error.ClassTypeVarInMethodContext(recordVarName))); } if (!recordVarNamesSet.contains(recordVarName)) { //cannot have constrained record vars that do not appear in the non-context type of the signature //such as r\field1 => Maybe a compiler.logMessage(new CompilerMessage(recordVarNameNode, new MessageKind.Error.RecordVariableNotUsedInTypeSignature(recordVarName))); } final ParseTreeNode fieldNameNode = recordVarNameNode.nextSibling(); final FieldName fieldName = getFieldName(fieldNameNode); Set<FieldName> lacksSet = recordVarNameToLacksSet.get(recordVarName); if (lacksSet == null) { lacksSet = new HashSet<FieldName>(); lacksSet.add(fieldName); recordVarNameToLacksSet.put(recordVarName, lacksSet); } else { if (!lacksSet.add(fieldName)) { //can't duplicate a lacks constraint on the same record variable e.g. r\field1, r\field1 //on repeated declarations and definitions. compiler.logMessage(new CompilerMessage(fieldNameNode, new MessageKind.Error.RepeatedLacksFieldConstraintOnRecordVariable(fieldName.toString(), recordVarName))); } } break; } default: { contextNode.unexpectedParseTreeNode(); return; } } } //populate typeVarNameToTypeMap. for (final Map.Entry<String, SortedSet<TypeClass>> entry : typeVarNameToConstraintSet.entrySet()) { final String typeVarName = entry.getKey(); final SortedSet<TypeClass> constraintSet = entry.getValue(); //Declared contexts with redundant constraints because of class inheritance are allowed. //For example, in (Eq a, Ord a, Num a) => ... Eq and Ord are redundant. They are //eliminated from the internal representation of the type class constraint set. typeVarNameToTypeMap.put(typeVarName, TypeVar.makeTypeVar(typeVarName, constraintSet, true)); } //populate recordVarNameToRecordVarMap. for (final String recordVarName : recordVarNamesSet) { Set<FieldName> lacksSet = recordVarNameToLacksSet.get(recordVarName); if (lacksSet == null) { lacksSet = RecordVar.NO_LACKS_FIELDS; } SortedSet<TypeClass> typeClassConstraintSet = recordVarNameToConstraintSet.get(recordVarName); if (typeClassConstraintSet == null) { typeClassConstraintSet = TypeClass.NO_CLASS_CONSTRAINTS; } final RecordVar recordVar = RecordVar.makeRecordVar(recordVarName, lacksSet, typeClassConstraintSet, true); recordVarNameToRecordVarMap.put(recordVarName, recordVar); } } /** * Generates a type expression for each top-level type declaration in the CAL program. * Creation date: (12/8/00 10:21:34 AM) * @param functionTypeDeclarationNodes */ private void calculateDeclaredTypes(final List<ParseTreeNode> functionTypeDeclarationNodes) { for (final ParseTreeNode topLevelTypeDeclarationNode : functionTypeDeclarationNodes) { topLevelTypeDeclarationNode.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_TYPE_DECLARATION); final ParseTreeNode optionalCALDocNode = topLevelTypeDeclarationNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode typeDeclarationNode = optionalCALDocNode.nextSibling(); typeDeclarationNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); final ParseTreeNode functionNameNode = typeDeclarationNode.firstChild(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final TypeExpr typeExpr = calculateDeclaredType(typeDeclarationNode); functionNameToDeclaredTypeMap.put(functionNameNode.getText(), typeExpr); } } /** * Generates a type expression for an individual type declaration. * Creation date: (April 29, 2002) * @param typeDeclarationNode * @return TypeExpr */ TypeExpr calculateDeclaredType(final ParseTreeNode typeDeclarationNode) { typeDeclarationNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); final ParseTreeNode functionNameNode = typeDeclarationNode.firstChild(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final ParseTreeNode typeSignatureNode = functionNameNode.nextSibling(); return calculateTypeFromSignature(typeSignatureNode, functionNameNode.getText()); } /** * A helper function for computing the type from a textual representation of a type signature. * @param typeSignatureNode * @param functionName name of the function that this type signature is a signature of, or null * if it is not a signature of a function. Used for error reporting only. * @return TypeExpr built up out of the typeSignatureNode */ private TypeExpr calculateTypeFromSignature(final ParseTreeNode typeSignatureNode, final String functionName) { typeSignatureNode.verifyType(CALTreeParserTokenTypes.TYPE_SIGNATURE); final ParseTreeNode contextListNode = typeSignatureNode.firstChild(); contextListNode.verifyType(CALTreeParserTokenTypes.TYPE_CONTEXT_LIST, CALTreeParserTokenTypes.TYPE_CONTEXT_NOTHING, CALTreeParserTokenTypes.TYPE_CONTEXT_SINGLETON); final ParseTreeNode typeNode = contextListNode.nextSibling(); final Set<String> typeVarNamesSet = new HashSet<String>(); final Set<String> recordVarNamesSet = new HashSet<String>(); calculateFreeVariablesInDeclaredType(typeNode, typeVarNamesSet, recordVarNamesSet, Collections.<String>emptySet()); final Map<String, TypeVar> typeVarNameToTypeMap = new HashMap<String, TypeVar>(); final Map<String, RecordVar> recordVarNameToRecordVarMap = new HashMap<String, RecordVar>(); calculateDeclaredTypeContext(contextListNode, typeVarNameToTypeMap, typeVarNamesSet, recordVarNameToRecordVarMap, recordVarNamesSet, Collections.<String>emptySet()); final TypeExpr typeExpr = calculateDeclaredTypeExpr(typeNode, typeVarNameToTypeMap, recordVarNameToRecordVarMap); if (typeExpr == null) { return null; } //kind check the type declaration. final Map<TypeVar, KindExpr> typeVarToKindExprMap = getInitialTypeVarToKindMap(typeVarNameToTypeMap); try { dataDeclarationChecker.kindCheckTypeExpr(typeVarToKindExprMap, typeExpr); } catch (TypeException typeException) { final MessageKind errorMessage; if (functionName != null) { // TypeChecker: Kind error in the type declaration for the function {functionName}. errorMessage = new MessageKind.Error.KindErrorInTypeDeclarationForFunction(functionName); } else { // TypeChecker: Kind error in the type signature. errorMessage = new MessageKind.Error.KindErrorInTypeSignature(); } compiler.logMessage(new CompilerMessage(typeNode, errorMessage, typeException)); } return typeExpr; } /** * In a context, we must ensure that the type class constraints imposed by each type class on a particular type * variable are compatible. For example, (Functor a, Eq a) => ... should give a compilation error since the kind of * Functor is * -> * and the kind of Eq is *. This compilation error should be reported in an earlier call to * calculateDeclaredTypeContext. This function gathers up the implied (TypeVar -> KindExpr) map from the context. * * @param typeVarNameToTypeMap (String -> TypeVar) * @return (TypeVar -> KindExpr) map from type variables, to its corresponding kind as determined by the context. */ private Map<TypeVar, KindExpr> getInitialTypeVarToKindMap(final Map<String, TypeVar> typeVarNameToTypeMap) { final Map<TypeVar, KindExpr> typeVarToKindExprMap = new HashMap<TypeVar, KindExpr>(); for (final Map.Entry<String, TypeVar> entry : typeVarNameToTypeMap.entrySet()) { final TypeVar typeVar = entry.getValue(); final KindExpr kindExpr; if (typeVar.noClassConstraints()) { kindExpr = new KindExpr.KindVar(); } else { //if the constraints on the type variable a are (C1 a, ..., Cn a), then //we already know that they must all unify, thus we can just pick the first. kindExpr = (typeVar.getTypeClassConstraintSet().first()).getKindExpr(); } typeVarToKindExprMap.put(typeVar, kindExpr); } return typeVarToKindExprMap; } /** * Calculates the type of a class method, in the meantime doing some static checks which are logged as * compiler messages. * <ol> * <li> the classTypeVarName must actually appear in the type signature of the class method. * <li> the context of any class method must not involve classTypeVarName * <li> classTypeVarName must not be used as a record variable. * </ol> * * @param classMethodNode * @param classTypeVarName For example, if this is a method in the declaration of the Ord a class, this is the variable "a". * @param classTypeVarType The classTypeVarName, properly constrained i.e. Ord a => a. * @param classTypeVarKindExpr the kind of the class type variable. It will be modified via kind inference as a * side effect of this method. * @return TypeExpr */ TypeExpr calculateDeclaredClassMethodType(final ParseTreeNode classMethodNode, final String classTypeVarName, final TypeVar classTypeVarType, final KindExpr classTypeVarKindExpr) { classMethodNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD); final ParseTreeNode classMethodNameNode = classMethodNode.getChild(2); classMethodNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String classMethodName = classMethodNameNode.getText(); final ParseTreeNode typeSignatureNode = classMethodNameNode.nextSibling(); typeSignatureNode.verifyType(CALTreeParserTokenTypes.TYPE_SIGNATURE); final ParseTreeNode contextListNode = typeSignatureNode.firstChild(); contextListNode.verifyType(CALTreeParserTokenTypes.TYPE_CONTEXT_LIST, CALTreeParserTokenTypes.TYPE_CONTEXT_NOTHING, CALTreeParserTokenTypes.TYPE_CONTEXT_SINGLETON); final ParseTreeNode typeNode = contextListNode.nextSibling(); final Set<String> typeVarNamesSet = new HashSet<String>(); final Set<String> recordVarNamesSet = new HashSet<String>(); final Set<String> classTypeVarNamesSet = new HashSet<String>(); //names of the type class type variables. classTypeVarNamesSet.add(classTypeVarName); calculateFreeVariablesInDeclaredType(typeNode, typeVarNamesSet, recordVarNamesSet, classTypeVarNamesSet); //the classTypeVarNamesSet must contain classTypeVarName. //This is because otherwise the class method could never be used without an ambiguous overloading //type error. if (!typeVarNamesSet.contains(classTypeVarName)) { compiler.logMessage(new CompilerMessage(classMethodNode, new MessageKind.Error.ClassMethodMustUseClassTypeVariable(classMethodName, classTypeVarName))); } final Map<String, TypeVar> typeVarNameToTypeMap = new HashMap<String, TypeVar>(); typeVarNameToTypeMap.put(classTypeVarName, classTypeVarType); final Map<String, RecordVar> recordVarNameToRecordVarMap = new HashMap<String, RecordVar>(); calculateDeclaredTypeContext(contextListNode, typeVarNameToTypeMap, typeVarNamesSet, recordVarNameToRecordVarMap, recordVarNamesSet, classTypeVarNamesSet); final TypeExpr typeExpr = calculateDeclaredTypeExpr(typeNode, typeVarNameToTypeMap, recordVarNameToRecordVarMap); //kind check the type. final Map<TypeVar, KindExpr> typeVarToKindExprMap = getInitialTypeVarToKindMap(typeVarNameToTypeMap); typeVarToKindExprMap.put(classTypeVarType, classTypeVarKindExpr); try { dataDeclarationChecker.kindCheckTypeExpr(typeVarToKindExprMap, typeExpr); } catch (TypeException typeException) { // TypeChecker: Kind error in the type declaration for the class method {0}. compiler.logMessage( new CompilerMessage( classMethodNode, new MessageKind.Error.KindErrorInTypeDeclarationForClassMethod(classMethodName), typeException)); } return typeExpr; } /** * Finds the names of all the type and record variables occurring in a type declaration (without the context). * * <p> * For example, in [a] -> (b, a), it would return typeVariables = {a, b}, recordVariables = {}. * In ({r | field1 :: [a] -> [a]}, {s | }) it would return typeVariables = {a}, recordVariables = {r, s}. * * <p> * This method also does some first pass static analysis of type declarations: * <ol> * <li> verify that field names in a record are all distinct * <li> verify that no record variable is used as a type variable and vice-versa * <li> verify that none of the record variables occurs in typeClassTypeVariables * </ol> * * <p> * The check that each of the typeClassTypeVariables actually does occur in typeVarNamesSet is done by the caller * of this method to provide a better context. * * <p> * Creation date: (3/27/01 2:07:32 PM) * @param typeNode * @param typeVarNamesSet (Set of Strings) empty when called, and populated by this method * @param recordVarNamesSet (Set of Strings) empty when called, and populated by this method * @param classTypeVarNamesSet (Set of Strings) Empty except in the case of class methods. These are the additional * type variables scoped over this type arising from the class declaration. Note that the set has one element for * a single parameter type class. */ private void calculateFreeVariablesInDeclaredType(final ParseTreeNode typeNode, final Set<String> typeVarNamesSet, final Set<String> recordVarNamesSet, final Set<String> classTypeVarNamesSet) { switch (typeNode.getType()) { case CALTreeParserTokenTypes.FUNCTION_TYPE_CONSTRUCTOR : case CALTreeParserTokenTypes.TUPLE_TYPE_CONSTRUCTOR : case CALTreeParserTokenTypes.LIST_TYPE_CONSTRUCTOR : case CALTreeParserTokenTypes.TYPE_APPLICATION : { for (final ParseTreeNode childNode : typeNode) { calculateFreeVariablesInDeclaredType(childNode, typeVarNamesSet, recordVarNamesSet, classTypeVarNamesSet); } return; } case CALTreeParserTokenTypes.QUALIFIED_CONS : return; case CALTreeParserTokenTypes.VAR_ID : { final String typeVarName = typeNode.getText(); typeVarNamesSet.add(typeVarName); if (recordVarNamesSet.contains(typeVarName)) { // The type variable {typeVarName} is already used as a record variable. compiler.logMessage(new CompilerMessage(typeNode, new MessageKind.Error.TypeVariableAlreadyUsedAsRecordVariable(typeVarName))); } return; } case CALTreeParserTokenTypes.RECORD_TYPE_CONSTRUCTOR: { final ParseTreeNode recordVarNode = typeNode.firstChild(); recordVarNode.verifyType(CALTreeParserTokenTypes.RECORD_VAR); final ParseTreeNode recordVarNameNode = recordVarNode.firstChild(); if (recordVarNameNode != null) { recordVarNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String recordVarName = recordVarNameNode.getText(); recordVarNamesSet.add(recordVarName); if (typeVarNamesSet.contains(recordVarName) || classTypeVarNamesSet.contains(recordVarName)) { // The record variable {recordVarName} is already used as a type variable. compiler.logMessage(new CompilerMessage(recordVarNameNode, new MessageKind.Error.RecordVariableAlreadyUsedAsTypeVariable(recordVarName))); } } final ParseTreeNode fieldTypeAssignmentListNode = typeNode.getChild(1); fieldTypeAssignmentListNode.verifyType(CALTreeParserTokenTypes.FIELD_TYPE_ASSIGNMENT_LIST); final Set<FieldName> fieldNamesSet = new HashSet<FieldName>(); for (final ParseTreeNode fieldTypeAssignmentNode : fieldTypeAssignmentListNode) { fieldTypeAssignmentNode.verifyType(CALTreeParserTokenTypes.FIELD_TYPE_ASSIGNMENT); final ParseTreeNode fieldNameNode = fieldTypeAssignmentNode.firstChild(); final FieldName fieldName = getFieldName(fieldNameNode); if (!fieldNamesSet.add(fieldName)) { // repeated occurrence of field name {fieldName} in record type. compiler.logMessage(new CompilerMessage(fieldTypeAssignmentNode, new MessageKind.Error.RepeatedOccurrenceOfFieldNameInRecordType(fieldName.toString()))); } final ParseTreeNode assignedTypeNode = fieldNameNode.nextSibling(); calculateFreeVariablesInDeclaredType(assignedTypeNode, typeVarNamesSet, recordVarNamesSet, classTypeVarNamesSet); } return; } default : { typeNode.unexpectedParseTreeNode(); return; } } } /** * Given a type signature, return the type expression that it represents. * @param typeSignatureNode the first ParseTreeNode in a parsed type signature. * Its type should be a context list (and its sibling a declaration node). * @param workingModule the module in which the type exists. * @return TypeExpr the type expression it represents, or null if invalid. */ private TypeExpr getTypeFromSignature(final ParseTreeNode typeSignatureNode, final ModuleName workingModule) { final CompilerMessageLogger oldLogger = compiler.getMessageLogger(); compiler.setCompilerMessageLogger(null); changeModule(workingModule); compiler.setCompilerMessageLogger(oldLogger); return calculateTypeFromSignature(typeSignatureNode, null); } /** * Get a TypeExpr from its string representation. * Note: any compiler messages held by the compiler will be lost. * * @param typeString the string representation. * @param workingModule the module in which the type exists. * @return TypeExpr a TypeExpr representation of the string, or null if the string does not represent a valid type. */ TypeExpr getTypeFromString(final String typeString, final ModuleName workingModule) { // Temporarily replace the logger. final CompilerMessageLogger oldLogger = compiler.getMessageLogger(); final CompilerMessageLogger checkLogger = new MessageLogger(true); compiler.setCompilerMessageLogger(checkLogger); final java.io.Reader stringReader = new java.io.StringReader(typeString); final CALParser parser = freshParser(compiler, stringReader); final CALTreeParser treeParser = new CALTreeParser(compiler); // Parse and type check and catch any fatal errors try { try { // Call the parser to parse a type declaration parser.startTypeSignature(); // Walk the parse tree as a sanity check on the generated AST and of the tree parser final ParseTreeNode typeSignatureNode = (ParseTreeNode)parser.getAST(); treeParser.startTypeSignature(typeSignatureNode); final TypeExpr typeExpr = getTypeFromSignature(typeSignatureNode, workingModule); //deep prune so that instantiated type variables are not part of the returned TypeExpr. //this has the effect of chosing a deterministic element in the equivalence class of //representations of this TypeExpr. return typeExpr.deepPrune(); } catch (antlr.RecognitionException e) { // syntax error final SourceRange sourceRange = CALParser.makeSourceRangeFromException(e); compiler.logMessage(new CompilerMessage(sourceRange, new MessageKind.Error.SyntaxError(), e)); } catch (antlr.TokenStreamException e) { // Bad token stream compiler.logMessage(new CompilerMessage(new MessageKind.Error.BadTokenStream(), e)); } catch (Exception e) { final int errorCount = compiler.getMessageLogger().getNErrors(); if (errorCount > 0 || e instanceof UnableToResolveForeignEntityException) { // If the exception is an UnableToResolveForeignEntityException, there is // a CompilerMessage inside that we should be logging. if (e instanceof UnableToResolveForeignEntityException) { try { compiler.logMessage(((UnableToResolveForeignEntityException)e).getCompilerMessage()); } catch (AbortCompilation ace) { //logMessage can throw a AbortCompilation if a FATAL message was sent. } } //if an error occurred previously, we continue to compile the program to try to report additional //meaningful compilation errors. However, this can produce spurious exceptions related to the fact //that the program state does not satisfy preconditions because of the initial error(s). We don't //report the spurious exception as an internal coding error. compiler.logMessage(new CompilerMessage(new MessageKind.Info.UnableToRecover())); } else { compiler.logMessage(new CompilerMessage(new MessageKind.Fatal.InternalCodingError(), e)); } } } catch (AbortCompilation e) { // Compilation aborted. } finally { // replace the old logger. compiler.setCompilerMessageLogger(oldLogger); oldLogger.logMessages(checkLogger); } //the failure case- be sure to return null return null; } /** * Type checks an adjunct. * * @param outerDefnListNode an outer defn list node containining the root parse tree node of the function adjunct * @param moduleName the name of the module in which the adjunct should be considered to be defined. */ void checkAdjunct(final ParseTreeNode outerDefnListNode, final ModuleName moduleName) throws UnableToResolveForeignEntityException { checkAdjunct(outerDefnListNode, moduleName, null); } /** * Type checks an adjunct and returns information about the specified function. * * @param outerDefnListNode an outer defn list node containining the root parse tree node of the function adjunct * @param moduleName the name of the module in which the adjunct should be considered to be defined. * @param targetName * @return information about the named target. */ private AdjunctInfo checkAdjunct(final ParseTreeNode outerDefnListNode, final ModuleName moduleName, final String targetName) throws UnableToResolveForeignEntityException { // Switch the environment to the target module. changeModule(moduleName); TypeExpr targetType = null; boolean explicitTargetType = false; outerDefnListNode.verifyType(CALTreeParserTokenTypes.OUTER_DEFN_LIST); final ParseTreeNode moduleDefnNode = new ParseTreeNode(CALTreeParserTokenTypes.MODULE_DEFN, "MODULE_DEFN"); final ParseTreeNode optionalCALDocNode = new ParseTreeNode(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT, "OPTIONAL_CALDOC_COMMENT"); final ParseTreeNode moduleNameNode = ModuleNameUtilities.makeParseTreeForModuleName(moduleName); final ParseTreeNode importListNode = new ParseTreeNode(CALTreeParserTokenTypes.IMPORT_DECLARATION_LIST, "IMPORT_DECLARATION_LIST"); final ParseTreeNode friendDeclarationListNode = new ParseTreeNode(CALTreeParserTokenTypes.FRIEND_DECLARATION_LIST, "FRIEND_DECLARATION_LIST"); moduleDefnNode.addChildren(new ParseTreeNode[]{optionalCALDocNode, moduleNameNode, importListNode, friendDeclarationListNode, outerDefnListNode}); //Check the top level declarations in the module ModuleLevelParseTrees moduleLevelParseTrees = new ModuleLevelParseTrees(moduleDefnNode, outerDefnListNode); //Checks that the top-level functions used in the program are defined only once, and that //each top-level function has at most one type declaration. checkNamesUsedForTopLevelFunctions(moduleLevelParseTrees); //calculate the type expressions determined by each top-level function type declaration calculateDeclaredTypes(moduleLevelParseTrees.getFunctionTypeDeclarationNodes()); // Check to see if the target SC has an explicitly declared type. if (targetName != null) { targetType = functionNameToDeclaredTypeMap.get(targetName); if (targetType != null) { explicitTargetType = true; } } //add the instances that are defined in this module to the currentModuleTypeInfo //the instance methods themselves need further checking latter. ClassInstanceChecker instanceChecker = new ClassInstanceChecker(compiler, currentModuleTypeInfo, typeClassChecker, dataDeclarationChecker); instanceChecker.checkClassInstanceDefinitions(moduleLevelParseTrees); //add the foreign functions in the current module to the environment calculateForeignFunctionTypes(moduleLevelParseTrees.getForeignFunctionDefnNodes()); //Reorder the top-level functions defined in the program so that dependees are checked before dependents. final Graph<String> g = performFunctionDependencyAnalysis(); //Used to augment the parse tree of a module with hidden overloading arguments, //and to add hidden dictionary functions. final OverloadingResolver overloadingResolver = new OverloadingResolver(compiler, currentModuleTypeInfo); //Type check the top-level functions. 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); if (targetType == null) { targetType = checkComponent(component, targetName); } else { checkComponent(component); } //now add dictionary arguments to the function defined in this declaration group, and fix //up applications within the function definitions. overloadingResolver.resolveOverloading(overloadingInfoList); //Free up memory associated with this potentially bulky object overloadingInfoList.clear(); } instanceChecker.checkTypesOfInstanceMethodResolvingFunctions(); //Do the lambda lifting final FreeVariableFinder finder = new FreeVariableFinder(compiler, topLevelFunctionNameSet, currentModuleTypeInfo); final LambdaLifter lifter = new LambdaLifter(compiler, finder, moduleName); lifter.lift(outerDefnListNode); //Add the hidden class method, dictionary and dictionary switching functions //necessary to resolve overloading. overloadingResolver.addOverloadingHelperFunctions(outerDefnListNode); return new AdjunctInfo(targetName, targetType == null ? null : targetType.deepPrune(), explicitTargetType); } AdjunctInfo checkAdjunct(final AdjunctSource adjunctSource, final ModuleName moduleName, final String targetName) { // Temporarily replace the logger. final CompilerMessageLogger oldLogger = compiler.getMessageLogger(); final CompilerMessageLogger checkLogger = new MessageLogger(true); // Internal compiler logger. compiler.setCompilerMessageLogger(checkLogger); // Parse and type check and catch any fatal errors try { try { final ParseTreeNode outerDefnNode; final CALTreeParser treeParser = new CALTreeParser(compiler); if (adjunctSource instanceof AdjunctSource.FromText) { final CALParser parser = freshParser(compiler, ((AdjunctSource.FromText)adjunctSource).getReader()); // Call the parser to parse an adjunct. parser.adjunct(); outerDefnNode = (ParseTreeNode)parser.getAST(); } else if (adjunctSource instanceof AdjunctSource.FromSourceModel) { outerDefnNode = ((AdjunctSource.FromSourceModel)adjunctSource).toParseTreeNode(); } else { throw new IllegalArgumentException( "CALTypeChecker.checkAdjunct - cannot handle adjunct source of type " + adjunctSource.getClass()); } // Walk the parse tree as a sanity check on the generated AST and of the tree parser treeParser.adjunct(outerDefnNode); outerDefnNode.verifyType(CALTreeParserTokenTypes.OUTER_DEFN_LIST); final AdjunctInfo ai = checkAdjunct (outerDefnNode, moduleName, targetName); return ai; } catch (antlr.RecognitionException e) { // syntax error final SourceRange sourceRange = CALParser.makeSourceRangeFromException(e); compiler.logMessage(new CompilerMessage(sourceRange, new MessageKind.Error.SyntaxError(), e)); } catch (antlr.TokenStreamException e) { // Bad token stream compiler.logMessage(new CompilerMessage(new MessageKind.Error.BadTokenStream(), e)); } } catch (AbortCompilation e) { // Compilation abort, we catch and continue } catch (Exception e) { // Major failure - internal coding error try { final int errorCount = compiler.getMessageLogger().getNErrors(); if (errorCount > 0 || e instanceof UnableToResolveForeignEntityException) { // If the exception is an UnableToResolveForeignEntityException, there is // a CompilerMessage inside that we should be logging. if (e instanceof UnableToResolveForeignEntityException) { try { compiler.logMessage(((UnableToResolveForeignEntityException)e).getCompilerMessage()); } catch (AbortCompilation ace) { //logMessage can throw a AbortCompilation if a FATAL message was sent. } } //if an error occurred previously, we continue to compile the program to try to report additional //meaningful compilation errors. However, this can produce spurious exceptions related to the fact //that the program state does not satisfy preconditions because of the initial error(s). We don't //report the spurious exception as an internal coding error. compiler.logMessage(new CompilerMessage(new MessageKind.Info.UnableToRecover())); } else { compiler.logMessage(new CompilerMessage(new MessageKind.Fatal.InternalCodingError(), e)); } } catch (AbortCompilation ace) { // Yeah, yeah, we know } } finally { // replace the old logger. compiler.setCompilerMessageLogger(oldLogger); try { oldLogger.logMessages(checkLogger); } catch (AbortCompilation e) { // Too many errors for the ol' logger. } } //the failure case- be sure to return null return null; } /** * Type check a strongly connected component representing a group of functions. * Creation date: (4/17/01 10:05:49 AM) * @param component */ private void checkComponent(final Graph<String>.Component component) { checkComponent (component, null); } /** * Type check a strongly connected component representing a group of functions and * return the type of the specified function. * Creation date: (4/17/01 10:05:49 AM) * @param component * @param functionOfInterest * @return the type of the specified function */ private TypeExpr checkComponent(final Graph<String>.Component component, final String functionOfInterest) { //Previously components of size 1 that did depend upon themselves were treated //analogously to let declarations in the Core language, and all other cases were //treated analogously to letrecs. It is no longer necessary to do this, and we can //just treat the letrec case. TypeExpr returnVal = null; NonGenericVars nonGenericVars = null; // Add all the function names in this component to the environment and // nonGenericVars while typing the component. They are removed from the nonGenericVars // when typing the next component. final int componentSize = component.size(); final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); for (int i = 0; i < componentSize; ++i) { final String functionName = component.getVertex(i).getName(); final TypeVar newTypeVar = new TypeVar(); final ParseTreeNode functionNode = functionNameToDefinitionNodeMap.get(functionName); final ParseTreeNode optionalCALDocNode = functionNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); final Scope scope = getScopeModifier(accessModifierNode); final List<String> namedArgumentsList = new ArrayList<String>(); final ParseTreeNode paramListNode = accessModifierNode.nextSibling().nextSibling(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); for (final ParseTreeNode varNode : paramListNode) { varNode.verifyType(CALTreeParserTokenTypes.LAZY_PARAM, CALTreeParserTokenTypes.STRICT_PARAM); final String varName = FreeVariableFinder.getDisplayName(varNode.getText()); namedArgumentsList.add(varName); } final String[] namedArguments = namedArgumentsList.toArray(new String[0]); final Function function = new Function(QualifiedName.make(currentModuleName, functionName), scope, namedArguments, newTypeVar, FunctionalAgent.Form.FUNCTION, nestingLevel); functionEnv = Env.extend(functionEnv, function); nonGenericVars = NonGenericVars.extend(nonGenericVars, newTypeVar); functionBoundNamesToTypeMap.put(functionName, newTypeVar); overloadingInfoList.add(new OverloadingInfo(function, functionNode, newTypeVar, null)); } // Type check the component. // We store a list of the type expressions that are obtained for each top level function // in the first phase, to be checked against the corresponding type declaration in the second phase. // // We need to do the checking in two phases because when the inferred type and its corresponding declared type // are unified in the first phase, the inferred type may not be the ultimate specific type of the function (as it // may be further restricted by the types of subsequent functions in the component) and thus not all type errors are // detectable then. The second phase, with its second loop, is able to handle the situation as the inferred types should all // have been restricted to their final form. // // An example of such a component is: // // gt :: (Prelude.Num a) => a -> a -> Boolean; // gt x y = // let // foo = gt3 0 1; // in // x > y; // // gt2 :: Int -> Int -> Boolean; // gt2 = gt; // // gt3 = gt2; // // Here, the local definition of foo in gt constrains gt to have a type of Int -> Int -> Boolean (since it uses gt3 // which is aliased to gt2, which in turn is aliased to gt, but constraining both arguments to type Int). Thus the // type declaration for gt is too general, but cannot be detected in the first phase as gt2 has not been type checked // (and more importantly has had its inferred type unified with its type declaration). // final List<TypeExpr> typeExprs = new ArrayList<TypeExpr>(); if (DEBUG_INFO) { System.out.println("Checking component -- Phase 1 - type inference:"); } for (int i = 0; i < componentSize; ++i) { // lookup parsetree corresponding to the function with name functionName final String functionName = component.getVertex(i).getName(); final ParseTreeNode functionNode = functionNameToDefinitionNodeMap.get(functionName); currentOverloadingInfo = overloadingInfoList.get(i); final TypeExpr typeExpr = analyzeTopLevelFunction(functionEnv, nonGenericVars, functionNode); // Even though we are not doing the checking of the type declarations in this phase, we still need // to unified the inferred type with the type declaration, since it may constrain the function to a // more specific type than the one inferred. patternMatch(functionName, typeExpr); if (DEBUG_INFO) { //this is a copy of the inferred type expr for debugging purposes only final TypeExpr inferredTypeExpr = DEBUG_INFO ? CopyEnv.freshType(typeExpr, null) : null; dumpTypingInfo(functionName, typeExpr, inferredTypeExpr); } // Add the type expression as one to be checked in the second phase. typeExprs.add(typeExpr); } if (DEBUG_INFO) { System.out.println("Checking component -- Phase 2 - checking type declarations:"); } for (int i = 0; i < componentSize; ++i) { // lookup parsetree corresponding to the function with name functionName final String functionName = component.getVertex(i).getName(); final TypeExpr typeExpr = typeExprs.get(i); //this is a copy of the inferred type expr for debugging purposes only final TypeExpr inferredTypeExpr = DEBUG_INFO ? CopyEnv.freshType(typeExpr, null) : null; // See that the inferred type is compatible with the declared type, if a type declaration is provided. if (componentSize > 1) { // optimization: we do not need to do the second pattern match when the componentSize is 1 // because the type unification in the first phase is sufficient for checking the declared type // against the (final) inferred type. patternMatch(functionName, typeExpr); } if (DEBUG_INFO) { dumpTypingInfo(functionName, typeExpr, inferredTypeExpr); } if (functionOfInterest != null && functionName.equals(functionOfInterest)) { returnVal = typeExpr; } inlineErrorCallsWithSourcePosition(currentModuleName, functionName); } //the types of the functions in this component (declaration group) are now free to be generalized. functionEnv.finishedTypeChecking(componentSize); for (int i = 0; i < componentSize; ++i) { final OverloadingInfo oi = overloadingInfoList.get(i); oi.finishedTypeCheckingFunction(null); } return returnVal; } /** * For each application in the errorCallList * * 1. If the call is an error call, set the ErrorInfo on the error call node. * 2. If the call is an assert call, inline the assert body and set the ErrorInfo on the error call node. * 3. If the call is an undefined call, inline the undefined body and set the ErrorInfo on the error call node. * * @param moduleName The name of the current module. * @param topLevelFunctionName The name of the function being processed. */ void inlineErrorCallsWithSourcePosition(final ModuleName moduleName, final String topLevelFunctionName){ for(final ParseTreeNode errorCallNode : errorCallList){ switch(errorCallNode.getType()){ case CALTreeParserTokenTypes.QUALIFIED_VAR: case CALTreeParserTokenTypes.SELECT_DATA_CONSTRUCTOR_FIELD: case CALTreeParserTokenTypes.VIRTUAL_TUPLE_CASE: case CALTreeParserTokenTypes.VIRTUAL_RECORD_CASE: case CALTreeParserTokenTypes.VIRTUAL_DATA_CONSTRUCTOR_CASE: { final SourcePosition sourcePosition; if (errorCallNode.getType() == CALTreeParserTokenTypes.QUALIFIED_VAR){ final ParseTreeNode nameNode = errorCallNode.getChild(1); sourcePosition = nameNode.getSourcePosition(); } else{ sourcePosition = errorCallNode.getSourcePosition(); } final ErrorInfo errorInfo; if (sourcePosition == null){ errorInfo = new ErrorInfo(QualifiedName.make(moduleName, topLevelFunctionName), 0, 0); } else{ errorInfo = new ErrorInfo(QualifiedName.make(moduleName, topLevelFunctionName), sourcePosition.getLine(), sourcePosition.getColumn()); } errorCallNode.setErrorInfoForErrorCall(errorInfo); } break; default: { final ParseTreeNode firstChildNode = errorCallNode.getChild(0); final ParseTreeNode moduleNode = firstChildNode.getChild(0); final ParseTreeNode nameNode = firstChildNode.getChild(1); final SourcePosition sourcePosition = nameNode.getSourcePosition(); final ErrorInfo errorInfo; if (sourcePosition == null){ errorInfo = new ErrorInfo(QualifiedName.make(moduleName, topLevelFunctionName), 0, 0); } else{ errorInfo = new ErrorInfo(QualifiedName.make(moduleName, topLevelFunctionName), sourcePosition.getLine(), sourcePosition.getColumn()); } // source position can be null for internally generated functions such // as those arrising from the use of the deriving clause if (ModuleNameUtilities.getModuleNameFromParseTree(moduleNode).equals(CAL_Prelude.MODULE_NAME)){ if (nameNode.getText().equals(CAL_Prelude.Functions.assert_.getUnqualifiedName()) && errorCallNode.getNumberOfChildren() == 2){ addSourcePositionTo_assert(errorCallNode, errorInfo); } else if (nameNode.getText().equals(CAL_Prelude.Functions.undefined.getUnqualifiedName())){ addSourcePositionTo_undefined(errorCallNode, errorInfo); } } } break; } } errorCallList.clear(); } /** * Change "assert <arg>" calls to "<arg> || error "Assert failed."" calls and set the error info on the call node. * * @param next The tree containing the assert call at the root. * @param errorInfo The error info to embed in the call node. * */ private static void addSourcePositionTo_assert(final ParseTreeNode next, final ErrorInfo errorInfo){ // assert b ==> f = b || error "Assert failed." // assert b ==> b || ... next.setText("@operator"); next.setType(CALTokenTypes.APPLICATION); final ParseTreeNode orNode = ParseTreeNode.makeQualifiedVarNode(CAL_Prelude.MODULE_NAME, CAL_Prelude.Functions.or.getUnqualifiedName(), null); // Create the function call node, Prelude.error2 final ParseTreeNode qvNode = ParseTreeNode.makeQualifiedVarNode(CAL_Prelude.MODULE_NAME, CAL_Prelude.Functions.error.getUnqualifiedName(), null); qvNode.setErrorInfoForErrorCall(errorInfo); // Create the literal with the error message final String encodedErrorMessage = StringEncoder.encodeString( "Assert failed." ); final ParseTreeNode errorMessageNode = new ParseTreeNode(CALTokenTypes.STRING_LITERAL, encodedErrorMessage, null); // Create the application of error2, @ Prelude.error2 <sourcePosition> <assertArg> final ParseTreeNode errorNode = new ParseTreeNode(CALTokenTypes.APPLICATION, "@", null); errorNode.setFirstChild(qvNode); qvNode.setNextSibling(errorMessageNode); // now add the second argument error <sourcePosition> <message> to the || final ParseTreeNode booleanExpression = next.getChild(1); booleanExpression.setNextSibling(errorNode); orNode.setNextSibling(booleanExpression); next.setFirstChild(orNode); } /** * Change "undefined" calls to "error "Prelude.undefined called."" and set the error information * on the call node. * * @param errorCallNode The tree containing the undefined call at the root. * @param errorInfo The error information to embed in the call node. * */ private static void addSourcePositionTo_undefined(final ParseTreeNode errorCallNode, final ErrorInfo errorInfo){ final ParseTreeNode firstChild = errorCallNode.firstChild(); firstChild.setErrorInfoForErrorCall(errorInfo); // Change the undefined call to an error2 call firstChild.getChild(1).setText(CAL_Prelude.Functions.error.getUnqualifiedName()); // Create the literal with the error message final String encodedErrorMessage = StringEncoder.encodeString( CAL_Prelude.Functions.undefined.getQualifiedName() + " called." ); final ParseTreeNode errorMessageNode = new ParseTreeNode(CALTokenTypes.STRING_LITERAL, encodedErrorMessage, null); // preserve the following arguments final ParseTreeNode secondChild = firstChild.nextSibling(); if (secondChild != null){ errorMessageNode.setNextSibling(secondChild); } // add the source position as the first arguement firstChild.setNextSibling(errorMessageNode); } /** * Get the types of all free gem parts in a graph - free inputs, free outputs, and trees. * * <p>This generates a let expression which generates all free input and output types simultaneously when evaluated. * Evaluation produces a type expression with arguments to the expression function yielding the types of the roots * and free arguments in the graph. * * @param rootNodes the set of rootNodes in the graph to evaluate * @param moduleName the name of the module in which the graph exists * @return A pair of two maps: * (CompositionNode.CompositionArg -> TypeExpr) Map from argument to its type. * (CompositionNode -> List (of TypeExpr)) map from rootNode to the derived types for its definition.. * The types in the List are: argument types first (in order), then output. * @throws TypeException if the graph fails type checking (eg. the connections are invalid). */ Pair<Map<CompositionNode.CompositionArgument, TypeExpr>, Map<CompositionNode, List<TypeExpr>>> checkGraph(final Set<? extends CompositionNode> rootNodes, final ModuleName moduleName) throws TypeException { // argument -> it type final Map<CompositionNode.CompositionArgument, TypeExpr> argMap = new HashMap<CompositionNode.CompositionArgument, TypeExpr>(); // rootNode -> its type final Map<CompositionNode, List<TypeExpr>> rootNodeMap = new HashMap<CompositionNode, List<TypeExpr>>(); // check for presence of any nodes if (rootNodes == null || rootNodes.isEmpty()) { return new Pair<Map<CompositionNode.CompositionArgument, TypeExpr>, Map<CompositionNode, List<TypeExpr>>>(argMap, rootNodeMap); } final CheckGraphSource checkGraphSource = CALSourceGenerator.getCheckGraphSource(rootNodes); final SourceModel.FunctionDefn functionSource = checkGraphSource.getGraphFunction(); final Map<CompositionNode, List<CompositionNode.CompositionArgument>> rootToArgumentsMap = checkGraphSource.getRootToArgumentsMap(); final List<CompositionNode.CompositionArgument> unusedArgumentList = checkGraphSource.getUnusedArgumentList(); final Map<CompositionNode.CompositionArgument, CompositionNode.CompositionArgument> recursiveEmitterArgumentToReflectedInputMap = checkGraphSource.getRecursiveEmitterArgumentToReflectedInputMap(); // get the type for the graph, break it up // Where there are n+1 roots (typed xi) and m+1 unused vars (typed yi) this returns the type // x0 -> x1 -> ... -> xn -> y0 -> y1 -> ... -> ym -> Double final TypeExpr functionType = checkFunction(new AdjunctSource.FromSourceModel(functionSource), moduleName); if (functionType == null) { String messageString = "Attempt to check the type of an invalid (broken) tree. Generated text:\n" + functionSource.toSourceText(); CALCompiler.COMPILER_LOGGER.log(Level.FINE, messageString); throw new TypeException(messageString); } final TypeExpr[] graphTypePieces = functionType.getTypePieces(); // Iterate over the roots.. int graphTypeIndex = 0; for (final Map.Entry<CompositionNode, List<CompositionNode.CompositionArgument>> entry : rootToArgumentsMap.entrySet()) { final CompositionNode rootNode = entry.getKey(); final List<CompositionNode.CompositionArgument> rootNodeArguments = entry.getValue(); // Get the root type. final TypeExpr rootTypes = graphTypePieces[graphTypeIndex]; // Break it up into argument and output types. final TypeExpr[] rootTypePieces = rootTypes.getTypePieces(rootNodeArguments.size()); // Populate the map with the argument type mappings. int rootTypeIndex = 0; for (final CompositionNode.CompositionArgument rootNodeArgument : rootNodeArguments) { argMap.put(rootNodeArgument, rootTypePieces[rootTypeIndex]); rootTypeIndex++; } // Add the mapping for root node types. rootNodeMap.put(rootNode, Arrays.asList(rootTypePieces)); graphTypeIndex++; } // Iterate over the unused arguments. for (final CompositionNode.CompositionArgument unusedArgument : unusedArgumentList) { // Get the argument type. final TypeExpr argumentType = graphTypePieces[graphTypeIndex]; // Add the mapping argMap.put(unusedArgument, argumentType); graphTypeIndex++; } // Iterate over the recursive emitter arguments. for (final Map.Entry<CompositionNode.CompositionArgument, CompositionNode.CompositionArgument> entry : recursiveEmitterArgumentToReflectedInputMap.entrySet()) { final CompositionNode.CompositionArgument recursiveEmitterArgument = entry.getKey(); final CompositionNode.CompositionArgument reflectedArgument = entry.getValue(); argMap.put(recursiveEmitterArgument, argMap.get(reflectedArgument)); } return new Pair<Map<CompositionNode.CompositionArgument, TypeExpr>, Map<CompositionNode, List<TypeExpr>>>(argMap, rootNodeMap); } /** * Type check a single module according to the Hindley-Milner typing system with extensions for * dependency analysis transformations. * * @param moduleDefnNode root ParseTreeNode for the module * @param foreignClassLoader the classloader to use to resolve foreign classes for the module. */ void checkModule(final ParseTreeNode moduleDefnNode, final ClassLoader foreignClassLoader) throws UnableToResolveForeignEntityException { moduleDefnNode.verifyType(CALTreeParserTokenTypes.MODULE_DEFN); clearState(); final ParseTreeNode optionalCALDocNode = moduleDefnNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode moduleNameNode = optionalCALDocNode.nextSibling(); final ModuleName currentModuleName = ModuleNameUtilities.getModuleNameFromParseTree(moduleNameNode); if (DEBUG_INFO) { System.out.println("-----------------------------------------------------"); System.out.println("Compiling module " + currentModuleName); } //Inform the packager that we will be adding entities for a new module final Packager packager = compiler.getPackager(); try { packager.newModule(currentModuleName, foreignClassLoader); } catch (Packager.PackagerException e) { // TypeChecker.checkModule: could not create a module {currentModuleName} in the package. compiler.logMessage(new CompilerMessage(new SourceRange(currentModuleName), new MessageKind.Fatal.CouldNotCreateModuleInPackage(currentModuleName))); } //Add references to the imported modules' type info to the current module's type info final ParseTreeNode importDeclarationListNode = moduleNameNode.nextSibling(); checkModuleImports (currentModuleName, importDeclarationListNode); final ParseTreeNode friendDeclarationListNode = importDeclarationListNode.nextSibling(); checkFriendDeclarations (friendDeclarationListNode); //Check the top level declarations in the module final ParseTreeNode outerDefnListNode = friendDeclarationListNode.nextSibling(); outerDefnListNode.verifyType(CALTreeParserTokenTypes.OUTER_DEFN_LIST); ModuleLevelParseTrees moduleLevelParseTrees = new ModuleLevelParseTrees(moduleDefnNode, outerDefnListNode); //Checks that the functions used in the program are defined only once, and that //each function has at most one type declaration. checkNamesUsedForTopLevelFunctions(moduleLevelParseTrees); //do the initial check of the type classes defined in the module. This does not check class methods yet. typeClassChecker = new TypeClassChecker(currentModuleTypeInfo, compiler); final List<ParseTreeNode> typeClassDefnNodes = moduleLevelParseTrees.getTypeClassDefnNodes(); typeClassChecker.checkTypeClassDefinitions(typeClassDefnNodes); //Determines the types of all data constructors, both built-in and those introduced //via data declarations. //todoBI should we really hold this state? dataDeclarationChecker = new DataDeclarationChecker(currentModuleTypeInfo, compiler, typeClassChecker); dataConstructorEnv = dataDeclarationChecker.checkDataDeclarations(moduleLevelParseTrees); //Store the type info for the data constructors defined in the current module if (dataConstructorEnv != null) { dataConstructorEnv.wrap(currentModuleTypeInfo); } //check class methods. We need to have checked data declarations prior to this to be able to verify //that the types of the class methods are valid. typeClassChecker.checkClassMethods(typeClassDefnNodes); //add the class methods to the environment //todoBI it is an abuse of terminology to have class methods added to the functionEnv. for (int i = 0, nTypeClasses = currentModuleTypeInfo.getNTypeClasses(); i < nTypeClasses; ++i) { final TypeClass typeClass = currentModuleTypeInfo.getNthTypeClass(i); for (int j = 0, nClassMethods = typeClass.getNClassMethods(); j < nClassMethods; ++j) { ClassMethod classMethod = typeClass.getNthClassMethod(j); functionEnv = Env.extend (functionEnv, classMethod); } } calculatePrimitiveFunctionTypes(moduleLevelParseTrees.getPrimitiveFunctionDeclarationNodes()); //the built-in primitive functions and class methods have been type checked fully at this point if (functionEnv != null) { functionEnv.finishedTypeChecking(); } //add the instances that are defined in this module to the currentModuleTypeInfo //the instance methods themselves need further checking latter. final ClassInstanceChecker instanceChecker = new ClassInstanceChecker(compiler, currentModuleTypeInfo, typeClassChecker, dataDeclarationChecker); instanceChecker.checkClassInstanceDefinitions(moduleLevelParseTrees); //calculate the type expressions determined by each top-level function type declaration calculateDeclaredTypes(moduleLevelParseTrees.getFunctionTypeDeclarationNodes()); //add the foreign functions in the current module to the environment calculateForeignFunctionTypes(moduleLevelParseTrees.getForeignFunctionDefnNodes()); //Reorder the top-level functions defined in the program so that dependees are checked before dependents. final Graph<String> g = performFunctionDependencyAnalysis(); //Used to augment the parse tree of a module with hidden overloading arguments, //and to add hidden dictionary functions. final OverloadingResolver overloadingResolver = new OverloadingResolver(compiler, currentModuleTypeInfo); //Type check the functions. 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); checkComponent(component); //now add dictionary arguments to the function defined in this declaration group, and fix //up applications within the function definitions. overloadingResolver.resolveOverloading(overloadingInfoList); //Free up memory associated with this potentially bulky object overloadingInfoList.clear(); } //warn for overloaded polymorphic let variables. These are actually *functions* in the runtime //even though they look like constants, and may have efficiency implications. if (WARNING_FOR_OVERLOADED_POLYMORPHIC_LET_VARS) { for (final String functionName : letVarNameToParseTreeMap.keySet()) { final ParseTreeNode functionNode = letVarNameToParseTreeMap.get(functionName); final TypeExpr functionType = functionBoundNamesToTypeMap.get(functionName); final int firstDollar = functionName.indexOf('$'); final String enclosingTopLevelFunctionName = functionName.substring(0, firstDollar); compiler.logMessage(new CompilerMessage(functionNode, new MessageKind.Info.LocalOverloadedPolymorphicConstant(FreeVariableFinder.getDisplayName(functionName), enclosingTopLevelFunctionName, functionType.toString()))); } } //Store the type info for the functions defined in the current module. if (functionEnv != null) { functionEnv.wrap(currentModuleTypeInfo); } // Store the type info for all local functions in the current module storeLocalFunctionTypes(currentModuleTypeInfo); typeClassChecker.checkDefaultClassMethodTypes(typeClassDefnNodes); instanceChecker.checkTypesOfInstanceMethodResolvingFunctions(); //Check the CALDoc comments final FreeVariableFinder finderForCALDocChecker = new FreeVariableFinder(compiler, topLevelFunctionNameSet, currentModuleTypeInfo); final CALDocChecker calDocChecker = new CALDocChecker(compiler, currentModuleTypeInfo, finderForCALDocChecker, this); calDocChecker.checkCALDocCommentsInModuleDefn(moduleDefnNode); //Do the lambda lifting final FreeVariableFinder finder = new FreeVariableFinder(compiler, topLevelFunctionNameSet, currentModuleTypeInfo); final LambdaLifter lifter = new LambdaLifter(compiler, finder, currentModuleName); lifter.lift(outerDefnListNode); //Add the hidden class method, dictionary and dictionary switching functions //necessary to resolve overloading. overloadingResolver.addOverloadingHelperFunctions(outerDefnListNode); instanceChecker.addDerivedInstanceFunctions(outerDefnListNode); } /** * Add type information for all the local functions in this module to their respective * toplevel functions' FunctionEntities in moduleTypeInfo. * @param moduleTypeInfo */ private void storeLocalFunctionTypes(final ModuleTypeInfo moduleTypeInfo) { final ModuleName currentModule = currentModuleTypeInfo.getModuleName(); final LocalFunctionIdentifierGenerator identifierGenerator = new LocalFunctionIdentifierGenerator(); // We want to process local functions in pre-order. ie, we want to process all the local functions // in a given let expression, then recursively process the first local function, the second, etc. // The disambiguation numbers generated by the unique-name transformation in FreeVariableFinder are // in pre-order as well, so sorting by top-level function name, local function name, disambiguation // index will give us the order we want. final SortedSet<String> localFunctionNames = new TreeSet<String>(new Comparator<String>() { /** {@inheritDoc} */ public int compare(String left, String right) { // Order first by name and second by disambiguation index. final int lastLeftDollar = left.lastIndexOf('$'); final int lastRightDollar = right.lastIndexOf('$'); if(lastLeftDollar == -1 || lastRightDollar == -1) { throw new IllegalStateException("unique-transformed names must contain at least 1 dollar sign"); } final int nameOrder = left.substring(0, lastLeftDollar).compareTo(right.substring(0, lastRightDollar)); if(nameOrder != 0) { return nameOrder; } final int leftId = Integer.parseInt(left.substring(lastLeftDollar + 1)); final int rightId = Integer.parseInt(right.substring(lastRightDollar + 1)); if(leftId > rightId) { return 1; } else if(rightId > leftId) { return -1; } else { return 0; } } }); localFunctionNames.addAll(localFunctionBoundNamesToNonGenericVarsMap.keySet()); for(final String uniqueName : localFunctionNames) { final NonGenericVars nonGenericVars = localFunctionBoundNamesToNonGenericVarsMap.get(uniqueName); final TypeExpr typeExpr = functionBoundNamesToTypeMap.get(uniqueName); // Don't record local functions in compiler-generated functions if(uniqueName.charAt(0) == '$') { continue; } boolean hasUninstantiatedNonGenerics = false; for(final TypeVar typeVar : typeExpr.getUninstantiatedTypeVars()) { if(!nonGenericVars.isGenericTypeVar(typeVar)) { hasUninstantiatedNonGenerics = true; break; } } for(final RecordVar recordVar : typeExpr.getUninstantiatedRecordVars()) { if(!nonGenericVars.isGenericRecordVar(recordVar)) { hasUninstantiatedNonGenerics = true; break; } } final String[] components = uniqueName.split("\\$"); final String toplevelFunctionName = components[0]; final String localFunctionName = components[1]; if(!toplevelFunctionName.equals(identifierGenerator.getCurrentFunction())) { identifierGenerator.reset(toplevelFunctionName); } final LocalFunctionIdentifier identifier = identifierGenerator.generateLocalFunctionIdentifier(currentModule, localFunctionName); localFunctionBoundNamesToLocalFunctionIdentifiersMap.put(uniqueName, identifier); final Function localFunction = localFunctionBoundNamesToFunctionEntityMap.get(uniqueName); localFunction.setLocalFunctionIdentifier(identifier); localFunction.setTypeContainsUninstantiatedNonGenerics(hasUninstantiatedNonGenerics); final Function toplevelFunction = moduleTypeInfo.getFunction(toplevelFunctionName); toplevelFunction.addLocalFunction(localFunction); } } /** * The primary purpose of this function is to process the import using declarations * and add this information to the currentModuleTypeInfo in preparation for further * static analysis. * @param currentModuleName * @param importDeclarationListNode */ private void checkModuleImports(final ModuleName currentModuleName, final ParseTreeNode importDeclarationListNode) { //Add references to the imported modules' type info to the current module's type info importDeclarationListNode.verifyType(CALTreeParserTokenTypes.IMPORT_DECLARATION_LIST); final Packager packager = compiler.getPackager(); currentModuleTypeInfo = packager.getModuleTypeInfo(currentModuleName); //(String -> ModuleName) map from an external function or class method's name to the name of its defining module. //The function or class method must be visible within this module (i.e. it must be public, and the //module in which it is defined must be imported in the current module). //These are the external sc and class methods that can be used in an unqualified way within this module. final Map<String, ModuleName> usingFunctionOrClassMethodMap = new HashMap<String, ModuleName>(); final Map<String, ModuleName> usingDataConstructorMap = new HashMap<String, ModuleName>(); final Map<String, ModuleName> usingTypeConstructorMap = new HashMap<String, ModuleName>(); final Map<String, ModuleName> usingTypeClassMap = new HashMap<String, ModuleName>(); for (final ParseTreeNode importDeclarationNode : importDeclarationListNode) { importDeclarationNode.verifyType(CALTreeParserTokenTypes.LITERAL_import); final ParseTreeNode importedModuleNameNode = importDeclarationNode.firstChild(); final ModuleName importedModuleName = ModuleNameUtilities.getModuleNameFromParseTree(importedModuleNameNode); final ModuleTypeInfo importedModuleTypeInfo = packager.getModuleTypeInfo(importedModuleName); if (importedModuleTypeInfo == null) { MessageKind message = new MessageKind.Error.UnresolvedExternalModuleImportWithNoSuggestions(importedModuleName); compiler.logMessage(new CompilerMessage(importDeclarationNode, message)); } else { currentModuleTypeInfo.addImportedModule(importedModuleTypeInfo); } if (DEBUG_INFO) { System.out.println("Imports " + importedModuleName); } //do the static analysis on the import using declarations to determine what external //symbols can be used in an unqualified way within this module. final ParseTreeNode usingClauseNode = importedModuleNameNode.nextSibling(); if (usingClauseNode != null) { usingClauseNode.verifyType(CALTreeParserTokenTypes.LITERAL_using); for (final ParseTreeNode usingItemNode : usingClauseNode) { switch (usingItemNode.getType()) { case CALTreeParserTokenTypes.LITERAL_function: { for (final ParseTreeNode functionNameNode : usingItemNode) { functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String functionName = functionNameNode.getText(); final ModuleName otherImportedModuleName = usingFunctionOrClassMethodMap.put(functionName, importedModuleName); if (otherImportedModuleName != null) { //cannot have //import A using function = foo;; //import B using function = foo;; //since the use of foo would be ambiguous: A.foo or B.foo. compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.FunctionAlreadyUsedInImportUsingDeclaration(functionName, otherImportedModuleName))); //restore state to give error messages with respect to the first import using function usingFunctionOrClassMethodMap.put(functionName, otherImportedModuleName); } final FunctionalAgent importedFunction = importedModuleTypeInfo.getFunctionOrClassMethod(functionName); if (importedFunction == null) { //function doesn't exist in importedModule compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.FunctionNotDefinedInModule(functionName, importedModuleName))); } else if (!currentModuleTypeInfo.isEntityVisible(importedFunction)) { //function exists in importedModule, but it is not visible in this module final MessageKind message; if (importedFunction instanceof Function) { //"The function {0} is not visible in module {1}." message = new MessageKind.Error.FunctionNotVisible((Function)importedFunction, currentModuleName); } else { //"The class method {0} is not visible in module {1}." message = new MessageKind.Error.ClassMethodNotVisible((ClassMethod)importedFunction, currentModuleName); } compiler.logMessage(new CompilerMessage(functionNameNode, message)); } } break; } case CALTreeParserTokenTypes.LITERAL_dataConstructor: { for (final ParseTreeNode dataConstructorNameNode : usingItemNode) { dataConstructorNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); final String dataConstructorName = dataConstructorNameNode.getText(); final ModuleName otherImportedModuleName = usingDataConstructorMap.put(dataConstructorName, importedModuleName); if (otherImportedModuleName != null) { //cannot have //import A using dataConstructor = Foo;; //import B using dataConstructor = Foo;; //since the use of Foo would be ambiguous: A.Foo or B.Foo. compiler.logMessage(new CompilerMessage(dataConstructorNameNode, new MessageKind.Error.DataConstructorAlreadyUsedInImportUsingDeclaration(dataConstructorName, otherImportedModuleName))); //restore state to give error messages with respect to the first import using dataConstructor usingDataConstructorMap.put(dataConstructorName, otherImportedModuleName); } final DataConstructor importedDataConstructor = importedModuleTypeInfo.getDataConstructor(dataConstructorName); if (importedDataConstructor == null) { //dataConstructor doesn't exist in importedModule compiler.logMessage(new CompilerMessage(dataConstructorNameNode, new MessageKind.Error.DataConstructorNotDefinedInModule(dataConstructorName, importedModuleName))); } else if (!currentModuleTypeInfo.isEntityVisible(importedDataConstructor)) { //dataConstructor exists in importedModule, but it is not visible in this module //"The data constructor {0} is not visible in module {1}." compiler.logMessage(new CompilerMessage(dataConstructorNameNode, new MessageKind.Error.DataConstructorNotVisible(importedDataConstructor, currentModuleName))); } } break; } case CALTreeParserTokenTypes.LITERAL_typeConstructor: { for (final ParseTreeNode typeConstructorNameNode : usingItemNode) { typeConstructorNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); final String typeConstructorName = typeConstructorNameNode.getText(); final ModuleName otherImportedModuleName = usingTypeConstructorMap.put(typeConstructorName, importedModuleName); if (otherImportedModuleName != null) { //cannot have //import A using typeConstructor = Foo;; //import B using typeConstructor = Foo;; //since the use of Foo would be ambiguous: A.Foo or B.Foo. compiler.logMessage(new CompilerMessage(typeConstructorNameNode, new MessageKind.Error.TypeConstructorAlreadyUsedInImportUsingDeclaration(typeConstructorName, otherImportedModuleName))); //restore state to give error messages with respect to the first import using typeConstructor usingTypeConstructorMap.put(typeConstructorName, otherImportedModuleName); } final TypeConstructor importedTypeConstructor = importedModuleTypeInfo.getTypeConstructor(typeConstructorName); if (importedTypeConstructor == null) { //typeConstructor doesn't exist in importedModule compiler.logMessage(new CompilerMessage(typeConstructorNameNode, new MessageKind.Error.TypeConstructorNotDefinedInModule(typeConstructorName, importedModuleName))); } else if (!currentModuleTypeInfo.isEntityVisible(importedTypeConstructor)) { //typeConstructor exists in importedModule, but it is not visible in this module //"The type {0} is not visible in module {1}." compiler.logMessage(new CompilerMessage(typeConstructorNameNode, new MessageKind.Error.TypeConstructorNotVisible(importedTypeConstructor, currentModuleName))); } } break; } case CALTreeParserTokenTypes.LITERAL_typeClass: { for (final ParseTreeNode typeClassNameNode : usingItemNode) { typeClassNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID); final String typeClassName = typeClassNameNode.getText(); final ModuleName otherImportedModuleName = usingTypeClassMap.put(typeClassName, importedModuleName); if (otherImportedModuleName != null) { //cannot have //import A using typeClass = Foo;; //import B using typeClass = Foo;; //since the use of Foo would be ambiguous: A.Foo or B.Foo. compiler.logMessage(new CompilerMessage(typeClassNameNode, new MessageKind.Error.TypeClassAlreadyUsedInImportUsingDeclaration(typeClassName, otherImportedModuleName))); //restore state to give error messages with respect to the first import using typeClass usingTypeClassMap.put(typeClassName, otherImportedModuleName); } final TypeClass importedTypeClass = importedModuleTypeInfo.getTypeClass(typeClassName); if (importedTypeClass == null) { //typeClass doesn't exist in importedModule compiler.logMessage(new CompilerMessage(typeClassNameNode, new MessageKind.Error.TypeClassNotDefinedInModule(typeClassName, importedModuleName))); } else if (!currentModuleTypeInfo.isEntityVisible(importedTypeClass)) { //typeClass exists in importedModule, but it is not visible in this module //"The class {0} is not visible in module {1}." compiler.logMessage(new CompilerMessage(typeClassNameNode, new MessageKind.Error.TypeClassNotVisible(importedTypeClass, currentModuleName))); } } break; } default : { usingItemNode.unexpectedParseTreeNode(); break; } } } } } currentModuleTypeInfo.finishAddingImportedModules(); currentModuleTypeInfo.addUsingFunctionOrClassMethodMap(usingFunctionOrClassMethodMap); currentModuleTypeInfo.addUsingDataConstructorMap(usingDataConstructorMap); currentModuleTypeInfo.addUsingTypeConstructorMap(usingTypeConstructorMap); currentModuleTypeInfo.addUsingTypeClassMap(usingTypeClassMap); if (DEBUG_INFO) { System.out.println(); } } /** * Add friend modules to the currentModuleTypeInfo. * Do static analysis on the friend declarations to check basic correctness. * For example, cannot repeat a friend * @param friendDeclarationListNode */ private void checkFriendDeclarations(final ParseTreeNode friendDeclarationListNode) { friendDeclarationListNode.verifyType(CALTreeParserTokenTypes.FRIEND_DECLARATION_LIST); final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); for (final ParseTreeNode friendDeclarationNode : friendDeclarationListNode) { friendDeclarationNode.verifyType(CALTreeParserTokenTypes.LITERAL_friend); final ParseTreeNode friendNameNode = friendDeclarationNode.firstChild(); final ModuleName friendModuleName = ModuleNameUtilities.getModuleNameFromParseTree(friendNameNode); if (currentModuleName.equals(friendModuleName)) { //a module cannot be a friend to itself compiler.logMessage(new CompilerMessage(friendNameNode, new MessageKind.Error.ModuleCannotBeFriendOfItself(friendModuleName))); } else if (currentModuleTypeInfo.hasFriendModule(friendModuleName)) { //repeated friend declarations for the same friend module are not allowed. compiler.logMessage(new CompilerMessage(friendNameNode, new MessageKind.Error.RepeatedFriendModuleDeclaration(friendModuleName))); } else if (currentModuleTypeInfo.getDependeeModuleTypeInfo(friendModuleName) != null) { //a module cannot have a friend module that is a direct or indirect import. //For example, if module A imports module B, then module B above cannot refer to any symbols //of A since to import A would result in a cyclic import. Thus it can't refer to the protected symbols //so there is no point in making a friend declaration for B. //If we ever allow recursive module imports, then this condition can be changed. //For now, this is a helper so people put their friend declarations in the right spot. compiler.logMessage(new CompilerMessage(friendNameNode, new MessageKind.Error.ModuleCannotBeFriendOfImport(currentModuleName, friendModuleName))); } else { currentModuleTypeInfo.addFriendModule(friendModuleName); } } } /** * Checks that each function defined in the module is defined only once, and that * each function has at most one type declaration. * * @param moduleLevelParseTrees */ private void checkNamesUsedForTopLevelFunctions(final ModuleLevelParseTrees moduleLevelParseTrees) { final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); primitiveFunctionNamesSet = new HashSet<String>(); final List<ParseTreeNode> primitiveFunctionDeclarationNodes = moduleLevelParseTrees.getPrimitiveFunctionDeclarationNodes(); for (final ParseTreeNode primitiveFunctionNode : primitiveFunctionDeclarationNodes) { primitiveFunctionNode.verifyType(CALTreeParserTokenTypes.PRIMITIVE_FUNCTION_DECLARATION); final ParseTreeNode typeDeclarationNode = primitiveFunctionNode.getChild(2); typeDeclarationNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); final ParseTreeNode primitiveFunctionNameNode = typeDeclarationNode.firstChild(); primitiveFunctionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String primitiveFunctionName = primitiveFunctionNameNode.getText(); if (!PrimitiveInfo.isPrimitiveFunction(QualifiedName.make(currentModuleName, primitiveFunctionName))) { compiler.logMessage(new CompilerMessage(primitiveFunctionNode, new MessageKind.Error.InvalidPrimitiveFunction(primitiveFunctionName))); } if (!primitiveFunctionNamesSet.add(primitiveFunctionName)) { // the primitive function has already been declared. This is an attempted re-declaration. compiler.logMessage(new CompilerMessage(primitiveFunctionNameNode, new MessageKind.Error.AttemptToRedeclarePrimitiveFunction(primitiveFunctionName))); } } foreignFunctionNamesSet = new HashSet<String>(); final List<ParseTreeNode> foreignSCDefnNodes = moduleLevelParseTrees.getForeignFunctionDefnNodes(); for (final ParseTreeNode foreignSCDefnNode : foreignSCDefnNodes) { final ParseTreeNode typeDeclarationNode = foreignSCDefnNode.getChild(3); typeDeclarationNode.verifyType(CALTreeParserTokenTypes.TYPE_DECLARATION); final ParseTreeNode foreignSCNameNode = typeDeclarationNode.firstChild(); foreignSCNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String foreignSCName = foreignSCNameNode.getText(); if (primitiveFunctionNamesSet.contains(foreignSCName)) { // the foreign function is built-in. Cannot declare it as foreign. compiler.logMessage(new CompilerMessage(foreignSCNameNode, new MessageKind.Error.ForeignDeclarationForBuiltinPrimitiveFunction(foreignSCName))); } if (!foreignFunctionNamesSet.add(foreignSCName)) { // the foreign function has already been defined. This is an attempted redefinition compiler.logMessage(new CompilerMessage(foreignSCNameNode, new MessageKind.Error.AttemptToRedefineFunction(foreignSCName))); } final ModuleName usingModuleName = currentModuleTypeInfo.getModuleOfUsingFunctionOrClassMethod(foreignSCName); if (usingModuleName != null) { //there is already an //import 'usingModuleName' using function = 'foreignSCName'; compiler.logMessage(new CompilerMessage(foreignSCNameNode, new MessageKind.Error.ForeignFunctionNameAlreadyUsedInImportUsingDeclaration(foreignSCName, usingModuleName))); } } final List<ParseTreeNode> functionDefnNodes = moduleLevelParseTrees.getFunctionDefnNodes(); for (final ParseTreeNode functionDefnNode : functionDefnNodes) { // Collect the names of the functions used in the program. This is needed // before finding the free variables in order to trap the use of undefined functions // in another function's definition. final ParseTreeNode accessModifierNode = functionDefnNode.getChild(1); accessModifierNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); final ParseTreeNode functionNameNode = accessModifierNode.nextSibling(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String functionName = functionNameNode.getText(); if (primitiveFunctionNamesSet.contains(functionName)) { compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.AttemptToRedefinePrimitiveFunction(functionName))); } if (foreignFunctionNamesSet.contains(functionName) || functionNameToDefinitionNodeMap.containsKey(functionName)) { // the function has already been defined. This is an attempted redefinition compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.AttemptToRedefineFunction(functionName))); } final ModuleName usingModuleName = currentModuleTypeInfo.getModuleOfUsingFunctionOrClassMethod(functionName); if (usingModuleName != null) { //there is already an //import 'usingModuleName' using function = 'foreignSCName'; compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.FunctionNameAlreadyUsedInImportUsingDeclaration(functionName, usingModuleName))); } // add a reference to the parseTree to the map so that it can be looked up later // when typing a component functionNameToDefinitionNodeMap.put(functionName, functionDefnNode); // Update the set of top level functions defined in the current module. topLevelFunctionNameSet.add(functionName); } //the names of the functions that have explicit type declarations final Set<String> declaredNamesSet = new HashSet<String>(); final List<ParseTreeNode> functionTypeDeclarationNodes = moduleLevelParseTrees.getFunctionTypeDeclarationNodes(); for (final ParseTreeNode topLevelTypeDeclarationNode : functionTypeDeclarationNodes) { final ParseTreeNode optionalCALDocNode = topLevelTypeDeclarationNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode functionTypeDeclarationNode = optionalCALDocNode.nextSibling(); final ParseTreeNode functionNameNode = functionTypeDeclarationNode.firstChild(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String declaredFunctionName = functionNameNode.getText(); if (primitiveFunctionNamesSet.contains(declaredFunctionName)) { // the function is built-in. Cannot declare a type for it. compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.TypeDeclarationForBuiltInPrimitiveFuncton(declaredFunctionName))); } if (foreignFunctionNamesSet.contains(declaredFunctionName)) { // the function is foreign. Cannot redeclare a type for it (it is already given in the foreign declaration). compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.TypeDeclarationForForeignFunction(declaredFunctionName))); } if (!functionNameToDefinitionNodeMap.containsKey(declaredFunctionName)) { // the function is declared but not defined. This is illegal in Haskell and CAL. compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.DefinitionMissing(declaredFunctionName))); } if (!declaredNamesSet.add(declaredFunctionName)) { // the function has already been declared. This is an attempted redeclaration. compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.RepeatedTypeDeclaration(declaredFunctionName))); } } } /** * Only call this after CALTypeChecker.checkNamesUsed and TypeClassChecker.checkNamesUsed have been called. * @param functionName name of the function (including built-in or foreign) assumed to belong to the current module being type checked. * @return boolean true if varName is the name of a foreign function, top level function or built-in function defined within the current module. */ boolean isTopLevelFunctionName(final String functionName) { //todoBI a lot of the need for carrying around foreignSCNamesSet, etc. could be removed if we added functions to //the currentModuleTypeInfo immediately. This is a future refactoring. return functionNameToDefinitionNodeMap.containsKey(functionName) || foreignFunctionNamesSet.contains(functionName) || primitiveFunctionNamesSet.contains(functionName); } boolean isPrimitiveOrForeignFunctionName(final String functionName) { return foreignFunctionNamesSet.contains(functionName) || primitiveFunctionNamesSet.contains(functionName); } /** * Ensures that the sets of foreign function names and primitive function names are * initialized to contain the proper content given the current module. */ private void ensureFieldsInitialized() { // if foreignFunctionNamesSet is null, create a new set and populate it // with information from the module type info if (foreignFunctionNamesSet == null) { foreignFunctionNamesSet = new HashSet<String>(); final int nFunctions = currentModuleTypeInfo.getNFunctions(); for (int i = 0; i < nFunctions; i++) { Function function = currentModuleTypeInfo.getNthFunction(i); if (function.getForeignFunctionInfo() != null) { foreignFunctionNamesSet.add(function.getName().getUnqualifiedName()); } } } // if primitiveFunctionNamesSet is null, create a new set and populate it // with information from the module type info if (primitiveFunctionNamesSet == null) { primitiveFunctionNamesSet = new HashSet<String>(); final int nFunctions = currentModuleTypeInfo.getNFunctions(); for (int i = 0; i < nFunctions; i++) { Function function = currentModuleTypeInfo.getNthFunction(i); if (function.isPrimitive()) { primitiveFunctionNamesSet.add(function.getName().getUnqualifiedName()); } } } } /** * This methods checks if a new function definition is well typed. It uses * the enviroment already built up while checking the main module. This * method is intended to be called by tools, and so exceptions are * suppressed. * * Creation date: (10/12/00 12:26:22 PM) * @return TypeExpr type of the defined function, or null if it is not well-typed * @param functionSource the adjunct source defining the function e.g. "add2 x = x + 2;" * @param functionModule the module in which the function should be considered to be defined */ TypeExpr checkFunction(final AdjunctSource functionSource, final ModuleName functionModule) { if (currentModuleTypeInfo == null) { changeModule(functionModule); } ensureFieldsInitialized(); final CompilerMessageLogger oldLogger = compiler.getMessageLogger(); final CompilerMessageLogger checkLogger = new MessageLogger(true); // Internal compiler logger. compiler.setCompilerMessageLogger(checkLogger); //todoBI there is a lot of overlap between checkFunction and checkAdjunct, Factor this better. // Parse and type check and catch any fatal errors try { try { final ParseTreeNode outerDefnListNode; final CALTreeParser treeParser = new CALTreeParser(compiler); if (functionSource instanceof AdjunctSource.FromText) { final CALParser parser = freshParser(compiler, ((AdjunctSource.FromText)functionSource).getReader()); // Call the parser to parse an adjunct. parser.adjunct(); outerDefnListNode = (ParseTreeNode)parser.getAST(); } else if (functionSource instanceof AdjunctSource.FromSourceModel) { outerDefnListNode = ((AdjunctSource.FromSourceModel)functionSource).toParseTreeNode(); } else { throw new IllegalArgumentException( "CALTypeChecker.checkFunction - cannot handle adjunct source of type " + functionSource.getClass()); } // Walk the parse tree as a sanity check on the generated AST and of the tree parser treeParser.adjunct(outerDefnListNode); if (outerDefnListNode.getNumberOfChildren() > 2) { throw new IllegalArgumentException("Programming Error: TypeChecker.checkAdjunct. Can only add a single adjunct."); } outerDefnListNode.verifyType(CALTreeParserTokenTypes.OUTER_DEFN_LIST); final ParseTreeNode outerDefnNode = outerDefnListNode.firstChild(); outerDefnNode.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN, CALTreeParserTokenTypes.TOP_LEVEL_TYPE_DECLARATION); final ParseTreeNode functionNode; if (outerDefnNode.getType() == CALTreeParserTokenTypes.TOP_LEVEL_TYPE_DECLARATION) { functionNode = outerDefnNode.nextSibling(); } else { functionNode = outerDefnNode; } functionNode.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN); changeModule (functionModule); final Set<String> functionNamesSet = new HashSet<String>(); FreeVariableFinder finder = new FreeVariableFinder(compiler, functionNamesSet, currentModuleTypeInfo); finder.resolveUnqualifiedIdentifiers(functionNode); final ParseTreeNode optionalCALDocNode = functionNode.firstChild(); optionalCALDocNode.verifyType(CALTreeParserTokenTypes.OPTIONAL_CALDOC_COMMENT); final ParseTreeNode accessModifierNode = optionalCALDocNode.nextSibling(); accessModifierNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); final Scope scope = getScopeModifier(accessModifierNode); final ParseTreeNode functionNameNode = accessModifierNode.nextSibling(); functionNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String functionName = functionNameNode.getText(); if(currentModuleTypeInfo.getFunction(functionName) != null) { compiler.logMessage(new CompilerMessage(functionNameNode, new MessageKind.Error.AttemptToRedefineFunction(functionName))); return null; } final TypeVar newTypeVar = new TypeVar(); final Function function = Function.makeTopLevelFunction(QualifiedName.make(functionModule, functionName), newTypeVar, scope); // extend with adjunct, but don't modify the original environment. This is a non-destructive check. final Env extendedFunctionEnv = Env.extend(functionEnv, function); currentOverloadingInfo = new OverloadingInfo(function, functionNode, newTypeVar, null); overloadingInfoList.add(currentOverloadingInfo); final NonGenericVars nonGenericVars = NonGenericVars.extend(null, newTypeVar); final TypeExpr typeExpr = analyzeTopLevelFunction(extendedFunctionEnv, nonGenericVars, functionNode); extendedFunctionEnv.finishedTypeChecking(1); currentOverloadingInfo.finishedTypeCheckingFunction(null); //Used to augment the parse tree of a module with hidden overloading arguments, //and to add hidden dictionary functions. final OverloadingResolver overloadingResolver = new OverloadingResolver(compiler, currentModuleTypeInfo); // now add dictionary arguments to the function, and fix up applications within the function definition. overloadingResolver.resolveOverloading(overloadingInfoList); if (DEBUG_INFO) { System.out.println(""); System.out.println("overloading for: " + functionName); System.out.println(functionNode.toStringTree()); } // Free up memory associated with this potentially bulky object overloadingInfoList.clear(); //Check the CALDoc comments final ParseTreeNode paramListNode = functionNameNode.nextSibling(); paramListNode.verifyType(CALTreeParserTokenTypes.FUNCTION_PARAM_LIST); final ParseTreeNode definingExprNode = paramListNode.nextSibling(); final FreeVariableFinder finderForCALDocChecker = new FreeVariableFinder(compiler, topLevelFunctionNameSet, currentModuleTypeInfo); final CALDocChecker calDocChecker = new CALDocChecker(compiler, currentModuleTypeInfo, finderForCALDocChecker, this); calDocChecker.checkCALDocCommentsInExpr(definingExprNode); //now do the lambda lifting final LambdaLifter lifter = new LambdaLifter(compiler, finder, currentModuleTypeInfo.getModuleName()); lifter.lift(outerDefnListNode); if (compiler.getMessageLogger().getMaxSeverity().compareTo(CompilerMessage.Severity.ERROR) >= 0) { return null; } //deep prune so that instantiated type variables are not part of the returned TypeExpr. //this has the effect of chosing a deterministic element in the equivalence class of //representations of this TypeExpr. return typeExpr.deepPrune(); } catch (antlr.RecognitionException e) { // syntax error final SourceRange sourceRange = CALParser.makeSourceRangeFromException(e); compiler.logMessage(new CompilerMessage(sourceRange, new MessageKind.Error.SyntaxError(), e)); } catch (antlr.TokenStreamException e) { // Bad token stream compiler.logMessage(new CompilerMessage(new MessageKind.Error.BadTokenStream(), e)); } } catch (AbortCompilation e) { // Compilation abort, we catch and continue } catch (Exception e) { // Major failure - internal coding error try { final int errorCount = compiler.getMessageLogger().getNErrors(); if (errorCount > 0 || e instanceof UnableToResolveForeignEntityException) { // If the exception is an UnableToResolveForeignEntityException, there is // a CompilerMessage inside that we should be logging. if (e instanceof UnableToResolveForeignEntityException) { try { compiler.logMessage(((UnableToResolveForeignEntityException)e).getCompilerMessage()); } catch (AbortCompilation ace) { //logMessage can throw a AbortCompilation if a FATAL message was sent. } } //if an error occurred previously, we continue to compile the program to try to report additional //meaningful compilation errors. However, this can produce spurious exceptions related to the fact //that the program state does not satisfy preconditions because of the initial error(s). We don't //report the spurious exception as an internal coding error. compiler.logMessage(new CompilerMessage(new MessageKind.Info.UnableToRecover())); } else { compiler.logMessage(new CompilerMessage(new MessageKind.Fatal.InternalCodingError(), e)); } } catch (AbortCompilation ace) { // Yeah, yeah, we know } } finally { // replace the old logger. compiler.setCompilerMessageLogger(oldLogger); try { oldLogger.logMessages(checkLogger); } catch (AbortCompilation e) { // Too many errors for the ol' logger. } } //the failure case- be sure to return null return null; } /** * Clears the state of the type checker to prepare to process a new module. * Creation date: (10/13/00 10:16:32 AM) */ private void clearState() { functionEnv = null; functionBoundNamesToTypeMap.clear(); localFunctionBoundNamesToNonGenericVarsMap.clear(); localFunctionBoundNamesToLocalFunctionIdentifiersMap.clear(); localFunctionBoundNamesToFunctionEntityMap.clear(); evaluatedLocalVariables.clear(); letVarNameToParseTreeMap.clear(); dataConstructorEnv = null; dataDeclarationChecker = null; currentModuleTypeInfo = null; functionNameToDefinitionNodeMap.clear(); functionNameToDeclaredTypeMap.clear(); topLevelFunctionNameSet.clear(); overloadingInfoList.clear(); nestingLevel = 0; currentOverloadingInfo = null; } /** * Changes the current module in the type checker. * Certain state will also be cleared such that the type checker will be able to process an adjunct if necessary. * This method should only be used when modules have been fully typechecked. It would be used, for instance, * if an adjunct needed to be type checked in a different module, or to make available certain type services * (eg. getting the type from a string). * @param moduleName the name of the module to which to change. */ private void changeModule(final ModuleName moduleName) { // reconstruct or clear the various environments and maps as appropriate. // switch modules.. final Packager packager = compiler.getPackager(); final ModuleTypeInfo mti = packager.getModuleTypeInfo(moduleName); if (mti == null) { compiler.logMessage(new CompilerMessage(new MessageKind.Fatal.ModuleNotInWorkspace(moduleName))); } currentModuleTypeInfo = new ModuleTypeInfo(mti); // functionEnv, dataConstructorEnv buildEnvFromTypeInfo(); functionBoundNamesToTypeMap.clear(); localFunctionBoundNamesToNonGenericVarsMap.clear(); localFunctionBoundNamesToLocalFunctionIdentifiersMap.clear(); localFunctionBoundNamesToFunctionEntityMap.clear(); evaluatedLocalVariables.clear(); letVarNameToParseTreeMap.clear(); // typeClassChecker typeClassChecker = new TypeClassChecker(currentModuleTypeInfo, compiler); // dataDeclarationChecker dataDeclarationChecker = new DataDeclarationChecker(currentModuleTypeInfo, compiler, typeClassChecker); // foreignSCNamesSet if (foreignFunctionNamesSet == null) { foreignFunctionNamesSet = new HashSet<String>(); } else { foreignFunctionNamesSet.clear(); } functionNameToDefinitionNodeMap.clear(); functionNameToDeclaredTypeMap.clear(); overloadingInfoList.clear(); nestingLevel = 0; currentOverloadingInfo = null; topLevelFunctionNameSet.clear(); // Update the topLevelSCNameSet with the names of functions in the current module. final int nFunctions = currentModuleTypeInfo.getNFunctions(); for (int i = 0; i < nFunctions; i++) { FunctionalAgent entity = currentModuleTypeInfo.getNthFunction(i); topLevelFunctionNameSet.add(entity.getName().getUnqualifiedName()); } } /** * Rebuild the current functions and data constructor environments from the current ModuleTypeInfo. */ private void buildEnvFromTypeInfo() { functionEnv = null; // add functions final int nFunctions = currentModuleTypeInfo.getNFunctions(); for (int i = 0; i < nFunctions; i++) { Function function = currentModuleTypeInfo.getNthFunction(i); functionEnv = Env.extend(functionEnv, function); } //add the class methods //todoBI it is an abuse of terminology to have class methods added to the functionEnv. final int nTypeClasses = currentModuleTypeInfo.getNTypeClasses(); for (int i = 0; i < nTypeClasses; ++i) { final TypeClass typeClass = currentModuleTypeInfo.getNthTypeClass(i); for (int j = 0, nClassMethods = typeClass.getNClassMethods(); j < nClassMethods; ++j) { final ClassMethod classMethod = typeClass.getNthClassMethod(j); functionEnv = Env.extend (functionEnv, classMethod); } } // data constructors dataConstructorEnv = null; final int nTypeConstructors = currentModuleTypeInfo.getNTypeConstructors(); for (int i = 0; i < nTypeConstructors; i++) { final TypeConstructor typeCons = currentModuleTypeInfo.getNthTypeConstructor(i); final int nDataConstructors = typeCons.getNDataConstructors(); for (int j = 0; j < nDataConstructors; j++) { DataConstructor dataConstructor = typeCons.getNthDataConstructor(j); dataConstructorEnv = Env.extend(dataConstructorEnv, dataConstructor); } } } /** * Construct a new parser state from the given reader. * The returned parser will have its lexer and stream selector set, and will be configured for ASTNodes of type ParseTreeNode. * Note that tree nodes created by this parser will not have any source name (filename) info. * * @param compiler The compiler. This will be used for message logging and for parser access to its stream selector. * @param reader the reader from which to parse. * @return CALParser a new parser configured for the given args. */ private static CALParser freshParser(final CALCompiler compiler, final java.io.Reader reader) { // Make a multiplexed lexer final CALMultiplexedLexer lexer = new CALMultiplexedLexer(compiler, reader, null); // Create a parser, it gets its tokens from the multiplexed lexer final CALParser parser = new CALParser(compiler, lexer); final String treeNodeClassName = ParseTreeNode.class.getName(); parser.setASTNodeClass(treeNodeClassName); return parser; } /** * Dump typing info for debugging purposes. * Creation date: (1/8/01 5:23:02 PM) * @param functionName * @param typeExpr * @param inferredTypeExpr */ private void dumpTypingInfo(final String functionName, final TypeExpr typeExpr, final TypeExpr inferredTypeExpr) { System.out.println("function " + functionName); System.out.println("inferred type :: " + inferredTypeExpr.toString()); final TypeExpr declaredTypeExpr = functionNameToDeclaredTypeMap.get(functionName); if (declaredTypeExpr != null) { System.out.println("declared type :: " + declaredTypeExpr.toString()); System.out.println("final type :: " + typeExpr.toString() + '\n'); } else { System.out.println("no declared type\n"); } /* System.out.println ("# args = " + typeExpr.getNApplications()); TypeExpr [] typePieces = typeExpr.getTypePieces (); for (int k = 0; k < typePieces.length; ++k) { System.out.println ("piece" + k + " : " + typePieces [k].toString ()); } */ } /** * Obtain the entity for this name. Returns null if the entity is not found. * Creation date: (1/29/01 4:23:46 PM) * @return FunctionalAgent * @param name QualifiedName the name of the identifier, as encoded in the Expression class. */ FunctionalAgent getEntity(final QualifiedName name) { //todoBI the only reason this can't look up from the currentModuleTypeInfo is because //of checking of instance resolution functions for adjuncts. When adjuncts are implemented //properly (i.e. with sufficient static checking) then this method can be simplified. final ModuleName moduleName = name.getModuleName(); final String unqualifiedName = name.getUnqualifiedName(); if (moduleName.equals(currentModuleTypeInfo.getModuleName())) { // We use the underlying environment method, but we have to check multiple environments // (one for SC's and one for data constructors) if (functionEnv != null) { final FunctionalAgent entity = functionEnv.retrieveEntity(unqualifiedName); if (entity != null) { return entity; } } if (dataConstructorEnv != null) { final FunctionalAgent entity = dataConstructorEnv.retrieveEntity(unqualifiedName); if (entity != null) { return entity; } } } ModuleTypeInfo moduleTypeInfo = currentModuleTypeInfo.getImportedModule(moduleName); if (moduleTypeInfo == null) { return null; } return moduleTypeInfo.getFunctionalAgent(unqualifiedName); } /** * Returns the name of the qualified entity to be used for display in error messages. * The FreeVariableFinder makes local names unique by changing a local name such as x occurring in the definition * of f to something like f$x$9. We want to get back to x for the purpose of error messages. * * Creation date: (7/10/01 12:06:08 PM) * @return String * @param qualifiedNameNode */ static private String getQualifiedNameDisplayString(final ParseTreeNode qualifiedNameNode) { if (qualifiedNameNode.getType() != CALTreeParserTokenTypes.QUALIFIED_CONS && qualifiedNameNode.getType() != CALTreeParserTokenTypes.QUALIFIED_VAR) { throw new IllegalArgumentException("TypeChecker: unexpected parse tree node " + qualifiedNameNode.toDebugString()); } final ParseTreeNode moduleNameNode = qualifiedNameNode.firstChild(); final String moduleName = ModuleNameUtilities.getMaybeModuleNameStringFromParseTree(moduleNameNode); final ParseTreeNode nameNode = moduleNameNode.nextSibling(); final String unqualifiedDisplayName = FreeVariableFinder.getDisplayName(nameNode.getText()); final String displayName; if (moduleName.length() > 0) { displayName = moduleName + '.' + unqualifiedDisplayName; } else { displayName = unqualifiedDisplayName; } return displayName; } /** * A helper function that returns the scope (i.e. public or private) modifier given the * access modifier ParseTreeNode. If the scope is omitted, it defaults to private. * Creation date: (6/18/01 11:27:30 AM) * @param accessModifierNode * @return the corresponding scope modifier. */ static Scope getScopeModifier(final ParseTreeNode accessModifierNode) { accessModifierNode.verifyType(CALTreeParserTokenTypes.ACCESS_MODIFIER); final ParseTreeNode scopeNode = accessModifierNode.firstChild(); //the default scope is private if (scopeNode == null) { return Scope.PRIVATE; } switch (scopeNode.getType()) { case CALTreeParserTokenTypes.LITERAL_private: return Scope.PRIVATE; case CALTreeParserTokenTypes.LITERAL_protected: return Scope.PROTECTED; case CALTreeParserTokenTypes.LITERAL_public: return Scope.PUBLIC; default: throw new IllegalStateException("Unexpected parse tree node " + scopeNode.toDebugString()); } } /** * Attempts to match the inferred type for the top-level function with the type determined by the type declaration. * Note that on output, typeExpr is the (possibly) restricted type which is the actual type of the function. * Creation date: (1/9/01 1:20:49 PM) * @param functionName name of the function * @param inferredTypeExpr the inferred type on input, the restricted type on output */ private void patternMatch(final String functionName, final TypeExpr inferredTypeExpr /*in-out*/) { final TypeExpr originalDeclaredTypeExpr = functionNameToDeclaredTypeMap.get(functionName); if (originalDeclaredTypeExpr != null) { final TypeExpr declaredTypeExpr = originalDeclaredTypeExpr.copyTypeExpr(); // we use a copy of the declared type, so as to keep the original one pristine for later checking final TypeExpr copyOfTypeExpr = inferredTypeExpr.copyTypeExpr(); try { TypeExpr.patternMatch(declaredTypeExpr, inferredTypeExpr, currentModuleTypeInfo); } catch (TypeException te) { final ParseTreeNode functionNode = functionNameToDefinitionNodeMap.get(functionName); //the declared type of the function is not compatible with its inferred type. compiler.logMessage(new CompilerMessage(functionNode, new MessageKind.Error.DeclaredTypeOfFunctionNotCompatibleWithInferredType(functionName, copyOfTypeExpr.toString()), te)); } } } /** * Perform dependency analysis to divide up the functions defined in a module into * a topologically ordered set of strongly connected components. * Creation date: (1/18/01 6:11:23 PM) * @return Graph */ private Graph<String> performFunctionDependencyAnalysis() { final FreeVariableFinder freeVariableFinder = new FreeVariableFinder(compiler, topLevelFunctionNameSet, currentModuleTypeInfo); return freeVariableFinder.performFunctionDependencyAnalysis(functionNameToDefinitionNodeMap); } /** * Retrieves a qualified data constructor from the module that is currently being typechecked, or from * the ModuleTypeInfo objects of previously type checked modules, as appropriate. * * If the data constructor cannot be found, an appropriate message is logged. Note: this message is typically * redundant since earlier checks will have determined if the data constructor can be resolved, but it * improves multiple error messages to handle things carefully here. * * Creation date: (6/29/01 6:22:32 PM) * @param qualifiedDataConsNode ParseTreeNode * @return DataConstructor null if the data constructor could not be retrieved. */ private DataConstructor retrieveQualifiedDataConstructor(final ParseTreeNode qualifiedDataConsNode) { qualifiedDataConsNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS); final QualifiedName dataConsName = qualifiedDataConsNode.toQualifiedName(); final ModuleName moduleName = dataConsName.getModuleName(); final ParseTreeNode consNode = qualifiedDataConsNode.getChild(1); consNode.verifyType(CALTreeParserTokenTypes.CONS_ID); final String unqualifiedDataConsName = dataConsName.getUnqualifiedName(); if (moduleName.equals(currentModuleTypeInfo.getModuleName())) { final DataConstructor dataCons = (DataConstructor)dataConstructorEnv.retrieveEntity(unqualifiedDataConsName); if (dataCons == null) { final String displayName = getQualifiedNameDisplayString(qualifiedDataConsNode); // TypeChecker: unknown data constructor {displayName}. compiler.logMessage(new CompilerMessage(consNode, new MessageKind.Error.UnknownDataConstructor(displayName))); } return dataCons; } final ModuleTypeInfo importedModuleTypeInfo = currentModuleTypeInfo.getImportedModule(moduleName); final DataConstructor dataCons = importedModuleTypeInfo.getDataConstructor(unqualifiedDataConsName); if (dataCons == null) { String displayName = getQualifiedNameDisplayString(qualifiedDataConsNode); // TypeChecker: unknown data constructor {displayName}. compiler.logMessage(new CompilerMessage(consNode, new MessageKind.Error.UnknownDataConstructor(displayName))); return null; } if (currentModuleTypeInfo.isEntityVisible(dataCons)) { return dataCons; } //"The data constructor {0} is not visible in module {1}." compiler.logMessage(new CompilerMessage(consNode, new MessageKind.Error.DataConstructorNotVisible(dataCons, currentModuleTypeInfo.getModuleName()))); return null; } /** * Retrieves a qualified variable from the module that is currently being typechecked, or from * the ModuleTypeInfo objects of previously type checked modules, as appropriate. * If the entity cannot be found, an appropriate message is logged. Note: this message is typically * redundant since earlier checks will have determined if the entity can be resolved, but it * improves multiple error messages to handle things carefully here. * * Creation date: (6/28/01 10:45:38 AM) * @param functionEnv * @param qualifiedVarNode * @param logErrors true if the inability to retreive a variable from the current module should be logged as an error. * This parameter is added to simplify the requirement for building a full environment for the let variable optimization. * @return FunctionalAgent null if the entity cannot be retreived. */ private FunctionalAgent retrieveQualifiedVar(final Env functionEnv, final ParseTreeNode qualifiedVarNode, final boolean logErrors) { qualifiedVarNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_VAR); final QualifiedName varName = qualifiedVarNode.toQualifiedName(); final ModuleName moduleName = varName.getModuleName(); final ParseTreeNode varNode = qualifiedVarNode.getChild(1); varNode.verifyType(CALTreeParserTokenTypes.VAR_ID); final String unqualifiedName = varName.getUnqualifiedName(); if (moduleName.equals(currentModuleTypeInfo.getModuleName())) { final FunctionalAgent entity = functionEnv.retrieveEntity(unqualifiedName); if (entity == null && logErrors) { String displayName = getQualifiedNameDisplayString(qualifiedVarNode); // TypeChecker: unknown function or variable {displayName}. compiler.logMessage(new CompilerMessage(varNode, new MessageKind.Error.UnknownFunctionOrVariable(displayName))); } return entity; } final ModuleTypeInfo importedModuleTypeInfo = currentModuleTypeInfo.getImportedModule(moduleName); final FunctionalAgent entity = importedModuleTypeInfo.getFunctionOrClassMethod(unqualifiedName); if (entity == null) { final String displayName = getQualifiedNameDisplayString(qualifiedVarNode); // TypeChecker: unknown function or variable {displayName}. compiler.logMessage(new CompilerMessage(varNode, new MessageKind.Error.UnknownFunctionOrVariable(displayName))); return null; } if (currentModuleTypeInfo.isEntityVisible(entity) || qualifiedVarNode.isInternallyGenerated()) { return entity; } // The identifier {qualifiedVarName} is not visible in {currentModuleName}. final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName(); final MessageKind message; if (entity instanceof Function) { //"The function {0} is not visible in module {1}." message = new MessageKind.Error.FunctionNotVisible((Function)entity, currentModuleName); } else { //"The class method {0} is not visible in module {1}." message = new MessageKind.Error.ClassMethodNotVisible((ClassMethod)entity, currentModuleName); } compiler.logMessage(new CompilerMessage(varNode, message)); return null; } /** * A helper method to get the field bindings from a parse tree node for a list of such bindings. * * This method assumes that there are no duplicate field bindings. * * @param fieldBindingVarAssignmentListNode the parse tree node for the list of field bindings. * @param fieldNameToParseTreeNodeMap (FieldName->ParseTreeNode) if non-null, this map will be populated with mappings * from field name to the parse tree node for that field name * @param recordBindings true for record bindings, false for data constructor case unpack alt bindings. * @return (FieldName -> String) Map from field name to the name bound for that field, in the order that the fields were given. */ static SortedMap<FieldName, String> getFieldBindingsMap(final ParseTreeNode fieldBindingVarAssignmentListNode, final Map<FieldName, ParseTreeNode> fieldNameToParseTreeNodeMap, final boolean recordBindings) { fieldBindingVarAssignmentListNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT_LIST); final SortedMap<FieldName, String> fieldBindingsMap = new TreeMap<FieldName, String>(); //FieldName -> String for (final ParseTreeNode fieldBindingVarAssignmentNode : fieldBindingVarAssignmentListNode) { fieldBindingVarAssignmentNode.verifyType(CALTreeParserTokenTypes.FIELD_BINDING_VAR_ASSIGNMENT); final ParseTreeNode fieldNameNode = fieldBindingVarAssignmentNode.firstChild(); fieldNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID, CALTreeParserTokenTypes.ORDINAL_FIELD_NAME); final FieldName fieldName = FieldName.make(fieldNameNode.getText()); final ParseTreeNode patternVarNode = fieldNameNode.nextSibling(); final String patternVarName; switch (patternVarNode.getType()) { case CALTreeParserTokenTypes.VAR_ID : { patternVarName = patternVarNode.getText(); break; } case CALTreeParserTokenTypes.UNDERSCORE : { if (recordBindings) { patternVarName = Expression.RecordCase.WILDCARD_VAR; } else { patternVarName = Expression.Switch.SwitchAlt.WILDCARD_VAR; } break; } default : { patternVarNode.unexpectedParseTreeNode(); return null; } } fieldBindingsMap.put(fieldName, patternVarName); if (fieldNameToParseTreeNodeMap != null) { fieldNameToParseTreeNodeMap.put(fieldName, fieldNameNode); } } return fieldBindingsMap; } /** * An internal helper method. * @param functionBoundName name of a top-level (non-foreign, non-primitive function), or the unique name of any * lower-case starting symbol occurring within a function definition. * @return TypeExpr */ TypeExpr getFunctionBoundNameType(final String functionBoundName) { return functionBoundNamesToTypeMap.get(functionBoundName); } /** * Returns true if the local variables is known by the compiler knows to be evaluated to weak-head normal form. * * These variables are: * a) strict function bound argument names (i.e. corresponding to plinged function arguments). * b) variables introduced by a case-pattern where the variable is guaranteed to be evaluated because of the * strictness annotations on the corresponding data constructor argument. * c) let variables of the form * x = expression known by the compiler to be evaluated to weak head normal form. * Some important cases of this are where the expression is: * -a top level applications of Prelude.eager * -literals (string, integers, double, lists, tuples, non-extension records) * -aliases for top-level positive arity functions and (zero of positive arity) data constructors * -we exclude zero-arity functions e.g. x = Prelude.undefined; should not be marked as being evaluated since * running it intentionally terminates in an error. * -aliases for evaluated local variables * -certain special data constructor field selections of the form (evaluated-expression).MyDataConstructor.myStrictField) * * @param uniqueArgName * @return true if uniqueArgName is known to be evaluated to weak head normal form. */ boolean isEvaluatedLocalVariable(final String uniqueArgName) { return evaluatedLocalVariables.contains(uniqueArgName); } /** * @param functionName the name of the top-level function. * @return the definition node of the function, or null if there is none. */ ParseTreeNode getFunctionDefinitionNode(final String functionName) { return functionNameToDefinitionNodeMap.get(functionName); } /** * @param functionName the name of the top-level function. * @return true if the function has a corresponding type declaration; false otherwise. */ boolean hasFunctionTypeDeclaration(final String functionName) { return functionNameToDeclaredTypeMap.containsKey(functionName); } /** * Looks up the LocalFunctionIdentifier for a local function with the specified unique-transformed name. * @param uniqueName * @return LocalFunctionIdentifier */ LocalFunctionIdentifier getLocalFunctionIdentifier(final String uniqueName) { return localFunctionBoundNamesToLocalFunctionIdentifiersMap.get(uniqueName); } /** * 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 functionName * @param functionParseTree * @param declaredFunctionTypeExpr may be null if no type is declared */ void addDerivedInstanceFunction(final String functionName, final ParseTreeNode functionParseTree, final TypeExpr declaredFunctionTypeExpr) { functionParseTree.verifyType(CALTreeParserTokenTypes.TOP_LEVEL_FUNCTION_DEFN); topLevelFunctionNameSet.add(functionName); functionNameToDefinitionNodeMap.put(functionName, functionParseTree); if (declaredFunctionTypeExpr != null) { functionNameToDeclaredTypeMap.put(functionName, declaredFunctionTypeExpr); } } /** * @return the current module name or null if not available */ ModuleName getCurrentModuleName(){ if (currentModuleTypeInfo == null){ return null; } else{ return currentModuleTypeInfo.getModuleName(); } } }