/*
* 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.
*/
/*
* TypeApp.java
* Created: May 11, 2006
* By: Bo Ilic
*/
package org.openquark.cal.compiler;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import org.openquark.cal.compiler.SourceModel.TypeExprDefn;
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.module.Cal.Core.CAL_Prelude;
/**
* Models a type application of two type expressions i.e.
* <code>operatorType operandType</code>
* <p>
* Note that TypeApp was originally introduced to deal with the sorts of types
* that arise in the declaration of certain type classes where higher-kinded type variables
* are involved. For example, the type of the map method of the Functor type class:
* <pre>
* map :: Functor f => (a -> b) -> (f a -> f b)
* </pre>
* The type application 'f a' does not have a TypeConsApp as its root.
* <p>
* One consequence of this is that there is an ambiguity in how types are represented, even not
* considering instantiated type variables. For example, [a] could be represented as
* <pre>
* TypeConsApp Prelude.List (TypeVar a)
* </pre>
* or
* <pre>
* TypeApp (TypeConsApp Prelude.List) (TypeVar a)
* </pre>
*
* @author Bo Ilic
*/
public final class TypeApp extends TypeExpr {
private static final int serializationSchema = 0;
/** the type operatorType in the type application (operatorType operandType). Cannot be null. */
private TypeExpr operatorType;
/** the type operandType in the type application (operatorType operandType). Cannot be null. */
private TypeExpr operandType;
TypeApp(TypeExpr operatorType, TypeExpr operandType) {
if (operatorType == null) {
throw new NullPointerException("operatorType cannot be null");
}
if (operandType == null) {
throw new NullPointerException("operandType cannot be null");
}
this.operatorType = operatorType;
this.operandType = operandType;
}
/** private constructor for use by the serialization code only */
private TypeApp() {
}
/** @return the type operatorType in type application (operatorType operandType). Cannot be null. */
public TypeExpr getOperatorType() {
return operatorType;
}
/** @return the type operandType in type application (operatorType operandType). Cannot be null. */
public TypeExpr getOperandType() {
return operandType;
}
/** {@inheritDoc} */
@Override
public boolean containsTypeExpr(TypeExpr searchTypeExpr) {
if (this == searchTypeExpr) {
return true;
}
return operatorType.containsTypeExpr(searchTypeExpr) ||
operandType.containsTypeExpr(searchTypeExpr);
}
/** {@inheritDoc} */
@Override
boolean containsRecordVar(RecordVar searchRecordVar) {
return operatorType.containsRecordVar(searchRecordVar) ||
operandType.containsRecordVar(searchRecordVar);
}
/** {@inheritDoc} */
@Override
public boolean isPolymorphic() {
return operatorType.isPolymorphic() || operandType.isPolymorphic();
}
/** {@inheritDoc} */
@Override
void getGenericClassConstrainedPolymorphicVars(Set<PolymorphicVar> varSet, NonGenericVars nonGenericVars) {
operatorType.getGenericClassConstrainedPolymorphicVars(varSet, nonGenericVars);
operandType.getGenericClassConstrainedPolymorphicVars(varSet, nonGenericVars);
}
/** {@inheritDoc} */
@Override
void getUninstantiatedTypeVars(Set<TypeVar> varSet) {
operatorType.getUninstantiatedTypeVars(varSet);
operandType.getUninstantiatedTypeVars(varSet);
}
/** {@inheritDoc} */
@Override
void getUninstantiatedRecordVars(Set<RecordVar> varSet) {
operatorType.getUninstantiatedRecordVars(varSet);
operandType.getUninstantiatedRecordVars(varSet);
}
/** {@inheritDoc} */
@Override
public TypeExpr getCorrespondingTypeExpr(TypeExpr correspondingSuperType, TypeExpr typeToFind) {
if (this == typeToFind) {
return correspondingSuperType;
}
TypeApp correspondingTypeApp = correspondingSuperType.rootTypeApp();
if (correspondingTypeApp == null) {
return null;
}
TypeExpr correspondingType = operatorType.getCorrespondingTypeExpr(correspondingTypeApp.operatorType, typeToFind);
if (correspondingType != null) {
return correspondingType;
}
return operandType.getCorrespondingTypeExpr(correspondingTypeApp.operandType, typeToFind);
}
/** {@inheritDoc} */
@Override
public int getArity() {
//recognize the special forms:
//(TypeApp (TypeConsApp Prelude.Function te1) te2)
//and
//(TypeApp (TypeApp (TypeConsApp Prelude.Function) te1) te2)
TypeExpr operatorType = this.getOperatorType().prune();
if (operatorType instanceof TypeConsApp) {
//recognize the form (TypeApp (TypeConsApp Prelude.Function e1) (e2))
TypeConsApp typeConsApp = (TypeConsApp)operatorType;
if (typeConsApp.getName().equals(CAL_Prelude.TypeConstructors.Function) && typeConsApp.getNArgs() == 1) {
return 1 + this.getOperandType().getArity();
}
} else if (operatorType instanceof TypeApp) {
//recognize the form (TypeApp (TypeApp (TypeConsApp Prelude.Function) te1) te2)
TypeApp typeApp = (TypeApp)operatorType;
TypeExpr nextOperatorType = typeApp.getOperatorType().prune();
if (nextOperatorType instanceof TypeConsApp) {
TypeConsApp typeConsApp = (TypeConsApp)nextOperatorType;
if (typeConsApp.getName().equals(CAL_Prelude.TypeConstructors.Function) && typeConsApp.getNArgs() == 0) {
return 1 + this.getOperandType().getArity();
}
}
}
return 0;
}
/** {@inheritDoc} */
@Override
void patternMatch(TypeExpr anotherTypeExpr, PatternMatchContext patternMatchContext) throws TypeException {
if (anotherTypeExpr instanceof TypeVar) {
TypeVar anotherTypeVar = (TypeVar) anotherTypeExpr;
//anotherTypeVar must be uninstantiated.
if (anotherTypeVar.getInstance()!= null) {
throw new IllegalArgumentException("TypeApp.patternMatch: programming error.");
}
//Can't instantiate a type variable from the declared type expression.
//For example, this error occurs with the following declaration.
//funnyHead :: [a] -> Prelude.Char;
//public funnyHead = List.head;
if (patternMatchContext.isUninstantiableTypeVar(anotherTypeVar)) {
throw new TypeException("Attempt to instantiate a type variable from the declared type.");
}
//can't pattern match a nongeneric type variable to a type involving uninstantiated type variables.
//For example, for the function:
//g x = let y :: [a]; y = x; in y;
//we attempt to instantiate a non-generic type variables (corresponding to x) to [a].
//It is OK though to instantiate to a concrete type e.g.
//g x = let y :: [Int]; y = x; in y;
NonGenericVars nonGenericVars = patternMatchContext.getNonGenericVars();
if (nonGenericVars != null &&
!nonGenericVars.isGenericTypeVar(anotherTypeVar) &&
!this.getUninstantiatedTypeVars().isEmpty()) {
throw new TypeException("Attempt to match a non-generic type variable to a type involving type variables.");
}
// check for infinite types e.g. an attempt to pattern match a to (a->Int).
if (containsUninstantiatedTypeVar(anotherTypeVar)) {
throw new TypeException("Type clash: attempt to create an infinite type.");
}
if (anotherTypeVar.noClassConstraints()) {
anotherTypeVar.setInstance(this);
return;
}
//type variable with constraints needs more complicated handling
int nArgs = 1;
TypeExpr operatorType = getOperatorType().prune();
while (operatorType instanceof TypeApp) {
++nArgs;
operatorType = ((TypeApp)operatorType).getOperatorType().prune();
}
if (!(operatorType instanceof TypeConsApp)) {
//for example,
//patternMatch (TypeApp a b, Eq c => c)
//will result in a type clash.
throw new TypeException("Type clash: a constrained type variable cannot match a type application not rooted in a type constructor.");
}
TypeConsApp rootTypeConsApp = (TypeConsApp)operatorType;
nArgs += rootTypeConsApp.getNArgs();
TypeExpr[] args = new TypeExpr[nArgs];
//fill in the arguments from the chain of TypeApp nodes.
int currentArgIndex = nArgs - 1;
operatorType = getOperatorType().prune();
while (operatorType instanceof TypeApp) {
TypeApp typeAppOperatorType = (TypeApp)operatorType;
args[currentArgIndex] = typeAppOperatorType.getOperandType();
operatorType = typeAppOperatorType.getOperatorType().prune();
--currentArgIndex;
}
//fill in the renaming arguments from the partially saturated TypeConsApp.
System.arraycopy(rootTypeConsApp.getArgs(), 0, args, 0, rootTypeConsApp.getNArgs());
TypeConsApp typeConsApp = new TypeConsApp(rootTypeConsApp.getRoot(), args);
typeConsApp.patternMatch(anotherTypeVar, patternMatchContext);
return;
} else if (anotherTypeExpr instanceof TypeConsApp) {
TypeConsApp anotherTypeConsApp = (TypeConsApp) anotherTypeExpr;
int nArgs = anotherTypeConsApp.getNArgs();
if (nArgs == 0) {
throw new TypeException("Type clash: type " + toString() + " is not a specialization of " + anotherTypeConsApp + ".");
}
TypeExpr[] partialArgs = new TypeExpr[nArgs - 1];
System.arraycopy(anotherTypeConsApp.getArgs(),0, partialArgs, 0, nArgs - 1);
TypeConsApp partialTypeConsApp = new TypeConsApp(anotherTypeConsApp.getRoot(), partialArgs);
getOperatorType().prune().patternMatch(partialTypeConsApp, patternMatchContext);
getOperandType().prune().patternMatch(anotherTypeConsApp.getArg(nArgs - 1).prune(), patternMatchContext);
return;
} else if (anotherTypeExpr instanceof TypeApp) {
TypeApp anotherTypeApp = (TypeApp)anotherTypeExpr;
getOperatorType().prune().patternMatch(anotherTypeApp.getOperatorType().prune(), patternMatchContext);
getOperandType().prune().patternMatch(anotherTypeApp.getOperandType().prune(), patternMatchContext);
return;
} else if (anotherTypeExpr instanceof RecordType) {
throw new TypeException("Type clash: The type declaration " + toString() + " does not match the record type " + anotherTypeExpr + ".");
}
throw new IllegalStateException();
}
/**
* Given (e1 e2 ... en) represented using TypeApp objects i.e.
* (TypeApp ... (TypeApp (TypeApp e1 e2) e3 ... en)
* Then we return the array [e1, e2, ..., en]. Note that e1, ..., en will be pruned and that this method prunes operators
* in recognizing the form (e1 e2 ... en)
* @return the expressions being applied (including the leftmost operator) in a chain of TypeApp nodes.
*/
private TypeExpr[] buildAppExpressions() {
int nArgs = 1;
TypeExpr operatorType = getOperatorType().prune();
while (operatorType instanceof TypeApp) {
++nArgs;
operatorType = ((TypeApp)operatorType).getOperatorType().prune();
}
TypeExpr[] appExpressions = new TypeExpr[nArgs + 1];
//fill in the arguments from the chain of TypeApp nodes.
int currentArgIndex = nArgs;
operatorType = this;
while (operatorType instanceof TypeApp) {
TypeApp typeAppOperatorType = (TypeApp)operatorType;
appExpressions[currentArgIndex] = typeAppOperatorType.getOperandType();
operatorType = typeAppOperatorType.getOperatorType().prune();
--currentArgIndex;
}
//fill in the leftmost operator
appExpressions[0] = operatorType.prune();
return appExpressions;
}
/** {@inheritDoc} */
@Override
TypeExpr prune() {
return this;
}
/** {@inheritDoc} */
@Override
TypeExpr deepPrune() {
operatorType = operatorType.deepPrune();
operandType = operandType.deepPrune();
return this;
}
/** {@inheritDoc} */
@Override
TypeExpr normalize() {
//we attempt to convert an TypeApp chain rooted in a TypeConstructor into a TypeConsApp.
//this is the normal form.
TypeExpr[] appExprs = buildAppExpressions();
TypeExpr rootExpr = appExprs[0];
if (rootExpr instanceof TypeConsApp) {
TypeConsApp rootTypeConsApp = (TypeConsApp)rootExpr;
final int nTypeConsArgs = rootTypeConsApp.getNArgs();
final int nTypeAppArgs = appExprs.length - 1;
final int nNormalizedArgs = nTypeConsArgs + nTypeAppArgs;
TypeExpr[] normalizedArgs = new TypeExpr[nNormalizedArgs];
for (int i = 0; i < nTypeConsArgs; ++i) {
normalizedArgs[i] = rootTypeConsApp.getArg(i).normalize();
}
for (int i = 0; i < nTypeAppArgs; ++i) {
normalizedArgs[i + nTypeConsArgs] = appExprs[i + 1].normalize();
}
return new TypeConsApp(rootTypeConsApp.getRoot(), normalizedArgs);
}
TypeExpr normalizedTypeApp = new TypeApp(appExprs[0].normalize(), appExprs[1].normalize());
for (int i = 2, nApps = appExprs.length; i < nApps; ++i) {
normalizedTypeApp = new TypeApp(normalizedTypeApp, appExprs[i].normalize());
}
return normalizedTypeApp;
}
/** {@inheritDoc} */
@Override
public boolean sameType(TypeExpr anotherTypeExpr) {
anotherTypeExpr = anotherTypeExpr.prune();
if (anotherTypeExpr instanceof TypeVar ||
anotherTypeExpr instanceof RecordType) {
return false;
}
if (anotherTypeExpr instanceof TypeApp ||
anotherTypeExpr instanceof TypeConsApp) {
return this.toString().equals(anotherTypeExpr.toString());
}
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override
void toSourceText(StringBuilder sb, PolymorphicVarContext polymorphicVarContext, ParenthesizationInfo parenthesizationInfo, ScopedEntityNamingPolicy namingPolicy) {
//There is an added complexity in representing forms such as
//(TypeApp Prelude.List Prelude.Int)
//(TypeApp (TypeConsApp Prelude.Function Prelude.Int) Prelude.Char)
//where special notation is used in the fully staturated case, but it is not apparent that we are in that case without
//examining deeper in the tree.
TypeExpr[] appExprs = buildAppExpressions();
TypeExpr rootExpr = appExprs[0];
if (rootExpr instanceof TypeConsApp) {
TypeConsApp rootTypeConsApp = (TypeConsApp)rootExpr;
TypeExpr[] augmentedArgs = new TypeExpr[rootTypeConsApp.getNArgs() + appExprs.length - 1];
System.arraycopy(rootTypeConsApp.getArgs(), 0, augmentedArgs, 0, rootTypeConsApp.getNArgs());
System.arraycopy(appExprs, 1, augmentedArgs, rootTypeConsApp.getNArgs(), appExprs.length - 1);
TypeConsApp augmentedRootTypeConsApp = new TypeConsApp(rootTypeConsApp.getRoot(), augmentedArgs);
augmentedRootTypeConsApp.toSourceText(sb, polymorphicVarContext, parenthesizationInfo, namingPolicy);
return;
}
// if here: type var application
final boolean reallyParenthesize = parenthesizationInfo == ParenthesizationInfo.ARG_OF_TEXTUAL_TYPE_CONS_OR_VAR;
if (reallyParenthesize) {
sb.append('(');
}
rootExpr.toSourceText(sb, polymorphicVarContext, ParenthesizationInfo.NONE, namingPolicy);
for (int i = 1, nArgs = appExprs.length; i < nArgs; ++i) {
sb.append(' ');
appExprs[i].toSourceText(sb, polymorphicVarContext, ParenthesizationInfo.ARG_OF_TEXTUAL_TYPE_CONS_OR_VAR, namingPolicy);
}
if (reallyParenthesize) {
sb.append(')');
}
}
/** {@inheritDoc} */
@Override
TypeExprDefn makeDefinitionSourceModel(PolymorphicVarContext polymorphicVarContext, ScopedEntityNamingPolicy namingPolicy) {
//There is an added complexity in representing forms such as
//(TypeApp Prelude.List Prelude.Int)
//(TypeApp (TypeConsApp Prelude.Function Prelude.Int) Prelude.Char)
//where special notation is used in the fully staturated case, but it is not apparent that we are in that case without
//examining deeper in the tree.
TypeExpr[] appExprs = buildAppExpressions();
TypeExpr rootExpr = appExprs[0];
final int nAppExprs = appExprs.length;
if (rootExpr instanceof TypeConsApp) {
TypeConsApp rootTypeConsApp = (TypeConsApp)rootExpr;
TypeExpr[] augmentedArgs = new TypeExpr[rootTypeConsApp.getNArgs() + nAppExprs - 1];
System.arraycopy(rootTypeConsApp.getArgs(), 0, augmentedArgs, 0, rootTypeConsApp.getNArgs());
System.arraycopy(appExprs, 1, augmentedArgs, rootTypeConsApp.getNArgs(), nAppExprs - 1);
TypeConsApp augmentedRootTypeConsApp = new TypeConsApp(rootTypeConsApp.getRoot(), augmentedArgs);
return augmentedRootTypeConsApp.makeDefinitionSourceModel(polymorphicVarContext, namingPolicy);
}
SourceModel.TypeExprDefn[] typeExprs = new SourceModel.TypeExprDefn[nAppExprs];
for (int i = 0; i < nAppExprs; ++i) {
typeExprs[i] = appExprs[i].makeDefinitionSourceModel(polymorphicVarContext, namingPolicy);
}
return SourceModel.TypeExprDefn.Application.make(typeExprs);
}
/** {@inheritDoc} */
@Override
int unifyType(TypeExpr anotherTypeExpr, ModuleTypeInfo contextModuleTypeInfo) throws TypeException {
if (anotherTypeExpr instanceof TypeVar || anotherTypeExpr instanceof TypeConsApp) {
return TypeExpr.unifyType(anotherTypeExpr, this, contextModuleTypeInfo);
} else if (anotherTypeExpr instanceof TypeApp) {
TypeApp anotherTypeApp = (TypeApp)anotherTypeExpr;
int typeCloseness = 1;
typeCloseness += TypeExpr.unifyType(operatorType, anotherTypeApp.operatorType, contextModuleTypeInfo);
typeCloseness += TypeExpr.unifyType(operandType, anotherTypeApp.operandType, contextModuleTypeInfo);
return typeCloseness;
} else if (anotherTypeExpr instanceof RecordType) {
throw new TypeException("Type clash: type application " + this + " does not match a record type " + anotherTypeExpr + ".");
}
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override
public boolean usesForeignType() {
return operatorType.usesForeignType() || operandType.usesForeignType();
}
/** {@inheritDoc} */
@Override
void writeActual(RecordOutputStream s, Map<TypeExpr, Short> visitedTypeExpr, Map<RecordVar, Short> visitedRecordVar) throws IOException {
s.startRecord(ModuleSerializationTags.TYPE_APP, serializationSchema);
operatorType.write(s, visitedTypeExpr, visitedRecordVar);
operandType.write(s, visitedTypeExpr, visitedRecordVar);
s.endRecord();
}
/**
* Read an instance of TypeAppfrom the RecordInputStream.
* Read position will be after the record header.
* @param s
* @param schema
* @param mti
* @param visitedTypeExpr
* @param visitedRecordVar
* @param msgLogger the logger to which to log deserialization messages.
* @return an instance of TypeConsApp.
* @throws IOException
*/
final static TypeExpr load (RecordInputStream s, int schema, ModuleTypeInfo mti, Map<Short, TypeExpr> visitedTypeExpr, Map<Short, RecordVar> visitedRecordVar, CompilerMessageLogger msgLogger) throws IOException {
DeserializationHelper.checkSerializationSchema(schema, serializationSchema, mti.getModuleName(), "TypeApp", msgLogger);
// We now have enough information to create the TypeApp instance and
// add it to the visitedTypeExpr map. We must do this before loading the
// argument types as they may refer to this TypeApp instance.
TypeApp typeApp = new TypeApp();
visitedTypeExpr.put(new Short((short)visitedTypeExpr.size()), typeApp);
typeApp.operatorType = TypeExpr.load(s, mti, visitedTypeExpr, visitedRecordVar, msgLogger);
typeApp.operandType = TypeExpr.load(s, mti, visitedTypeExpr, visitedRecordVar, msgLogger);
s.skipRestOfRecord();
return typeApp;
}
}