def isSubclassOf(self, other): if self is other or self is builtins.getNothingClass(): return True elif other is builtins.getNothingClass(): return False else: return other in self.superclasses()
def substituteForBase(self, base): assert base is not builtins.getNothingClass() assert self.clas is not builtins.getNothingClass() if self.clas is base: return self baseType = next(sty for sty in self.clas.supertypes if sty.clas is base) return baseType.substitute(self.clas.typeParameters, self.typeArguments)
def substituteForBaseClass(self, base): assert base is not builtins.getNothingClass() assert self.clas is not builtins.getNothingClass() supertypePath = self.clas.findTypePathToBaseClass(base) ty = self for sty in supertypePath: ty = sty.substitute(ty.clas.typeParameters, ty.typeArguments) return ty
def addTypeDefn(irTypeDefn): subtypeGraph.addVertex(irTypeDefn.id) if isinstance(irTypeDefn, ir.ObjectTypeDefn): for supertype in irTypeDefn.supertypes: if supertype.isNullable(): raise InheritanceException.fromDefn(irTypeDefn, "cannot inherit nullable type") if supertype.clas is getNothingClass(): raise InheritanceException.fromDefn(irTypeDefn, "cannot inherit Nothing") supertypeId = getIdForType(supertype) if irTypeDefn.id is supertypeId: raise InheritanceException.fromDefn(irTypeDefn, "cannot inherit from itself") if not supertype.clas.isLocal(): NonLocalObjectTypeDefnScope.ensureForDefn(supertype.clas, info) subtypeGraph.addEdge(irTypeDefn.id, supertypeId) elif isinstance(irTypeDefn, ir.TypeParameter): upperBoundId = getIdForType(irTypeDefn.upperBound) if irTypeDefn.id is upperBoundId: raise InheritanceException.fromDefn(irTypeDefn, "cannot be upper bounded by itself") subtypeGraph.addEdge(irTypeDefn.id, upperBoundId) lowerBoundId = getIdForType(irTypeDefn.lowerBound) if irTypeDefn.id is lowerBoundId: raise InheritanceException.fromDefn(irTypeDefn, "cannot be lower bounded by itself") subtypeGraph.addEdge(lowerBoundId, irTypeDefn.id) else: raise NotImplementedError()
def getReturnType(self): if self.declaredReturnType: return self.declaredReturnType elif self.bodyReturnType: return self.bodyReturnType else: return ir_t.ClassType.forReceiver(getNothingClass())
def glb_(self, ty, stack): if (self, ty) in stack: if self.isObject() and ty.isObject(): return getNothingClassType() else: return NoType if self == ty: return self elif self is NoType: return ty elif ty is NoType: return self elif self.isSubtypeOf(ty): return self elif ty.isSubtypeOf(self): return ty elif self.isObject() and ty.isObject(): # Ok, this is kind of a cop-out. Don't judge me. # TODO: once there are intersection types, use them instead. if self.isNullable() and ty.isNullable(): flags = frozenset([NULLABLE_TYPE_FLAG]) else: flags = frozenset() return ClassType(builtins.getNothingClass(), (), flags) else: return NoType
def addTypeDefn(irTypeDefn): subtypeGraph.addVertex(irTypeDefn.id) if isinstance(irTypeDefn, ir.ObjectTypeDefn): for supertype in irTypeDefn.supertypes: if supertype.isNullable(): raise InheritanceException.fromDefn( irTypeDefn, "cannot inherit nullable type") if supertype.clas is getNothingClass(): raise InheritanceException.fromDefn( irTypeDefn, "cannot inherit Nothing") supertypeId = getIdForType(supertype) if irTypeDefn.id is supertypeId: raise InheritanceException.fromDefn( irTypeDefn, "cannot inherit from itself") if not supertype.clas.isLocal(): NonLocalObjectTypeDefnScope.ensureForDefn( supertype.clas, info) subtypeGraph.addEdge(irTypeDefn.id, supertypeId) elif isinstance(irTypeDefn, ir.TypeParameter): upperBoundId = getIdForType(irTypeDefn.upperBound) if irTypeDefn.id is upperBoundId: raise InheritanceException.fromDefn( irTypeDefn, "cannot be upper bounded by itself") subtypeGraph.addEdge(irTypeDefn.id, upperBoundId) lowerBoundId = getIdForType(irTypeDefn.lowerBound) if irTypeDefn.id is lowerBoundId: raise InheritanceException.fromDefn( irTypeDefn, "cannot be lower bounded by itself") subtypeGraph.addEdge(lowerBoundId, irTypeDefn.id) else: raise NotImplementedError()
def findTypePathToBaseClass(self, base): """Returns a list of supertypes (ClassTypes), which represent a path through the class DAG from this class to the given base class. The path does not include a type for this class, but it does include the supertype for the base. If the given class is not a base, returns None. This class must not be Nothing, since there is no well-defined path in that case.""" assert self is not builtins.getNothingClass() path = [] indexStack = [0] assert self.id is not None visited = set([self.id]) while len(indexStack) > 0: index = indexStack[-1] indexStack[-1] += 1 clas = path[-1].clas if len(path) > 0 else self if clas is base: return path elif index == len(clas.supertypes): if len(path) > 0: path.pop() indexStack.pop() elif clas.supertypes[index].clas.id not in visited: supertype = clas.supertypes[index] assert supertype.clas.id is not None visited.add(supertype.clas.id) path.append(supertype) indexStack.append(0) return None
def superclasses(self): """Returns a generator of superclasses in depth-first order, including this class.""" assert self is not builtins.getNothingClass() yield self clas = self while len(clas.supertypes) > 0: clas = clas.supertypes[0].clas yield clas
def findCommonBaseClass(self, other): """Returns a class which (a) is a superclass of both classes, and (b) has no subclasses which are superclasses of both classes.""" if self is other: return self if self is builtins.getNothingClass(): return other if other is builtins.getNothingClass(): return self selfBases = list(self.superclasses()) otherBases = list(other.superclasses()) if selfBases[-1] is not otherBases[-1]: return None selfLast = -len(selfBases) - 1 otherLast = -len(otherBases) - 1 i = -1 while i > selfLast and i > otherLast and selfBases[i] is otherBases[i]: i -= 1 return selfBases[i + 1]
def superclass(self): assert self is not builtins.getNothingClass() if len(self.supertypes) == 0: return None else: return self.supertypes[0].clas
def isSubtypeOfRules_(self, other, subEnv): left = self right = other # Basic rules that apply to all types. if left.isEquivalent(right): return True if left is AnyType or right is NoType: return False if right is AnyType or left is NoType: return True # Primitive types. if left.isPrimitive() or right.isPrimitive(): return False assert left.isObject() and right.isObject() # Existential types. if isinstance(right, ExistentialType): utils.each(subEnv.addVariable, right.variables) return left.isSubtypeOf_(right.ty, subEnv) if subEnv.isExistentialVar(right): return subEnv.trySubstitute(left, right) if isinstance(left, ExistentialType): return left.ty.isSubtypeOf_(right, subEnv) # A nullable type cannot be a subtype of a non-nullable type. Note that existential # types cannot be nullable (but their inner types can). if left.isNullable() and not right.isNullable(): return False # Variable types. if isinstance(left, VariableType) and isinstance(right, VariableType): # If both are variable types, try to find a common bound. leftBound = left rightFirstBound = right while isinstance(leftBound, VariableType): rightBound = rightFirstBound while isinstance(rightBound, VariableType): if leftBound.typeParameter is rightBound.typeParameter and \ (rightBound.isNullable() or not leftBound.isNullable()): return True rightBound = rightBound.lowerBound() leftBound = leftBound.upperBound() while isinstance(left, VariableType): left = left.upperBound() while isinstance(right, VariableType): right = right.lowerBound() # Class types. assert isinstance(left, ClassType) and isinstance(right, ClassType) # Special cases for `Nothing`. if left.clas is builtins.getNothingClass(): return True if right.clas is builtins.getNothingClass(): return False # Check that left is derived from right, and substitute for the same definition. leftBase = left.clas.findBaseType(right.clas) if leftBase is None: return False left = leftBase.substitute(left.clas.typeParameters, left.typeArguments) # Compare type arguments, based on variance. for lty, rty, tp in \ zip(left.typeArguments, right.typeArguments, left.clas.typeParameters): # This could be more compact, but it's better for debugging to have the cases split. variance = tp.variance() if variance is flags.COVARIANT: if not lty.isSubtypeOf_(rty, subEnv): return False elif variance is flags.CONTRAVARIANT: if not rty.isSubtypeOf_(lty, subEnv): return False else: assert variance is INVARIANT if not (subEnv.isExistentialVar(rty) and subEnv.trySubstitute(lty, rty)) and \ not lty.isEquivalent(rty): return False return True
def _lubRec(self, other, stack): # We need to be able to detect infinite recursion in order to ensure termination. # Consider the case below: # class A[+T] # class B <: A[B] # class C <: A[C] # Suppose we want to find B lub C. # The correct answer is A[B lub C] = A[A[B lub C]] = A[A[A[B lub C]]] ... # Since we have no way to correctly express the least upper bound in that case, we # settle for returning a close upper bound: A[Object]. if (self, other) in stack: if self.isObject() and other.isObject(): return getRootClassType() else: return AnyType # Basic rules that apply to all types. if self == other: return self if self is AnyType or other is AnyType: return self if self is NoType: return other if other is NoType: return self # Rules below apply only to object types. if self.isObject() and other.isObject(): # If either side is nullable, the result is nullable. if self.isNullable() or other.isNullable(): combinedFlags = frozenset([NULLABLE_TYPE_FLAG]) else: combinedFlags = frozenset() # If both types are variables with a common variable bound, return that. if isinstance(self, VariableType) and isinstance(other, VariableType): sharedBound = self.typeParameter.findCommonUpperBound(other.typeParameter) if sharedBound is not None: return VariableType(sharedBound, combinedFlags) # If either type is Nothing, return the other one. if isinstance(self, ClassType) and self.clas is builtins.getNothingClass(): return other.withFlags(combinedFlags) if isinstance(other, ClassType) and other.clas is builtins.getNothingClass(): return self.withFlags(combinedFlags) # Since there is no common bound, the result will be a class type, so we peel back # the bounds until both sides are class types. left = self while isinstance(left, VariableType): left = left.typeParameter.upperBound right = other while isinstance(right, VariableType): right = right.typeParameter.upperBound assert isinstance(left, ClassType) and isinstance(right, ClassType) # Find a common base class. We don't assume that there is a single root class # (even though there is), so this can fail. baseClass = left.clas.findCommonBaseClass(right.clas) while baseClass is not None: left = left.substituteForBaseClass(baseClass) right = right.substituteForBaseClass(baseClass) # We need to combine the type arguments, according to the variance of the # corresponding type parameters. This is not necessarily possible. If we get # stuck, we'll try again with the superclass. leftArgs = left.substituteForBaseClass(baseClass).typeArguments rightArgs = right.substituteForBaseClass(baseClass).typeArguments combinedArgs = [] combineSuccess = True for param, leftArg, rightArg in zip(baseClass.typeParameters, leftArgs, rightArgs): variance = param.variance() if variance is INVARIANT: if leftArg == rightArg: combined = leftArg else: combined = AnyType else: stack.append((self, other)) if variance is flags.COVARIANT: combined = leftArg._lubRec(rightArg, stack) else: assert variance is flags.CONTRAVARIANT combined = leftArg._glbRec(rightArg, stack) stack.pop() if combined is AnyType: combineSuccess = False break combinedArgs.append(combined) if combineSuccess: return ClassType(baseClass, tuple(combinedArgs), combinedFlags) baseClass = baseClass.superclass() # If we get here, then we ran out of superclasses. Fall through. return AnyType
def lubRec_(self, other, stack): # We need to be able to detect infinite recursion in order to ensure termination. # Consider the case below: # class A[+T] # class B <: A[B] # class C <: A[C] # Suppose we want to find B lub C. # The correct answer is A[B lub C] = A[A[B lub C]] = A[A[A[B lub C]]] ... # Since we have no way to correctly express the least upper bound in that case, we # settle for returning a close upper bound: A[Object]. if (self, other) in stack: if self.isObject() and other.isObject(): return getRootClassType() else: return AnyType # Basic rules that apply to all types. if self.isEquivalent(other): return self if self is AnyType or other is AnyType: return self if self is NoType: return other if other is NoType: return self # Rules for existential types (always recursive) if isinstance(self, ExistentialType) or isinstance(other, ExistentialType): stack.append((self, other)) # At this point, we consider both types to be existential. Note that any type # can be trivially closed with an existential. For example: # String == forsome [X] String # We get the lub of the inner types. selfInnerType = self.ty if isinstance(self, ExistentialType) else self otherInnerType = other.ty if isinstance(other, ExistentialType) else other innerLubType = selfInnerType.lubRec_(otherInnerType, stack) stack.pop() # Find which variables are still being used in the lub. If some of the variables # from `self` and `other` are still present, return an existential with those. # Note that unused variables can be removed from an existential type. For example: # forsome [X] Option[String] == Option[String] # We must be careful the returned type has variables in a deterministic order. # Technically, the order shouldn't matter, but it's infeasible to test whether two # types are equivalent with arbitrary ordered variables, since they cannot easily # be sorted. selfVariables = self.variables if isinstance(self, ExistentialType) else () otherVariables = other.variables if isinstance(other, ExistentialType) else () return ExistentialType.close(selfVariables + otherVariables, innerLubType) # Rules below apply only to object types. if self.isObject() and other.isObject(): # If either side is nullable, the result is nullable. if self.isNullable() or other.isNullable(): combinedFlags = frozenset([NULLABLE_TYPE_FLAG]) else: combinedFlags = frozenset() # If both types are variables with a common variable bound, return that. if isinstance(self, VariableType) and isinstance(other, VariableType): sharedBound = self.typeParameter.findCommonUpperBound(other.typeParameter) if sharedBound is not None: return VariableType(sharedBound, combinedFlags) # If either type is Nothing, return the other one. if isinstance(self, ClassType) and self.clas is builtins.getNothingClass(): return other.withFlags(combinedFlags) if isinstance(other, ClassType) and other.clas is builtins.getNothingClass(): return self.withFlags(combinedFlags) # Since there is no common bound, the result will be a class type, so we peel back # the bounds until both sides are class types. left = self while isinstance(left, VariableType): left = left.typeParameter.upperBound right = other while isinstance(right, VariableType): right = right.typeParameter.upperBound assert isinstance(left, ClassType) and isinstance(right, ClassType) # Find a common base class. We don't assume that there is a single root class # (even though there is), so this can fail. baseClass = left.clas.findCommonBaseClass(right.clas) while baseClass is not None: left = left.substituteForBaseClass(baseClass) right = right.substituteForBaseClass(baseClass) # We need to combine the type arguments, according to the variance of the # corresponding type parameters. This is not necessarily possible. If we get # stuck, we'll try again with the superclass. leftArgs = left.typeArguments rightArgs = right.typeArguments combinedArgs = [] combineSuccess = True for param, leftArg, rightArg in zip(baseClass.typeParameters, leftArgs, rightArgs): variance = param.variance() if variance is INVARIANT: if leftArg == rightArg: combined = leftArg else: combined = AnyType else: stack.append((self, other)) if variance is flags.COVARIANT: combined = leftArg.lubRec_(rightArg, stack) else: assert variance is flags.CONTRAVARIANT combined = leftArg.glbRec_(rightArg, stack) stack.pop() if combined is AnyType: combineSuccess = False break combinedArgs.append(combined) if combineSuccess: return ClassType(baseClass, tuple(combinedArgs), combinedFlags) baseClass = baseClass.superclass() # If we get here, then we ran out of superclasses. Fall through. return AnyType
def testFindCommonBaseClassWithNothing(self): nothing = getNothingClass() self.assertIs(self.A, self.A.findCommonBaseClass(nothing)) self.assertIs(self.A, nothing.findCommonBaseClass(self.A))
def getNothingClassType(): return ClassType(builtins.getNothingClass())
def testThrowExpr(self): info = self.analyzeFromSource("def f(exn: Exception) = throw exn") self.assertEquals(ClassType(getNothingClass()), info.package.findFunction(name="f").returnType) self.assertEquals(NoType, info.getType(info.ast.definitions[0].body))
def getNullType(): return ClassType(builtins.getNothingClass(), (), frozenset([NULLABLE_TYPE_FLAG]))
def testFindCommonBaseClassWithNothing(self): nothing = builtins.getNothingClass() self.assertIs(self.A, self.A.findCommonBaseClass(nothing)) self.assertIs(self.A, nothing.findCommonBaseClass(self.A))