Exemple #1
0
    def test_very_mutual_recursion(self):
        ast = self._import(a="""
      from typing import List
      X = List[Y]
      Y = List[Z]
      Z = List[X]
    """)

        actual_x = ast.Lookup("a.X")
        expected_x = pytd.Alias(name="a.X",
                                type=pytd.GenericType(
                                    base_type=pytd.ClassType("builtins.list"),
                                    parameters=(pytd.LateType(
                                        "a.Y", recursive=True), )))
        self.assertEqual(actual_x, expected_x)

        actual_y = ast.Lookup("a.Y")
        expected_y = pytd.Alias(name="a.Y",
                                type=pytd.GenericType(
                                    base_type=pytd.ClassType("builtins.list"),
                                    parameters=(pytd.LateType(
                                        "a.Z", recursive=True), )))
        self.assertEqual(actual_y, expected_y)

        actual_z = ast.Lookup("a.Z")
        expected_z = pytd.Alias(
            name="a.Z",
            type=pytd.GenericType(
                base_type=pytd.ClassType("builtins.list"),
                parameters=(pytd.GenericType(
                    base_type=pytd.ClassType("builtins.list"),
                    parameters=(pytd.LateType("a.Y", recursive=True), )), )))
        self.assertEqual(actual_z, expected_z)
Exemple #2
0
 def new_alias_or_constant(self, name, value):
   """Build an alias or constant."""
   # This is here rather than in _Definitions because we need to build a
   # constant or alias from a partially converted typed_ast subtree.
   if name == "__slots__":
     if not (isinstance(value, ast3.List) and
             all(types.Pyval.is_str(x) for x in value.elts)):
       raise ParseError("__slots__ must be a list of strings")
     return types.SlotDecl(tuple(x.value for x in value.elts))
   elif isinstance(value, types.Pyval):
     return pytd.Constant(name, value.to_pytd())
   elif isinstance(value, types.Ellipsis):
     return pytd.Constant(name, pytd.AnythingType())
   elif isinstance(value, pytd.NamedType):
     res = self.defs.resolve_type(value.name)
     return pytd.Alias(name, res)
   elif isinstance(value, ast3.List):
     if name != "__all__":
       raise ParseError("Only __slots__ and __all__ can be literal lists")
     return pytd.Constant(name, pytdgen.pytd_list("str"))
   elif isinstance(value, ast3.Tuple):
     # TODO(mdemello): Consistent with the current parser, but should it
     # properly be Tuple[Type]?
     return pytd.Constant(name, pytd.NamedType("tuple"))
   elif isinstance(value, ast3.Name):
     value = self.defs.resolve_type(value.id)
     return pytd.Alias(name, value)
   else:
     # TODO(mdemello): add a case for TypeVar()
     # Convert any complex type aliases
     value = self.convert_node(value)
     return pytd.Alias(name, value)
