/*
* 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.
*/
/*
* TypeClassChecker.java
* Creation date (Aug 26, 2002).
* By: Bo Ilic
*/
package org.openquark.cal.compiler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import org.openquark.cal.util.Graph;
import org.openquark.cal.util.VertexBuilder;
import org.openquark.cal.util.VertexBuilderList;
/**
* A class used to check that type class definitions within a module are valid, and to then
* add them to the the environment.
*
* <p>
* Creation date (Aug 26, 2002).
* @author Bo Ilic
*/
final class TypeClassChecker {
/** Set to true to have debug info printed while running the type class checker. */
private static final boolean DEBUG_INFO = false;
private final CALCompiler compiler;
/** Type classes for the current module are added here. */
private final ModuleTypeInfo currentModuleTypeInfo;
/**
* Method TypeClassChecker.
* @param currentModuleTypeInfo
* @param compiler
*/
TypeClassChecker(ModuleTypeInfo currentModuleTypeInfo, CALCompiler compiler) {
if (currentModuleTypeInfo == null || compiler == null) {
throw new NullPointerException();
}
this.currentModuleTypeInfo = currentModuleTypeInfo;
this.compiler = compiler;
}
/**
* Method checkTypeClassDefinitions.
* Does static analysis on the type classes defined within the current module, and adds a
* TypeClass object for each of them to the ModuleTypeInfo for the current module.
* Note that class methods are not checked here.
* @param typeClassDefnNodes
*/
void checkTypeClassDefinitions(List<ParseTreeNode> typeClassDefnNodes) {
checkNamesUsed(typeClassDefnNodes);
checkClassDependencies(typeClassDefnNodes);
}
/**
* This method does an initial pass over the type classes and checks that each type class is
* defined at most once, and that each class method is defined at most once.
*
* @param typeClassDefnNodes
*/
private void checkNamesUsed(List<ParseTreeNode> typeClassDefnNodes) {
//Haskell also has the restriction that type class names and type constructor names must not be the same
//within the same scope e.g. the Prelude module can't have a type class named Int because it has the type Int.
//This is necessary in Haskell because of the syntax for importing and exporting symbols from modules,
//but is not a necessary restriction for CAL.
//the names of all the classes defined in this module
Set<String> classNamesSet = new HashSet<String>();
//the names of all the class methods defined in this module
Set<String> classMethodNamesSet = new HashSet<String>();
final ModuleName currentModule = currentModuleTypeInfo.getModuleName();
for (final ParseTreeNode classNode : typeClassDefnNodes) {
classNode.verifyType(CALTreeParserTokenTypes.TYPE_CLASS_DEFN);
ParseTreeNode optionalDocComment = classNode.firstChild();
ParseTreeNode classScopeNode = optionalDocComment.nextSibling();
Scope classScope = CALTypeChecker.getScopeModifier(classScopeNode);
ParseTreeNode contextListNode = classScopeNode.nextSibling();
contextListNode.verifyType(CALTreeParserTokenTypes.CLASS_CONTEXT_LIST, CALTreeParserTokenTypes.CLASS_CONTEXT_SINGLETON, CALTreeParserTokenTypes.CLASS_CONTEXT_NOTHING);
ParseTreeNode classNameNode = contextListNode.nextSibling();
classNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String className = classNameNode.getText();
if (!classNamesSet.add(className)) {
//class name is defined more than once
compiler.logMessage(new CompilerMessage(classNameNode, new MessageKind.Error.RepeatedDefinitionOfClass(className)));
}
final ParseTreeNode typeClassTypeVarNameNode = classNameNode.nextSibling();
typeClassTypeVarNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
final String typeClassTypeVarName = typeClassTypeVarNameNode.getText();
ModuleName usingModuleName = currentModuleTypeInfo.getModuleOfUsingTypeClass(className);
if (usingModuleName != null) {
//there is already an
//import 'usingModuleName' using typeClass = 'className';
compiler.logMessage(new CompilerMessage(classNameNode, new MessageKind.Error.TypeClassNameAlreadyUsedInImportUsingDeclaration(classNameNode.toString(), usingModuleName)));
}
TypeClass typeClass = new TypeClass (QualifiedName.make(currentModule, className), classScope, new KindExpr.KindVar(), typeClassTypeVarName);
currentModuleTypeInfo.addTypeClass(typeClass);
ParseTreeNode classMethodListNode = classNameNode.nextSibling().nextSibling();
classMethodListNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD_LIST);
for (final ParseTreeNode classMethodNode : classMethodListNode) {
classMethodNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD);
ParseTreeNode classMethodNameNode = classMethodNode.getChild(2);
classMethodNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String classMethodName = classMethodNameNode.getText();
if (compiler.getTypeChecker().isTopLevelFunctionName(classMethodName)) {
// The name {classMethodName} is already used as a function name in this module.
compiler.logMessage (new CompilerMessage(classMethodNode, new MessageKind.Error.NameAlreadyUsedAsFunctionName(classMethodName)));
}
if(!classMethodNamesSet.add(classMethodName)) {
//the class method is defined more than once
compiler.logMessage(new CompilerMessage(classMethodNameNode, new MessageKind.Error.RepeatedDefinitionOfClassMethod(classMethodName)));
}
ModuleName usingModuleNameForClassMethod = currentModuleTypeInfo.getModuleOfUsingFunctionOrClassMethod(classMethodName);
if (usingModuleNameForClassMethod != null) {
//there is already an
//import 'usingModuleNameForClassMethod' using function = 'classMethodName';
compiler.logMessage(new CompilerMessage(classMethodNameNode, new MessageKind.Error.ClassMethodNameAlreadyUsedInImportUsingDeclaration(classMethodName, usingModuleNameForClassMethod)));
}
}
}
}
/**
* Checks that dependencies between type classes are valid.
* In particular, in the course of this method we check:
* <ol>
* <li> that all the type classes referred to in the class contexts are resolvable
* <li> that all the type variables in the class context and class are the same i.e. (Eq a => Ord a, the a's are the same)
* <li> that the inheritance graph of type classes is acyclic
* </ol>
* <p>
* Once this check is passed, the only thing that can fail in type checking the type classes is information
* derived from the class methods.
*
* @param typeClassDefnNodes
*/
private void checkClassDependencies(List<ParseTreeNode> typeClassDefnNodes) {
VertexBuilderList<String> vertexBuilderList = new VertexBuilderList<String>();
for (final ParseTreeNode classDefnNode : typeClassDefnNodes) {
classDefnNode.verifyType(CALTreeParserTokenTypes.TYPE_CLASS_DEFN);
ParseTreeNode contextListNode = classDefnNode.getChild(2);
ParseTreeNode classNameNode = contextListNode.nextSibling();
classNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String className = classNameNode.getText();
ParseTreeNode typeVarNameNode = classNameNode.nextSibling();
typeVarNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
Set<String> dependeeClassSet = checkClassContext (contextListNode, currentModuleTypeInfo.getTypeClass(className), typeVarNameNode.getText());
vertexBuilderList.add (new VertexBuilder<String>(className, dependeeClassSet));
}
checkClassDependenciesHelper(vertexBuilderList);
}
/**
* Check the inheritance graph for cyclic dependencies and give an appropriate error message if there
* are any. Note: to check for cyclic dependencies we only look at superclasses of a class that belong
* to the current module. This is because we cannot have mutually recursive modules in CAL, and we've
* already verified by this point that that is the case.
*
* @param vertexBuilderList list of classes defined in this module, along with their dependee classes.
*/
private void checkClassDependenciesHelper(VertexBuilderList<String> vertexBuilderList) {
// should never fail. It is a redundant check since makeModuleDependencyGraph should throw an exception otherwise.
if (!vertexBuilderList.makesValidGraph()) {
throw new IllegalStateException("Internal coding error during dependency analysis.");
}
Graph<String> g = new Graph<String>(vertexBuilderList);
g = g.calculateStronglyConnectedComponents();
int nTypeClasses = vertexBuilderList.size();
int nComponents = g.getNStronglyConnectedComponents();
if (nTypeClasses != nComponents) {
//There is a recursive type class dependency. This is not allowed.
for (int i = 0; i < nComponents; ++i) {
Graph<String>.Component component = g.getStronglyConnectedComponent(i);
int componentSize = component.size();
if (componentSize > 1) {
StringBuilder cyclicNames = new StringBuilder();
String cyclicClassName = null;
for (int j = 0; j < componentSize; ++j) {
if (j > 0) {
cyclicNames.append(", ");
}
cyclicClassName = component.getVertex(j).getName();
cyclicNames.append(cyclicClassName);
}
// Cyclic class context dependencies between classes: {cyclicNames}.
compiler.logMessage(new CompilerMessage(new MessageKind.Fatal.CyclicClassContextDependenciesBetweenClasses(cyclicNames.toString())));
}
}
throw new IllegalStateException("TypeChecker: Programming error.");
}
}
/**
* Checks the class context i.e. the list of superclasses of the class being defined.
* For example, the superclasses:
* <ol>
* <li> must resolve to a type class that actually exists (either within the current module or
* an imported module).
* <li> must be distinct
* <li> must depend on the same type variable as does the type class itself
* </ol>
*
* @param contextListNode
* @param typeClass the type class whose context is being checked.
* @param typeVarName name of the class type variable e.g. in "class (Eq a) => Ord a" it is "a".
* @return Set (String) the classes within the current module upon which this class directly depends.
*/
private Set<String> checkClassContext(ParseTreeNode contextListNode, TypeClass typeClass, String typeVarName) {
contextListNode.verifyType(CALTreeParserTokenTypes.CLASS_CONTEXT_LIST, CALTreeParserTokenTypes.CLASS_CONTEXT_SINGLETON, CALTreeParserTokenTypes.CLASS_CONTEXT_NOTHING);
//QualifiedName set of the names of the classes in the context.
Set<QualifiedName> contextClassSet = new HashSet<QualifiedName>();
//String set of the names of the classes in the context that belong to the current module.
Set<String> dependeeClassSet = new HashSet<String>();
ModuleName currentModuleName = currentModuleTypeInfo.getModuleName();
for (final ParseTreeNode contextNode : contextListNode) {
contextNode.verifyType(CALTreeParserTokenTypes.CLASS_CONTEXT);
ParseTreeNode contextClassNameNode = contextNode.firstChild();
//make sure that the class name referred to in the context actually exists, and
//resolve unqualified names to their fully module qualified versions.
TypeClass contextTypeClass = resolveClassName(contextClassNameNode);
//check that there is not a duplicate appearance of a class constraint in the constraint list.
QualifiedName contextClassName = contextTypeClass.getName();
if (!contextClassSet.add(contextClassName)) {
//a repeated constraint such as
//(Eq a, Eq 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(contextClassNameNode, new MessageKind.Error.RepeatedClassConstraint(contextClassName.getQualifiedName())));
}
ParseTreeNode typeVarNameNode = contextClassNameNode.nextSibling();
typeVarNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
if (!typeVarName.equals(typeVarNameNode.getText())) {
//the type variable used in the context part must be the same as the type variable used in the class part
//For example, can't have "Eq b => Ord a"
compiler.logMessage(new CompilerMessage(typeVarNameNode, new MessageKind.Error.ContextTypeVariableUndefined(typeVarNameNode.getText(), typeVarName)));
}
if (contextClassName.equals(typeClass.getName())) {
//todoBI the reason the following error is FATAL instead of just an ERROR, is that continuing compilation after this
//error will hang CAL, because further analysis gets stuck in a cyclic loop. We will need to adopt a more sophisticated
//approach to recovery after an error, including possibly a timer to terminate compilation after the first error has
//been reported.
//a type class cannot include itself in its context e.g. "(Eq a, Ord a) => Ord a".
compiler.logMessage(new CompilerMessage(contextClassNameNode, new MessageKind.Fatal.TypeClassCannotIncludeItselfInClassConstraintList(contextClassName.getQualifiedName())));
}
//add the context class as a parent class.
//todoBI there may be redundant parent classes e.g. (Eq a, Ord a) => Num a, then Eq a is a redundant parent class
//we need to remove these after performing cylic dependency analysis.
typeClass.addParentClass(contextTypeClass);
if (contextClassName.getModuleName().equals(currentModuleName)) {
dependeeClassSet.add(contextClassName.getUnqualifiedName());
}
}
return dependeeClassSet;
}
/**
* The kind of a type class is determined by kind inference. This algorithm looks at the kinds of the superclasses as
* well as the kinds of the types of the class methods. Class methods are allowed to have constrained type signatures.
* In particular, this means that we need to do kind checking in dependency groups where a type class depends on
* <ol>
* <li> each type class in its superclass context
* <li> each type class in the context of each of its class methods
* </ol>
* <p>
* Since module imports cannot be cyclic, we can ignore type classes defined in other than the current module in
* determining the dependency graph for kind checking.
*
* @return Graph vertices in the graph are String names of type classes in this module. Kind Checking must be done
* one connected component at a time.
*/
private Graph<String> calculateClassDependencyGraphForKindChecking(List<ParseTreeNode> typeClassDefnNodes) {
VertexBuilderList<String> vertexBuilderList = new VertexBuilderList<String>();
for (final ParseTreeNode classDefnNode : typeClassDefnNodes) {
classDefnNode.verifyType(CALTreeParserTokenTypes.TYPE_CLASS_DEFN);
ParseTreeNode contextListNode = classDefnNode.getChild(2);
ParseTreeNode classNameNode = contextListNode.nextSibling();
classNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String className = classNameNode.getText();
ParseTreeNode typeVarNameNode = classNameNode.nextSibling();
typeVarNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
Set<String> dependeeClassSet = new HashSet<String>();
addContextDependencies(dependeeClassSet, contextListNode);
ParseTreeNode classMethodListNode = typeVarNameNode.nextSibling();
classMethodListNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD_LIST);
for (final ParseTreeNode classMethodNode : classMethodListNode) {
classMethodNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD);
ParseTreeNode typeSignatureNode = classMethodNode.getChild(3);
typeSignatureNode.verifyType(CALTreeParserTokenTypes.TYPE_SIGNATURE);
ParseTreeNode methodContextListNode = typeSignatureNode.firstChild();
methodContextListNode.verifyType(CALTreeParserTokenTypes.TYPE_CONTEXT_LIST, CALTreeParserTokenTypes.TYPE_CONTEXT_NOTHING, CALTreeParserTokenTypes.TYPE_CONTEXT_SINGLETON);
addContextDependencies(dependeeClassSet, methodContextListNode);
}
vertexBuilderList.add (new VertexBuilder<String>(className, dependeeClassSet));
}
Graph<String> g = new Graph<String>(vertexBuilderList);
return g.calculateStronglyConnectedComponents();
}
/**
* Adds the names of the type classes appearing in a type class's context or a class method's context that are also
* defined within the current module to the classDependencies argument Set.
*
* Some minimal static checking is done- basically that the type class is resolvable.
*
* @param classDependencies (String set) of type
* @param contextListNode a type class or class method context
*/
private final void addContextDependencies(
final Set<String> classDependencies,
final ParseTreeNode contextListNode) {
contextListNode.verifyType(CALTreeParserTokenTypes.TYPE_CONTEXT_LIST, CALTreeParserTokenTypes.CLASS_CONTEXT_LIST, CALTreeParserTokenTypes.TYPE_CONTEXT_NOTHING, CALTreeParserTokenTypes.TYPE_CONTEXT_SINGLETON, CALTreeParserTokenTypes.CLASS_CONTEXT_SINGLETON, CALTreeParserTokenTypes.CLASS_CONTEXT_NOTHING);
ModuleName currentModuleName = currentModuleTypeInfo.getModuleName();
for (final ParseTreeNode contextNode : contextListNode) {
switch (contextNode.getType())
{
case CALTreeParserTokenTypes.CLASS_CONTEXT :
{
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.
TypeClass typeClass = resolveClassName(typeClassNameNode);
QualifiedName typeClassName = typeClass.getName();
if (typeClassName.getModuleName().equals(currentModuleName)) {
classDependencies.add(typeClassName.getUnqualifiedName());
}
break;
}
case CALTreeParserTokenTypes.LACKS_FIELD_CONTEXT:
{
break;
}
default:
{
contextNode.unexpectedParseTreeNode();
return;
}
}
}
}
/**
* Adds the class methods to their owning type class, and checks that
* each class method can be assigned a valid type.
* <p>
* In addition, this method determines the kind of the class. The kind of a class must be the
* same as the kind of its dependee superclasses. In addition, it must satisfy the constraints
* introduced by kind-checking of the class methods within the class. Otherwise a CAL compilation
* error will be reported.
* <p>
* Finally, this method verifies that any default class methods are actually resolvable to visible
* functions (in particular, not class methods or data constructors). Checking that the default class
* methods have a valid type is be done later, since the types of all functions defined in the module
* must first be known before this can be done.
*
* @param typeClassDefnNodes
*/
void checkClassMethods(final List<ParseTreeNode> typeClassDefnNodes) {
final ModuleName currentModuleName = currentModuleTypeInfo.getModuleName();
final CALTypeChecker typeChecker = compiler.getTypeChecker();
Map<String, ParseTreeNode> classNameToParseTreeNodeMap = new HashMap<String, ParseTreeNode>(); //String -> ParseTreeNode
for (final ParseTreeNode classDefnNode : typeClassDefnNodes) {
classDefnNode.verifyType(CALTreeParserTokenTypes.TYPE_CLASS_DEFN);
ParseTreeNode contextListNode = classDefnNode.getChild(2);
ParseTreeNode classNameNode = contextListNode.nextSibling();
classNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String className = classNameNode.getText();
classNameToParseTreeNodeMap.put(className, classDefnNode);
}
final Graph<String> g = calculateClassDependencyGraphForKindChecking(typeClassDefnNodes);
//Kind check the type class definitions. Proceed one strongly connected component at a time.
for (int componentN = 0, nComponents = g.getNStronglyConnectedComponents(); componentN < nComponents; ++componentN) {
Graph<String>.Component component = g.getStronglyConnectedComponent(componentN);
int componentSize = component.size();
for (int i = 0; i < componentSize; ++i) {
String className = component.getVertex(i).getName();
ParseTreeNode classNode = classNameToParseTreeNodeMap.get(className);
classNode.verifyType(CALTreeParserTokenTypes.TYPE_CLASS_DEFN);
TypeClass typeClass = currentModuleTypeInfo.getTypeClass(className);
//classTypeVar refers to the type variable that is scoped over the entire type class definition
//including the class methods.
ParseTreeNode classTypeVarNode = classNode.getChild(4);
classTypeVarNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
final String classTypeVarName = classTypeVarNode.getText();
final int nParentClasses = typeClass.getNParentClasses();
if (nParentClasses > 0) {
//for class (C1 t, C2 t, ..., Cn t) => C t,
//we unify kinds
//C1 and C2
//C2 and C3
//..
//Cn and C
//
//this will provide us with the best error message in the common cases that the superclasses are not
//in the same kind-checking dependency group as the class itself
if (nParentClasses > 1) {
for (int parentClassN = 1; parentClassN < nParentClasses; ++parentClassN) {
TypeClass previousParentClass = typeClass.getNthParentClass(parentClassN - 1);
TypeClass currentParentClass = typeClass.getNthParentClass(parentClassN);
try {
KindExpr.unifyKind(previousParentClass.getKindExpr(), currentParentClass.getKindExpr());
} catch (TypeException typeException) {
//The kind of the type class Foo (* -> *) must be the same as the kind of the type class Bar ((* -> * -> *))
//"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(classNode,
new MessageKind.Error.KindErrorInClassConstraints(
classTypeVarName,
previousParentClass,
currentParentClass)));
}
}
}
TypeClass lastParentClass = typeClass.getNthParentClass(nParentClasses - 1);
try {
KindExpr.unifyKind(lastParentClass.getKindExpr(), typeClass.getKindExpr());
} catch (TypeException typeException) {
//The kind of the type class Foo (* -> *) must be the same as the kind of the type class Bar ((* -> * -> *))
//"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(classNode,
new MessageKind.Error.KindErrorInClassConstraints(
classTypeVarName,
lastParentClass,
typeClass)));
}
}
SortedSet<TypeClass> typeClassConstraintSet = TypeClass.makeNewClassConstraintSet();
typeClassConstraintSet.add(typeClass);
final TypeVar classTypeVar = TypeVar.makeTypeVar(classTypeVarName, typeClassConstraintSet, true);
ParseTreeNode classMethodListNode = classTypeVarNode.nextSibling();
classMethodListNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD_LIST);
int ordinal = 0;
final int nClassMethods = classMethodListNode.getNumberOfChildren();
final int nAncestors = typeClass.calculateAncestorClassList().size();
final KindExpr classTypeVarKindExpr = typeClass.getKindExpr();
for (final ParseTreeNode classMethodNode : classMethodListNode) {
classMethodNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD);
ParseTreeNode classMethodOptionalCALDocNode = classMethodNode.firstChild();
ParseTreeNode classMethodAccessModifierNode = classMethodOptionalCALDocNode.nextSibling();
Scope classMethodScope = CALTypeChecker.getScopeModifier(classMethodAccessModifierNode);
ParseTreeNode classMethodNameNode = classMethodAccessModifierNode.nextSibling();
classMethodNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String classMethodName = classMethodNameNode.getText();
TypeExpr typeExpr = typeChecker.calculateDeclaredClassMethodType(classMethodNode, classTypeVarName, classTypeVar, classTypeVarKindExpr);
//the index of the class type variable in the ordered list of overloaded record and type vars in the class
//method's type. This is used during overload resolution.
int classTypeVarPolymorphicIndex = new ArrayList<PolymorphicVar>(typeExpr.getConstrainedPolymorphicVars()).indexOf(classTypeVar);
//the index for the class method in the dictionary of its type class. This has a special value
//of -1 if the class method is the single class method of a type class with no ancestors.
//Otherwise it is equal to the number of ancestors classes (i.e. including grandparents etc) +
//the ordinal of the method.
int dictionaryIndex;
if (nAncestors == 0 && nClassMethods == 1) {
dictionaryIndex = -1;
} else {
dictionaryIndex = nAncestors + ordinal;
}
ParseTreeNode defaultClassMethodNameNode = (ParseTreeNode)classMethodNameNode.getNextSibling().getNextSibling();
final QualifiedName defaultClassMethodName;
if (defaultClassMethodNameNode != null) {
defaultClassMethodName = ClassInstanceChecker.resolveMethodName(defaultClassMethodNameNode, compiler, currentModuleTypeInfo);
} else {
//no default supplied. This is OK.
defaultClassMethodName = null;
}
ClassMethod classMethod = new ClassMethod (QualifiedName.make(currentModuleName, classMethodName),
classMethodScope, new String[] {}, typeExpr, ordinal, dictionaryIndex, classTypeVarPolymorphicIndex, defaultClassMethodName);
typeClass.addClassMethod(classMethod);
++ordinal;
}
}
for (int i = 0; i < componentSize; ++i) {
String typeClassName = component.getVertex(i).getName();
TypeClass typeClass = currentModuleTypeInfo.getTypeClass(typeClassName);
typeClass.finishedKindChecking();
}
}
if (DEBUG_INFO) {
for (int i = 0, nTypeClasses = currentModuleTypeInfo.getNTypeClasses(); i < nTypeClasses; ++i) {
TypeClass typeClass = currentModuleTypeInfo.getNthTypeClass(i);
System.out.println(typeClass);
}
}
}
/**
* Checks that the type of each default class method is the same as the type of the class method,
* and logs an error otherwise. This check must be done after type checking for the module is complete.
*
* @param typeClassDefnNodes
*/
void checkDefaultClassMethodTypes(List<ParseTreeNode> typeClassDefnNodes) {
for (final ParseTreeNode classDefnNode : typeClassDefnNodes) {
classDefnNode.verifyType(CALTreeParserTokenTypes.TYPE_CLASS_DEFN);
ParseTreeNode classNameNode = classDefnNode.getChild(3);
classNameNode.verifyType(CALTreeParserTokenTypes.CONS_ID);
String className = classNameNode.getText();
TypeClass typeClass = currentModuleTypeInfo.getTypeClass(className);
ParseTreeNode classMethodListNode = classNameNode.nextSibling().nextSibling();
classMethodListNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD_LIST);
for (final ParseTreeNode classMethodNode : classMethodListNode) {
classMethodNode.verifyType(CALTreeParserTokenTypes.CLASS_METHOD);
ParseTreeNode classMethodNameNode = classMethodNode.getChild(2);
classMethodNameNode.verifyType(CALTreeParserTokenTypes.VAR_ID);
String classMethodName = classMethodNameNode.getText();
ClassMethod classMethod = typeClass.getClassMethod(classMethodName);
QualifiedName defaultClassMethodName = classMethod.getDefaultClassMethodName();
if (defaultClassMethodName != null) {
Function defaultClassMethod = currentModuleTypeInfo.getVisibleFunction(defaultClassMethodName);
if (!classMethod.getTypeExpr().sameType(defaultClassMethod.getTypeExpr())) {
ParseTreeNode defaultClassMethodNameNode = classMethodNameNode.nextSibling().nextSibling();
compiler.logMessage(
new CompilerMessage(
defaultClassMethodNameNode,
new MessageKind.Error.InvalidDefaultClassMethodType(
defaultClassMethod.getName(),
classMethodName,
classMethod.getTypeExpr().toString(),
defaultClassMethod.getTypeExpr().toString())));
}
}
}
}
}
/**
* Verifies that the type class referred to actually exists.
* If the type class referred to has its module name omitted, but the module name
* can be inferred, then the module name is written into the ParseTreeNode as
* a side effect, so that subsequent code can assume a fully qualified name.
* @param qualifiedClassNameNode
* @return TypeClass the TypeClass object (possibly not fully constructed) or null if a resolution can't be made.
*/
TypeClass resolveClassName(ParseTreeNode qualifiedClassNameNode) {
return resolveClassName(qualifiedClassNameNode, currentModuleTypeInfo, compiler);
}
/**
* Verifies that the type class referred to actually exists.
* If the type class referred to has its module name omitted, but the module name
* can be inferred, then the module name is written into the ParseTreeNode as
* a side effect, so that subsequent code can assume a fully qualified name.
* @param qualifiedClassNameNode the node containing the qualified type class name to be resolved.
* @param currentModuleTypeInfo the type info for the current module.
* @param compiler the CALCompiler instance for error reporting.
* @return TypeClass the TypeClass object (possibly not fully constructed) or null if a resolution can't be made.
*/
static TypeClass resolveClassName(ParseTreeNode qualifiedClassNameNode, ModuleTypeInfo currentModuleTypeInfo, CALCompiler compiler) {
return resolveClassName(qualifiedClassNameNode, currentModuleTypeInfo, compiler, false);
}
/**
* Verifies that the type class referred to actually exists.
* If the type class referred to has its module name omitted, but the module name
* can be inferred, then the module name is written into the ParseTreeNode as
* a side effect, so that subsequent code can assume a fully qualified name.
* @param qualifiedClassNameNode the node containing the qualified type class name to be resolved.
* @param currentModuleTypeInfo the type info for the current module.
* @param compiler the CALCompiler instance for error reporting.
* @param suppressErrorMessageLogging whether to suppress the logging of error messages to the CALCompiler instance
* @return TypeClass the TypeClass object (possibly not fully constructed) or null if a resolution can't be made.
*/
static TypeClass resolveClassName(ParseTreeNode qualifiedClassNameNode, ModuleTypeInfo currentModuleTypeInfo, CALCompiler compiler, boolean suppressErrorMessageLogging) {
qualifiedClassNameNode.verifyType(CALTreeParserTokenTypes.QUALIFIED_CONS);
ParseTreeNode moduleNameNode = qualifiedClassNameNode.firstChild();
String maybeModuleName = ModuleNameUtilities.resolveMaybeModuleNameInParseTree(moduleNameNode, currentModuleTypeInfo, compiler, suppressErrorMessageLogging);
ParseTreeNode classNameNode = moduleNameNode.nextSibling();
String className = classNameNode.getText();
ModuleName currentModuleName = currentModuleTypeInfo.getModuleName();
if (maybeModuleName.length() > 0) {
//an explicitly qualified type class name
ModuleName moduleName = ModuleName.make(maybeModuleName);
if (moduleName.equals(currentModuleName)) {
//the type class must be in the current module
TypeClass typeClass = currentModuleTypeInfo.getTypeClass(className);
if (typeClass == null) {
if (!suppressErrorMessageLogging) {
// The class {qualifiedClassName} does not exist in {currentModuleName}.
QualifiedName qualifiedClassName = QualifiedName.make(moduleName, className);
compiler.logMessage(new CompilerMessage(classNameNode, new MessageKind.Error.ClassDoesNotExistInModule(qualifiedClassName.getQualifiedName(), currentModuleName)));
}
return null;
}
checkResolvedTypeClassReference(compiler, qualifiedClassNameNode, typeClass.getName(), suppressErrorMessageLogging);
//Success- the type class was found in the current module's environment
return typeClass;
}
//the class must be in an imported module
ModuleTypeInfo importedModuleTypeInfo = currentModuleTypeInfo.getImportedModule(moduleName);
if (importedModuleTypeInfo == null) {
if (!suppressErrorMessageLogging) {
// The module {moduleName} has not been imported into {currentModuleName}.
compiler.logMessage(new CompilerMessage(moduleNameNode, new MessageKind.Error.ModuleHasNotBeenImported(moduleName, currentModuleName)));
}
return null;
}
TypeClass typeClass = importedModuleTypeInfo.getTypeClass(className);
if (typeClass == null) {
if (!suppressErrorMessageLogging) {
QualifiedName qualifiedClassName = QualifiedName.make(moduleName, className);
// The class {qualifiedClassName} does not exist.
compiler.logMessage(new CompilerMessage(classNameNode, new MessageKind.Error.TypeClassDoesNotExist(qualifiedClassName.getQualifiedName())));
}
return null;
} else if (!currentModuleTypeInfo.isEntityVisible(typeClass)) {
if (!suppressErrorMessageLogging) {
//"The class {0} is not visible in module {1}."
compiler.logMessage(new CompilerMessage(classNameNode, new MessageKind.Error.TypeClassNotVisible(typeClass, currentModuleName)));
}
return null;
}
checkResolvedTypeClassReference(compiler, qualifiedClassNameNode, typeClass.getName(), suppressErrorMessageLogging);
//Success- the module is indeed imported and has a visible type class of the specified name.
return typeClass;
}
//An unqualified type class
TypeClass typeClass = currentModuleTypeInfo.getTypeClass(className);
if (typeClass != null) {
checkResolvedTypeClassReference(compiler, qualifiedClassNameNode, typeClass.getName(), suppressErrorMessageLogging);
//The type class is defined in the current module
ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, currentModuleName);
return typeClass;
}
//We now know that the type class can't be defined within the current module.
//check if it is a "using typeClass" and then patch up the module name.
ModuleName usingModuleName = currentModuleTypeInfo.getModuleOfUsingTypeClass(className);
if (usingModuleName != null) {
final QualifiedName qualifiedClassName = QualifiedName.make(usingModuleName, className);
checkResolvedTypeClassReference(compiler, qualifiedClassNameNode, qualifiedClassName, suppressErrorMessageLogging);
ModuleNameUtilities.setModuleNameIntoParseTree(moduleNameNode, usingModuleName);
//this call is guaranteed to return a type class due to earlier static checks on the using clause
return currentModuleTypeInfo.getVisibleTypeClass(qualifiedClassName);
}
if (!suppressErrorMessageLogging) {
//We now know that the type class can't be defined within the current module and is not a "using typeClass".
//Check if it is defined in another module.
//This will be an error since the user must supply a module qualification, but we
//can attempt to give a good error message.
List<QualifiedName> candidateEntityNames = new ArrayList<QualifiedName>();
int nImportedModules = currentModuleTypeInfo.getNImportedModules();
for (int i = 0; i < nImportedModules; ++i) {
TypeClass candidate = currentModuleTypeInfo.getNthImportedModule(i).getTypeClass(className);
if (candidate != null && currentModuleTypeInfo.isEntityVisible(candidate)) {
candidateEntityNames.add(candidate.getName());
}
}
int numCandidates = candidateEntityNames.size();
if(numCandidates == 0) {
// Attempt to use undefined identifier {varName}.
compiler.logMessage(new CompilerMessage(classNameNode, new MessageKind.Error.AttemptToUseUndefinedClass(className)));
} else if(numCandidates == 1) {
QualifiedName candidateName = candidateEntityNames.get(0);
// Attempt to use undefined class {className}. Was {candidateName} intended?
compiler.logMessage(new CompilerMessage(classNameNode, new MessageKind.Error.AttemptToUseUndefinedClassSuggestion(className, candidateName)));
} else {
// The reference to the class {className} is ambiguous. It could mean any of {candidateEntityNames}.
compiler.logMessage(new CompilerMessage(classNameNode, new MessageKind.Error.AmbiguousClassReference(className, candidateEntityNames)));
}
}
return null;
}
/**
* Performs late static checks on a type class reference that has already been resolved.
*
* Currently, this performs a deprecation check on a type class reference, logging a warning message if the type class is deprecated.
* @param compiler the compiler instance.
* @param nameNode the parse tree node representing the reference.
* @param qualifiedName the qualified name of the reference.
* @param suppressCompilerMessageLogging whether to suppress message logging.
*/
static void checkResolvedTypeClassReference(final CALCompiler compiler, final ParseTreeNode nameNode, final QualifiedName qualifiedName, final boolean suppressCompilerMessageLogging) {
if (!suppressCompilerMessageLogging) {
if (compiler.getDeprecationScanner().isTypeClassDeprecated(qualifiedName)) {
compiler.logMessage(new CompilerMessage(nameNode, new MessageKind.Warning.DeprecatedTypeClass(qualifiedName)));
}
}
}
}