}
// Get a handle to the symbol defining the function.
Symbol fnSymbol = symTab.resolve(mFunctionName);
if (null == fnSymbol) {
throw new TypeCheckException("No such function: " + mFunctionName);
}
fnSymbol = fnSymbol.resolveAliases();
if (!(fnSymbol instanceof FnSymbol)) {
// This symbol isn't a function call?
throw new TypeCheckException("Symbol " + mFunctionName + " is not a function");
}
mFnSymbol = (FnSymbol) fnSymbol; // memoize this for later.
// Get a list of argument types from the function symbol. These may include
// universal types we need to concretize.
List<Type> abstractArgTypes = new ArrayList<Type>(mFnSymbol.getArgumentTypes());
// Argument types for varargs, after the fixed args.
List<Type> abstractVarArgTypes = mFnSymbol.getVarArgTypes();
// Check that arity matches.
int argsRemaining = mArgExprs.size();
argsRemaining -= abstractArgTypes.size();
if (argsRemaining < 0 || (argsRemaining > 0 && abstractVarArgTypes.size() == 0)) {
// Too few actual args, or too many args (and this is not a varargs fn).
throw new TypeCheckException("Function " + mFunctionName + " requires "
+ abstractArgTypes.size() + " arguments, but received " + mArgExprs.size());
}
if (argsRemaining > 0 && abstractVarArgTypes.size() > 0) {
// varargs may need to come in pairs, etc. Check that we have a correct multiple
// of the number of varargs available.
int argRemainder = argsRemaining % abstractVarArgTypes.size();
if (0 != argRemainder) {
throw new TypeCheckException("Function " + mFunctionName + " requires varargs "
+ "in sets of " + abstractVarArgTypes.size() + ", but this call has "
+ argRemainder + " too few.");
}
}
// For each actual vararg, add its type to the abstractArgTypes list.
if (abstractVarArgTypes.size() > 0) {
int numVarArgSets = (mArgExprs.size() - abstractArgTypes.size())
/ abstractVarArgTypes.size();
for (int i = 0; i < numVarArgSets; i++) {
abstractArgTypes.addAll(abstractVarArgTypes);
}
}
assert mArgExprs.size() == abstractArgTypes.size();
// Check that each expression type can promote to the argument type.
for (int i = 0; i < mArgExprs.size(); i++) {
Type exprType = mArgExprs.get(i).getType(symTab);
mExprTypes.add(exprType);
if (!exprType.promotesTo(abstractArgTypes.get(i))) {
throw new TypeCheckException("Invalid argument to function " + mFunctionName
+ ": argument " + i + " has type " + exprType + "; requires type "
+ abstractArgTypes.get(i));
}
}
mArgTypes = new Type[mArgExprs.size()];
// Now identify all the UniversalType instances in here, and the
// actual constraints on each of these.
Map<UniversalType, List<Type>> unifications = new HashMap<UniversalType, List<Type>>();
for (int i = 0; i < abstractArgTypes.size(); i++) {
Type abstractType = abstractArgTypes.get(i);
Type actualType = mExprTypes.get(i);
UniversalConstraintExtractor constraintExtractor = new UniversalConstraintExtractor();
if (constraintExtractor.extractConstraint(abstractType, actualType)) {
// Found a UniversalType. Make sure it's mapped to a list of actual constraints.
UniversalType univType = constraintExtractor.getUniversalType();
List<Type> actualConstraints = unifications.get(univType);
if (null == actualConstraints) {
actualConstraints = new ArrayList<Type>();
unifications.put(univType, actualConstraints);
}
// Add the actual constraint of the expression being applied as this argument.
actualConstraints.add(constraintExtractor.getConstraintType());
}
}
// Perform unifications on all the UniversalType expressions.
Map<Type, Type> unificationOut = new HashMap<Type, Type>();
for (Map.Entry<UniversalType, List<Type>> unification : unifications.entrySet()) {
UniversalType univType = unification.getKey();
List<Type> actualConstraints = unification.getValue();
Type out = univType.getRuntimeType(actualConstraints);
unificationOut.put(univType, out);
}
// Finally, generate a list of concrete argument types for coercion purposes.
for (int i = 0; i < abstractArgTypes.size(); i++ ) {
Type abstractType = abstractArgTypes.get(i);
mArgTypes[i] = abstractType.replaceUniversal(unificationOut);
assert mArgTypes[i] != null;
}
// Also set mReturnType; if this referenced a UniversalType, use the resolved
// version. Otherwise, use the version from the function directly.
Type fnRetType = mFnSymbol.getReturnType();
try {
mReturnType = fnRetType.replaceUniversal(unificationOut);
} catch (TypeCheckException tce) {
// We can only resolve against our arguments, not our caller's type.
if (fnRetType instanceof ListType) {
// If the unresolved typevar is an argument to a list type, we can
// return this -- it's going to be an empty list, so we can return
// LIST<NULL>
// TODO(aaron): This allows to_list() to produce an empty list, but
// putting this check here feels a bit like a hack to me.
mReturnType = new ListType(Type.getNullable(Type.TypeName.NULL));
} else {
// This fails for being too abstract.
throw new TypeCheckException("Output type of function " + mFunctionName
+ " is an unresolved UniversalType: " + fnRetType, tce);
}
}
assert null != mReturnType;