Skip to content

Conversation

@AlexWaygood
Copy link
Member

@AlexWaygood AlexWaygood commented Sep 27, 2025

Summary

Currently we'll use the fully qualified name of a class in some cases when reporting instances where one class was expected but another one was found and the two classes have the same name. But there are still many cases where we won't do that -- e.g. in #20368, this is one of the new diagnostics:

error[invalid-return-type] Return type does not match returned value: expected `Iterable[Model]`, found `Iterable[Model]`

This PR switches the DisplaySettings::from_possibly_ambiguous_type_pair() function to use a TypeVisitor implementation, so that we can be confident it will deeply recurse into a pair of types and identify possibly ambiguous type names wherever they might be hiding.

Test Plan

Mdtests

@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label Sep 27, 2025
@AlexWaygood AlexWaygood changed the title alex/invalid returns generic disambiguate [ty] Improve disambiguation of class names in diagnostics Sep 27, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Sep 27, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

@github-actions
Copy link
Contributor

github-actions bot commented Sep 27, 2025

mypy_primer results

Changes were detected when running on open source projects
pandera (https://github.com/pandera-dev/pandera)
- tests/pandas/test_dtypes.py:170:1: error[invalid-assignment] Object of type `list[Unknown | tuple[dict[Unknown | <class 'int'> | <class 'Int'> | <class 'Int8'> | <class 'Int16'> | <class 'Int32'> | <class 'Int64'> | <class 'signedinteger[_8Bit]'> | <class 'signedinteger[_16Bit]'> | <class 'signedinteger[_32Bit]'> | <class 'signedinteger[_64Bit]'>, Unknown | str], list[Unknown | int]] | tuple[dict[Unknown | <class 'INT8'> | <class 'INT16'> | <class 'INT32'> | <class 'INT64'>, Unknown | str], list[Unknown | int | None]] | tuple[dict[Unknown | <class 'UInt'> | <class 'UInt8'> | <class 'UInt16'> | <class 'UInt32'> | <class 'UInt64'>, Unknown | str], list[Unknown | int]] | tuple[dict[Unknown | <class 'UINT8'> | <class 'UINT16'> | <class 'UINT32'> | <class 'UINT64'>, Unknown | str], list[Unknown | int | None]] | tuple[dict[Unknown | <class 'float'> | <class 'Float'> | <class 'Float16'> | <class 'Float32'> | <class 'Float64'> | <class 'float64'>, Unknown | str], list[Unknown | float]] | tuple[dict[Unknown | <class 'complex'> | <class 'Complex'> | <class 'Complex64'> | <class 'Complex128'>, Unknown | str], list[Unknown | complex]] | tuple[dict[Unknown | <class 'bool'> | <class 'Bool'> | <class 'bool'>, Unknown | str], list[Unknown | bool]] | tuple[dict[Unknown | <class 'BooleanDtype'> | <class 'BOOL'>, Unknown | str], list[Unknown | bool | None]] | tuple[dict[Unknown | <class 'str'> | <class 'String'> | <class 'str_'>, Unknown | str], list[Unknown | str]] | tuple[dict[Unknown | <class 'object'> | <class 'object_'>, Unknown | str], list[Unknown | str]] | tuple[dict[Unknown | <class 'StringDtype'>, Unknown | str], list[Unknown | int | None]] | tuple[dict[Unknown | <class 'Category'> | Category | CategoricalDtype, Unknown | str | CategoricalDtype], list[Unknown | int | None]] | tuple[dict[Unknown | <class 'datetime'> | <class 'datetime64'> | <class 'Timestamp'> | DatetimeTZDtype | <class 'DateTime'> | DateTime, Unknown | str], Unknown] | tuple[dict[Unknown | PeriodDtype, Unknown | str], Unknown] | tuple[dict[Unknown | <class 'SparseDtype'> | SparseDtype, Unknown | SparseDtype], Series[Any]] | tuple[dict[Unknown | IntervalDtype, Unknown | str], Series[Any]]]` is not assignable to `list[tuple[dict[Unknown, Unknown], list[Unknown]]]`
+ tests/pandas/test_dtypes.py:170:1: error[invalid-assignment] Object of type `list[Unknown | tuple[dict[Unknown | <class 'int'> | <class 'Int'> | <class 'Int8'> | <class 'Int16'> | <class 'Int32'> | <class 'Int64'> | <class 'signedinteger[_8Bit]'> | <class 'signedinteger[_16Bit]'> | <class 'signedinteger[_32Bit]'> | <class 'signedinteger[_64Bit]'>, Unknown | str], list[Unknown | int]] | tuple[dict[Unknown | <class 'INT8'> | <class 'INT16'> | <class 'INT32'> | <class 'INT64'>, Unknown | str], list[Unknown | int | None]] | tuple[dict[Unknown | <class 'UInt'> | <class 'UInt8'> | <class 'UInt16'> | <class 'UInt32'> | <class 'UInt64'>, Unknown | str], list[Unknown | int]] | tuple[dict[Unknown | <class 'UINT8'> | <class 'UINT16'> | <class 'UINT32'> | <class 'UINT64'>, Unknown | str], list[Unknown | int | None]] | tuple[dict[Unknown | <class 'float'> | <class 'Float'> | <class 'Float16'> | <class 'Float32'> | <class 'Float64'> | <class 'float64'>, Unknown | str], list[Unknown | float]] | tuple[dict[Unknown | <class 'complex'> | <class 'Complex'> | <class 'Complex64'> | <class 'Complex128'>, Unknown | str], list[Unknown | complex]] | tuple[dict[Unknown | <class 'builtins.bool'> | <class 'Bool'> | <class 'numpy.bool'>, Unknown | str], list[Unknown | builtins.bool]] | tuple[dict[Unknown | <class 'BooleanDtype'> | <class 'BOOL'>, Unknown | str], list[Unknown | builtins.bool | None]] | tuple[dict[Unknown | <class 'str'> | <class 'String'> | <class 'str_'>, Unknown | str], list[Unknown | str]] | tuple[dict[Unknown | <class 'object'> | <class 'object_'>, Unknown | str], list[Unknown | str]] | tuple[dict[Unknown | <class 'StringDtype'>, Unknown | str], list[Unknown | int | None]] | tuple[dict[Unknown | <class 'Category'> | Category | CategoricalDtype, Unknown | str | CategoricalDtype], list[Unknown | int | None]] | tuple[dict[Unknown | <class 'datetime'> | <class 'datetime64'> | <class 'Timestamp'> | DatetimeTZDtype | <class 'DateTime'> | DateTime, Unknown | str], Unknown] | tuple[dict[Unknown | PeriodDtype, Unknown | str], Unknown] | tuple[dict[Unknown | <class 'SparseDtype'> | SparseDtype, Unknown | SparseDtype], Series[Any]] | tuple[dict[Unknown | IntervalDtype, Unknown | str], Series[Any]]]` is not assignable to `list[tuple[dict[Unknown, Unknown], list[Unknown]]]`

mongo-python-driver (https://github.com/mongodb/mongo-python-driver)
- pymongo/client_options.py:145:16: error[invalid-return-type] Return type does not match returned value: expected `tuple[SSLContext | None, bool]`, found `tuple[SSLContext | SSLContext | Unknown, Any]`
+ pymongo/client_options.py:145:16: error[invalid-return-type] Return type does not match returned value: expected `tuple[pymongo.pyopenssl_context.SSLContext | None, bool]`, found `tuple[pymongo.pyopenssl_context.SSLContext | ssl.SSLContext | Unknown, Any]`

setuptools (https://github.com/pypa/setuptools)
- setuptools/_distutils/dist.py:979:20: error[invalid-return-type] Return type does not match returned value: expected `Command`, found `(str & Command) | (Command & Command) | Unknown`
+ setuptools/_distutils/dist.py:979:20: error[invalid-return-type] Return type does not match returned value: expected `setuptools._distutils.cmd.Command`, found `(str & distutils.cmd.Command) | (setuptools._distutils.cmd.Command & distutils.cmd.Command) | Unknown`
- setuptools/_distutils/dist.py:989:16: error[invalid-return-type] Return type does not match returned value: expected `Command`, found `(str & Command) | (Command & Command) | Unknown`
+ setuptools/_distutils/dist.py:989:16: error[invalid-return-type] Return type does not match returned value: expected `setuptools._distutils.cmd.Command`, found `(str & distutils.cmd.Command) | (setuptools._distutils.cmd.Command & distutils.cmd.Command) | Unknown`

bokeh (https://github.com/bokeh/bokeh)
- src/bokeh/model/model.py:496:16: error[invalid-return-type] Return type does not match returned value: expected `set[Model]`, found `set[Model]`
+ src/bokeh/model/model.py:496:16: error[invalid-return-type] Return type does not match returned value: expected `set[src.bokeh.model.model.Model]`, found `set[src.bokeh.model.model.Model]`

jax (https://github.com/google/jax)
- jax/_src/mesh_utils.py:552:12: error[invalid-return-type] Return type does not match returned value: expected `builtins.bool`, found `numpy.bool[bool]`
+ jax/_src/mesh_utils.py:552:12: error[invalid-return-type] Return type does not match returned value: expected `builtins.bool`, found `numpy.bool[builtins.bool]`
- jax/_src/mesh_utils.py:582:12: error[invalid-return-type] Return type does not match returned value: expected `builtins.bool`, found `numpy.bool[bool]`
+ jax/_src/mesh_utils.py:582:12: error[invalid-return-type] Return type does not match returned value: expected `builtins.bool`, found `numpy.bool[builtins.bool]`
No memory usage changes detected ✅

@AlexWaygood AlexWaygood marked this pull request as ready for review September 27, 2025 19:25
@AlexWaygood AlexWaygood added the diagnostics Related to reporting of diagnostics. label Sep 27, 2025
@sharkdp
Copy link
Contributor

sharkdp commented Sep 29, 2025

- src/bokeh/model/model.py:496:16: error[invalid-return-type] Return type does not match returned value: expected `set[Model]`, found `set[Model]`
+ src/bokeh/model/model.py:496:16: error[invalid-return-type] Return type does not match returned value: expected `set[src.bokeh.model.model.Model]`, found `set[src.bokeh.model.model.Model]`

obviously not related to your PR, but it looks like something is wrong here?

Copy link
Contributor

@sharkdp sharkdp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much. Slightly unfortunate that we gain .clones in multiple places now, but this is probably not performance-critical.

@AlexWaygood
Copy link
Member Author

- src/bokeh/model/model.py:496:16: error[invalid-return-type] Return type does not match returned value: expected `set[Model]`, found `set[Model]`
+ src/bokeh/model/model.py:496:16: error[invalid-return-type] Return type does not match returned value: expected `set[src.bokeh.model.model.Model]`, found `set[src.bokeh.model.model.Model]`

obviously not related to your PR, but it looks like something is wrong here?

Yes, definitely (multiple things, in fact...) -- but at least we now know that this specific case isn't solved by printing the fully qualified name in the diagnostics 😆

@AlexWaygood
Copy link
Member Author

Slightly unfortunate that we gain .clones in multiple places now, but this is probably not performance-critical.

Yes, I didn't love this, but couldn't see an easy way to get around it. I think DisplaySettings should be fairly cheap to clone, though, since it uses an Rc<HashSet> internally rather than a HashSet

@AlexWaygood AlexWaygood merged commit 3f640da into main Sep 29, 2025
38 checks passed
@AlexWaygood AlexWaygood deleted the alex/invalid-returns-generic-disambiguate branch September 29, 2025 10:43
AlexWaygood added a commit that referenced this pull request Sep 29, 2025
#20629)

This PR ensures that we always put `./src` before `.` in our list of
first-party search paths. This better emulates the fact that at runtime,
the module name of a file `src/foo.py` would almost certainly be `foo`
rather than `src.foo`.

I wondered if fixing this might fix
#20603 (comment). It
seems like that's not the case, but it also seems like it leads to
better diagnostics because we report much more intuitive module names to
the user in our error messages -- so, it's probably a good change
anyway.
dcreager added a commit that referenced this pull request Sep 30, 2025
* main: (21 commits)
  [ty] Literal promotion refactor (#20646)
  [ty] Add tests for nested generic functions (#20631)
  [`cli`] Add conflict between `--add-noqa` and `--diff` options (#20642)
  [ty] Ensure first-party search paths always appear in a sensible order (#20629)
  [ty] Use `typing.Self` for the first parameter of instance methods (#20517)
  [ty] Remove unnecessary `parsed_module()` calls (#20630)
  Remove `TextEmitter` (#20595)
  [ty] Use fully qualified names to distinguish ambiguous protocols in diagnostics (#20627)
  [ty] Ecosystem analyzer: relax timeout thresholds (#20626)
  [ty] Apply type mappings to functions eagerly (#20596)
  [ty] Improve disambiguation of class names in diagnostics (#20603)
  Add the *The Basics* title back to CONTRIBUTING.md (#20624)
  [`playground`] Fix quick fixes for empty ranges in playground (#20599)
  Update dependency ruff to v0.13.2 (#20622)
  [`ruff`] Fix minor typos in doc comments (#20623)
  Update dependency PyYAML to v6.0.3 (#20621)
  Update cargo-bins/cargo-binstall action to v1.15.6 (#20620)
  Fixed documentation for try_consider_else (#20587)
  [ty] Use `Top` materializations for `TypeIs` special form (#20591)
  [ty] Simplify `Any | (Any & T)` to `Any` (#20593)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

diagnostics Related to reporting of diagnostics. ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants