class Types {

	public static boolean isSubtype(Type subType, Type superType) {
	    if (Types.isEqual(subType, superType)) return true;
	    if (subType instanceof IntegerType && superType instanceof FloatType) {
	        return true;
	    }
	    if (superType instanceof TopReferenceType && isRefType(subType)) {
	        return true;
	    }
	    if (subType instanceof BottomReferenceType && isRefType(superType)) {
	        return true;
	    }
	    if (subType instanceof ObjectType && superType instanceof ObjectType) {
	        // NOTE: this only handles concrete types and does not reason about MRO/MRW
	        ObjectType subObjType = (ObjectType) subType;
	        List<Property> superTypeProps = ((ObjectType)superType).properties();
	        // each super type property should be present in the subtype with (1) the same
	        // type and (2) an equally strong or stronger read-write permission
	        for (Property superProp: superTypeProps) {
	            if (!subObjType.hasProperty(superProp.getName())) {
	                return false;
	            }
	            Property subProp = subObjType.getProperty(superProp.getName());
	            if (!Types.isEqual(superProp.getType(), subProp.getType())) {
	                return false;
	            }
	            if (superProp.isRW() && !subProp.isRW()) { // permissions weakened
	                return false;
	            }
	        }
	        return true;
	    }
	    return false;
	}

}

class TypesTest {

    @Test
    public void testRecursiveSubtype() {

        ObjectType o1 = new ObjectType();
        o1.setProperty("f", o1);
        ObjectType o2 = new ObjectType();
        o2.setProperty("f", o2);
        Assert.assertTrue(Types.isSubtype(o1, o2));

        FunctionType f1 = new FunctionType(oneElementArrayList(null), oneElementArrayList("1"), new VoidType());
        f1.setParamType(f1, 0);
        FunctionType f2 = new FunctionType(oneElementArrayList(null), oneElementArrayList("1"), new VoidType());
        f2.setParamType(f2, 0);
        Assert.assertTrue(Types.isSubtype(f1, f2));
    }

}