/*
* 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.
*/
/*
* TypeConstructor.java
* Created: July 24, 2001
* By: Bo Ilic
*/
package org.openquark.cal.compiler;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import org.openquark.cal.internal.serialization.ModuleSerializationTags;
import org.openquark.cal.internal.serialization.RecordInputStream;
import org.openquark.cal.internal.serialization.RecordOutputStream;
import org.openquark.cal.internal.serialization.RecordInputStream.RecordHeaderInfo;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.util.ArrayMap;
/**
* Represents type constructors in CAL e.g. "Cal.Core.Prelude.Int", "Cal.Core.Prelude.Maybe".
* <P>
* Creation date: (July 24, 2001)
* @author Bo Ilic
*/
public final class TypeConstructor extends ScopedEntity {
private static final int serializationSchema = 0;
/**
* This class is used to store information about a deriving clause element
* It records the name of class instance and the position in the source code
* of that name
* @author mbyne
*/
final static class DerivingClauseInfo {
/** the name of the type - this cannot be null*/
final private QualifiedName name;
/** the source range of the deriving type - this may be null*/
final private SourceRange range;
DerivingClauseInfo(QualifiedName name, SourceRange range) {
if (name == null) {
throw new NullPointerException("The argument name cannot be null");
}
this.name = name;
this.range = range;
}
/** get the source range of the name, this may be null if it is not available*/
SourceRange getSourceRange() {
return range;
}
/** get the name of the class*/
QualifiedName getName() {
return name;
}
}
/**
* The array of possible record tags used in calls to {@link RecordInputStream#findRecord(short[])} by
* the {@link #loadInit} and {@link #loadFinal} methods.
*/
private static final short[] TYPE_CONSTRUCTOR_ENTITY_RECORD_TAGS = new short[] {
ModuleSerializationTags.TYPE_CONSTRUCTOR_ENTITY,
ModuleSerializationTags.FOREIGN_TYPE_CONSTRUCTOR_ENTITY
};
/**
* the kind of this TypeConstructor. For example, Prelude.List has kind * -> * while Prelude.Ordering has kind *.
* For type constuctor entities that have successfully passed type checking, this will not have any kind variables in it.
*/
private KindExpr kindExpr;
/**
* extra information about the Java implementation of this type if it was defined using a foreign data declaration.
* null if this is not a foreign type.
*/
private ForeignTypeInfo foreignTypeInfo;
/**
* (String -> DataConstructor)
* the data constructors defined in the data declaration for the type. They are ordered in the same
* order as in the data declaration.
*/
private final ArrayMap<String, DataConstructor> dataConstructorMap;
/**
* Names and positions of the type classes appearing in the "deriving" clause of the type.
* These define the instances that are automatically derived for the type.
* They are ordered in the order that they appear textually in the deriving clause.
* There are no duplicates.
*/
private DerivingClauseInfo[] derivingClauseTypeClassInfos;
static private final DerivingClauseInfo[] NO_DERIVING_CLAUSE = new DerivingClauseInfo[0];
/** the TypeConstructor for the Prelude.Function type */
static final TypeConstructor FUNCTION = new TypeConstructor(CAL_Prelude.TypeConstructors.Function, Scope.PUBLIC, KindExpr.makeKindChain(3), null, NO_DERIVING_CLAUSE, null);
/**
* map from the QualifiedName of the built-in type constructor to its TypeConstructor. Note: the name is an internal name for the
* type constructors that do not have CAL source names.
*/
private static final Map<QualifiedName, TypeConstructor> builtInTypeConstructors = makeBuiltInTypeConstructors();
/**
* TypeConstructor constructor comment.
* @param name
* @param scope
* @param kindExpr
* @param foreignTypeInfo
* @param derivingClauseTypeClassNames
* @param calDocComment
*/
private TypeConstructor(QualifiedName name, Scope scope,
KindExpr kindExpr, ForeignTypeInfo foreignTypeInfo,
DerivingClauseInfo[] derivingClauseTypeClassNames,
CALDocComment calDocComment) {
super(name, scope, calDocComment);
if (kindExpr == null || derivingClauseTypeClassNames == null) {
throw new NullPointerException();
}
if (foreignTypeInfo != null && kindExpr != KindExpr.STAR) {
throw new IllegalArgumentException ("foreign type constructors must have kind *.");
}
this.kindExpr = kindExpr;
this.foreignTypeInfo = foreignTypeInfo;
this.derivingClauseTypeClassInfos = derivingClauseTypeClassNames;
this.dataConstructorMap = new ArrayMap<String, DataConstructor>();
}
// zero argument constructor used for serialization.
private TypeConstructor() {
this.dataConstructorMap = new ArrayMap<String, DataConstructor>();
}
/**
* The factory method for TypeConstructor.
* @param name
* @param scope
* @param kindExpr
* @param foreignTypeInfo use null if this is not a foreign data type.
* @param derivingClauseTypeClassNames
* @param calDocComment the CALDoc associated with this entity, or null if there is none.
* @return TypeConstructor
*/
static final TypeConstructor makeTypeConstructor(QualifiedName name, Scope scope, KindExpr kindExpr, ForeignTypeInfo foreignTypeInfo, DerivingClauseInfo[] derivingClauseTypeClassNames, CALDocComment calDocComment) {
TypeConstructor typeCons = builtInTypeConstructors.get(name);
if (typeCons != null) {
return typeCons;
}
return new TypeConstructor (name, scope, kindExpr, foreignTypeInfo, derivingClauseTypeClassNames, calDocComment);
}
/**
* The kind of this TypeConstructor.
* For example, the kind of Prelude.Function this will be * -> * -> *.
* The kind of Prelude.Maybe is * -> *.
* The kind of Prelude.Int it is *.
* One important point to note is that the kind of a TypeConstructor will not involve kind variables.
* @return KindExpr
*/
KindExpr getKindExpr() {
return kindExpr;
}
/**
* The number of type variable arguments taken by the fully saturated type constructor.
* For example,
* the type arities of Int, String, Char are 0
* the type arity of List is 1.
* the type arity of Either is 2.
*
* For example, if the kind is *->*->*, then this is 2.
* @return int the number of type variable arguments taken by the fully saturated type constructor.
*/
public int getTypeArity() {
return kindExpr.getNArguments();
}
/**
* @return ForeignTypeInfo information about the Java type corresponding to this foreign type if
* this is a foreign type, or null if not a foreign type.
*/
public final ForeignTypeInfo getForeignTypeInfo() {
return foreignTypeInfo;
}
/**
* Returns a map from the built-in type names to their TypeConstructor objects.
* All built-in types must belong to the Prelude module.
*
* "Cal.Core.Prelude.Function a b" is equivalent to "a -> b"
* Type application uses the special syntax of juxtiposition e.g. Maybe Int.
*
* @return Map (QualifiedName->TypeConstructor)
*/
private static final Map<QualifiedName, TypeConstructor> makeBuiltInTypeConstructors() {
Map<QualifiedName, TypeConstructor> tcs = new LinkedHashMap<QualifiedName, TypeConstructor> ();
//reason for being built-in: Function is neither an algebraic type, nor a foreign type. It is truly something special.
addBuiltInTypeConstructor(tcs, TypeConstructor.FUNCTION);
return tcs;
}
/**
* Method addBuiltInTypeConstructor.
* @param tcs
* @param typeConstructor
*/
private static final void addBuiltInTypeConstructor(Map<QualifiedName, TypeConstructor> tcs, TypeConstructor typeConstructor){
tcs.put(typeConstructor.getName(), typeConstructor);
}
/**
* Adds all the built-in types to the ModuleTypeInfo object.
* @param moduleTypeInfo the module in which to add the built-in types to. Should be the Prelude module.
*/
static final void addBuiltInTypes(ModuleTypeInfo moduleTypeInfo) {
if (!moduleTypeInfo.getModuleName().equals(CAL_Prelude.MODULE_NAME)) {
throw new IllegalArgumentException ("the built-in type constructors belong to the Prelude module.");
}
for (final QualifiedName qualifiedName : builtInTypeConstructors.keySet()) {
moduleTypeInfo.addTypeConstructor(builtInTypeConstructors.get(qualifiedName));
}
}
/**
* Note: all built-in types belong to the Prelude module.
* @param typeName name of the built-in type.
* @return TypeConstructor entity for the built-in type of null if it does not exist
*/
static final TypeConstructor getBuiltInType(QualifiedName typeName) {
return builtInTypeConstructors.get(typeName);
}
/**
* Call this method when kind checking for this TypeConstructor is complete and
* prior to kind checking later dependency groups.
* The effect is to ground the remaining kind variables by * after kind inference
* has determined the most general kind of this TypeConstructor. This is because CAL does
* not support polymorphic kinds.
*/
void finishedKindChecking() {
kindExpr = kindExpr.bindKindVariablesToConstant();
}
/**
* @return textual description of this type constructor entity.
*/
@Override
public String toString() {
return super.toString() + " hasKind " + kindExpr + " foreignInfo " + foreignTypeInfo;
}
/**
* Method getDataConstructor.
* @param dataConstructorName the unqualified data constructor name constructing values of this type
* @return DataConstructor null if this type does not define a data constructor having the given name
*/
public DataConstructor getDataConstructor(String dataConstructorName) {
return dataConstructorMap.get(dataConstructorName);
}
/**
* Method getNDataConstructors.
* @return int the number of data constructors defined for this type in its data declaration. Will be 0 for foreign types.
*/
public int getNDataConstructors() {
return dataConstructorMap.size();
}
/**
* Method getNthDataConstructor.
* @param n zero based index
* @return DataConstructor the nth data constructor defined in the data declaration for the type.
*/
public DataConstructor getNthDataConstructor(int n) {
return dataConstructorMap.getNthValue(n);
}
/**
* Method addDataConstructor.
* @param dataConstructor as defined within a data declaration for this type. Add in order of their declaration.
*/
void addDataConstructor(DataConstructor dataConstructor) {
QualifiedName dataConstructorName = dataConstructor.getName();
if (!getName().getModuleName().equals(dataConstructorName.getModuleName())) {
throw new IllegalArgumentException("the data constructor added must belong to the same module as the type.");
}
if(dataConstructorMap.put(dataConstructorName.getUnqualifiedName(), dataConstructor) != null) {
throw new IllegalArgumentException("The type " + this + " already defines the data constructor " + dataConstructorName.getUnqualifiedName() + ".");
}
}
/**
* Note: do not expose as public. Clients do not need to know if an instance declaration is defined via a deriving clause
* or via a "regular" instance declaration.
*
* @return int the number of type class names in the deriving clause. May be 0.
*/
int getNDerivedInstances() {
return derivingClauseTypeClassInfos.length;
}
/**
* Note: do not expose as public. Clients do not need to know if an instance declaration is defined via a deriving clause
* or via a "regular" instance declaration.
*
* @param n index where 0 <= n < getNDerivedInstances.
* @return QualifiedName the type class name.
*/
QualifiedName getDerivingClauseTypeClassName(int n) {
return derivingClauseTypeClassInfos[n].getName();
}
/**
* Note: do not expose as public. Clients do not need to know if an instance declaration is defined via a deriving clause
* or via a "regular" instance declaration.
*
* returns the source range for the code that requests the derived instance.
*
* @param n index where 0 <= n < getNDerivedInstances.
* @return the source range of the declaration
*/
SourceRange getDerivingClauseTypeClassPosition(int n) {
if (derivingClauseTypeClassInfos[n].getSourceRange() == null)
return null;
return derivingClauseTypeClassInfos[n].getSourceRange();
}
boolean hasDerivingClause(QualifiedName typeClassName) {
for (int i = 0, nDerivedInstances = getNDerivedInstances(); i < nDerivedInstances; ++i) {
if (getDerivingClauseTypeClassName(i).equals(typeClassName)) {
return true;
}
}
return false;
}
/**
* Write this instance of TypeConstructor to the RecordOutputStream
* @param s
* @throws IOException
*/
@Override
void write (RecordOutputStream s) throws IOException {
if (foreignTypeInfo == null) {
s.startRecord(ModuleSerializationTags.TYPE_CONSTRUCTOR_ENTITY, serializationSchema);
} else {
s.startRecord(ModuleSerializationTags.FOREIGN_TYPE_CONSTRUCTOR_ENTITY, serializationSchema);
}
super.writeContent(s);
kindExpr.write(s);
if (foreignTypeInfo != null) {
try {
foreignTypeInfo.write(s);
} catch (UnableToResolveForeignEntityException e) {
// If we are serializing the info, then it must not have been originally deserialized lazily
final IllegalStateException illegalStateException = new IllegalStateException("Java entities in the ForeignTypeInfo should have been eagerly resolved during the compilation process");
illegalStateException.initCause(e);
throw illegalStateException;
}
}
final int nDataConstructors = dataConstructorMap.size();
s.writeShortCompressed(nDataConstructors);
for (int i = 0; i < nDataConstructors; ++i) {
DataConstructor dc = dataConstructorMap.getNthValue(i);
dc.write(s);
}
//serialize the field derivingClauseTypeClassNames
final int nDerivingClauseTypeClassNames = derivingClauseTypeClassInfos.length;
s.writeShortCompressed(nDerivingClauseTypeClassNames);
for (int i = 0; i < nDerivingClauseTypeClassNames; ++i) {
QualifiedName typeClassName = derivingClauseTypeClassInfos[i].getName();
s.writeQualifiedName(typeClassName);
}
s.endRecord();
}
/**
* Load an instance of TypeConstructor from the RecordInputStream
* This simply creates an instance of the TypeConstructor and
* loads the members of the ScopedEntityImpl base class. The rest of
* the member have to be resolved through loadFinal().
* This is done so that the set of TypeConstructor instances can be
* build up before loading the members that can have mutually recursive and out-of-order
* references.
* @param s
* @param mti
* @param msgLogger the logger to which to log deserialization messages.
* @return the instance of TypeConstructor with members of the ScopedEntityImpl base class loaded
* @throws IOException
*/
static TypeConstructor loadInit (RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException {
RecordHeaderInfo rhi = s.findRecord(TYPE_CONSTRUCTOR_ENTITY_RECORD_TAGS);
if (rhi == null) {
throw new IOException ("Unable to find TypeConstructor record.");
}
DeserializationHelper.checkSerializationSchema(rhi.getSchema(), serializationSchema, mti.getModuleName(), "TypeConstructor", msgLogger);
TypeConstructor typeCons = new TypeConstructor();
try {
// Note we must do a reassignment of the local variable tce since readInitContent
// may return a different TypeConstructor if this is a built-in type.
typeCons = typeCons.readInitContent(s, rhi.getSchema(), mti, msgLogger);
} catch (IOException e) {
QualifiedName qn = typeCons.getName();
throw new IOException ("Error loading TypeConstructor " + (qn == null ? "" : qn.getQualifiedName()) + ": " + e.getLocalizedMessage());
}
return typeCons;
}
/**
* Load an instance of TypeConstructor from the RecordInputStream
* This simply creates an instance of the TypeConstructor and
* loads the members of the ScopedEntityImpl base class. The rest of
* the member have to be resolved through finalLoad().
* @param s
* @param mti
* @param msgLogger the logger to which to log deserialization messages.
* @throws IOException
*/
void loadFinal (RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException {
RecordHeaderInfo rhi = s.findRecord(TYPE_CONSTRUCTOR_ENTITY_RECORD_TAGS);
if (rhi == null) {
throw new IOException ("Unable to find TypeConstructor record.");
}
DeserializationHelper.checkSerializationSchema(rhi.getSchema(), serializationSchema, mti.getModuleName(), "TypeConstructor", msgLogger);
try {
super.readContent (s, mti, msgLogger);
// QualifiedName
QualifiedName name = this.getName();
TypeConstructor builtIn = getBuiltInType(name);
if (builtIn != null) {
s.skipRestOfRecord();
return;
}
// kindExpr
kindExpr = KindExpr.load(s, mti.getModuleName(), msgLogger);
// ForeignTypeInfo
if (rhi.getRecordTag() == ModuleSerializationTags.FOREIGN_TYPE_CONSTRUCTOR_ENTITY) {
foreignTypeInfo = ForeignTypeInfo.load(s, mti.getModuleName(), mti.getModule().getForeignClassLoader(), msgLogger);
}
int nDCs = s.readShortCompressed();
for (int i = 0; i < nDCs; ++i) {
DataConstructor dc = DataConstructor.load(s, mti, msgLogger);
addDataConstructor(dc);
}
//load the field derivingClauseTypeClassNames
final int nDerivingClauseTypeClassNames = s.readShortCompressed();
if (nDerivingClauseTypeClassNames == 0) {
derivingClauseTypeClassInfos = NO_DERIVING_CLAUSE;
} else {
derivingClauseTypeClassInfos = new DerivingClauseInfo[nDerivingClauseTypeClassNames];
for (int i = 0; i < nDerivingClauseTypeClassNames; ++i) {
QualifiedName typeClassName = s.readQualifiedName();
derivingClauseTypeClassInfos[i] = new DerivingClauseInfo(typeClassName, null);
}
}
s.skipRestOfRecord();
} catch (IOException e) {
QualifiedName qn = getName();
throw new IOException ("Error loading TypeConstructor " + (qn == null ? "" : qn.getQualifiedName()) + ": " + e.getLocalizedMessage());
}
}
/**
* Do an initial read to establish the existence of this TypeConstructor. The reading of the
* content has to be split into two stages since you can have a closely connected set of TypeConstructor.
* @param s - the RecordInputStream
* @param schema - schema of the record in the stream
* @param mti - current ModuleTypeInfo
* @param msgLogger the logger to which to log deserialization messages.
* @return - 'this' if the type is not a built-in. Otherwise returns the built-in TypeConstructor.
* @throws IOException
*/
private TypeConstructor readInitContent (RecordInputStream s, int schema, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException {
super.readContent (s, mti, msgLogger);
// QualifiedName
QualifiedName name = this.getName();
TypeConstructor builtIn = getBuiltInType(name);
if (builtIn != null) {
s.skipRestOfRecord();
return builtIn;
}
// Now that we've loaded the name of the TypeConstructor we can add it to the ModuleTypeInfo.
mti.addTypeConstructor(this);
s.skipRestOfRecord();
return this;
}
}