Exemple #3
0
    def value_to_pytd_def(self, node, v, name):
        """Get a PyTD definition for this object.

    Args:
      node: The node.
      v: The object.
      name: The object name.

    Returns:
      A PyTD definition.
    """
        if isinstance(v, abstract.Module):
            return pytd.Alias(name, pytd.Module(name, module_name=v.full_name))
        elif isinstance(v, abstract.BoundFunction):
            d = self.value_to_pytd_def(node, v.underlying, name)
            assert isinstance(d, pytd.Function)
            sigs = tuple(
                sig.Replace(params=sig.params[1:]) for sig in d.signatures)
            return d.Replace(signatures=sigs)
        elif isinstance(v, attr_overlay.Attrs):
            ret = pytd.NamedType("typing.Callable")
            md = metadata.to_pytd(v.to_metadata())
            return pytd.Annotated(ret, ("'pytype_metadata'", md))
        elif (isinstance(v, abstract.PyTDFunction)
              and not isinstance(v, typing_overlay.TypeVar)):
            return pytd.Function(
                name=name,
                signatures=tuple(sig.pytd_sig for sig in v.signatures),
                kind=v.kind,
                flags=pytd.MethodFlag.abstract_flag(v.is_abstract))
        elif isinstance(v, abstract.InterpreterFunction):
            return self._function_to_def(node, v, name)
        elif isinstance(v, abstract.SimpleFunction):
            return self._simple_func_to_def(node, v, name)
        elif isinstance(v, (abstract.ParameterizedClass, abstract.Union)):
            return pytd.Alias(name, v.get_instance_type(node))
        elif isinstance(v, abstract.PyTDClass) and v.module:
            # This happens if a module does e.g. "from x import y as z", i.e., copies
            # something from another module to the local namespace. We *could*
            # reproduce the entire class, but we choose a more dense representation.
            return v.to_type(node)
        elif isinstance(v, typed_dict.TypedDictClass):
            return self._typed_dict_to_def(node, v, name)
        elif isinstance(v, abstract.PyTDClass):  # a namedtuple instance
            assert name != v.name
            return pytd.Alias(name, pytd.NamedType(v.name))
        elif isinstance(v, abstract.InterpreterClass):
            if v.official_name is None or name == v.official_name:
                return self._class_to_def(node, v, name)
            else:
                return pytd.Alias(name, pytd.NamedType(v.official_name))
        elif isinstance(v, abstract.TypeParameter):
            return self._typeparam_to_def(node, v, name)
        elif isinstance(v, abstract.Unsolvable):
            return pytd.Constant(name, v.to_type(node))
        else:
            raise NotImplementedError(v.__class__.__name__)
Exemple #4
0
  def add_import(self, from_package, import_list):
    """Add an import.

    Args:
      from_package: A dotted package name if this is a "from" statement, or None
          if it is an "import" statement.
      import_list: A list of imported items, which are either strings or pairs
          of strings.  Pairs are used when items are renamed during import
          using "as".
    """
    if not self._current_condition.active:
      return
    if from_package:
      # from a.b.c import d, ...
      for item in import_list:
        if isinstance(item, tuple):
          name, new_name = item
        else:
          name = new_name = item
        qualified_name = "%s.%s" % (from_package, name)
        if from_package == "__PACKAGE__" and isinstance(item, str):
          # This will always be a simple module import (from . cannot import a
          # NamedType, and without 'as' the name will not be reexported).
          t = pytd.Module(name=new_name, module_name=qualified_name)
        else:
          # We should ideally not need this check, but we have typing
          # special-cased in some places.
          if not qualified_name.startswith("typing.") and name != "*":
            # Mark this as an externally imported type, so that AddNamePrefix
            # does not prefix it with the current package name.
            qualified_name = (parser_constants.EXTERNAL_NAME_PREFIX +
                              qualified_name)
          t = pytd.NamedType(qualified_name)
        if name == "*":
          # A star import is stored as
          # 'imported_mod.* = imported_mod.*'. The imported module needs to be
          # in the alias name so that multiple star imports are handled
          # properly. LookupExternalTypes() replaces the alias with the
          # contents of the imported module.
          assert new_name == name
          new_name = t.name
        self._type_map[new_name] = t
        if from_package != "typing" or self._ast_name == "protocols":
          self._aliases.append(pytd.Alias(new_name, t))
          self._module_path_map[name] = qualified_name
    else:
      # import a, b as c, ...
      for item in import_list:
        if isinstance(item, tuple):
          name, new_name = item
          t = pytd.Module(name=new_name, module_name=name)
          self._aliases.append(pytd.Alias(new_name, t))
        else:
          # We don't care about imports that are not aliased.
          pass
