/*******************************************************************************
* Copyright (c) 2009-2013 CWI
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI
* * Mark Hills - Mark.Hills@cwi.nl (CWI)
* * Arnold Lankamp - Arnold.Lankamp@cwi.nl
* * Anastasia Izmaylova - A.Izmaylova@cwi.nl - CWI
*******************************************************************************/
package org.rascalmpl.interpreter.types;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.rascalmpl.value.IConstructor;
import org.rascalmpl.value.IList;
import org.rascalmpl.value.IListWriter;
import org.rascalmpl.value.ISetWriter;
import org.rascalmpl.value.IValueFactory;
import org.rascalmpl.value.exceptions.FactTypeUseException;
import org.rascalmpl.value.exceptions.IllegalOperationException;
import org.rascalmpl.value.type.Type;
import org.rascalmpl.value.type.TypeFactory;
import org.rascalmpl.value.type.TypeFactory.TypeReifier;
import org.rascalmpl.value.type.TypeStore;
import org.rascalmpl.values.uptr.RascalValueFactory;
/**
* Function types are an extension of the pdb's type system, especially tailored to Rascal's functions
*/
public class FunctionType extends RascalType {
private final Type returnType;
private final Type argumentTypes;
private final Type keywordParameters;
private static final RascalTypeFactory RTF = RascalTypeFactory.getInstance();
/*package*/ FunctionType(Type returnType, Type argumentTypes, Type keywordParameters) {
this.argumentTypes = argumentTypes.isBottom() ? TF.tupleEmpty() : argumentTypes;
this.returnType = returnType;
this.keywordParameters = keywordParameters == null ? null : keywordParameters.isBottom() || (keywordParameters.isTuple() && keywordParameters.getArity() == 0) ? null : keywordParameters;
}
public static class Reifier implements TypeReifier {
@Override
public Type getSymbolConstructorType() {
throw new UnsupportedOperationException();
}
@Override
public Set<Type> getSymbolConstructorTypes() {
return Arrays.stream(new Type[] {
normalFunctionSymbol(),
oldNormalFunctionSymbol(),
// TODO: remove this deprecated representation. A prod type is the same as a function type
prodFunctionSymbol()
}).collect(Collectors.toSet());
}
private Type prodFunctionSymbol() {
return symbols().typeSymbolConstructor("prod", symbols().symbolADT(), "sort", TF.stringType(), "name", TF.listType(symbols().symbolADT()), "parameters", TF.setType(symbols().attrADT()), "attributes");
}
private Type normalFunctionSymbol() {
return symbols().typeSymbolConstructor("func", symbols().symbolADT(), "ret", TF.listType(symbols().symbolADT()), "parameters", TF.listType(symbols().symbolADT()), "kwTypes");
}
private Type oldNormalFunctionSymbol() {
return symbols().typeSymbolConstructor("func", symbols().symbolADT(), "ret", TF.listType(symbols().symbolADT()), "parameters");
}
@Override
public Type fromSymbol(IConstructor symbol, TypeStore store, Function<IConstructor, Set<IConstructor>> grammar) {
if (symbol.getConstructorType() == prodFunctionSymbol()) {
// TODO remove support for deprecated representation after bootstrap
Type returnType = symbols().fromSymbol((IConstructor) symbol.get("sort"), store, grammar);
Type parameters = symbols().fromSymbols((IList) symbol.get("parameters"), store, grammar);
return RTF.functionType(returnType, parameters, TF.tupleEmpty());
} else {
Type returnType = symbols().fromSymbol((IConstructor) symbol.get("ret"), store, grammar);
Type parameters = symbols().fromSymbols((IList) symbol.get("parameters"), store, grammar);
if (symbol.getConstructorType() == oldNormalFunctionSymbol()) {
return RTF.functionType(returnType, parameters, TF.tupleEmpty());
}
Type kwTypes = symbols().fromSymbols((IList) symbol.get("kwTypes"), store, grammar);
// TODO: while merging the other branch had tf.voidType()...
return RTF.functionType(returnType, parameters, kwTypes);
}
}
@Override
public boolean isRecursive() {
return true;
}
@Override
public Type randomInstance(Supplier<Type> next, TypeStore store, Random rnd) {
return RascalTypeFactory.getInstance().functionType(next.get(), randomTuple(next, store, rnd), rnd.nextBoolean() ? tf().voidType() : randomTuple(next, store, rnd));
}
@Override
public void asProductions(Type type, IValueFactory vf, TypeStore store, ISetWriter grammar,
Set<IConstructor> done) {
((FunctionType) type).getReturnType().asProductions(vf, store, grammar, done);
for (Type arg : ((FunctionType) type).getArgumentTypes()) {
arg.asProductions(vf, store, grammar, done);
}
}
@Override
public IConstructor toSymbol(Type type, IValueFactory vf, TypeStore store, ISetWriter grammar, Set<IConstructor> done) {
IListWriter w = vf.listWriter();
int i = 0;
Type args = ((FunctionType) type).getArgumentTypes();
for (Type arg : args) {
IConstructor sym = arg.asSymbol(vf, store, grammar, done);
if (args.hasFieldNames()) {
sym = symbols().labelSymbol(vf, sym, args.getFieldName(i));
}
i++;
w.append(sym);
}
IListWriter kw = vf.listWriter();
i = 0;
Type kwArgs = ((FunctionType) type).getKeywordParameterTypes();
if (kwArgs != null && !kwArgs.isBottom()) {
for (Type arg : kwArgs) {
IConstructor sym = arg.asSymbol(vf, store, grammar, done);
if (kwArgs.hasFieldNames()) {
sym = symbols().labelSymbol(vf, sym, kwArgs.getFieldName(i));
}
i++;
kw.append(sym);
}
}
return vf.constructor(normalFunctionSymbol(), ((FunctionType) type).getReturnType().asSymbol(vf, store, grammar, done), w.done(), kw.done());
}
}
@Override
public TypeReifier getTypeReifier() {
return new Reifier();
}
@Override
public boolean isFunction() {
return true;
}
@Override
public Type asAbstractDataType() {
return RascalValueFactory.Production;
}
@Override
public Type getFieldType(int i) {
return argumentTypes.getFieldType(i);
}
@Override
public Type getFieldType(String fieldName) throws FactTypeUseException {
return argumentTypes.getFieldType(fieldName);
}
@Override
public int getFieldIndex(String fieldName) {
return argumentTypes.getFieldIndex(fieldName);
}
@Override
public String getFieldName(int i) {
return argumentTypes.getFieldName(i);
}
@Override
public String[] getFieldNames() {
return argumentTypes.getFieldNames();
}
@Override
public Type getFieldTypes() {
return argumentTypes;
}
@Override
public <T, E extends Throwable> T accept(IRascalTypeVisitor<T, E> visitor) throws E {
return visitor.visitFunction(this);
}
public Type getReturnType() {
return returnType;
}
public Type getArgumentTypes() {
return argumentTypes;
}
@Override
public int getArity() {
return argumentTypes.getArity();
}
public Type getKeywordParameterTypes() {
return keywordParameters == null ? TypeFactory.getInstance().voidType() : keywordParameters;
}
public Type getKeywordParameterType(String label) {
return keywordParameters != null ? keywordParameters.getFieldType(label) : null;
}
public boolean hasKeywordParameter(String label) {
return keywordParameters != null ? keywordParameters.hasField(label) : false;
}
public boolean hasKeywordParameters() {
return keywordParameters != null;
}
@Override
protected boolean isSupertypeOf(RascalType type) {
return type.isSubtypeOfFunction(this);
}
@Override
protected Type lub(RascalType type) {
return type.lubWithFunction(this);
}
@Override
protected Type glb(RascalType type) {
return type.glbWithFunction(this);
}
@Override
public boolean isSubtypeOfFunction(RascalType other) {
// Rascal functions are co-variant in the return type position and
// contra-variant in the argument positions, such that a sub-function
// can safely simulate a super function.
FunctionType otherType = (FunctionType) other;
if (getReturnType().isSubtypeOf(otherType.getReturnType())) {
if (otherType.getArgumentTypes().isSubtypeOf(getArgumentTypes())) {
return true;
}
// type parameterized functions are never sub-types before instantiation
// because the argument types are co-variant. This would be weird since
// instantiated functions are supposed to be substitutable for their generic
// counter parts. So, we try to instantiate first, and then check again.
Map<Type,Type> bindings = new HashMap<Type,Type>();
if (!otherType.match(this, bindings)) {
return false;
}
if (bindings.size() != 0) {
return isSubtypeOf(otherType.instantiate(bindings));
}
}
return false;
}
@Override
protected Type lubWithFunction(RascalType type) {
if(this == type) {
return this;
}
FunctionType f = (FunctionType) type;
Type returnType = getReturnType().lub(f.getReturnType());
Type argumentTypes = getArgumentTypes().glb(f.getArgumentTypes());
if (argumentTypes.isTuple() && argumentTypes.getArity() == getArity()) {
return RTF.functionType(returnType,
argumentTypes,
getKeywordParameterTypes() == f.getKeywordParameterTypes() ? getKeywordParameterTypes() : TF.voidType());
}
return TF.valueType();
}
@Override
protected Type glbWithFunction(RascalType type) {
if(this == type) {
return this;
}
FunctionType f = (FunctionType) type;
Type returnType = getReturnType().glb(f.getReturnType());
Type argumentTypes = getArgumentTypes().lub(f.getArgumentTypes());
if(argumentTypes.isTuple()) {
// TODO: figure out what glb means for keyword parameters
return RTF.functionType(returnType, argumentTypes, TF.voidType());
}
return TF.voidType();
}
@Override
protected boolean isSubtypeOfOverloadedFunction(RascalType type) {
OverloadedFunctionType function = (OverloadedFunctionType) type;
for (FunctionType f : function.getAlternatives()) {
if (!this.isSubtypeOf(f)) {
return false;
}
}
return true;
}
@Override
protected Type lubWithOverloadedFunction(RascalType type) {
return type.lubWithFunction(this);
}
@Override
protected Type glbWithOverloadedFunction(RascalType type) {
return type.glbWithFunction(this);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(returnType);
sb.append(' ');
sb.append('(');
int i = 0;
for (Type arg : argumentTypes) {
if (i > 0) {
sb.append(", ");
}
sb.append(arg.toString());
if (argumentTypes.hasFieldNames()) {
sb.append(" " + argumentTypes.getFieldName(i));
}
i++;
}
if (keywordParameters != null) {
i = 0;
for (Type arg : keywordParameters) {
sb.append(", ");
sb.append(arg.toString());
if (argumentTypes.hasFieldNames()) {
sb.append(" " + argumentTypes.getFieldName(i) + " = ...");
}
i++;
}
}
sb.append(')');
return sb.toString();
}
@Override
public int hashCode() {
return 19 + 19 * returnType.hashCode() + 23 * argumentTypes.hashCode()
+ (keywordParameters != null ? 29 * keywordParameters.hashCode() : 0)
;
}
@Override
public boolean equals(Object o) {
if (o instanceof FunctionType) {
FunctionType other = (FunctionType) o;
if (returnType != other.returnType) {
return false;
}
if (argumentTypes != other.argumentTypes) {
return false;
}
if (keywordParameters != other.keywordParameters) {
return false;
}
return true;
}
return false;
}
@Override
public Type instantiate(Map<Type, Type> bindings) {
return RTF.functionType(returnType.instantiate(bindings), argumentTypes.instantiate(bindings), keywordParameters);
}
@Override
public boolean match(Type matched, Map<Type, Type> bindings)
throws FactTypeUseException {
// super.match(matched, bindings); match calls isSubTypeOf which calls match, watch out for infinite recursion
if (matched.isBottom()) {
return returnType.match(matched, bindings);
} else {
// Fix for cases where we have aliases to function types, aliases to aliases to function types, etc
while (matched.isAliased()) {
matched = matched.getAliased();
}
if (matched instanceof OverloadedFunctionType) {
OverloadedFunctionType of = (OverloadedFunctionType) matched;
// at least one needs to match (also at most one can match)
for (Type f : of.getAlternatives()) {
if (this.match(f, bindings)) {
return true;
}
}
return false;
}
else if (matched instanceof FunctionType) {
return argumentTypes.match(((FunctionType) matched).getArgumentTypes(), bindings)
&& returnType.match(((FunctionType) matched).getReturnType(), bindings);
}
else {
return false;
}
}
}
@Override
public Type compose(Type right) {
if (right.isBottom()) {
return right;
}
Set<FunctionType> newAlternatives = new HashSet<FunctionType>();
if(right instanceof FunctionType) {
if(TF.tupleType(((FunctionType) right).returnType).isSubtypeOf(this.argumentTypes)) {
return RTF.functionType(this.returnType, ((FunctionType) right).getArgumentTypes(), ((FunctionType) right).keywordParameters);
}
} else if(right instanceof OverloadedFunctionType) {
for(FunctionType ftype : ((OverloadedFunctionType) right).getAlternatives()) {
if(TF.tupleType(ftype.getReturnType()).isSubtypeOf(this.argumentTypes)) {
newAlternatives.add((FunctionType) RTF.functionType(this.returnType, ftype.getArgumentTypes(), ftype.keywordParameters));
}
}
} else {
throw new IllegalOperationException("compose", this, right);
}
if(!newAlternatives.isEmpty())
return RTF.overloadedFunctionType(newAlternatives);
return TF.voidType();
}
}