[ty] Improve debuggability of protocol types#19662
Conversation
Diagnostic diff on typing conformance testsChanges were detected when running ty on typing conformance tests--- old-output.txt 2025-08-01 14:10:34.764459119 +0000
+++ new-output.txt 2025-08-01 14:10:34.827459530 +0000
@@ -368,8 +368,8 @@
generics_basic.py:34:12: error[unsupported-operator] Operator `+` is unsupported between objects of type `AnyStr` and `AnyStr`
generics_basic.py:139:5: error[type-assertion-failure] Argument does not have asserted type `int`
generics_basic.py:140:5: error[type-assertion-failure] Argument does not have asserted type `int`
-generics_basic.py:157:5: error[invalid-argument-type] Method `__getitem__` of type `bound method MyMap1[str, int].__getitem__(key: str, /) -> int` cannot be called with key of type `Literal[0]` on object of type `MyMap1[str, int]`
-generics_basic.py:158:5: error[invalid-argument-type] Method `__getitem__` of type `bound method MyMap2[int, str].__getitem__(key: str, /) -> int` cannot be called with key of type `Literal[0]` on object of type `MyMap2[int, str]`
+generics_basic.py:157:5: error[call-non-callable] Method `__getitem__` of type `bound method MyMap1[str, int].__getitem__(key: str, /) -> int` is not callable on object of type `MyMap1[str, int]`
+generics_basic.py:158:5: error[call-non-callable] Method `__getitem__` of type `bound method MyMap2[int, str].__getitem__(key: str, /) -> int` is not callable on object of type `MyMap2[int, str]`
generics_basic.py:162:12: error[invalid-argument-type] `<class 'int'>` is not a valid argument to `Generic`
generics_basic.py:163:12: error[invalid-argument-type] `<class 'int'>` is not a valid argument to `Protocol`
generics_basic.py:171:1: error[invalid-generic-class] `Generic` base class must include all type variables used in other base classes
@@ -704,7 +704,6 @@
namedtuples_usage.py:30:1: error[type-assertion-failure] Argument does not have asserted type `str`
namedtuples_usage.py:31:1: error[type-assertion-failure] Argument does not have asserted type `int`
namedtuples_usage.py:32:1: error[type-assertion-failure] Argument does not have asserted type `int`
-namedtuples_usage.py:41:1: error[invalid-assignment] Cannot assign to object of type `Point` with no `__setitem__` method
namedtuples_usage.py:49:1: error[type-assertion-failure] Argument does not have asserted type `int`
namedtuples_usage.py:50:1: error[type-assertion-failure] Argument does not have asserted type `str`
narrowing_typeguard.py:17:9: error[type-assertion-failure] Argument does not have asserted type `tuple[str, str]`
@@ -725,7 +724,7 @@
narrowing_typeis.py:96:9: error[type-assertion-failure] Argument does not have asserted type `B`
narrowing_typeis.py:132:20: error[invalid-argument-type] Argument to function `takes_callable_str` is incorrect: Expected `(object, /) -> str`, found `def simple_typeguard(val: object) -> TypeIs[int]`
narrowing_typeis.py:191:18: error[invalid-argument-type] Argument to function `takes_int_typeis` is incorrect: Expected `(object, /) -> TypeIs[int]`, found `def bool_typeis(val: object) -> TypeIs[bool]`
-overloads_basic.py:39:1: error[invalid-argument-type] Method `__getitem__` of type `Overload[(__i: int) -> int, (__s: slice[Any, Any, Any]) -> bytes]` cannot be called with key of type `Literal[""]` on object of type `Bytes`
+overloads_basic.py:39:1: error[call-non-callable] Method `__getitem__` of type `Overload[(__i: int) -> int, (__s: slice[Any, Any, Any]) -> bytes]` is not callable on object of type `Bytes`
overloads_definitions.py:20:5: error[invalid-overload] Overloaded function `func1` requires at least two overloads
overloads_definitions.py:33:5: error[invalid-overload] Overloaded non-stub function `func2` must have an implementation
overloads_definitions.py:63:9: error[invalid-overload] Overloaded non-stub function `not_abstract` must have an implementation
@@ -889,4 +888,4 @@
tuples_type_form.py:36:1: error[invalid-assignment] Object of type `tuple[Literal[1], Literal[2], Literal[3], Literal[""]]` is not assignable to `tuple[int, ...]`
typeddicts_operations.py:60:1: error[type-assertion-failure] Argument does not have asserted type `str | None`
typeddicts_type_consistency.py:101:1: error[invalid-assignment] Object of type `Unknown | None` is not assignable to `str`
-Found 890 diagnostics
+Found 889 diagnostics |
|
sharkdp
left a comment
There was a problem hiding this comment.
Cool. I can see how this can be very helpful. I like that you are constructing the "debug" representation manually instead of relying on {:?}.
Still, I'm a little worried that we should maybe be splitting ty_extensions into "public" and "definitely internal" parts (see my comment here), because this definitely belongs in the latter category. But this is also true for other functions in ty_extensions, so not only related to this PR.
|
Thinking a bit more about this... if we do want to eventually add something like |
I think this is a fair concern, although I'm not sure if we should indeed communicate that anything about Either way. I guess the pythonic solution to this problem could be to mark the "more internal" functions as private: |
I think I'd prefer |
Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
|
Yes, it would be nice to expose some parts of
So I agree that we have a number of internal functions in |
It could be. But I think I'd want more information from |
* main: (39 commits) [ty] Initial test suite for `TypedDict` (#19686) [ty] Improve debuggability of protocol types (#19662) [ty] Simplify lifetime requirements for `PySlice` trait (#19687) [ty] Improve `isinstance()` truthiness analysis for generic types (#19668) [`refurb`] Make example error out-of-the-box (`FURB164`) (#19673) Fix link: unused_import.rs (#19648) [ty] Remove `Specialization::display` (full) (#19682) [ty] Remove `KnownModule::is_enum` (#19681) [ty] Support `__setitem__` and improve `__getitem__` related diagnostics (#19578) [ty] Sync vendored typeshed stubs (#19676) [`flake8-use-pathlib`] Expand `PTH201` to check all `PurePath` subclasses (#19440) [`refurb`] Make example error out-of-the-box (`FURB180`) (#19672) [`pyupgrade`] Prevent infinite loop with `I002` (`UP010`, `UP035`) (#19413) [ty] Improve the `Display` for generic `type[]` types (#19667) [ty] Refactor `TypeInferenceBuilder::infer_subscript_expression_types` (#19658) Fix tests on 32-bit architectures (#19652) [ty] Move `pandas-stubs` to bad.txt (#19659) [ty] Remove special casing for string-literal-in-tuple `__contains__` (#19642) Update pre-commit's `ruff` id (#19654) Update salsa (#19449) ...
Summary
As our protocol implementation continues to progress, it's increasingly important for debuggability to know what types are being captured for each member in a protocol's interface and what kind of a member each member is treated as. One way of obtaining this information is to use a
dbg!()call, but this means that you have to recompile ty (which takes a while), and also prints out a lot of detail that you don't need, making it hard to see the information that you do need.This PR adds a
ty_extensions.reveal_protocol_interfacefunction to solve this issue. If you pass a protocol to this function, ty emits a diagnostic that prints a formatted representation of the underlying interface.Test Plan
mdtests