Exemple #5
0
  def value_to_pytd_def(self, node, v, name):
    """Get a PyTD definition for this object.

    Args:
      node: The node.
      v: The object.
      name: The object name.

    Returns:
      A PyTD definition.
    """
    if isinstance(v, abstract.PyTDFunction):
      return pytd.Function(
          name, tuple(sig.pytd_sig for sig in v.signatures), pytd.METHOD)
    elif isinstance(v, abstract.InterpreterFunction):
      return self._function_to_def(node, v, name)
    elif isinstance(v, abstract.ParameterizedClass):
      return pytd.Alias(name, v.get_instance_type(node))
    elif isinstance(v, abstract.PyTDClass):
      # This happens if a module does e.g. "from x import y as z", i.e., copies
      # something from another module to the local namespace. We *could*
      # reproduce the entire class, but we choose a more dense representation.
      return v.to_type(node)
    elif isinstance(v, abstract.InterpreterClass):
      return self._class_to_def(node, v, name)
    elif isinstance(v, abstract.TypeVariable):
      return pytd.TypeParameter(name, None)
    elif isinstance(v, abstract.Unsolvable):
      return pytd.Constant(name, v.to_type(node))
    else:
      raise NotImplementedError(v.__class__.__name__)
Exemple #6
0
 def new_alias_or_constant(self, name_and_value):
   name, value = name_and_value
   if name == "__slots__":
     return _SlotDecl(value)
   elif value in [pytd.NamedType("True"), pytd.NamedType("False")]:
     return pytd.Constant(name, pytd.NamedType("bool"))
   else:
     return pytd.Alias(name, value)
Exemple #7
0
  def testAliasPrinting(self):
    a = pytd.Alias("MyList", pytd.GenericType(
        pytd.NamedType("typing.List"), (pytd.AnythingType(),)))
    ty = pytd_utils.CreateModule("test", aliases=(a,))
    expected = textwrap.dedent("""
      from typing import Any, List

      MyList = List[Any]""")
    self.assertMultiLineEqual(expected.strip(), pytd.Print(ty).strip())
Exemple #8
0
    def value_to_pytd_def(self, node, v, name):
        """Get a PyTD definition for this object.

    Args:
      node: The node.
      v: The object.
      name: The object name.

    Returns:
      A PyTD definition.
    """
        if (isinstance(v, abstract.PyTDFunction)
                and not isinstance(v, typing.TypeVar)):
            return pytd.Function(
                name=name,
                signatures=tuple(sig.pytd_sig for sig in v.signatures),
                kind=v.kind,
                flags=pytd.Function.abstract_flag(v.is_abstract))
        elif isinstance(v, abstract.InterpreterFunction):
            return self._function_to_def(node, v, name)
        elif isinstance(v, abstract.SimpleFunction):
            return self._simple_func_to_def(node, v, name)
        elif isinstance(v, abstract.ParameterizedClass):
            return pytd.Alias(name, v.get_instance_type(node))
        elif isinstance(v, abstract.PyTDClass) and v.module:
            # This happens if a module does e.g. "from x import y as z", i.e., copies
            # something from another module to the local namespace. We *could*
            # reproduce the entire class, but we choose a more dense representation.
            return v.to_type(node)
        elif isinstance(v, abstract.PyTDClass):  # a namedtuple instance
            assert name != v.name
            return pytd.Alias(name, pytd.NamedType(v.name))
        elif isinstance(v, abstract.InterpreterClass):
            if v.official_name is None or name == v.official_name:
                return self._class_to_def(node, v, name)
            else:
                return pytd.Alias(name, pytd.NamedType(v.official_name))
        elif isinstance(v, abstract.TypeParameter):
            return self._typeparam_to_def(node, v, name)
        elif isinstance(v, abstract.Unsolvable):
            return pytd.Constant(name, v.to_type(node))
        else:
            raise NotImplementedError(v.__class__.__name__)
Exemple #9
0
 def test_basic(self):
     ast = self._import(a="""
   from typing import List
   X = List[X]
 """)
     actual_x = ast.Lookup("a.X")
     expected_x = pytd.Alias(name="a.X",
                             type=pytd.GenericType(
                                 base_type=pytd.ClassType("builtins.list"),
                                 parameters=(pytd.LateType(
                                     "a.X", recursive=True), )))
     self.assertEqual(actual_x, expected_x)
Exemple #10
0
  def testAliasPrinting(self):
    a = pytd.Alias("MyList", pytd.GenericType(
        pytd.NamedType("typing.List"), (pytd.AnythingType(),)))
    ty = pytd.TypeDeclUnit(
        name="test",
        constants=(),
        type_params=(),
        classes=(),
        functions=(),
        aliases=(a,))
    expected = textwrap.dedent("""
      from typing import Any, List

      MyList = List[Any]""")
    self.assertMultiLineEqual(expected.strip(), pytd.Print(ty).strip())
