def pytd_for_types(self, defs): data = [] for name, var in defs.items(): if name in output.TOP_LEVEL_IGNORE or self._is_builtin( name, var.data): continue options = var.FilteredData(self.exitpoint) if (len(options) > 1 and not all( isinstance(o, (abstract.Function, abstract.BoundFunction)) for o in options)): # 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.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.exitpoint, name) # Deep definition except NotImplementedError: d = option.to_type(self.exitpoint) # Type only if isinstance(d, pytd.NothingType): assert isinstance(option, abstract.Empty) d = pytd.AnythingType() 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 " + name) data.append(pytd.Constant(name, pytd.AnythingType())) return pytd_utils.WrapTypeDeclUnit("inferred", data)
def pytd_for_types(self, defs, ignore): for name, var in defs.items(): abstract.variable_set_official_name(var, name) data = [] for name, var in defs.items(): if name in output.TOP_LEVEL_IGNORE or name in ignore: continue options = var.FilteredData(self.exitpoint) if (len(options) > 1 and not all(isinstance(o, (abstract.Function, abstract.BoundFunction)) for o in options)): # 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.exitpoint) for t in options) data.append(pytd.Constant(name, combined_types)) else: for option in options: if hasattr(option, "to_pytd_def"): d = option.to_pytd_def(self.exitpoint, name) # Deep definition else: d = option.to_type(self.exitpoint) # Type only if isinstance(d, pytd.TYPE): data.append(pytd.Constant(name, d)) else: data.append(d) return pytd_utils.WrapTypeDeclUnit("inferred", data)
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_parent = types.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, parents=(class_parent,), methods=tuple(methods), constants=class_constants, decorators=(), classes=(), slots=tuple(n for n, _ in fields), template=())
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)
def _setup_pytdclass(self, node, cls): # We need to rewrite the member map of the PytdClass. members = dict(cls._member_map) # pylint: disable=protected-access member_types = [] for name, pytd_val in members.items(): # Only constants need to be transformed. # TODO(tsudol): Ensure only valid enum members are transformed. if not isinstance(pytd_val, pytd.Constant): continue # Build instances directly, because you can't call instantiate() when # creating the class -- pytype complains about recursive types. member = abstract.Instance(cls, self.vm) member.members["name"] = self.vm.convert.constant_to_var( pyval=pytd.Constant(name="name", type=self._str_pytd), node=node) member.members["value"] = self.vm.convert.constant_to_var( pyval=pytd.Constant(name="value", type=pytd_val.type), node=node) cls._member_map[name] = member # pylint: disable=protected-access cls.members[name] = member.to_variable(node) member_types.append(pytd_val.type) member_type = self.vm.convert.constant_to_value( pytd_utils.JoinTypes(member_types)) cls.members["__new__"] = self._make_new(node, member_type, cls) cls.members["__eq__"] = EnumCmpEQ(self.vm).to_variable(node) return node
def new_named_tuple(self, base_name, fields): """Return a type for a named tuple (implicitly generates a class). Args: base_name: The named tuple's name. fields: A list of (name, type) tuples. Returns: A NamedType() for the generated class that describes the named tuple. """ # Handle previously defined NamedTuples with the same name prev_list = self._generated_classes[base_name] class_name = "namedtuple-%s-%d" % (base_name, len(prev_list)) class_parent = self._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 self._NAMEDTUPLE_MEMBERS) methods = _merge_method_signatures( [self._namedtuple_new(class_name, fields), self._namedtuple_init()]) nt_class = pytd.Class(name=class_name, metaclass=None, parents=(class_parent,), methods=tuple(methods), constants=class_constants, slots=tuple(n for n, _ in fields), template=()) self._generated_classes[base_name].append(nt_class) return pytd.NamedType(nt_class.name)
def pytd_for_types(self, defs): # If a variable is annotated, we'll always output that type. annotated_names = set() data = [] pytd_convert = self.convert.pytd_convert annots = abstract_utils.get_annotations_dict(defs) for name, t in pytd_convert.annotations_to_instance_types( self.exitpoint, annots): annotated_names.add(name) data.append(pytd.Constant(name, t)) for name, var in defs.items(): if (name in output.TOP_LEVEL_IGNORE or name in annotated_names or self._is_typing_member(name, var)): continue options = var.FilteredData(self.exitpoint, strict=False) if (len(options) > 1 and not all(isinstance(o, abstract.FUNCTION_TYPES) for o in options)): if all(isinstance(o, (abstract.ParameterizedClass, abstract.TypeParameter, abstract.Union)) for o in options ) and self.options.preserve_union_macros: # type alias data.append(pytd_utils.JoinTypes(t.to_pytd_def(self.exitpoint, name) for t in options)) 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.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.exitpoint, name) # Deep definition except NotImplementedError: d = option.to_type(self.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)
def pytd_for_types(self, defs): data = [] pytd_convert = self.convert.pytd_convert annots = pytd_convert.get_annotations_dict(defs) for name, t in pytd_convert.uninitialized_annotations_to_instance_types( self.exitpoint, annots, defs): data.append(pytd.Constant(name, t)) for name, var in defs.items(): if name in output.TOP_LEVEL_IGNORE or self._is_builtin( name, var.data): continue options = [] for value, is_annotation in pytd_convert.get_annotated_values( self.exitpoint, name, var, annots): if is_annotation: data.append(pytd.Constant(name, value)) else: options.append(value) if (len(options) > 1 and not all( isinstance(o, (abstract.Function, abstract.BoundFunction)) for o in options)): # 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.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.exitpoint, name) # Deep definition except NotImplementedError: d = option.to_type(self.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)
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__)
def _assign(self, node, target, value): name = target.id # Record and erase TypeVar and ParamSpec definitions. if isinstance(value, _TypeVar): self.defs.add_type_var(name, value) return Splice([]) elif isinstance(value, _ParamSpec): self.defs.add_param_spec(name, value) return Splice([]) if node.type_comment: # TODO(mdemello): can pyi files have aliases with typecomments? ret = pytd.Constant(name, node.type_comment) else: ret = self.new_alias_or_constant(name, value) if self.in_function: # Should never happen, but this keeps pytype happy. if isinstance(ret, types.SlotDecl): raise ParseError("Cannot change the type of __slots__") return function.Mutator(name, ret.type) if self.level == 0: self.defs.add_alias_or_constant(ret) return ret
def visit_Assign(self, node): targets = node.targets if len(targets) > 1 or isinstance(targets[0], ast3.Tuple): msg = "Assignments must be of the form 'name = value'" raise ParseError(msg) self.convert_node_annotations(node) target = targets[0] name = target.id # Record and erase typevar definitions. if isinstance(node.value, _TypeVar): self.defs.add_type_var(name, node.value) return Splice([]) if node.type_comment: # TODO(mdemello): can pyi files have aliases with typecomments? ret = pytd.Constant(name, node.type_comment) else: ret = self.new_alias_or_constant(name, node.value) if self.in_function: # Should never happen, but this keeps pytype happy. if isinstance(ret, types.SlotDecl): raise ParseError("Cannot change the type of __slots__") return function.Mutator(name, ret.type) if self.level == 0: self.defs.add_alias_or_constant(ret) return ret
def new_constant(self, name, value): """Return a Constant. Args: name: The name of the constant. value: None, 0, or a pytd type. Returns: A Constant object. Raises: ParseError: if value is an int other than 0. """ if value is None: t = pytd.AnythingType() elif isinstance(value, int): if value != 0: raise ParseError("Only '0' allowed as int literal") t = pytd.NamedType("int") elif isinstance(value, float): if value != 0.0: raise ParseError("Only '0.0' allowed as float literal") t = pytd.NamedType("float") else: t = value return pytd.Constant(name, t)
def VisitClass(self, node): constants = list(node.constants) for fn in self.const_properties: types = [x.return_type for x in fn.signatures] constants.append(pytd.Constant(name=fn.name, type=join_types(types))) methods = [x for x in node.methods if x not in self.const_properties] return node.Replace(constants=tuple(constants), methods=tuple(methods))
def new_named_tuple(self, base_name, fields): """Return a type for a named tuple (implicitly generates a class). Args: base_name: The named tuple's name. fields: A list of (name, type) tuples. Returns: A NamedType() for the generated class that describes the named tuple. """ # Handle previously defined NamedTuples with the same name prev_list = self._generated_classes[base_name] name_dedup = "~%d" % len(prev_list) if prev_list else "" class_name = "`%s%s`" % (base_name, name_dedup) class_parent = self._heterogeneous_tuple(pytd.NamedType("tuple"), tuple(t for _, t in fields)) class_constants = tuple(pytd.Constant(n, t) for n, t in fields) nt_class = pytd.Class(name=class_name, metaclass=None, parents=(class_parent, ), methods=(), constants=class_constants, template=()) self._generated_classes[base_name].append(nt_class) return pytd.NamedType(nt_class.name)
def to_structural_def(self, node, class_name): """Convert this Unknown to a pytd.Class.""" self_param = (pytd.Parameter("self", pytd.AnythingType(), pytd.ParameterKind.REGULAR, False, None), ) starargs = None starstarargs = None def _make_sig(args, ret): return pytd.Signature(self_param + self._make_params(node, args), starargs, starstarargs, return_type=Unknown._to_pytd(node, ret), exceptions=(), template=()) calls = tuple( pytd_utils.OrderedSet( _make_sig(args, ret) for args, _, ret in self._calls)) if calls: methods = (pytd.Function("__call__", calls, pytd.MethodKind.METHOD), ) else: methods = () return pytd.Class(name=class_name, metaclass=None, bases=(pytd.NamedType("builtins.object"), ), methods=methods, constants=tuple( pytd.Constant(name, Unknown._to_pytd(node, c)) for name, c in self.members.items()), classes=(), decorators=(), slots=None, template=())
def WrapTypeDeclUnit(name, items): """Given a list (classes, functions, etc.), wrap a pytd around them. Args: name: The name attribute of the resulting TypeDeclUnit. items: A list of items. Can contain pytd.Class, pytd.Function and pytd.Constant. Returns: A pytd.TypeDeclUnit. Raises: ValueError: In case of an invalid item in the list. NameError: For name conflicts. """ functions = collections.OrderedDict() classes = collections.OrderedDict() constants = collections.defaultdict(TypeBuilder) aliases = collections.OrderedDict() for item in items: if isinstance(item, pytd.Function): if item.name in functions: if item.kind != functions[item.name].kind: raise ValueError("Can't combine %s and %s", item.kind, functions[item.name].kind) functions[item.name] = pytd.Function( item.name, functions[item.name].signatures + item.signatures, item.kind) else: functions[item.name] = item elif isinstance(item, pytd.Class): if item.name in classes: raise NameError("Duplicate top level class: %r", item.name) classes[item.name] = item elif isinstance(item, pytd.Constant): constants[item.name].add_type(item.type) elif isinstance(item, pytd.Alias): if item.name in aliases: raise NameError("Duplicate top level alias or import: %r", item.name) aliases[item.name] = item else: raise ValueError("Invalid top level pytd item: %r" % type(item)) _check_intersection(functions, classes, "function", "class") _check_intersection(functions, constants, "functions", "constant") _check_intersection(functions, aliases, "functions", "aliases") _check_intersection(classes, constants, "class", "constant") _check_intersection(classes, aliases, "class", "alias") _check_intersection(constants, aliases, "constant", "alias") return pytd.TypeDeclUnit(name=name, constants=tuple( pytd.Constant(name, t.build()) for name, t in sorted(constants.items())), type_params=tuple(), classes=tuple(classes.values()), functions=tuple(functions.values()), aliases=tuple(aliases.values()))
def visit_AnnAssign(self, node): name = node.target.id typ = node.annotation val = self.convert_node(node.value) if val and not types.is_any(val): msg = f"Default value for {name}: {typ.name} can only be '...', got {val}" raise ParseError(msg) return pytd.Constant(name, typ, val)
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)
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__)
def VisitClass(self, node): constants = list(node.constants) for fn in self.const_properties[-1]: ptypes = [x.return_type for x in fn.signatures] prop = pytd.Annotated(base_type=pytd_utils.JoinTypes(ptypes), annotations=("'property'",)) constants.append(pytd.Constant(name=fn.name, type=prop)) methods = [x for x in node.methods if x not in self.const_properties[-1]] return node.Replace(constants=tuple(constants), methods=tuple(methods))
def builtin_keyword_constants(): # We cannot define these in a pytd file because assigning to a keyword breaks # the python parser. defs = [ ("True", "bool"), ("False", "bool"), ("None", "NoneType"), ("__debug__", "bool") ] return [pytd.Constant(name, pytd.NamedType(typ)) for name, typ in defs]
def testPrintImportsNamedType(self): # Can't get tree by parsing so build explicitly node = pytd.Constant("x", pytd.NamedType("typing.List")) tree = pytd_utils.CreateModule(name=None, constants=(node,)) expected_src = textwrap.dedent(""" from typing import List x: List """).strip() res = pytd.Print(tree) self.assertMultiLineEqual(res, expected_src)
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): return pytd_utils.AliasMethod(value.Replace(name=alias.name), from_constant=isinstance( prev_value, pytd.Constant)) else: return value.Replace(name=alias.name)
def _merge_property_signatures(signatures): name_to_property_types = collections.OrderedDict() for signature in signatures: if signature.name not in name_to_property_types: name_to_property_types[signature.name] = [] property_type = _parse_signature_as_property(signature) if property_type: name_to_property_types[signature.name].append(property_type) return [ pytd.Constant( name=name, type=join_types(types) if types else pytd.AnythingType()) for name, types in name_to_property_types.items()]
def testLookupConstant(self): src1 = textwrap.dedent(""" Foo = ... # type: type """) src2 = textwrap.dedent(""" class Bar(object): bar = ... # type: foo.Foo """) ast1 = self.Parse(src1, name="foo") ast2 = self.Parse(src2, name="bar") ast2 = ast2.Visit(visitors.LookupExternalTypes({"foo": ast1, "bar": ast2})) self.assertEqual(ast2.Lookup("bar.Bar").constants[0], pytd.Constant(name="bar", type=pytd.AnythingType()))
def test_basic(self): src = textwrap.dedent(""" if sys.version_info[:2] >= (3, 9) and sys.platform == 'linux': a: int elif sys.version_info[0] == 3: a: bool else: a: str """) root = ast_parser.parse_pyi(src, "foo.py", "foo", (3, 6)) self.assertCountEqual(root.constants, ( pytd.Constant("foo.a", pytd.NamedType("bool")), ))
def testPrintImportsNamedType(self): # Can't get tree by parsing so build explicitly node = pytd.Constant("x", pytd.NamedType("typing.List")) tree = pytd.TypeDeclUnit(constants=(node,), type_params=(), functions=(), classes=(), aliases=(), name=None) expected_src = textwrap.dedent(""" import typing x = ... # type: typing.List """).strip() res = pytd.Print(tree) self.assertMultiLineEqual(res, expected_src)
def _class_to_def(self, node, v, class_name): """Convert an InterpreterClass to a PyTD definition.""" methods = {} constants = collections.defaultdict(pytd_utils.TypeBuilder) # class-level attributes for name, member in v.members.items(): if name not in CLASS_LEVEL_IGNORE: for value in member.FilteredData(v.vm.exitpoint): if isinstance(value, abstract.Function): val = self.value_to_pytd_def(node, value, name) if isinstance(val, pytd.Function): methods[name] = val elif isinstance(v, pytd.TYPE): constants[name].add_type(val) else: raise AssertionError(str(type(val))) else: constants[name].add_type(value.to_type(node)) # instance-level attributes for instance in v.instances: for name, member in instance.members.items(): if name not in CLASS_LEVEL_IGNORE: for value in member.FilteredData(v.vm.exitpoint): constants[name].add_type(value.to_type(node)) 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()) bases = [pytd_utils.JoinTypes(b.get_instance_type(node) for b in basevar.data) for basevar in v.bases() if basevar is not v.vm.convert.oldstyleclass_type] 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) return pytd.Class(name=class_name, metaclass=metaclass, parents=tuple(bases), methods=tuple(methods.values()), constants=tuple(constants), template=())
def _typed_dict_to_def(self, node, v, name): constants = [] for k, var in v.props.fields.items(): typ = pytd_utils.JoinTypes( self.value_instance_to_pytd_type(node, p, None, set(), {}) for p in var.data) constants.append(pytd.Constant(k, typ)) bases = (pytd.NamedType("typing.TypedDict"), ) return pytd.Class(name=name, metaclass=None, bases=bases, methods=(), constants=tuple(constants), classes=(), decorators=(), slots=None, template=())
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))