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 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 testRemoveInheritedMethodsWithLateType(self): src = textwrap.dedent(""" class Foo(other.Bar): def bar() -> float """) expected = textwrap.dedent(""" class Foo(other.Bar): def bar() -> float """) ast = self.Parse(src) ast = ast.Visit( visitors.ReplaceTypes({"other.Bar": pytd.LateType("other.Bar")})) ast = ast.Visit(optimize.RemoveInheritedMethods()) ast = ast.Visit(visitors.LateTypeToClassType()) self.AssertSourceEquals(ast, expected)
def testFindCommonSuperClasses(self): src = textwrap.dedent(""" x = ... # type: int or other.Bar """) expected = textwrap.dedent(""" x = ... # type: int or other.Bar """) ast = self.Parse(src) ast = ast.Visit(visitors.ReplaceTypes( {"other.Bar": pytd.LateType("other.Bar")})) hierarchy = ast.Visit(visitors.ExtractSuperClassesByName()) ast = ast.Visit(optimize.FindCommonSuperClasses( optimize.SuperClassHierarchy(hierarchy))) ast = ast.Visit(visitors.LateTypeToClassType()) self.AssertSourceEquals(ast, expected)
def test_find_common_superclasses(self): src = pytd_src(""" x = ... # type: Union[int, other.Bar] """) expected = pytd_src(""" x = ... # type: Union[int, other.Bar] """) ast = self.Parse(src) ast = ast.Visit( visitors.ReplaceTypes({"other.Bar": pytd.LateType("other.Bar")})) hierarchy = ast.Visit(visitors.ExtractSuperClassesByName()) ast = ast.Visit( optimize.FindCommonSuperClasses( optimize.SuperClassHierarchy(hierarchy))) ast = ast.Visit(visitors.LateTypeToClassType()) self.AssertSourceEquals(ast, expected)
def test_anything_late(self): m = type_match.TypeMatch({}) eq = m.match_type_against_type(pytd.AnythingType(), pytd.LateType("X"), {}) self.assertEqual(eq, booleq.TRUE)
def test_named_late(self): m = type_match.TypeMatch({}) eq = m.match_type_against_type(pytd.LateType("X"), pytd.NamedType("X"), {}) self.assertEqual(eq, booleq.FALSE)
def value_instance_to_pytd_type(self, node, v, instance, seen, view): """Get the PyTD type an instance of this object would have. Args: node: The node. v: The object. instance: The instance. seen: Already seen instances. view: A Variable -> binding map. Returns: A PyTD type. """ if abstract_utils.is_recursive_annotation(v): return pytd.LateType( v.unflatten_expr() if self._detailed else v.expr) elif isinstance(v, abstract.Union): return pytd.UnionType( tuple( self.value_instance_to_pytd_type(node, t, instance, seen, view) for t in v.options)) elif isinstance(v, abstract.AnnotationContainer): return self.value_instance_to_pytd_type(node, v.base_cls, instance, seen, view) elif isinstance(v, abstract.LiteralClass): if not v.value: # TODO(b/173742489): Remove this workaround once we support literal # enums. return pytd.AnythingType() if isinstance(v.value.pyval, (str, bytes)): # Strings are stored as strings of their representations, prefix and # quotes and all. value = repr(v.value.pyval) elif isinstance(v.value.pyval, bool): # True and False are stored as pytd constants. value = self.ctx.loader.lookup_builtin( f"builtins.{v.value.pyval}") else: # Ints are stored as their literal values. Note that Literal[None] or a # nested literal will never appear here, since we simplified it to None # or unnested it, respectively, in typing_overlay. Literal[<enum>] does # not appear here yet because it is unsupported. assert isinstance(v.value.pyval, int), v.value.pyval value = v.value.pyval return pytd.Literal(value) elif isinstance(v, typed_dict.TypedDictClass): # TypedDict inherits from abstract.Dict for analysis purposes, but when # outputting to a pyi we do not want to treat it as a generic type. return pytd.NamedType(v.name) elif isinstance(v, abstract.Class): if not self._detailed and v.official_name is None: return pytd.AnythingType() if seen is None: # We make the set immutable to ensure that the seen instances for # different parameter values don't interfere with one another. seen = frozenset() if instance in seen: # We have a circular dependency in our types (e.g., lst[0] == lst). Stop # descending into the type parameters. type_params = () else: type_params = tuple(t.name for t in v.template) if instance is not None: seen |= {instance} type_arguments = self._value_to_parameter_types( node, v, instance, type_params, seen, view) base = pytd_utils.NamedTypeWithModule(v.official_name or v.name, v.module) if self._is_tuple(v, instance): homogeneous = False elif v.full_name == "typing.Callable": homogeneous = not isinstance(v, abstract.CallableClass) else: homogeneous = len(type_arguments) == 1 return pytd_utils.MakeClassOrContainerType(base, type_arguments, homogeneous) elif isinstance(v, abstract.TypeParameter): # We generate the full definition because, if this type parameter is # imported, we will need the definition in order to declare it later. return self._typeparam_to_def(node, v, v.name) elif isinstance(v, typing_overlay.NoReturn): return pytd.NothingType() else: log.info("Using Any for instance of %s", v.name) return pytd.AnythingType()