Exemple #11
0
    def add_alias_or_constant(self, name, value):
        """Add an alias or constant.

    Args:
      name: The name of the alias or constant.
      value: A pytd type.  If the type is NamedType("True") or
          NamedType("False") the name becomes a constant of type bool,
          otherwise it becomes an alias.
    """
        if not self._current_condition.active:
            return
        # TODO(dbaum): Consider merging this with new_constant().
        if value in [pytd.NamedType("True"), pytd.NamedType("False")]:
            self._constants.append(pytd.Constant(name, pytd.NamedType("bool")))
        else:
            self._type_map[name] = value
            self._aliases.append(pytd.Alias(name, value))
Exemple #12
0
  def add_import(self, from_package, import_list):
    """Add an import.

    Args:
      from_package: A dotted package name if this is a "from" statement, or None
          if it is an "import" statement.
      import_list: A list of imported items, which are either strings or pairs
          of strings.  Pairs are used when items are renamed during import
          using "as".

    Raises:
      ParseError: If an import statement uses a rename.
    """
    if from_package:
      if not self._current_condition.active:
        return
      # from a.b.c import d, ...
      for item in import_list:
        if isinstance(item, tuple):
          name, new_name = item
        else:
          name = new_name = item
        t = pytd.NamedType("%s.%s" % (from_package, name))
        if name == "*":
          # A star import is stored as
          # 'imported_mod.* = imported_mod.*'. The imported module needs to be
          # in the alias name so that multiple star imports are handled
          # properly. LookupExternalTypes() replaces the alias with the
          # contents of the imported module.
          assert new_name == name
          new_name = t.name
        self._type_map[new_name] = t
        if from_package != "typing" or self._ast_name == "protocols":
          self._aliases.append(pytd.Alias(new_name, t))
          self._module_path_map[name] = "%s.%s" % (from_package, name)
    else:
      # No need to check _current_condition since there are no side effects.
      # import a, b as c, ...
      for item in import_list:
        # simple import, no impact on pyi, but check for unsupported rename.
        if isinstance(item, tuple):
          raise ParseError(
              "Renaming of modules not supported. Use 'from' syntax.")
Exemple #13
0
def get_decorators(decorators: List[str], type_map: Dict[str, pytd_node.Node]):
    """Process a class decorator list."""

    # Drop the @type_check_only decorator from classes
    # TODO(mdemello): Workaround for the bug that typing.foo class decorators
    # don't add the import, since typing.type_check_only is the only one.
    decorators = [x for x in decorators if x != "type_check_only"]

    # Check for some function/method-only decorators
    nonclass = {"property", "classmethod", "staticmethod", "overload"}
    unsupported_decorators = set(decorators) & nonclass
    if unsupported_decorators:
        raise ParseError("Unsupported class decorators: %s" %
                         ", ".join(unsupported_decorators))

    # Convert decorators to named types. These are wrapped as aliases because we
    # otherwise do not allow referencing functions as types.
    return [
        pytd.Alias(d,
                   type_map.get(d) or pytd.NamedType(d)) for d in decorators
    ]
Exemple #14
0
    def add_import(self, from_package, import_list):
        """Add an import.

    Args:
      from_package: A dotted package name if this is a "from" statement, or None
          if it is an "import" statement.
      import_list: A list of imported items, which are either strings or pairs
          of strings.  Pairs are used when items are renamed during import
          using "as".

    Raises:
      ParseError: If an import statement uses a rename.
    """
        if from_package:
            if not self._current_condition.active:
                return
            # from a.b.c import d, ...
            for item in import_list:
                if isinstance(item, tuple):
                    name, new_name = item
                else:
                    name = new_name = item
                if name != "*":
                    t = pytd.NamedType("%s.%s" % (from_package, name))
                    self._type_map[new_name] = t
                    if from_package != "typing":
                        self._aliases.append(pytd.Alias(new_name, t))
                else:
                    pass  # TODO(kramm): Handle '*' imports in pyi
        else:
            # No need to check _current_condition since there are no side effects.
            # import a, b as c, ...
            for item in import_list:
                # simple import, no impact on pyi, but check for unsupported rename.
                if isinstance(item, tuple):
                    raise ParseError(
                        "Renaming of modules not supported. Use 'from' syntax."
                    )
