Exemple #1
0
    def build_class(self, class_name, bases, keywords, decorators,
                    defs) -> pytd.Class:
        """Build a pytd.Class from definitions collected from an ast node."""
        parents, namedtuple_index = classdef.get_parents(bases)
        metaclass = classdef.get_metaclass(keywords, parents)
        constants, methods, aliases, slots, classes = _split_definitions(defs)

        # Make sure we don't have duplicate definitions.
        classdef.check_for_duplicate_defs(methods, constants, aliases)

        # Generate a NamedTuple proxy base class if needed
        if namedtuple_index is not None:
            namedtuple_parent = self.new_named_tuple(class_name,
                                                     [(c.name, c.type)
                                                      for c in constants])
            parents[namedtuple_index] = namedtuple_parent
            constants = []

        if aliases:
            vals_dict = {
                val.name: val
                for val in constants + aliases + methods + classes
            }
            for val in aliases:
                name = val.name
                seen_names = set()
                while isinstance(val, pytd.Alias):
                    if isinstance(val.type, pytd.NamedType):
                        _, _, base_name = val.type.name.rpartition(".")
                        if base_name in seen_names:
                            # This happens in cases like:
                            # class X:
                            #   Y = something.Y
                            # Since we try to resolve aliases immediately, we don't know what
                            # type to fill in when the alias value comes from outside the
                            # class. The best we can do is Any.
                            val = pytd.Constant(name, pytd.AnythingType())
                            continue
                        seen_names.add(base_name)
                        if base_name in vals_dict:
                            val = vals_dict[base_name]
                            continue
                    # The alias value comes from outside the class. The best we can do is
                    # to fill in Any.
                    val = pytd.Constant(name, pytd.AnythingType())
                if isinstance(val, function.NameAndSig):
                    val = dataclasses.replace(val, name=name)
                    methods.append(val)
                else:
                    if isinstance(val, pytd.Class):
                        t = pytdgen.pytd_type(
                            pytd.NamedType(class_name + "." + val.name))
                    else:
                        t = val.type
                    constants.append(pytd.Constant(name, t))

        parents = [p for p in parents if not isinstance(p, pytd.NothingType)]
        methods = function.merge_method_signatures(methods)
        if not parents and class_name not in ["classobj", "object"]:
            # A parent-less class inherits from classobj in Python 2 and from object
            # in Python 3. typeshed assumes the Python 3 behavior for all stubs, so we
            # do the same here.
            parents = (pytd.NamedType("object"), )

        return pytd.Class(name=class_name,
                          metaclass=metaclass,
                          parents=tuple(parents),
                          methods=tuple(methods),
                          constants=tuple(constants),
                          classes=tuple(classes),
                          decorators=tuple(decorators),
                          slots=slots,
                          template=())
Exemple #2
0
def _maybe_resolve_alias(alias, name_to_class, name_to_constant):
    """Resolve the alias if possible.

  Args:
    alias: A pytd.Alias
    name_to_class: A class map used for resolution.
    name_to_constant: A constant map used for resolution.

  Returns:
    None, if the alias pointed to an un-aliasable type.
    The resolved value, if the alias was resolved.
    The alias, if it was not resolved.
  """
    if not isinstance(alias.type, pytd.NamedType):
        return alias
    if alias.type.name in _TYPING_SETS:
        # Filter out aliases to `typing` members that don't appear in typing.pytd
        # to avoid lookup errors.
        return None
    if "." not in alias.type.name:
        # We'll handle nested classes specially, since they need to be represented
        # as constants to distinguish them from imports.
        return alias
    parts = alias.type.name.split(".")
    if parts[0] not in name_to_class and parts[0] not in name_to_constant:
        return alias
    prev_value = None
    value = name_to_class.get(parts[0]) or name_to_constant[parts[0]]
    for part in parts[1:]:
        prev_value = value
        # We can immediately return upon encountering an error, as load_pytd will
        # complain when it can't resolve the alias.
        if isinstance(value, pytd.Constant):
            if (not isinstance(value.type, pytd.NamedType)
                    or value.type.name not in name_to_class):
                # TODO(rechen): Parameterized constants of generic classes should
                # probably also be allowed.
                return alias
            value = name_to_class[value.type.name]
        if not isinstance(value, pytd.Class):
            return alias
        try:
            value = value.Lookup(part)
        except KeyError:
            return alias
    if isinstance(value, pytd.Class):
        return pytd.Constant(
            alias.name, pytdgen.pytd_type(pytd.NamedType(alias.type.name)))
    elif isinstance(value, pytd.Function):
        # We allow module-level aliases of methods from classes and class instances.
        # When a static method is aliased, or a normal method is aliased from a
        # class (not an instance), the entire method signature is copied. Otherwise,
        # the first parameter ('self' or 'cls') is dropped.
        new_value = value.Replace(name=alias.name).Replace(kind="method")
        if value.kind == "staticmethod" or (
                value.kind == "method" and isinstance(prev_value, pytd.Class)):
            return new_value
        return new_value.Replace(signatures=tuple(
            s.Replace(params=s.params[1:]) for s in new_value.signatures))
    else:
        return value.Replace(name=alias.name)
def _maybe_resolve_alias(alias, name_to_class, name_to_constant):
  """Resolve the alias if possible.

  Args:
    alias: A pytd.Alias
    name_to_class: A class map used for resolution.
    name_to_constant: A constant map used for resolution.

  Returns:
    None, if the alias pointed to an un-aliasable type.
    The resolved value, if the alias was resolved.
    The alias, if it was not resolved.
  """
  if not isinstance(alias.type, pytd.NamedType):
    return alias
  if alias.type.name in _TYPING_SETS:
    # Filter out aliases to `typing` members that don't appear in typing.pytd
    # to avoid lookup errors.
    return None
  if "." not in alias.type.name:
    # We'll handle nested classes specially, since they need to be represented
    # as constants to distinguish them from imports.
    return alias
  parts = alias.type.name.split(".")
  if parts[0] not in name_to_class and parts[0] not in name_to_constant:
    return alias
  prev_value = None
  value = name_to_class.get(parts[0]) or name_to_constant[parts[0]]
  for part in parts[1:]:
    prev_value = value
    # We can immediately return upon encountering an error, as load_pytd will
    # complain when it can't resolve the alias.
    if isinstance(value, pytd.Constant):
      if (not isinstance(value.type, pytd.NamedType) or
          value.type.name not in name_to_class):
        # TODO(rechen): Parameterized constants of generic classes should
        # probably also be allowed.
        return alias
      value = name_to_class[value.type.name]
    if not isinstance(value, pytd.Class):
      return alias
    try:
      value = value.Lookup(part)
    except KeyError:
      for parent in value.parents:
        if parent.name not in name_to_class:
          # If the parent is unknown, we don't know whether it contains 'part',
          # so it cannot be resolved.
          return alias
        try:
          value = name_to_class[parent.name].Lookup(part)
        except KeyError:
          continue  # continue up the MRO
        else:
          break  # 'part' found!
      else:
        return alias  # unresolved
  if isinstance(value, pytd.Class):
    return pytd.Constant(
        alias.name, pytdgen.pytd_type(pytd.NamedType(alias.type.name)))
  elif isinstance(value, pytd.Function):
    return pytd_utils.AliasMethod(
        value.Replace(name=alias.name),
        from_constant=isinstance(prev_value, pytd.Constant))
  else:
    return value.Replace(name=alias.name)