Esempio n. 1
0
    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"))))
Esempio n. 2
0
    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"))))
Esempio n. 3
0
 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)
Esempio n. 4
0
 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)
     base1 = pytd.ClassType(t1.base_type.cls.name, t1.base_type.cls)
     base2 = pytd.ClassType(t2.base_type.cls.name, t2.base_type.cls)
     base_type_cmp = self.match_type_against_type(base1, base2, subst)
     if base_type_cmp is booleq.FALSE:
         return booleq.FALSE
     if not isinstance(t1, pytd.TupleType) and isinstance(
             t2, pytd.TupleType):
         p1, = t1.parameters
         param_cmp = [
             self.match_type_against_type(p1, p2, subst)
             for p2 in t2.parameters
         ]
     else:
         t1_parameters = t1.parameters
         if isinstance(t1, pytd.TupleType):
             if isinstance(t2, pytd.TupleType):
                 if len(t1_parameters) != len(t2.parameters):
                     return booleq.FALSE
             else:
                 t1_parameters = (pytd.UnionType(type_list=t1_parameters), )
         # Matching, e.g., Dict[str, int] against Iterable[K] is legitimate.
         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)
Esempio n. 5
0
    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"))))
Esempio n. 6
0
    def match_Signature_against_Function(self, sig, f, subst, skip_self=False):  # pylint: disable=invalid-name
        def make_or(inner):
            return booleq.Or(
                self.match_Signature_against_Signature(
                    inner, s, subst, skip_self) for s in f.signatures)

        return booleq.And(
            make_or(inner) for inner in visitors.ExpandSignature(sig))
Esempio n. 7
0
  def match_Signature_against_Signature(self, sig1, sig2, subst,
                                        skip_self=False):
    """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 parameter, which is
        considered (and verified) to be "self".
    Returns:
      An instance of booleq.BooleanTerm, i.e. a boolean formula.
    """
    assert not sig1.template
    # 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})
    params1 = sig1.params
    params2 = sig2.params
    if skip_self:
      # Methods in an ~unknown need to declare their methods with "self"
      assert params1 and params1[0].name == "self"
      params1 = params1[1:]
      if params2 and params2[0].name == "self":
        params2 = params2[1:]
    equalities = []
    if len(params1) > len(params2) and not sig2.has_optional:
      return booleq.FALSE  # extra parameters
    if sig1.starargs is not None and sig2.starargs is not None:
      equalities.append(self.match_type_against_type(
          sig1.starargs.type, sig2.starargs.type, subst))
    if sig1.starstarargs is not None and sig2.starstarargs is not None:
      equalities.append(self.match_type_against_type(
          sig1.starstarargs.type, sig2.starstarargs.type, subst))
    # TODO(kramm): Handle kwonly parameters (on either side). Presumably, a
    #              kwonly on the left side means that it was a keyword param.
    for p1, p2 in zip(params1, params2):
      if p1.optional and not p2.optional:
        return booleq.FALSE  # needed for optimize.py:RemoveRedundantSignatures
    for i, p2 in enumerate(params2):
      if i >= len(params1):
        if not p2.optional:
          return booleq.FALSE  # missing parameter
        else:
          pass
      else:
        p1 = params1[i]
        if p1.name != p2.name and not (
            pytd_utils.ANON_PARAM.match(p1.name) or
            pytd_utils.ANON_PARAM.match(p2.name)):
          return booleq.FALSE
        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)
Esempio n. 8
0
 def match_Functions_against_Class(self, methods, cls2, subst):
   implications = []
   cache = {}
   for f1 in methods:
     implication = self.match_Function_against_Class(f1, cls2, subst, cache)
     implications.append(implication)
     if implication is booleq.FALSE:
       break
   # TODO(b/159058933): class attributes
   return booleq.And(implications)
Esempio n. 9
0
 def match_Class_against_Class(self, cls1, cls2, subst):  # pylint: disable=invalid-name
     """Match a pytd.Class against another pytd.Class."""
     implications = []
     cache = {}
     for f1 in cls1.methods:
         implication = self.match_Function_against_Class(
             f1, cls2, subst, cache)
         implications.append(implication)
         if implication is booleq.FALSE:
             break
     # TODO(kramm): class attributes
     return booleq.And(implications)
Esempio n. 10
0
 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)
Esempio n. 11
0
 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)
     base1 = pytd.ClassType(t1.base_type.cls.name, t1.base_type.cls)
     base2 = pytd.ClassType(t2.base_type.cls.name, t2.base_type.cls)
     base_type_cmp = self.match_type_against_type(base1, base2, subst)
     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)
Esempio n. 12
0
    def match_Signature_against_Signature(self,
                                          sig1,
                                          sig2,
                                          subst,
                                          skip_self=False):
        """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
        # 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})
        params1 = sig1.params
        params2 = sig2.params
        if skip_self:
            # Methods in an ~unknown need to declare their methods with "self"
            assert params1 and params1[0].name == "self"
            params1 = params1[1:]
            if params2 and params2[0].name == "self":
                params2 = params2[1:]
        equalities = []
        if len(params1) > len(params2) and not sig2.has_optional:
            return booleq.FALSE  # extra parameters
        for i, p2 in enumerate(params2):
            if not p2.optional:
                if i >= len(params1):
                    return booleq.FALSE  # missing parameter
                p1 = params1[i]
                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)
Esempio n. 13
0
 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)
Esempio n. 14
0
    def match_Signature_against_Signature(self, sig1, sig2, subst, skip_self):  # pylint: disable=invalid-name
        """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:
            # Methods in an ~unknown need to declare their methods with "self"
            assert (params1 and params1[0].name == "self") or sig2.has_optional
            if params1 and params1[0].name == "self":
                params1 = params1[1:]
            # For loaded pytd, we allow methods to omit the "self" parameter.
            if params2 and params2[0].name == "self":
                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
Esempio n. 15
0
 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)
Esempio n. 16
0
 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)
Esempio n. 17
0
 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)))
Esempio n. 18
0
 def match_Function_against_Function(self, f1, f2, subst, skip_self=False):  # pylint: disable=invalid-name
     return booleq.And(
         self.match_Signature_against_Function(s1, f2, subst, skip_self)
         for s1 in f1.signatures)
Esempio n. 19
0
 def match_Signature_against_Function(self, sig, f, subst, skip_self=False):  # pylint: disable=invalid-name
     return booleq.And(
         booleq.Or(
             self.match_Signature_against_Signature(
                 inner, s, subst, skip_self) for s in f.signatures)
         for inner in sig.Visit(visitors.ExpandSignatures()))
Esempio n. 20
0
 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)))