Exemple #15
0
    def value_to_pytd_type(self, node, v, seen, view):
        """Get a PyTD type representing this object, as seen at a node.

    Args:
      node: The node from which we want to observe this object.
      v: The object.
      seen: The set of values seen before while computing the type.
      view: A Variable -> binding map.

    Returns:
      A PyTD type.
    """
        if isinstance(v, (abstract.Empty, typing_overlay.NoReturn)):
            return pytd.NothingType()
        elif isinstance(v, abstract.TypeParameterInstance):
            if (v.module in self._scopes
                    or v.instance is abstract_utils.DUMMY_CONTAINER):
                return self._typeparam_to_def(node, v.param, v.param.name)
            elif v.instance.get_instance_type_parameter(v.full_name).bindings:
                # The type parameter was initialized. Set the view to None, since we
                # don't include v.instance in the view.
                return pytd_utils.JoinTypes(
                    self.value_to_pytd_type(node, p, seen, None) for p in
                    v.instance.get_instance_type_parameter(v.full_name).data)
            elif v.param.constraints:
                return pytd_utils.JoinTypes(
                    self.value_instance_to_pytd_type(node, p, None, seen, view)
                    for p in v.param.constraints)
            elif v.param.bound:
                return self.value_instance_to_pytd_type(
                    node, v.param.bound, None, seen, view)
            else:
                return pytd.AnythingType()
        elif isinstance(v, typing_overlay.TypeVar):
            return pytd.NamedType("builtins.type")
        elif isinstance(v, dataclass_overlay.FieldInstance):
            if not v.default:
                return pytd.AnythingType()
            return pytd_utils.JoinTypes(
                self.value_to_pytd_type(node, d, seen, view)
                for d in v.default.data)
        elif isinstance(v, attr_overlay.AttribInstance):
            ret = self.value_to_pytd_type(node, v.typ, seen, view)
            md = metadata.to_pytd(v.to_metadata())
            return pytd.Annotated(ret, ("'pytype_metadata'", md))
        elif isinstance(v, special_builtins.PropertyInstance):
            return pytd.NamedType("builtins.property")
        elif isinstance(v, typed_dict.TypedDict):
            return pytd.NamedType(v.props.name)
        elif isinstance(v, abstract.FUNCTION_TYPES):
            try:
                signatures = function.get_signatures(v)
            except NotImplementedError:
                return pytd.NamedType("typing.Callable")
            if len(signatures) == 1:
                val = self.signature_to_callable(signatures[0])
                if not isinstance(
                        v, abstract.PYTD_FUNCTION_TYPES) or not val.formal:
                    # This is a workaround to make sure we don't put unexpected type
                    # parameters in call traces.
                    return self.value_instance_to_pytd_type(
                        node, val, None, seen, view)
            return pytd.NamedType("typing.Callable")
        elif isinstance(v, (abstract.ClassMethod, abstract.StaticMethod)):
            return self.value_to_pytd_type(node, v.method, seen, view)
        elif isinstance(v, (special_builtins.IsInstance,
                            special_builtins.ClassMethodCallable)):
            return pytd.NamedType("typing.Callable")
        elif isinstance(v, abstract.Class):
            param = self.value_instance_to_pytd_type(node, v, None, seen, view)
            return pytd.GenericType(base_type=pytd.NamedType("builtins.type"),
                                    parameters=(param, ))
        elif isinstance(v, abstract.Module):
            return pytd.Alias(v.name,
                              pytd.Module(v.name, module_name=v.full_name))
        elif (self._output_mode >= Converter.OutputMode.LITERAL
              and isinstance(v, abstract.ConcreteValue)
              and isinstance(v.pyval, (int, str, bytes))):
            # LITERAL mode is used only for pretty-printing, so we just stringify the
            # inner value rather than properly converting it.
            return pytd.Literal(repr(v.pyval))
        elif isinstance(v, abstract.SimpleValue):
            ret = self.value_instance_to_pytd_type(node,
                                                   v.cls,
                                                   v,
                                                   seen=seen,
                                                   view=view)
            ret.Visit(
                visitors.FillInLocalPointers(
                    {"builtins": self.ctx.loader.builtins}))
            return ret
        elif isinstance(v, abstract.Union):
            return pytd_utils.JoinTypes(
                self.value_to_pytd_type(node, o, seen, view)
                for o in v.options)
        elif isinstance(v, special_builtins.SuperInstance):
            return pytd.NamedType("builtins.super")
        elif isinstance(v, abstract.TypeParameter):
            # Arguably, the type of a type parameter is NamedType("typing.TypeVar"),
            # but pytype doesn't know how to handle that, so let's just go with Any
            # unless self._detailed is set.
            if self._detailed:
                return pytd.NamedType("typing.TypeVar")
            else:
                return pytd.AnythingType()
        elif isinstance(v, abstract.Unsolvable):
            return pytd.AnythingType()
        elif isinstance(v, abstract.Unknown):
            return pytd.NamedType(v.class_name)
        elif isinstance(v, abstract.BuildClass):
            return pytd.NamedType("typing.Callable")
        elif isinstance(v, abstract.FinalAnnotation):
            param = self.value_to_pytd_type(node, v.annotation, seen, view)
            return pytd.GenericType(base_type=pytd.NamedType("typing.Final"),
                                    parameters=(param, ))
        else:
            raise NotImplementedError(v.__class__.__name__)
