/* * This file is part of the X10 project (http://x10-lang.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.opensource.org/licenses/eclipse-1.0.php * * This file was originally derived from the Polyglot extensible compiler framework. * * (C) Copyright 2000-2007 Polyglot project group, Cornell University * (C) Copyright IBM Corporation 2007-2012. */ package polyglot.types; import java.util.*; import polyglot.frontend.Globals; import polyglot.main.Reporter; import polyglot.types.TypeSystem_c.ConstructorMatcher; import x10.types.MethodInstance; import x10.types.X10TypeEnv_c; /** * Typing environment. * * For a given typing rule Gamma |- Phi, this is Gamma. Phi is a method of * TypeEnv. */ public abstract class TypeEnv_c implements TypeEnv, Cloneable { protected Context context; // the actual context. Eliminate this and merge // with Context. protected TypeSystem ts; protected Reporter reporter; public TypeEnv_c(Context context) { assert context != null; this.context = context; this.ts = context.typeSystem(); this.reporter = this.ts.extensionInfo().getOptions().reporter; } public X10TypeEnv_c shallowCopy() { try { return (X10TypeEnv_c) super.clone(); } catch (CloneNotSupportedException e) { assert false; return null; } } /** * Returns true iff the type t can be coerced to a String in the given * Context. If a type can be coerced to a String then it can be concatenated * with Strings, e.g. if o is of type T, then the code snippet "" + o would * be allowed. */ public boolean canCoerceToString(Type t) { // every Object can be coerced to a string, as can any primitive, // except void. return !t.isVoid(); } /** * Returns true iff type1 and type2 are equivalent. */ public boolean typeEquals(Type type1, Type type2) { if (type1 == type2) return true; if (type1 == null || type2 == null) return false; if (type1 instanceof JavaArrayType && type2 instanceof JavaArrayType) { JavaArrayType at1 = (JavaArrayType) type1; JavaArrayType at2 = (JavaArrayType) type2; return typeEquals(at1.base(), at2.base()); } return ts.equals((TypeObject) type1, (TypeObject) type2); } /** * Returns true iff type1 and type2 are equivalent. */ public boolean packageEquals(Package type1, Package type2) { if (type1 == type2) return true; if (type1 == null || type2 == null) return false; return type1.packageEquals(type2); } /** * Requires: all type arguments are canonical. ToType is not a NullType. * * Returns true iff a cast from fromType to toType is valid; in other words, * some non-null members of fromType are also members of toType. **/ public boolean isCastValid(Type fromType, Type toType) { if (fromType instanceof NullType) { return toType.isNull() || toType.isReference(); } if (fromType.isJavaPrimitive() && toType.isJavaPrimitive()) { if (fromType.isVoid() || toType.isVoid()) return false; if (ts.typeEquals(fromType, toType, context)) return true; if (fromType.isNumeric() && toType.isNumeric()) return true; return false; } if (fromType instanceof JavaArrayType && toType instanceof JavaArrayType) { JavaArrayType fromAT = (JavaArrayType) fromType; JavaArrayType toAT = (JavaArrayType) toType; Type fromBase = fromAT.base(); Type toBase = toAT.base(); if (fromBase.isJavaPrimitive()) return ts.typeEquals(toBase, fromBase, context); if (toBase.isJavaPrimitive()) return false; if (fromBase.isNull()) return false; if (toBase.isNull()) return false; // Both are reference types. return ts.isCastValid(fromBase, toBase, context); } if (fromType instanceof JavaArrayType && toType instanceof ObjectType) { // Ancestor is not an array, but child is. Check if the array // is a subtype of the ancestor. This happens when ancestor // is java.lang.Object. return ts.isSubtype(fromType, toType, context); } if (fromType instanceof ClassType && toType instanceof JavaArrayType) { // From type is not an array, but to type is. Check if the array // is a subtype of the from type. This happens when from type // is java.lang.Object. return ts.isSubtype(toType, fromType, context); } if (fromType instanceof ClassType && toType instanceof ClassType) { ClassType fromCT = (ClassType) fromType; ClassType toCT = (ClassType) toType; // From and to are neither primitive nor an array. They are // distinct. boolean fromInterface = fromCT.flags().isInterface(); boolean toInterface = toCT.flags().isInterface(); boolean fromFinal = fromCT.flags().isFinal() || Types.isX10Struct(fromCT); boolean toFinal = toCT.flags().isFinal(); // This is taken from Section 5.5 of the JLS. if (fromInterface) { // From is an interface if (!toInterface && !toFinal) { // To is a non-final class. return true; } if (toFinal) { // To is a final class. return ts.isSubtype(toType, fromCT, context); } // To and From are both interfaces. return true; } else { // From is not an interface. if (!toInterface) { // Nether from nor to is an interface. return ts.isSubtype(fromCT, toType, context) || ts.isSubtype(toType, fromCT, context); } if (fromFinal) { // From is a final class, and to is an interface return ts.isSubtype(fromCT, toType, context); } // From is a non-final class, and to is an interface. return true; } } if (fromType instanceof ObjectType) { if (!toType.isReference()) return false; return ts.isSubtype(fromType, toType, context) || ts.isSubtype(toType, fromType, context); } return false; } /** * Requires: all type arguments are canonical. * * Returns true iff an implicit cast from fromType to toType is valid; in * other words, every member of fromType is member of toType. * * Returns true iff child and ancestor are non-primitive types, and a * variable of type child may be legally assigned to a variable of type * ancestor. * */ public boolean isImplicitCastValid(Type fromType, Type toType) { if (fromType.isJavaPrimitive() && toType.isJavaPrimitive()) { if (toType.isVoid()) return false; if (fromType.isVoid()) return false; if (ts.typeEquals(toType, fromType, context)) return true; if (toType.isBoolean()) return fromType.isBoolean(); if (fromType.isBoolean()) return false; if (!fromType.isNumeric() || !toType.isNumeric()) return false; if (toType.isDouble()) return true; if (fromType.isDouble()) return false; if (toType.isFloat()) return true; if (fromType.isFloat()) return false; if (toType.isLong()) return true; if (fromType.isLong()) return false; if (toType.isInt()) return true; if (fromType.isInt()) return false; if (toType.isShort()) return fromType.isShort() || fromType.isByte(); if (fromType.isShort()) return false; if (toType.isChar()) return fromType.isChar(); if (fromType.isChar()) return false; if (toType.isByte()) return fromType.isByte(); if (fromType.isByte()) return false; return false; } if (fromType instanceof JavaArrayType && toType instanceof JavaArrayType) { JavaArrayType fromAT = (JavaArrayType) fromType; Type fromBase = fromAT.base(); JavaArrayType toAT = (JavaArrayType) toType; Type toBase = toAT.base(); if (fromBase.isJavaPrimitive() || toBase.isJavaPrimitive()) { return ts.typeEquals(fromBase, toBase, context); } else { return isImplicitCastValid(fromBase, toBase); } } if (fromType instanceof NullType) { return toType.isNull() || toType.isReference(); } if (fromType instanceof ObjectType) { // This handles classes and also coercions from Array to Object return ts.isSubtype(fromType, toType, context); } return false; } /** * Returns true if <code>value</code> can be implicitly cast to Primitive * type <code>t</code>. */ public boolean numericConversionValid(Type t, Object value) { if (value instanceof Float || value instanceof Double) return false; long v; if (value instanceof Number) { v = ((Number) value).longValue(); } else if (value instanceof Character) { v = ((Character) value).charValue(); } else { return false; } if (t.isLong()) return true; if (t.isInt()) return Integer.MIN_VALUE <= v && v <= Integer.MAX_VALUE; if (t.isChar()) return Character.MIN_VALUE <= v && v <= Character.MAX_VALUE; if (t.isShort()) return Short.MIN_VALUE <= v && v <= Short.MAX_VALUE; if (t.isByte()) return Byte.MIN_VALUE <= v && v <= Byte.MAX_VALUE; return false; } // // // Functions for one-type checking and resolution. // // /** * Checks whether the member mi can be accessed from Context "context". */ public boolean isAccessible(MemberInstance<?> mi) { ClassDef contextClass = context.currentClassDef(); Type target = mi.container(); Flags flags = mi.flags(); if (!target.isClass()) { // public members of non-classes are accessible; // non-public members of non-classes are inaccessible return flags.isPublic(); } if (contextClass == null) { return flags.isPublic(); } ClassType contextClassType = contextClass.asType(); ClassType targetClass = target.toClass(); if (!classAccessible(targetClass.def())) { return false; } if (ts.equals(targetClass.def(), contextClass)) return true; // If the current class and the target class are both in the // same class body, then protection doesn't matter, i.e. // protected and private members may be accessed. Do this by // working up through contextClass's containers. if (ts.isEnclosed(contextClass, targetClass.def()) || ts.isEnclosed(targetClass.def(), contextClass)) return true; ClassDef cd = contextClass; while (!cd.isTopLevel()) { cd = cd.outer().get(); if (ts.isEnclosed(targetClass.def(), cd)) return true; } // protected if (flags.isProtected()) { // If the current class is in a // class body that extends/implements the target class, then // protected members can be accessed. Do this by // working up through contextClass's containers. if (ts.descendsFrom(ts.classDefOf(contextClassType), ts.classDefOf(targetClass))) { return true; } ClassType ct = contextClassType; while (!ct.isTopLevel()) { ct = ct.outer(); if (ts.descendsFrom(ts.classDefOf(ct), ts.classDefOf(targetClass))) { return true; } } } return accessibleFromPackage(flags, targetClass.package_(), contextClassType.package_()); } /** True if the class targetClass accessible from the context. */ public boolean classAccessible(ClassDef targetClass) { ClassDef contextClass = context.currentClassDef(); if (contextClass == null) { return classAccessibleFromPackage(targetClass, context.package_()); } if (targetClass.isMember()) { return isAccessible(targetClass.asType()); } ClassType contextClassType = contextClass.asType(); // Local and anonymous classes are accessible if they can be named. // This method wouldn't be called if they weren't named. if (!targetClass.isTopLevel()) { return true; } // targetClass must be a top-level class // same class if (ts.equals(targetClass, contextClass)) return true; if (ts.isEnclosed(contextClass, targetClass)) return true; return classAccessibleFromPackage(targetClass, contextClassType.package_()); } /** True if the class targetClass accessible from the package pkg. */ public boolean classAccessibleFromPackage(ClassDef targetClass, Package pkg) { // Local and anonymous classes are not accessible from the outermost // scope of a compilation unit. if (!targetClass.isTopLevel() && !targetClass.isMember()) return false; Flags flags = targetClass.flags(); if (targetClass.isMember()) { if (!targetClass.container().get().isClass()) { // public members of non-classes are accessible return flags.isPublic(); } if (!classAccessibleFromPackage(targetClass.container().get().toClass().def(), pkg)) { return false; } } return accessibleFromPackage(flags, Types.get(targetClass.package_()), pkg); } /** * Return true if a member (in an accessible container) or a top-level class * with access flags <code>flags</code> in package <code>pkg1</code> is * accessible from package <code>pkg2</code>. */ public boolean accessibleFromPackage(Flags flags, Package pkg1, Package pkg2) { // Check if public. if (flags.isPublic()) { return true; } // Check if same package. if (flags.isPackage() || flags.isProtected()) { if (pkg1 == null && pkg2 == null) return true; if (pkg1 == null || pkg2 == null) return false; if (pkg1.packageEquals(pkg2)) return true; } // Otherwise private. return false; } public boolean isSubtype(Type t1, Type t2) { if (typeEquals(t1, t2)) return true; if (t1.isNull()) { return t2.isNull() || t2.isReference(); } if (t1.isReference() && t2.isNull()) { return false; } Type child = t1; Type ancestor = t2; if (child instanceof ObjectType) { ObjectType childRT = (ObjectType) child; // Check subclass relation. if (childRT.superClass() != null) { if (isSubtype(childRT.superClass(), ancestor)) { return true; } } // Next check interfaces. for (Type parentType : childRT.interfaces()) { if (isSubtype(parentType, ancestor)) { return true; } } } return false; } /** * Returns whether method 1 is <i>more specific</i> than method 2, where * <i>more specific</i> is defined as JLS 15.11.2.2 */ public <T extends ProcedureDef> boolean moreSpecific(ProcedureInstance<T> p1, ProcedureInstance<T> p2) { return p1.moreSpecific(null, p2, context); } /** * Requires: all type arguments are canonical. Returns the least common * ancestor of Type1 and Type2 **/ public Type leastCommonAncestor(Type type1, Type type2) { if (typeEquals(type1, type2)) return type1; if (type1.isNumeric() && type2.isNumeric()) { if (isImplicitCastValid(type1, type2)) { return type2; } if (isImplicitCastValid(type2, type1)) { return type1; } if (type1.isChar() && type2.isByte() || type1.isByte() && type2.isChar()) { return ts.Int(); } if (type1.isChar() && type2.isShort() || type1.isShort() && type2.isChar()) { return ts.Int(); } } if (type1.isArray() && type2.isArray()) { if (type1.toArray().base().isReference() && type2.toArray().base().isReference()) { return ts.arrayOf(leastCommonAncestor(type1.toArray().base(), type2.toArray().base())); } else { return ts.Any(); } } if (type1.isReference() && type2.isNull()) return type1; if (type2.isReference() && type1.isNull()) return type2; // Don't consider interfaces. if (type1.isClass() && type1.toClass().flags().isInterface()) { return ts.Any(); } if (type2.isClass() && type2.toClass().flags().isInterface()) { return ts.Any(); } if (isSubtype(type1, type2)) return type2; if (isSubtype(type2, type1)) return type1; if (type1 instanceof ObjectType && type2 instanceof ObjectType) { // Walk up the hierarchy Type t1 = leastCommonAncestor(((ObjectType) type1).superClass(), type2); Type t2 = leastCommonAncestor(((ObjectType) type2).superClass(), type1); if (typeEquals(t1, t2)) return t1; return ts.Any(); } return ts.Any(); //throw new SemanticException("No least common ancestor found for types \"" + type1 + "\" and \"" + type2 + "\"."); } /** Return true if t overrides mi */ public boolean hasMethod(Type t, MethodInstance mi) { return t instanceof ContainerType && ((ContainerType) t).hasMethod(mi, context); } /** Return true if t overrides mi */ public boolean hasFormals(ProcedureInstance<? extends ProcedureDef> pi, List<Type> formalTypes) { return ((ProcedureInstance_c<?>) pi).hasFormals(formalTypes, context); } public List<MethodInstance> overrides(MethodInstance mi) { List<MethodInstance> l = new ArrayList<MethodInstance>(); ContainerType rt = mi.container(); while (rt != null) { // add any method with the same name and formalTypes from rt l.addAll(rt.methods(mi.name(), mi.formalTypes(), context)); ContainerType sup = null; if (rt instanceof ObjectType) { ObjectType ot = (ObjectType) rt; if (ot.superClass() instanceof ContainerType) { sup = (ContainerType) ot.superClass(); } } rt = sup; } ; return l; } public List<MethodInstance> implemented(MethodInstance mi) { return implemented(mi, mi.container()); } public List<MethodInstance> implemented(MethodInstance mi, ContainerType st) { if (st == null) { return Collections.<MethodInstance> emptyList(); } List<MethodInstance> l = new LinkedList<MethodInstance>(); l.addAll(st.methods(mi.name(), mi.formalTypes(), context)); if (st instanceof ObjectType) { ObjectType rt = (ObjectType) st; Type superType = rt.superClass(); if (superType instanceof ContainerType) { l.addAll(implemented(mi, (ContainerType) superType)); } List<Type> ints = rt.interfaces(); for (Type t : ints) { if (t instanceof ContainerType) { ContainerType rt2 = (ContainerType) t; l.addAll(implemented(mi, rt2)); } } } return l; } /** * Assert that <code>ct</code> implements all abstract methods required; * that is, if it is a concrete class, then it must implement all interfaces * and abstract methods that it or it's superclasses declare, and if it is * an abstract class then any methods that it overrides are overridden * correctly. */ public void checkClassConformance(ClassType ct) throws SemanticException { assert false; if (ct.flags().isAbstract()) { // don't need to check interfaces or abstract classes return; } // build up a list of superclasses and interfaces that ct // extends/implements that may contain abstract methods that // ct must define. List<Type> superInterfaces = ts.abstractSuperInterfaces(ct); // check each abstract method of the classes and interfaces in // superInterfaces for (Iterator<Type> i = superInterfaces.iterator(); i.hasNext();) { Type it = i.next(); // Everything from Any you get 'for free' // [DC] perhaps it == ts.Any() is more appropriate here? if (ts.typeEquals(it, ts.Any(), context)) continue; if (it instanceof ContainerType) { ContainerType rt = (ContainerType) it; for (Iterator<MethodInstance> j = rt.methods().iterator(); j.hasNext();) { MethodInstance mi = j.next(); if (!mi.flags().isAbstract()) { // the method isn't abstract, so ct doesn't have to // implement it. continue; } MethodInstance mj = ts.findImplementingMethod(ct, mi, context); if (mj == null) { if (!ct.flags().isAbstract()) { throw new SemanticException(ct.fullName() + " should be declared abstract; it does not define " + mi.signature()+ ", which is declared in " + rt.toClass().fullName(), ct.errorPosition()); } else { // no implementation, but that's ok, the class is // abstract. } } else if (!typeEquals(ct, mj.container()) && !typeEquals(ct, mi.container())) { try { // check that mj can override mi, which // includes access protection checks. checkOverride(mj, mi); } catch (SemanticException e) { // change the position of the semantic // exception to be the class that we // are checking. throw new SemanticException(e.getMessage(), ct.errorPosition()); } } else { // the method implementation mj or mi was // declared in ct. So other checks will take // care of access issues } } } } } public boolean canOverride(MethodInstance mi, MethodInstance mj) { try { checkOverride(mi, mj); return true; } catch (SemanticException e) { return false; } } public void checkOverride(MethodInstance mi, MethodInstance mj) throws SemanticException { // HACK: Java5 allows return types to be covariant. We'll allow // covariant // return if we mj is defined in a class file. boolean allowCovariantReturn = false; if (mj.container() instanceof ClassType) { ClassType ct = (ClassType) mj.container(); if (ct.def().fromJavaClassFile()) { allowCovariantReturn = true; } } checkOverride(mi, mj, allowCovariantReturn); } public void checkOverride(MethodInstance mi, MethodInstance mj, boolean allowCovariantReturn) throws SemanticException { if (mi == mj) return; if (!(mi.name().equals(mj.name()) && mi.hasFormals(mj.formalTypes(), context))) { throw new SemanticException(mi.signature() + " in " + mi.container() + " cannot override " + mj.signature() + " in " + mj.container()+ "; incompatible " + "parameter types", mi.errorPosition()); } boolean shouldReport = reporter.should_report(Reporter.types, 3); if (allowCovariantReturn ? !isSubtype(mi.returnType(), mj.returnType()) : !typeEquals(mi.returnType(), mj.returnType())) { if (shouldReport) reporter.report(3, "return type " + mi.returnType() + " != " + mj.returnType()); throw new SemanticException(mi.signature() + " in " + mi.container() + " cannot override " + mj.signature() + " in " + mj.container()+ "; attempting to use incompatible return type." + "\n\tFound: " + mi.returnType() + "\n\tExpected: " + mj.returnType(),mi.errorPosition()); } /* if (!ts.throwsSubset(mi, mj)) { if (Report.should_report(Report.types, 3)) Report.report(3, mi.throwTypes() + " not subset of " + mj.throwTypes()); throw new SemanticException(mi.signature() + " in " + mi.container() + " cannot override " + mj.signature() + " in " + mj.container() + "; the throw set " + mi.throwTypes() + " is not a subset of the " + "overridden method's throw set " + mj.throwTypes() + ".", mi.position()); } */ if (mi.flags().moreRestrictiveThan(mj.flags())) { if (shouldReport) reporter.report(3, mi.flags() + " more restrictive than " + mj.flags()); throw new SemanticException(mi.signature() + " in " + mi.container() + " cannot override " + mj.signature() + " in " + mj.container()+ "; attempting to assign weaker " + "access privileges", mi.errorPosition()); } if (mi.flags().isStatic() != mj.flags().isStatic()) { if (shouldReport) reporter.report(3, mi.signature() + " is " + (mi.flags().isStatic() ? "" : "not") + " static but " + mj.signature() + " is " + (mj.flags().isStatic() ? "" : "not") + " static"); throw new SemanticException(mi.signature() + " in " + mi.container() + " cannot override " + mj.signature() + " in " + mj.container()+ "; overridden method is " + (mj.flags().isStatic() ? "" : "not") + "static", mi.errorPosition()); } if (!mi.def().equals(mj.def()) && mj.flags().isFinal()) { // mi can "override" a final method mj if mi and mj are the same // method instance. if (shouldReport) reporter.report(3, mj.flags() + " final"); throw new SemanticException(mi.signature() + " in " + mi.container() + " cannot override " + mj.signature() + " in " + mj.container()+ "; overridden method is final", mi.errorPosition()); } } /** * Returns true iff <m1> is the same method as <m2> */ public boolean isSameMethod(MethodInstance m1, MethodInstance m2) { return m1.name().equals(m2.name()) && m1.hasFormals(m2.formalTypes(), context); } public boolean callValid(ProcedureInstance<? extends ProcedureDef> prototype, Type thisType, List<Type> argTypes) { return ((ProcedureInstance_c<?>) prototype).callValid(thisType, argTypes, context); } public abstract Type findMemberType(Type container, Name name) throws SemanticException; }