def _TestTypeParameters(self, reverse=False): ast = parser.parse_string(pytd_src(""" from typing import Any, Generic class `~unknown0`(): def next(self) -> Any: ... T = TypeVar('T') class A(Generic[T], object): def next(self) -> Any: ... class B(): pass def left(x: `~unknown0`) -> Any: ... def right(x: A[B]) -> Any: ... """), options=self.options) ast = self.LinkAgainstSimpleBuiltins(ast) m = type_match.TypeMatch() left, right = ast.Lookup("left"), ast.Lookup("right") match = m.match(right, left, {}) if reverse else m.match( left, right, {}) unknown0 = escape.unknown(0) self.assertEqual( match, booleq.And((booleq.Eq(unknown0, "A"), booleq.Eq(f"{unknown0}.A.T", "B")))) self.assertIn(f"{unknown0}.A.T", m.solver.variables)
def test_strict(self): ast = parser.parse_string(pytd_src(""" import typing T = TypeVar('T') class list(typing.Generic[T], object): pass class A(): pass class B(A): pass class `~unknown0`(): pass a = ... # type: A def left() -> `~unknown0`: ... def right() -> list[A]: ... """), options=self.options) ast = self.LinkAgainstSimpleBuiltins(ast) m = type_match.TypeMatch(type_match.get_all_subclasses([ast])) left, right = ast.Lookup("left"), ast.Lookup("right") unknown0 = escape.unknown(0) self.assertEqual( m.match(left, right, {}), booleq.And((booleq.Eq(unknown0, "list"), booleq.Eq(f"{unknown0}.list.T", "A"))))
def testStrict(self): ast = parser.parse_string( textwrap.dedent(""" T = TypeVar('T') class list(typing.Generic[T], object): pass class A(): pass class B(A): pass class `~unknown0`(): pass a = ... # type: A def left() -> `~unknown0` def right() -> list[A] """)) ast = visitors.LookupClasses(ast, self.mini_builtins) m = type_match.TypeMatch(type_match.get_all_subclasses([ast])) left, right = ast.Lookup("left"), ast.Lookup("right") self.assertEquals( m.match(left, right, {}), booleq.And((booleq.Eq("~unknown0", "list"), booleq.Eq("~unknown0.list.T", "A"))))
def test_strict(self): ast = parser.parse_string(textwrap.dedent(""" import typing T = TypeVar('T') class list(typing.Generic[T], object): pass class A(): pass class B(A): pass class `~unknown0`(): pass a = ... # type: A def left() -> `~unknown0` def right() -> list[A] """), python_version=self.python_version) ast = self.LinkAgainstSimpleBuiltins(ast) m = type_match.TypeMatch(type_match.get_all_subclasses([ast])) left, right = ast.Lookup("left"), ast.Lookup("right") self.assertEqual( m.match(left, right, {}), booleq.And((booleq.Eq("~unknown0", "list"), booleq.Eq("~unknown0.list.T", "A"))))
def match_unknown_against_protocol(self, matcher, solver, unknown, complete): """Given an ~unknown, match it against a class. Args: matcher: An instance of pytd.type_match.TypeMatch. solver: An instance of pytd.booleq.Solver. unknown: The unknown class to match complete: A complete class to match against. (E.g. a built-in or a user defined class) Returns: An instance of pytd.booleq.BooleanTerm. """ assert is_unknown(unknown) assert is_complete(complete) type_params = {p.type_param: matcher.type_parameter(unknown, complete, p) for p in complete.template} subst = type_params.copy() implication = matcher.match_Protocol_against_Unknown( complete, unknown, subst) if implication is not booleq.FALSE and type_params: # If we're matching against a templated class (E.g. list[T]), record the # fact that we'll also have to solve the type parameters. for param in type_params.values(): solver.register_variable(param.name) solver.implies(booleq.Eq(unknown.name, complete.name), implication)
def _TestTypeParameters(self, reverse=False): ast = parser.parse_string(textwrap.dedent(""" import typing class `~unknown0`(): def next(self) -> ? T = TypeVar('T') class A(typing.Generic[T], object): def next(self) -> ? class B(): pass def left(x: `~unknown0`) -> ? def right(x: A[B]) -> ? """), python_version=self.PYTHON_VERSION) ast = self.LinkAgainstSimpleBuiltins(ast) m = type_match.TypeMatch() left, right = ast.Lookup("left"), ast.Lookup("right") match = m.match(right, left, {}) if reverse else m.match(left, right, {}) self.assertEqual(match, booleq.And((booleq.Eq("~unknown0", "A"), booleq.Eq("~unknown0.A.T", "B")))) self.assertIn("~unknown0.A.T", m.solver.variables)
def _TestTypeParameters(self, reverse=False): ast = parser.parse_string( textwrap.dedent(""" class `~unknown0`(): def next(self) -> ? T = TypeVar('T') class A(typing.Generic[T], object): def next(self) -> ? class B(): pass def left(x: `~unknown0`) -> ? def right(x: A[B]) -> ? """)) ast = visitors.LookupClasses(ast, self.mini_builtins) m = type_match.TypeMatch() left, right = ast.Lookup("left"), ast.Lookup("right") match = m.match(right, left, {}) if reverse else m.match( left, right, {}) self.assertEquals( match, booleq.And((booleq.Eq("~unknown0", "A"), booleq.Eq("~unknown0.A.T", "B")))) self.assertIn("~unknown0.A.T", m.solver.variables)
def match_Unknown_against_Generic(self, t1, t2, subst): # pylint: disable=invalid-name 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.cls.name) type_params = [ self.type_parameter(t1, t2.base_type.cls, item) for item in t2.base_type.cls.template ] for type_param in type_params: self.solver.register_variable(type_param.name) 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 match_Generic_against_Generic(self, t1, t2, subst): # pylint: disable=invalid-name """Match a pytd.GenericType against another pytd.GenericType.""" assert isinstance(t1.base_type, pytd.ClassType), type(t1.base_type) assert isinstance(t2.base_type, pytd.ClassType), type(t2.base_type) # 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.cls.name, t2.base_type.cls.name) if base_type_cmp is booleq.FALSE: return booleq.FALSE assert len(t1.parameters) == len(t2.parameters), t1.base_type.cls.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_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(kramm): Use utils:TypeMatcher to simplify this? if isinstance(t2, pytd.AnythingType): # We can match anything against AnythingType. (It's like top) return booleq.TRUE elif isinstance(t1, pytd.AnythingType): if self.any_also_is_bottom: # We can match AnythingType against everything. (It's like bottom) return booleq.TRUE else: return booleq.FALSE elif isinstance(t1, pytd.NothingType): # nothing as an actual type matches against everything, since it # represents an empty value. return booleq.TRUE elif isinstance(t2, pytd.NothingType): # We can't match anything against nothing as an expected type (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) or isinstance(t1, StrictType) and isinstance(t2, pytd.ClassType)): # For strict types, avoid subclasses of the left side. return booleq.Eq(self._full_name(t1), self._full_name(t2)) elif (isinstance(t1, pytd.ClassType) and hasattr(t2, "name") and t2.name == "__builtin__.object"): return booleq.TRUE elif (hasattr(t1, "name") and hasattr(t2, "name") and t1.name in ("__builtin__.type", "typing.Callable") and t2.name in ("__builtin__.type", "typing.Callable")): return booleq.TRUE 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. 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): if self.any_also_is_bottom: # E.g. list (a.k.a. list[Any]) matches against list[str] return self.match_type_against_type(t1, t2.base_type, subst) else: 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) elif isinstance(t1, pytd.LateType) or isinstance(t2, pytd.LateType): # Unresolved types never match against anything. return booleq.FALSE else: raise AssertionError("Don't know how to match %s against %s" % (type(t1), type(t2)))
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(kramm): Use 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) or isinstance(t1, StrictType) and isinstance(t2, pytd.ClassType)): # For strict types, avoid subclasses of the left side. return booleq.Eq(self._full_name(t1), self._full_name(t2)) 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. 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)))