Exemple #16
0
 def pytd_for_types(self, defs):
     # If a variable is annotated, we'll always output that type.
     annotated_names = set()
     data = []
     annots = abstract_utils.get_annotations_dict(defs)
     for name, t in self.ctx.pytd_convert.annotations_to_instance_types(
             self.ctx.exitpoint, annots):
         annotated_names.add(name)
         data.append(pytd.Constant(name, t))
     for name, var in defs.items():
         if (name in abstract_utils.TOP_LEVEL_IGNORE
                 or name in annotated_names
                 or self._is_typing_member(name, var)):
             continue
         options = var.FilteredData(self.ctx.exitpoint, strict=False)
         if (len(options) > 1 and not all(
                 isinstance(o, abstract.FUNCTION_TYPES) for o in options)):
             if all(isinstance(o, abstract.TypeParameter) for o in options):
                 pytd_def = pytd_utils.JoinTypes(
                     t.to_pytd_def(self.ctx.exitpoint, name)
                     for t in options)
                 if isinstance(pytd_def, pytd.TypeParameter):
                     data.append(pytd_def)
                 else:
                     # We have multiple definitions for the same TypeVar name. There's no
                     # good way to handle this.
                     data.append(pytd.Constant(name, pytd.AnythingType()))
             elif all(
                     isinstance(o, (abstract.ParameterizedClass,
                                    abstract.Union))
                     for o in options):  # type alias
                 pytd_def = pytd_utils.JoinTypes(
                     t.to_pytd_def(self.ctx.exitpoint, name).type
                     for t in options)
                 data.append(pytd.Alias(name, pytd_def))
             else:
                 # It's ambiguous whether this is a type, a function or something
                 # else, so encode it as a constant.
                 combined_types = pytd_utils.JoinTypes(
                     t.to_type(self.ctx.exitpoint) for t in options)
                 data.append(pytd.Constant(name, combined_types))
         elif options:
             for option in options:
                 try:
                     d = option.to_pytd_def(self.ctx.exitpoint,
                                            name)  # Deep definition
                 except NotImplementedError:
                     d = option.to_type(self.ctx.exitpoint)  # Type only
                     if isinstance(d, pytd.NothingType):
                         if isinstance(option, abstract.Empty):
                             d = pytd.AnythingType()
                         else:
                             assert isinstance(option,
                                               typing_overlay.NoReturn)
                 if isinstance(d, pytd.Type) and not isinstance(
                         d, pytd.TypeParameter):
                     data.append(pytd.Constant(name, d))
                 else:
                     data.append(d)
         else:
             log.error("No visible options for %s", name)
             data.append(pytd.Constant(name, pytd.AnythingType()))
     return pytd_utils.WrapTypeDeclUnit("inferred", data)
