def _new_named_tuple(self, class_name: str, fields: List[Tuple[str, Any]]) -> pytd.Class: """Generates a pytd class for a named tuple. Args: class_name: The name of the generated class fields: A list of (name, type) tuples. Returns: A generated class that describes the named tuple. """ class_base = pytdgen.heterogeneous_tuple(pytd.NamedType("tuple"), tuple(t for _, t in fields)) class_constants = tuple(pytd.Constant(n, t) for n, t in fields) # Since the user-defined fields are the only namedtuple attributes commonly # used, we define all the other attributes as Any for simplicity. class_constants += tuple( pytd.Constant(name, pytd.AnythingType()) for name in _NAMEDTUPLE_MEMBERS) methods = function.merge_method_signatures( [self._make_new(class_name, fields), self._make_init()]) return pytd.Class(name=class_name, metaclass=None, bases=(class_base, ), methods=tuple(methods), constants=class_constants, decorators=(), classes=(), slots=tuple(n for n, _ in fields), template=())
def build_type_decl_unit(self, defs) -> pytd.TypeDeclUnit: """Return a pytd.TypeDeclUnit for the given defs (plus parser state).""" # defs contains both constant and function definitions. constants, functions, aliases, slots, classes = _split_definitions( defs) assert not slots # slots aren't allowed on the module level # TODO(mdemello): alias/constant handling is broken in some weird manner. # assert not aliases # We handle top-level aliases in add_alias_or_constant # constants.extend(self.constants) if self.module_info.module_name == "builtins": constants.extend(types.builtin_keyword_constants()) generated_classes = sum(self.generated_classes.values(), []) classes = generated_classes + classes functions = function.merge_method_signatures(functions) name_to_class = {c.name: c for c in classes} name_to_constant = {c.name: c for c in constants} aliases = [] for a in self.aliases.values(): t = _maybe_resolve_alias(a, name_to_class, name_to_constant) if t is None: continue elif isinstance(t, pytd.Function): functions.append(t) elif isinstance(t, pytd.Constant): constants.append(t) else: assert isinstance(t, pytd.Alias) aliases.append(t) all_names = ([f.name for f in functions] + [c.name for c in constants] + [c.name for c in self.type_params] + [c.name for c in classes] + [c.name for c in aliases]) duplicates = [ name for name, count in collections.Counter(all_names).items() if count >= 2 ] if duplicates: raise ParseError("Duplicate top-level identifier(s): " + ", ".join(duplicates)) properties = [x for x in functions if x.kind == pytd.PROPERTY] if properties: prop_names = ", ".join(p.name for p in properties) raise ParseError( "Module-level functions with property decorators: " + prop_names) return pytd.TypeDeclUnit(name=None, constants=tuple(constants), type_params=tuple(self.type_params), functions=tuple(functions), classes=tuple(classes), aliases=tuple(aliases))
def new_new_type(self, name, typ): """Returns a type for a NewType.""" args = [("self", pytd.AnythingType()), ("val", typ)] ret = pytd.NamedType("NoneType") methods = function.merge_method_signatures( [function.NameAndSig.make("__init__", args, ret)]) cls_name = escape.pack_newtype_base_class( name, len(self.generated_classes[name])) cls = pytd.Class(name=cls_name, metaclass=None, parents=(typ, ), methods=tuple(methods), constants=(), decorators=(), classes=(), slots=None, template=()) self.generated_classes[name].append(cls) return pytd.NamedType(cls_name)
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=())
def make_dataclass(cls: pytd.Class) -> pytd.Class: _check_defaults(cls) init = _make_init(cls) methods = cls.methods + tuple(function.merge_method_signatures([init])) return cls.Replace(methods=methods)