/** * 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())); } } } }