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)
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 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 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
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 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 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())
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__)
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)
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())
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))
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.")
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 ]
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." )
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__)
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)
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)
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
def pytd_alias(self): return pytd.Alias(self.new_name, self.pytd_node)