/**
* Copyright (c) 2009-2011, The HATS Consortium. All rights reserved.
* This file is licensed under the terms of the Modified BSD License.
*/
package abs.frontend.typechecker;
import java.util.*;
import abs.common.Constants;
import abs.frontend.analyser.ErrorMessage;
import abs.frontend.analyser.SemanticError;
import abs.frontend.analyser.SemanticWarning;
import abs.frontend.analyser.SemanticConditionList;
import abs.frontend.analyser.TypeError;
import abs.frontend.ast.List;
import abs.frontend.ast.*;
import abs.frontend.parser.SourcePosition;
import com.google.common.collect.ImmutableSet;
public class TypeCheckerHelper {
// Warn about using constructors that should not have been exported from
// their module (used in DataConstructorExp.typeCheck() defined in
// TypeChecker.jadd)
public static Set<String> deprecatedConstructors
= ImmutableSet.of("ABS.StdLib.Set", "ABS.StdLib.EmptySet", "ABS.StdLib.Insert",
"ABS.StdLib.Map", "ABS.StdLib.EmptyMap", "ABS.StdLib.InsertAssoc");
public static void typeCheck(ConstructorPattern p, SemanticConditionList e, Type t) {
DataConstructor c = p.getDataConstructor();
if (c == null) {
e.add(new SemanticError(p, ErrorMessage.CONSTRUCTOR_NOT_RESOLVABLE, p.getConstructor()));
return;
}
if (c.getNumConstructorArg() != p.getNumParam()) {
e.add(new TypeError(p, ErrorMessage.WRONG_NUMBER_OF_ARGS, c.getNumConstructorArg(), p.getNumParam()));
return;
}
assert t.isDataType() || t.isExceptionType() : t; // TODO: more instances of this? Maybe exception should imply datatype?
if (!t.getDecl().equals(c.getDataTypeDecl())) {
e.add(new TypeError(p, ErrorMessage.WRONG_CONSTRUCTOR, t.toString(), p.getConstructor()));
}
Type myType = p.getType();
if (!(myType instanceof DataTypeType))
return;
if (!(t instanceof DataTypeType)) {
e.add(new TypeError(p, ErrorMessage.TYPE_MISMATCH, myType, t));
return;
}
DataTypeType myDType = (DataTypeType) myType;
DataTypeType otherType = (DataTypeType) t;
if (!myDType.getDecl().equals(otherType.getDecl())) {
e.add(new TypeError(p, ErrorMessage.TYPE_MISMATCH, myDType, t));
return;
}
typeCheckMatchingParamsPattern(e, p, c);
}
public static void checkAssignment(SemanticConditionList l, ASTNode<?> n, Type lht, Exp rhte) {
Type te = rhte.getType();
if (!te.isAssignableTo(lht)) {
l.add(new TypeError(n, ErrorMessage.CANNOT_ASSIGN, te, lht));
}
}
public static void typeCheckParamList(SemanticConditionList l, HasParams params) {
Set<String> names = new HashSet<String>();
for (ParamDecl d : params.getParams()) {
if (!names.add(d.getName())) {
l.add(new TypeError(d, ErrorMessage.DUPLICATE_PARAM_NAME, d.getName()));
}
d.typeCheck(l);
}
}
public static void typeCheckRunMethodSig(SemanticConditionList l, MethodSig m) {
if (m.getNumParam() > 0) {
l.add(new TypeError(m, ErrorMessage.RUN_METHOD_WRONG_NUM_PARAMS, m.getNumParam()));
}
if (!m.getReturnType().getType().isUnitType()) {
l.add(new TypeError(m, ErrorMessage.RUN_METHOD_WRONG_RETURN_TYPE,
m.getReturnType().getType().toString()));
}
}
public static void typeCheckMatchingParams(SemanticConditionList l, DataConstructorExp n, DataConstructor c) {
assert n.getDecl() == c;
final Map<TypeParameter, Type> binding = n.getTypeParamBinding(n, c);
typeCheckEqual(l, n, c.applyBindings(binding));
}
private static void typeCheckMatchingParamsPattern(SemanticConditionList l, ConstructorPattern n, DataConstructor decl) {
Map<TypeParameter, Type> binding = decl.getTypeParamBinding(n, n.getTypes());
java.util.List<Type> types = decl.applyBindings(binding);
typeCheckEqualPattern(l, n, types);
}
public static java.util.List<Type> applyBindings(Map<TypeParameter, Type> binding, java.util.List<Type> types) {
ArrayList<Type> res = new ArrayList<Type>(types.size());
for (Type t : types) {
res.add(t.applyBinding(binding));
}
return res;
}
public static void typeCheckMatchingParams(SemanticConditionList l, FnApp n, ParametricFunctionDecl decl) {
Map<TypeParameter, Type> binding = n.getTypeParamBindingFromParamDecl(decl);
java.util.List<Type> types = decl.applyBindings(binding);
typeCheckEqual(l, n, types);
}
public static void typeCheckEqual(SemanticConditionList l, ASTNode<?> n, java.util.List<Type> params) {
List<PureExp> args = ((HasActualParams)n).getParams();
if (params.size() != args.getNumChild()) {
l.add(new TypeError(n, ErrorMessage.WRONG_NUMBER_OF_ARGS, params.size(), args.getNumChild()));
} else {
for (int i = 0; i < params.size(); i++) {
Type argType = params.get(i);
PureExp exp = args.getChild(i);
exp.typeCheck(l);
Type expType = exp.getType();
if (!expType.isAssignableTo(argType)) {
l.add(new TypeError(n, ErrorMessage.TYPE_MISMATCH, exp.getType(), argType));
}
}
}
}
private static void typeCheckEqualPattern(SemanticConditionList l, ConstructorPattern n, java.util.List<Type> params) {
List<Pattern> args = n.getParams();
if (params.size() != args .getNumChild()) {
l.add(new TypeError(n, ErrorMessage.WRONG_NUMBER_OF_ARGS, params.size(), args.getNumChild()));
} else {
for (int i = 0; i < params.size(); i++) {
Type argType = params.get(i);
Pattern exp = args.getChild(i);
exp.typeCheck(l, argType);
}
}
}
public static void typeCheckDeltaClause(DeltaClause clause, Map<String,DeltaDecl> deltaNames, Set<String> definedFeatures, SemanticConditionList e) {
/* Does the delta exist? */
final Deltaspec spec = clause.getDeltaspec();
if (! deltaNames.containsKey(spec.getDeltaID()))
e.add(new TypeError(spec, ErrorMessage.NAME_NOT_RESOLVABLE, spec.getDeltaID()));
else {
DeltaDecl dd = deltaNames.get(spec.getDeltaID());
if (dd.getNumParam() != spec.getNumDeltaparam()) {
e.add(new TypeError(spec, ErrorMessage.WRONG_NUMBER_OF_ARGS,dd.getNumParam(),spec.getNumDeltaparam()));
} else {
for (int i=0; i<dd.getNumParam(); i++) {
DeltaParamDecl formal = dd.getParam(i);
Deltaparam actual = spec.getDeltaparam(i);
// TODO: W00t?!
if (actual instanceof Const) {
Value a = ((Const) actual).getValue();
if (! formal.accepts(a)) {
e.add(new TypeError(a, ErrorMessage.CANNOT_ASSIGN, a.getName(), formal.getType().getSimpleName()));
}
}
}
}
}
/* Do the referenced features exist? */
if (clause.hasAppCond()) {
clause.getAppCond().typeCheck(definedFeatures, e);
}
if (clause.hasFromAppCond()) {
clause.getFromAppCond().typeCheck(definedFeatures, e);
}
/* What about deltas mentioned in the 'after' clause? */
for (DeltaID did : clause.getAfterDeltaIDs()) {
if (! deltaNames.containsKey(did.getName())) {
e.add(new TypeError(did, ErrorMessage.NAME_NOT_RESOLVABLE, did.getName()));
}
}
}
public static void typeCheckProductDecl(ProductDecl prod,
Map<String,Feature> featureNames,
Set<String> prodNames,
Map<String,DeltaDecl> deltaNames,
Set<String> updateNames,
SemanticConditionList e) {
if (featureNames != null) {
// Do the features exist in the PL declaration (and also check feature attributes)?
Model m = prod.getModel();
for (Feature f : prod.getProduct().getFeatures()) {
if (!featureNames.containsKey(f.getName()))
e.add(new TypeError(prod, ErrorMessage.NAME_NOT_RESOLVABLE, f.getName()));
else {
Collection<DeltaClause> dcs = findDeltasForFeature(m,f);
for (int i = 0; i<f.getNumAttrAssignment(); i++) {
AttrAssignment aa = f.getAttrAssignment(i);
for (DeltaClause dc : dcs) {
DeltaDecl dd = m.findDelta(dc.getDeltaspec().getDeltaID());
DeltaParamDecl dp = dd.getParam(i);
// FIXME: we assumed here that delta
// parameters and feature parameters have
// same order, arity. This is clearly
// wrong, and parameters for the delta are
// named with the feature. we should find a
// dp with the same name as aa, and ignore
// any superfluous aa (the value is simply
// not used by this delta).
if (dp != null && !dp.accepts(aa.getValue())) {
e.add(new TypeError(aa, ErrorMessage.CANNOT_ASSIGN, aa.getValue().getName(), dp.getType().getSimpleName()));
}
}
}
}
}
}
// Check the right side of product expression that contains in prodNames
Set<String> productNames = new HashSet<String>();
prod.getProductExpr().setRightSideProductNames(productNames);
for (String productName : productNames) {
if (!prodNames.contains(productName)) {
e.add(new TypeError(prod, ErrorMessage.UNDECLARED_PRODUCT, productName));
}
}
// Check solution from getProduct()
if (prod.getProduct() != null) {
java.util.List<String> errors = prod.getModel().instantiateCSModel().checkSolutionWithErrors(
prod.getProduct().getSolution(),
prod.getModel());
if (!errors.isEmpty()) {
String failedConstraints = "";
for (String s: errors)
failedConstraints += "\n- " + s;
e.add(new TypeError(prod, ErrorMessage.INVALID_PRODUCT, prod.getName(), failedConstraints));
}
}
Set<String> seen = new HashSet<String>();
// FIXME: deal with reconfigurations
// for (Reconfiguration recf : prod.getReconfigurations()) {
// if (!seen.add(recf.getTargetProductID()))
// e.add(new TypeError(recf, ErrorMessage.DUPLICATE_RECONFIGURATION, recf.getTargetProductID()));
//
// // Does the reconfiguration target product exist?
// if (! prodNames.contains(recf.getTargetProductID()))
// e.add(new TypeError(recf, ErrorMessage.NAME_NOT_RESOLVABLE, recf.getTargetProductID()));
// // Do the deltas used for reconfiguration exist?
// for (DeltaID d : recf.getDeltaIDs()) {
// if (! deltaNames.containsKey(d.getName()))
// e.add(new TypeError(recf, ErrorMessage.NAME_NOT_RESOLVABLE, d.getName()));
// }
// // Does the update used for reconfiguration exist?
// if (! updateNames.contains(recf.getUpdateID()))
// e.add(new TypeError(recf, ErrorMessage.NAME_NOT_RESOLVABLE, recf.getUpdateID()));
// }
}
/**
* Look for all deltas that have a particular feature in the application condition --
* up to the boolean madness that lies within AppConds (delta D(F.x) when ~F will
* be checked when actually trying to flatten the product, I hope.
*/
private static Collection<DeltaClause> findDeltasForFeature(Model m, Feature f) {
Collection<DeltaClause> dcs = new ArrayList<DeltaClause>();
for (int i = 0; i < m.getProductLine().getNumDeltaClause(); i++) {
DeltaClause dc = m.getProductLine().getDeltaClause(i);
if (dc.refersTo(f)) {
dcs.add(dc);
}
}
return dcs;
}
public static <T extends ASTNode<?>> java.util.List<Type> getTypes(List<T> params) {
ArrayList<Type> res = new ArrayList<Type>();
for (ASTNode<?> u : params) {
res.add(((HasType)u).getType());
}
return res;
}
public static void addTypeParamBinding(ASTNode<?> node, Map<TypeParameter, Type> binding, java.util.List<Type> params,
java.util.List<Type> args) {
if (params.size() != args.size())
throw new TypeCheckerException(new TypeError(node, ErrorMessage.WRONG_NUMBER_OF_ARGS, params.size(),args.size()));
for (int i = 0; i < params.size(); i++) {
Type paramType = params.get(i);
Type argType = args.get(i);
if (argType == null)
return;
if (argType.isBoundedType()) {
BoundedType bt = (BoundedType) argType;
if (bt.hasBoundType())
argType = bt.getBoundType();
}
if (paramType.isTypeParameter()) {
if (binding.containsKey(paramType)) {
Type prevArgType = binding.get(paramType);
if (prevArgType.isAssignableTo(argType)
&& !argType.isAssignableTo(prevArgType))
{
// Replace, e.g., "Int" with "Rat". If the two types
// do not match at all, we'll raise a type error
// later.
binding.put((TypeParameter)paramType, argType);
}
} else {
binding.put((TypeParameter) paramType, argType);
}
} else if (paramType.isDataType() && argType.isDataType()) {
DataTypeType paramdt = (DataTypeType) paramType;
DataTypeType argdt = (DataTypeType) argType;
if (paramdt.numTypeArgs() == argdt.numTypeArgs()) {
addTypeParamBinding(node, binding, paramdt.getTypeArgs(), argdt.getTypeArgs());
}
}
}
}
static final StarImport STDLIB_IMPORT = new StarImport(Constants.STDLIB_NAME);
public static void checkForDuplicateDecls(ModuleDecl mod, SemanticConditionList errors) {
Map<KindedName, ResolvedName> duplicateNames = new HashMap<KindedName, ResolvedName>();
Map<KindedName, ResolvedName> names = getVisibleNames(mod, duplicateNames);
for (KindedName n : duplicateNames.keySet()) {
ResolvedName rn = names.get(n);
ResolvedName origrn = duplicateNames.get(n);
ErrorMessage msg = null;
String location = "";
if (origrn instanceof ResolvedDeclName) {
Decl decl = ((ResolvedDeclName)origrn).getDecl();
location = " at " + decl.getFileName() + ":" + decl.getStartLine() + ":" + decl.getStartColumn();
} else if (origrn instanceof ResolvedAmbigiousName) {
Decl decl = ((AmbiguousDecl)((ResolvedAmbigiousName)origrn).getDecl()).getAlternative().get(0);
location = " at " + decl.getFileName() + ":" + decl.getStartLine() + ":" + decl.getStartColumn();
}
switch (n.getKind()) {
case CLASS:
msg = ErrorMessage.DUPLICATE_CLASS_NAME;
break;
case FUN:
msg = ErrorMessage.DUPLICATE_FUN_NAME;
break;
case DATA_CONSTRUCTOR:
msg = ErrorMessage.DUPLICATE_CONSTRUCTOR;
break;
case TYPE_DECL:
msg = ErrorMessage.DUPLICATE_TYPE_DECL;
break;
case EXCEPTION:
msg = ErrorMessage.DUPLICATE_EXCEPTION_DECL;
break;
case MODULE:
assert false; // doesn't happen, no modules within modules
break;
default:
assert false; // detect if we added a new KindedName.Kind
break;
}
errors.add(new TypeError(rn.getDecl(), msg, n.getName(), location));
}
}
public static ResolvedMap getDefinedNames(ModuleDecl mod,
Map<KindedName, ResolvedName> foundDuplicates) {
ResolvedMap res = new ResolvedMap();
ResolvedModuleName moduleName = new ResolvedModuleName(mod);
for (Decl d : mod.getDeclList()) {
ResolvedDeclName rn = new ResolvedDeclName(moduleName, d);
if (res.containsKey(rn.getSimpleName()))
foundDuplicates.put(rn.getSimpleName(), res.get(rn.getSimpleName()));
res.put(rn.getSimpleName(), rn);
res.put(rn.getQualifiedName(), rn);
if (d instanceof DataTypeDecl) {
DataTypeDecl dataDecl = (DataTypeDecl) d;
for (DataConstructor c : dataDecl.getDataConstructors()) {
rn = new ResolvedDeclName(moduleName, c);
if (res.containsKey(rn.getSimpleName()))
foundDuplicates.put(rn.getSimpleName(), res.get(rn.getSimpleName()));
res.put(rn.getSimpleName(), rn);
res.put(rn.getQualifiedName(), rn);
}
} else if (d.isException()) {
ExceptionDecl ed = (ExceptionDecl) d;
DataConstructor ec = ed.dataConstructor;
assert ec != null : ed.getName();
if (ec.getName().equals(d.getName())) {
// should always be true, see Main.java where the data
// constructor gets constructed
rn = new ResolvedDeclName(moduleName, ec);
// If it's already in there, is it from the same location -- from stdlib?
ResolvedName tryIt = res.get(rn);
if (tryIt != null && tryIt.getDecl() != ed)
foundDuplicates.put(rn.getSimpleName(), tryIt);
else {
res.put(rn.getQualifiedName(), rn);
}
}
}
}
return res;
}
public static ResolvedMap getImportedNames(ModuleDecl mod) {
ResolvedMap res = new ResolvedMap();
for (Import i : mod.getImports()) {
if (i instanceof StarImport) {
StarImport si = (StarImport) i;
ModuleDecl md = mod.lookupModule(si.getModuleName());
if (md != null) {
res.addAllNamesNoHiding(md.getExportedNames());
}
} else if (i instanceof NamedImport) {
NamedImport ni = (NamedImport) i;
for (Name n : ni.getNames()) {
// statements like "import X;" lead to earlier type error;
// avoid calling lookupModule(null) here
if (!n.isSimple()) {
ModuleDecl md = mod.lookupModule(n.getModuleName());
if (md != null)
try {
res.addAllNames(md.getExportedNames(), n);
} catch (TypeCheckerException e) {} // NADA
}
}
} else if (i instanceof FromImport) {
FromImport fi = (FromImport) i;
ModuleDecl md = mod.lookupModule(fi.getModuleName());
if (md != null) {
ResolvedMap en = md.getExportedNames();
for (Name n : fi.getNames()) {
res.putKindedNamesNoHiding(n.getString(), en);
res.putKindedNamesNoHiding(fi.getModuleName() + "." + n.getString(), en);
}
}
}
}
return res;
}
public static ResolvedMap getVisibleNames(ModuleDecl mod, Map<KindedName, ResolvedName> foundDuplicates) {
ResolvedMap res = new ResolvedMap();
ResolvedMap ownNames = getDefinedNames(mod, foundDuplicates);
// add imported names:
res.putAll(mod.getImportedNames());
// Find shadowing entries
for (KindedName entry : ownNames.keySet()) {
if (res.containsKey(entry) && !ownNames.get(entry).equals(res.get(entry))) {
foundDuplicates.put(entry, res.get(entry));
}
}
// defined names hide imported names for the purpose of this method
res.putAll(ownNames);
return res;
}
public static ResolvedMap getExportedNames(ModuleDecl mod) {
ResolvedMap res = new ResolvedMap();
for (Export e : mod.getExports()) {
if (e instanceof StarExport) {
StarExport se = (StarExport) e;
if (!se.hasModuleName()) {
res.putAll(mod.getDefinedNames());
} else {
String moduleName = se.getModuleName().getName();
res.putNamesOfModule(mod, mod.getVisibleNames(), moduleName, null);
}
} else if (e instanceof FromExport) {
FromExport fe = (FromExport) e;
String moduleName = fe.getModuleName();
for (Name n : fe.getNames()) {
String simpleName = n.getSimpleName();
res.putNamesOfModule(mod, mod.getVisibleNames(), moduleName, simpleName);
}
} else if (e instanceof NamedExport) {
NamedExport ne = (NamedExport) e;
for (Name n : ne.getNames()) {
String simpleName = n.getSimpleName();
res.putKindedNames(simpleName, mod.getVisibleNames());
res.putKindedNames(mod.getName() + "." + simpleName, mod.getVisibleNames());
}
}
}
return res;
}
public static void typeCheckBinary(SemanticConditionList e, Binary b, Type t) {
b.getLeft().assertHasType(e, t);
b.getRight().assertHasType(e, t);
b.getLeft().typeCheck(e);
b.getRight().typeCheck(e);
}
/**
* checks whether the local variable v was already defined in the current function
*/
public static void checkForDuplicatesOfVarDecl(SemanticConditionList e, VarDeclStmt v) {
String varName = v.getVarDecl().getName();
VarOrFieldDecl otherVar = v.lookupVarOrFieldName(varName , false);
if (otherVar != null && v.inSameMethodOrBlock(otherVar)) {
e.add(new TypeError(v,ErrorMessage.VARIABLE_ALREADY_DECLARED, varName));
}
}
/**
* check a list of compilation units for duplicate module names, product names, delta names
*/
public static void checkForDuplicateModules(SemanticConditionList errors, Iterable<CompilationUnit> compilationUnits) {
Set<String> seenModules = new HashSet<String>();
for (CompilationUnit u : compilationUnits) {
for (ModuleDecl module : u.getModuleDecls()) {
if (!seenModules.add(module.getName())) {
errors.add(new TypeError(module, ErrorMessage.DUPLICATE_MODULE_NAME,module.getName()));
}
}
}
}
public static void checkForDuplicateProducts(SemanticConditionList errors, Iterable<CompilationUnit> compilationUnits) {
Set<String> seen = new HashSet<String>();
for (CompilationUnit u : compilationUnits) {
for (ProductDecl p : u.getProductDecls()) {
if (!seen.add(p.getName()))
errors.add(new TypeError(p, ErrorMessage.DUPLICATE_PRODUCT, p.getName()));
}
}
}
public static void checkForDuplicateDeltas(SemanticConditionList errors, Iterable<CompilationUnit> compilationUnits) {
Set<String> seen = new HashSet<String>();
for (CompilationUnit u : compilationUnits) {
for (DeltaDecl d : u.getDeltaDecls()) {
if (!seen.add(d.getName()))
errors.add(new TypeError(d, ErrorMessage.DUPLICATE_DELTA, d.getName()));
}
}
}
public static void checkForDuplicateUpdates(SemanticConditionList errors, Iterable<CompilationUnit> compilationUnits) {
Set<String> seen = new HashSet<String>();
for (CompilationUnit u : compilationUnits) {
for (UpdateDecl d : u.getUpdateDecls()) {
if (!seen.add(d.getName()))
errors.add(new TypeError(d, ErrorMessage.DUPLICATE_UPDATE, d.getName()));
}
}
}
/**
* get all the alternative declarations of an ambiguous declaration formated as a list
* which can be used in error messages
* @param a
* @return
*/
public static String getAlternativesAsString(AmbiguousDecl a) {
String result = "";
for (Decl alternative : a.getAlternative()) {
result += "\n * " + alternative.qualifiedName() + " (defined in " +
alternative.getFileName() + ", line " + alternative.getStartLine() + ")";
}
return result;
}
public static void checkDataTypeUse(SemanticConditionList e, DataTypeUse use) {
Type type = use.getType();
if (type.getDecl() instanceof ParametricDataTypeDecl) {
DataTypeType t = (DataTypeType) type;
int expected = ((ParametricDataTypeDecl)type.getDecl()).getNumTypeParameter();
if (expected != t.numTypeArgs()) {
e.add(new TypeError(use, ErrorMessage.WRONG_NUMBER_OF_TYPE_ARGS,type.toString(),""+expected,""+t.numTypeArgs()));
} else if (expected > 0) {
if (use instanceof ParametricDataTypeUse) {
for (TypeUse du : ((ParametricDataTypeUse)use).getParams()) {
du.typeCheck(e);
}
} else if (use.getDecl() instanceof TypeSynDecl) {
// nothing to check as this is already checked at the TypeSynDecl
} else {
e.add(new TypeError(use, ErrorMessage.WRONG_NUMBER_OF_TYPE_ARGS,type.toString(),""+expected,"0"));
}
}
}
}
public static void checkDefBeforeUse(SemanticConditionList e, VarOrFieldUse use) {
if (use.getType().isUnknownType()) {
e.add(new TypeError(use,ErrorMessage.NAME_NOT_RESOLVABLE, use.getName()));
} else {
/* Check that fields are not used before they are defined,
* when we are NOT inside a method, e.g. when initialising a field upon declaration.
*/
// FIXME: this could break down wrt deltas
boolean isUsedInFieldDecl = use instanceof FieldUse;
if (isUsedInFieldDecl && use.getContextMethod() == null
&& SourcePosition.larger(use.getDecl().getEndLine(), use.getDecl().getEndColumn(), use.getStartLine(), use.getStartColumn())) {
e.add(new TypeError(use,
isUsedInFieldDecl ? ErrorMessage.FIELD_USE_BEFORE_DEFINITION : ErrorMessage.VAR_USE_BEFORE_DEFINITION,
use.getName()));
}
}
}
}