def testHash(self): eq1 = booleq.Eq("a", "b") eq2 = booleq.Eq("b", "c") eq3 = booleq.Eq("c", "d") self.assertEqual(hash(booleq.Eq("x", "y")), hash(booleq.Eq("y", "x"))) self.assertEqual(hash(booleq.Or([eq1, eq2, eq3])), hash(booleq.Or([eq2, eq3, eq1]))) self.assertEqual(hash(booleq.And([eq1, eq2, eq3])), hash(booleq.And([eq2, eq3, eq1])))
def match_signature_against_function(self, sig, f, subst, skip_self=False): # TODO: We should abort after the first matching signature, to get # more precise types in the presence of overloading. # TODO ... except it's possible that further computation will # invalidate the first matching signature, so we need # a way to preserve the alternatives and backtrack # through them if necessary return booleq.And( booleq.Or( self.match_signature_against_signature( sig, s, subst, skip_self) for s in f.signatures) for inner_sig in sig.Visit(optimize.ExpandSignatures()))
def match_generic_against_unknown(self, t1, t2, subst): assert isinstance(t1.base_type, pytd.ClassType) base_match = booleq.Eq(t1.base_type.name, t2.name) type_params = [ self.type_parameter(t2, t1.base_type.cls, item) for item in t1.base_type.cls.template ] params = [ self.match_type_against_type(p1, p2, subst) for p1, p2 in zip(t1.parameters, type_params) ] return booleq.And([base_match] + params)
def match_unknown_against_generic(self, t1, t2, subst): assert isinstance(t2.base_type, pytd.ClassType) # No inheritance for base classes - you can only inherit from an # instantiated template, but not from a template itself. base_match = booleq.Eq(t1.name, t2.base_type.name) type_params = [ self.type_parameter(t1, t2.base_type.cls, item) for item in t2.base_type.cls.template ] params = [ self.match_type_against_type(p1, p2, subst) for p1, p2 in zip(type_params, t2.parameters) ] return booleq.And([base_match] + params)
def testAnd(self): self.assertEqual(booleq.TRUE, booleq.And([])) self.assertEqual(booleq.TRUE, booleq.And([booleq.TRUE])) self.assertEqual(booleq.TRUE, booleq.And([booleq.TRUE, booleq.TRUE])) self.assertEqual(booleq.FALSE, booleq.And([booleq.TRUE, booleq.FALSE])) self.assertEqual(booleq.Eq("a", "b"), booleq.And([booleq.Eq("a", "b"), booleq.TRUE])) self.assertEqual(booleq.FALSE, booleq.And([booleq.Eq("a", "b"), booleq.FALSE]))
def match_class_against_class(self, cls1, cls2, subst): """Match a pytd.Class against another pytd.Class.""" implications = [] for f1 in cls1.methods: try: f2 = cls2.Lookup(f1.name) except KeyError: # The class we're matching against doesn't even have this method. This # is the easiest and most common case. # TODO: Search base classes implication = booleq.FALSE else: implication = self.match_function_against_function( f1, f2, subst, skip_self=True) implications.append(implication) if implication is booleq.FALSE: break # TODO: class attributes return booleq.And(implications)
def match_generic_against_generic(self, t1, t2, subst): """Match a pytd.GenericType against another pytd.GenericType.""" assert isinstance(t1.base_type, pytd.ClassType) assert isinstance(t2.base_type, pytd.ClassType) # We don't do inheritance for base types, since right now, inheriting from # instantiations of templated types is not supported by pytd. if (is_complete(t1.base_type.cls) and is_complete(t2.base_type.cls) and t1.base_type.cls.name != t2.base_type.cls.name): # Optimization: If the base types are incompatible, these two generic # types can never match. base_type_cmp = booleq.FALSE else: base_type_cmp = booleq.Eq(t1.base_type.name, t2.base_type.name) if base_type_cmp == booleq.FALSE: return booleq.FALSE assert len(t1.parameters) == len(t2.parameters), t1.base_type.name # Type parameters are covariant: # E.g. passing list<int> as argument for list<object> succeeds. param_cmp = [ self.match_type_against_type(p1, p2, subst) for p1, p2 in zip(t1.parameters, t2.parameters) ] return booleq.And([base_type_cmp] + param_cmp)
def match_signature_against_signature(self, sig1, sig2, subst, skip_self): """Match a pytd.Signature against another pytd.Signature. Args: sig1: The caller sig2: The callee subst: Current type parameters. skip_self: If True, doesn't compare the first paramter, which is considered (and verified) to be "self". Returns: An instance of booleq.BooleanTerm, i.e. a boolean formula. """ assert not sig1.template assert not sig1.has_optional # Signatures have type parameters, too. We ignore them, since they can # be anything. (See maybe_lookup_type_param()) subst.update({p.type_param: None for p in sig2.template}) params2 = sig2.params params1 = sig1.params[:len(params2 )] if sig2.has_optional else sig1.params if skip_self: assert params1[0].name == "self" assert params2[0].name == "self" params1 = params1[1:] params2 = params2[1:] if len(params1) == len(params2): equalities = [] for p1, p2 in zip(params1, params2): equalities.append( self.match_type_against_type(p1.type, p2.type, subst)) equalities.append( self.match_type_against_type(sig1.return_type, sig2.return_type, subst)) return booleq.And(equalities) else: return booleq.FALSE
def testOrder(self): eq1 = booleq.Eq("a", "b") eq2 = booleq.Eq("b", "c") self.assertEqual(booleq.Or([eq1, eq2]), booleq.Or([eq2, eq1])) self.assertEqual(booleq.And([eq1, eq2]), booleq.And([eq2, eq1]))
def match_function_against_function(self, f1, f2, subst, skip_self=False): return booleq.And( self.match_signature_against_function(s1, f2, subst, skip_self) for s1 in f1.signatures)
def match_type_against_type(self, t1, t2, subst): """Match a pytd.TYPE against another pytd.TYPE.""" t1 = self.maybe_lookup_type_param(t1, subst) t2 = self.maybe_lookup_type_param(t2, subst) # TODO: Use pytypedecl/utils:TypeMatcher to simplify this? if isinstance(t1, pytd.AnythingType) or isinstance( t2, pytd.AnythingType): # We can match anything against AnythingType return booleq.TRUE elif isinstance(t1, pytd.NothingType) and isinstance( t2, pytd.NothingType): # nothing matches against nothing. return booleq.TRUE elif isinstance(t1, pytd.NothingType) or isinstance( t2, pytd.NothingType): # We can't match anything against nothing. (Except nothing itself, above) return booleq.FALSE elif isinstance(t1, pytd.UnionType): return booleq.And( self.match_type_against_type(u, t2, subst) for u in t1.type_list) elif isinstance(t2, pytd.UnionType): return booleq.Or( self.match_type_against_type(t1, u, subst) for u in t2.type_list) elif isinstance(t1, pytd.ClassType) and isinstance(t2, StrictType): # For strict types, avoid subclasses of the left side. return booleq.Eq(t1.name, t2.name) elif isinstance(t1, pytd.ClassType): # ClassTypes are similar to Unions, except they're disjunctions: We can # match the type or any of its base classes against the formal parameter. return booleq.Or( self.match_type_against_type(t, t2, subst) for t in self.expand_superclasses(t1)) elif isinstance(t2, pytd.ClassType): # ClassTypes on the right are exactly like Unions: We can match against # this type or any of its subclasses. # TODO: # if not allow_subclass: # return self.match_type_against_type(t1, self.unclass(t2), subst) return booleq.Or( self.match_type_against_type(t1, t, subst) for t in self.expand_subclasses(t2)) assert not isinstance(t1, pytd.ClassType) assert not isinstance(t2, pytd.ClassType) if is_unknown(t1) and isinstance(t2, pytd.GenericType): return self.match_unknown_against_generic(t1, t2, subst) elif isinstance(t1, pytd.GenericType) and is_unknown(t2): return self.match_generic_against_unknown(t1, t2, subst) elif isinstance(t1, pytd.GenericType) and isinstance( t2, pytd.GenericType): return self.match_generic_against_generic(t1, t2, subst) elif isinstance(t1, pytd.GenericType): # E.g. list<...> matches against list, or even object. return self.match_type_against_type(t1.base_type, t2, subst) elif isinstance(t2, pytd.GenericType): assert t1 != t2.base_type return booleq.FALSE elif is_unknown(t1) and is_unknown(t2): return booleq.Eq(t1.name, t2.name) elif (isinstance(t1, (pytd.NamedType, StrictType)) and isinstance(t2, (pytd.NamedType, StrictType))): if is_complete(t1) and is_complete(t2) and t1.name != t2.name: # Optimization: If we know these two can never be equal, just return # false right away. return booleq.FALSE else: return booleq.Eq(t1.name, t2.name) else: raise AssertionError("Don't know how to match %s against %s" % (type(t1), type(t2)))