diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1059f458..4120c3e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,13 +67,11 @@ jobs: allow-prereleases: true - name: Install coverage - if: ${{ !startsWith(matrix.python-version, 'pypy') }} run: | # Be wary that this does not install typing_extensions in the future pip install coverage - name: Test typing_extensions with coverage - if: ${{ !startsWith(matrix.python-version, 'pypy') }} run: | # Be wary of running `pip install` here, since it becomes easy for us to # accidentally pick up typing_extensions as installed by a dependency @@ -82,18 +80,9 @@ jobs: # Run tests under coverage export COVERAGE_FILE=.coverage_${{ matrix.python-version }} python -m coverage run -m unittest test_typing_extensions.py - - name: Test typing_extensions no coverage on pypy - if: ${{ startsWith(matrix.python-version, 'pypy') }} - run: | - # Be wary of running `pip install` here, since it becomes easy for us to - # accidentally pick up typing_extensions as installed by a dependency - cd src - python --version # just to make sure we're running the right one - python -m unittest test_typing_extensions.py - name: Archive code coverage results id: archive-coverage - if: ${{ !startsWith(matrix.python-version, 'pypy') }} uses: actions/upload-artifact@v4 with: name: .coverage_${{ matrix.python-version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index e9293e79..733505a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Unreleased +- Fix incorrect behaviour on Python 3.9 and Python 3.10 that meant that + calling `isinstance` with `typing_extensions.Concatenate[...]` or + `typing_extensions.Unpack[...]` as the first argument could have a different + result in some situations depending on whether or not a profiling function had been + set using `sys.setprofile`. This affected both CPython and PyPy implementations. + Patch by Brian Schubert. - Fix `__init_subclass__()` behavior in the presence of multiple inheritance involving an `@deprecated`-decorated base class. Backport of CPython PR [#138210](https://github.com/python/cpython/pull/138210) by Brian Schubert. diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 9047860e..88fa699e 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -6230,6 +6230,47 @@ def test_is_param_expr(self): self.assertTrue(typing._is_param_expr(concat)) self.assertTrue(typing._is_param_expr(typing_concat)) + def test_isinstance_results_unaffected_by_presence_of_tracing_function(self): + # See https://github.com/python/typing_extensions/issues/661 + + code = textwrap.dedent( + """\ + import sys, typing + + def trace_call(*args): + return trace_call + + def run(): + sys.modules.pop("typing_extensions", None) + from typing_extensions import Concatenate + return isinstance(Concatenate[...], typing._GenericAlias) + isinstance_result_1 = run() + sys.setprofile(trace_call) + isinstance_result_2 = run() + sys.stdout.write(f"{isinstance_result_1} {isinstance_result_2}") + """ + ) + + # Run this in an isolated process or it pollutes the environment + # and makes other tests fail: + try: + proc = subprocess.run( + [sys.executable, "-c", code], check=True, capture_output=True, text=True, + ) + except subprocess.CalledProcessError as exc: + print("stdout", exc.stdout, sep="\n") + print("stderr", exc.stderr, sep="\n") + raise + + # Sanity checks that assert the test is working as expected + self.assertIsInstance(proc.stdout, str) + result1, result2 = proc.stdout.split(" ") + self.assertIn(result1, {"True", "False"}) + self.assertIn(result2, {"True", "False"}) + + # The actual test: + self.assertEqual(result1, result2) + class TypeGuardTests(BaseTestCase): def test_basics(self): TypeGuard[int] # OK @@ -6652,6 +6693,46 @@ def test_type_var_inheritance(self): self.assertFalse(isinstance(Unpack[Ts], TypeVar)) self.assertFalse(isinstance(Unpack[Ts], typing.TypeVar)) + def test_isinstance_results_unaffected_by_presence_of_tracing_function(self): + # See https://github.com/python/typing_extensions/issues/661 + + code = textwrap.dedent( + """\ + import sys, typing + + def trace_call(*args): + return trace_call + + def run(): + sys.modules.pop("typing_extensions", None) + from typing_extensions import TypeVarTuple, Unpack + return isinstance(Unpack[TypeVarTuple("Ts")], typing.TypeVar) + isinstance_result_1 = run() + sys.setprofile(trace_call) + isinstance_result_2 = run() + sys.stdout.write(f"{isinstance_result_1} {isinstance_result_2}") + """ + ) + + # Run this in an isolated process or it pollutes the environment + # and makes other tests fail: + try: + proc = subprocess.run( + [sys.executable, "-c", code], check=True, capture_output=True, text=True, + ) + except subprocess.CalledProcessError as exc: + print("stdout", exc.stdout, sep="\n") + print("stderr", exc.stderr, sep="\n") + raise + + # Sanity checks that assert the test is working as expected + self.assertIsInstance(proc.stdout, str) + result1, result2 = proc.stdout.split(" ") + self.assertIn(result1, {"True", "False"}) + self.assertIn(result2, {"True", "False"}) + + # The actual test: + self.assertEqual(result1, result2) class TypeVarTupleTests(BaseTestCase): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 73502af3..bd67a80a 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -1986,7 +1986,9 @@ class _ConcatenateGenericAlias(list): __class__ = typing._GenericAlias def __init__(self, origin, args): - super().__init__(args) + # Cannot use `super().__init__` here because of the `__class__` assignment + # in the class body (https://github.com/python/typing_extensions/issues/661) + list.__init__(self, args) self.__origin__ = origin self.__args__ = args @@ -2545,7 +2547,10 @@ def __typing_is_unpacked_typevartuple__(self): def __getitem__(self, args): if self.__typing_is_unpacked_typevartuple__: return args - return super().__getitem__(args) + # Cannot use `super().__getitem__` here because of the `__class__` assignment + # in the class body on Python <=3.11 + # (https://github.com/python/typing_extensions/issues/661) + return typing._GenericAlias.__getitem__(self, args) @_UnpackSpecialForm def Unpack(self, parameters):