Exemple #17
0
 def new_alias_or_constant(self, name_and_value):
   name, value = name_and_value
   if value in [pytd.NamedType("True"), pytd.NamedType("False")]:
     return pytd.Constant(name, pytd.NamedType("bool"))
   else:
     return pytd.Alias(name, value)
Exemple #18
0
    def _class_to_def(self, node, v, class_name):
        """Convert an InterpreterClass to a PyTD definition."""
        self._scopes.append(class_name)
        methods = {}
        constants = collections.defaultdict(pytd_utils.TypeBuilder)

        annots = abstract_utils.get_annotations_dict(v.members)
        annotated_names = set()

        def add_constants(iterator):
            for name, t in iterator:
                if t is None:
                    # Remove the entry from constants
                    annotated_names.add(name)
                elif name not in annotated_names:
                    constants[name].add_type(t)
                    annotated_names.add(name)

        add_constants(
            self._ordered_attrs_to_instance_types(node, v.metadata, annots))
        add_constants(self.annotations_to_instance_types(node, annots))

        def get_decorated_method(name, value, func_slot):
            fvar = getattr(value, func_slot)
            func = abstract_utils.get_atomic_value(fvar, abstract.Function)
            defn = self.value_to_pytd_def(node, func, name)
            defn = defn.Visit(visitors.DropMutableParameters())
            return defn

        def add_decorated_method(name, value, kind):
            try:
                defn = get_decorated_method(name, value, "func")
            except (AttributeError, abstract_utils.ConversionError):
                constants[name].add_type(pytd.AnythingType())
                return
            defn = defn.Replace(kind=kind)
            methods[name] = defn

        # If decorators are output as aliases to NamedTypes, they will be converted
        # to Functions and fail a verification step if those functions have type
        # parameters. Since we just want the function name, and since we have a
        # fully resolved name at this stage, we just output a minimal pytd.Function
        sig = pytd.Signature((), None, None, pytd.AnythingType(), (), ())
        decorators = [
            pytd.Alias(x, pytd.Function(x, (sig, ), pytd.MethodTypes.METHOD,
                                        0)) for x in v.decorators
        ]

        # class-level attributes
        for name, member in v.members.items():
            if name in CLASS_LEVEL_IGNORE or name in annotated_names:
                continue
            for value in member.FilteredData(self.vm.exitpoint, strict=False):
                if isinstance(value, special_builtins.PropertyInstance):
                    # For simplicity, output properties as constants, since our parser
                    # turns them into constants anyway.
                    if value.fget:
                        for typ in self._function_to_return_types(
                                node, value.fget):
                            constants[name].add_type(
                                pytd.Annotated(typ, ("'property'", )))
                    else:
                        constants[name].add_type(
                            pytd.Annotated(pytd.AnythingType(),
                                           ("'property'", )))
                elif isinstance(value, special_builtins.StaticMethodInstance):
                    add_decorated_method(name, value,
                                         pytd.MethodTypes.STATICMETHOD)
                elif isinstance(value, special_builtins.ClassMethodInstance):
                    add_decorated_method(name, value,
                                         pytd.MethodTypes.CLASSMETHOD)
                elif isinstance(value, abstract.Function):
                    # value_to_pytd_def returns different pytd node types depending on the
                    # input type, which pytype struggles to reason about.
                    method = cast(pytd.Function,
                                  self.value_to_pytd_def(node, value, name))
                    keep = lambda name: not name or name.startswith(v.name)
                    signatures = tuple(
                        s for s in method.signatures
                        if not s.params or keep(s.params[0].type.name))
                    if signatures and signatures != method.signatures:
                        # Filter out calls made from subclasses unless they are the only
                        # ones recorded; when inferring types for ParentClass.__init__, we
                        # do not want `self: Union[ParentClass, Subclass]`.
                        method = method.Replace(signatures=signatures)
                    # TODO(rechen): Removing mutations altogether won't work for generic
                    # classes. To support those, we'll need to change the mutated type's
                    # base to the current class, rename aliased type parameters, and
                    # replace any parameter not in the class or function template with
                    # its upper value.
                    methods[name] = method.Visit(
                        visitors.DropMutableParameters())
                else:
                    cls = self.vm.convert.merge_classes([value])
                    node, attr = self.vm.attribute_handler.get_attribute(
                        node, cls, "__get__")
                    if attr:
                        # This attribute is a descriptor. Its type is the return value of
                        # its __get__ method.
                        for typ in self._function_to_return_types(node, attr):
                            constants[name].add_type(typ)
                    else:
                        constants[name].add_type(value.to_type(node))

        # Instance-level attributes: all attributes from 'canonical' instances (that
        # is, ones created by analyze.py:analyze_class()) are added. Attributes from
        # non-canonical instances are added if their canonical values do not contain
        # type parameters.
        ignore = set(annotated_names)
        canonical_attributes = set()

        def add_attributes_from(instance):
            for name, member in instance.members.items():
                if name in CLASS_LEVEL_IGNORE or name in ignore:
                    continue
                for value in member.FilteredData(self.vm.exitpoint,
                                                 strict=False):
                    typ = value.to_type(node)
                    if pytd_utils.GetTypeParameters(typ):
                        # This attribute's type comes from an annotation that contains a
                        # type parameter; we do not want to merge in substituted values of
                        # the type parameter.
                        canonical_attributes.add(name)
                    constants[name].add_type(typ)

        for instance in v.canonical_instances:
            add_attributes_from(instance)
        ignore |= canonical_attributes
        for instance in v.instances - v.canonical_instances:
            add_attributes_from(instance)

        for name in list(methods):
            if name in constants:
                # If something is both a constant and a method, it means that the class
                # is, at some point, overwriting its own methods with an attribute.
                del methods[name]
                constants[name].add_type(pytd.AnythingType())

        constants = [
            pytd.Constant(name, builder.build())
            for name, builder in constants.items() if builder
        ]

        metaclass = v.metaclass(node)
        if metaclass is not None:
            metaclass = metaclass.get_instance_type(node)

        # Some of the class's bases may not be in global scope, so they won't show
        # up in the output. In that case, fold the base class's type information
        # into this class's pytd.
        bases = []
        missing_bases = []
        for basevar in v.bases():
            if basevar.data == [self.vm.convert.oldstyleclass_type]:
                continue
            elif len(basevar.bindings) == 1:
                b, = basevar.data
                if b.official_name is None and isinstance(
                        b, abstract.InterpreterClass):
                    missing_bases.append(b)
                else:
                    bases.append(b.get_instance_type(node))
            else:
                bases.append(
                    pytd_utils.JoinTypes(
                        b.get_instance_type(node) for b in basevar.data))

        # Collect nested classes
        # TODO(mdemello): We cannot put these in the output yet; they fail in
        # load_dependencies because of the dotted class name (google/pytype#150)
        classes = [
            self._class_to_def(node, x, x.name) for x in v.get_inner_classes()
        ]
        classes = [x.Replace(name=class_name + "." + x.name) for x in classes]

        cls = pytd.Class(name=class_name,
                         metaclass=metaclass,
                         parents=tuple(bases),
                         methods=tuple(methods.values()),
                         constants=tuple(constants),
                         classes=(),
                         decorators=tuple(decorators),
                         slots=v.slots,
                         template=())
        for base in missing_bases:
            base_cls = self.value_to_pytd_def(node, base, base.name)
            cls = pytd_utils.MergeBaseClass(cls, base_cls)
        self._scopes.pop()
        return cls
Exemple #19
0
 def pytd_alias(self):
     return pytd.Alias(self.new_name, self.pytd_node)