From f4675e890ac7f93df8008246b0f91f4e66656ae4 Mon Sep 17 00:00:00 2001 From: Hana Joo Date: Fri, 25 Apr 2025 07:59:49 -0700 Subject: [PATCH] Support INTRINSIC_SUBSCRIPT_GENERIC by wrapping it around `typing.Generic` PiperOrigin-RevId: 751416263 --- pytype/tests/test_annotations.py | 12 ++++++ pytype/tests/test_typevar1.py | 72 ++++++++++++++++++++++++++++++++ pytype/vm.py | 14 ++++++- 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/pytype/tests/test_annotations.py b/pytype/tests/test_annotations.py index aade2dcd6..55406d234 100644 --- a/pytype/tests/test_annotations.py +++ b/pytype/tests/test_annotations.py @@ -404,6 +404,18 @@ class A(typing.Generic[_T]): """, ) + def test_generic_and_double_typevar(self): + self.assertNoCrash( + self.Check, + """ + import typing + _T = typing.TypeVar("_T") + _S = typing.TypeVar("_S") + class A(typing.Generic[_T, _S]): + ... + """, + ) + def test_jump_into_class_through_annotation(self): self.Check(""" class Foo: diff --git a/pytype/tests/test_typevar1.py b/pytype/tests/test_typevar1.py index e878caa9f..608f202b5 100644 --- a/pytype/tests/test_typevar1.py +++ b/pytype/tests/test_typevar1.py @@ -113,6 +113,78 @@ def foo(a: T, b: S) -> tuple[S, T]: ... """, ) + @test_utils.skipBeforePy((3, 12), "PEP 695 - 3.12 feature") + def test_unused_typevar_pep695_class_single_type_var(self): + ty = self.Infer(""" + class A[T]: pass + """) + self.assertTypesMatchPytd( + ty, + """ + from typing import Any, Generic, TypeVar + + T = TypeVar('T') + + class A(Generic[T]): + __type_params__: tuple[Any] + """, + ) + + @test_utils.skipBeforePy((3, 12), "PEP 695 - 3.12 feature") + def test_unused_typevar_pep695_class_double_type_var(self): + ty = self.Infer(""" + class A[T, S]: pass + """) + self.assertTypesMatchPytd( + ty, + """ + from typing import Any, Generic, TypeVar + S = TypeVar('S') + T = TypeVar('T') + + class A(Generic[T, S]): + __type_params__: tuple[Any, Any] + """, + ) + + @test_utils.skipBeforePy((3, 12), "PEP 695 - 3.12 feature") + def test_unused_typevar_pep695_class_both_generic_and_base(self): + errors = self.CheckWithErrors(""" + from typing import Generic, TypeVar + U = TypeVar('U') + class A[T, S](Generic[U]): pass # invalid-annotation[e1] + """) + self.assertErrorRegexes( + errors, + { + "e1": ( + r"Invalid type annotation 'A' \nCannot inherit from" + r" Generic\[...\] multiple times" + ), + }, + ) + + @test_utils.skipBeforePy((3, 12), "PEP 695 - 3.12 feature") + def test_unused_typevar_pep695_class_inherit_from_base(self): + ty = self.Infer(""" + class Base[T]: pass + class Derived[S, T](Base[T]): pass + """) + self.assertTypesMatchPytd( + ty, + """ + from typing import Any, Generic, TypeVar + S = TypeVar('S') + T = TypeVar('T') + + class Base(Generic[T]): + __type_params__: tuple[Any] + + class Derived(Base[T], Generic[S, T]): + __type_params__: tuple[Any, Any] + """, + ) + def test_import_typevar(self): with test_utils.Tempdir() as d: d.create_file("a.pyi", """T = TypeVar("T")""") diff --git a/pytype/vm.py b/pytype/vm.py index 36203d008..2c3259d53 100644 --- a/pytype/vm.py +++ b/pytype/vm.py @@ -277,6 +277,12 @@ def _typings_paramspec(self): self.ctx.root_node ) + @functools.cached_property + def _typings_generic(self): + return typing_overlay.Generic("Generic", self.ctx).to_variable( + self.ctx.root_node + ) + def run_instruction( self, op: opcodes.Opcode, state: frame_state.FrameState ) -> frame_state.FrameState: @@ -3915,8 +3921,12 @@ def byte_INTRINSIC_TYPEVARTUPLE(self, state): return state def byte_INTRINSIC_SUBSCRIPT_GENERIC(self, state): - # TODO: b/350910471 - Implement to support PEP 695 - return state + state, type_parameters = state.pop() + state = state.push(self._typings_generic) + # This will be a tuple of type parameters in order. + state = state.push(type_parameters) + # Returning Generic[S, T] + return self.binary_operator(state, "__getitem__") def byte_INTRINSIC_TYPEALIAS(self, state): """This intrinsic creates a type alias and puts the result on the stack."""