Skip to content

Commit 18ae2b3

Browse files
authored
Backport recent improvements to the error message when trying to call issubclass() against a protocol with non-method members (#304)
1 parent e4d9d8b commit 18ae2b3

File tree

3 files changed

+26
-1
lines changed

3 files changed

+26
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
is called on all objects that define `__set_name__` and exist in the values
1414
of the `NamedTuple` class's class dictionary. Patch by Alex Waygood,
1515
backporting https://github.com/python/cpython/pull/111876.
16+
- Improve the error message when trying to call `issubclass()` against a
17+
`Protocol` that has non-method members. Patch by Alex Waygood (backporting
18+
https://github.com/python/cpython/pull/112344, by Randolph Scholz).
1619

1720
# Release 4.8.0 (September 17, 2023)
1821

src/test_typing_extensions.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3446,6 +3446,23 @@ def method(self) -> None: ...
34463446
self.assertIsInstance(Foo(), ProtocolWithMixedMembers)
34473447
self.assertNotIsInstance(42, ProtocolWithMixedMembers)
34483448

3449+
@skip_if_early_py313_alpha
3450+
def test_protocol_issubclass_error_message(self):
3451+
class Vec2D(Protocol):
3452+
x: float
3453+
y: float
3454+
3455+
def square_norm(self) -> float:
3456+
return self.x ** 2 + self.y ** 2
3457+
3458+
self.assertEqual(Vec2D.__protocol_attrs__, {'x', 'y', 'square_norm'})
3459+
expected_error_message = (
3460+
"Protocols with non-method members don't support issubclass()."
3461+
" Non-method members: 'x', 'y'."
3462+
)
3463+
with self.assertRaisesRegex(TypeError, re.escape(expected_error_message)):
3464+
issubclass(int, Vec2D)
3465+
34493466

34503467
class Point2DGeneric(Generic[T], TypedDict):
34513468
a: T

src/typing_extensions.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,8 +570,13 @@ def __subclasscheck__(cls, other):
570570
not cls.__callable_proto_members_only__
571571
and cls.__dict__.get("__subclasshook__") is _proto_hook
572572
):
573+
non_method_attrs = sorted(
574+
attr for attr in cls.__protocol_attrs__
575+
if not callable(getattr(cls, attr, None))
576+
)
573577
raise TypeError(
574-
"Protocols with non-method members don't support issubclass()"
578+
"Protocols with non-method members don't support issubclass()."
579+
f" Non-method members: {str(non_method_attrs)[1:-1]}."
575580
)
576581
if not getattr(cls, '_is_runtime_protocol', False):
577582
raise TypeError(

0 commit comments

Comments
 (0)