Skip to content

[ty] Split invalid-base error code into two error codes#18245

Merged
AlexWaygood merged 2 commits intomainfrom
alex/split-invalid-base
May 21, 2025
Merged

[ty] Split invalid-base error code into two error codes#18245
AlexWaygood merged 2 commits intomainfrom
alex/split-invalid-base

Conversation

@AlexWaygood
Copy link
Member

Summary

Our invalid-base diagnostic currently catches two somewhat distinct things:

  1. It catches class bases that will actually cause the class definition to raise an exception at runtime (any class base where the type of the base is not assignable to type)
  2. It catches class bases that will probably not raise an exception at runtime, but which we cannot allow to pass without a diagnostic because we would not be able to accurately infer the MRO of the resulting class (union types, type[] types, instances of type, etc.)

The fact that it's doing two distinct things makes it hard to document the lint, and would also make it hard for users to suppress the lint in a fine-grained way (the first category of errors is much more serious than the second category). This PR therefore splits the diagnostic into two: invalid-base and unsupported-base. It also adds documentation for both new error codes.

Test Plan

Snapshots

@AlexWaygood AlexWaygood added ty Multi-file analysis & type inference diagnostics Related to reporting of diagnostics. labels May 21, 2025
@github-actions
Copy link
Contributor

github-actions bot commented May 21, 2025

mypy_primer results

Changes were detected when running on open source projects
pybind11 (https://github.com/pybind/pybind11)
- error[invalid-base] pybind11/setup_helpers.py:89:25: Invalid class base with type `<class 'Extension'> | <class 'Extension'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] pybind11/setup_helpers.py:89:25: Unsupported class base with type `<class 'Extension'> | <class 'Extension'>`
- error[invalid-base] pybind11/setup_helpers.py:271:17: Invalid class base with type `<class 'build_ext'> | <class 'build_ext'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] pybind11/setup_helpers.py:271:17: Unsupported class base with type `<class 'build_ext'> | <class 'build_ext'>`

werkzeug (https://github.com/pallets/werkzeug)
- error[invalid-base] src/werkzeug/serving.py:879:25: Invalid class base with type `<class 'ForkingMixIn'> | <class 'ForkingMixIn'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] src/werkzeug/serving.py:879:25: Unsupported class base with type `<class 'ForkingMixIn'> | <class 'ForkingMixIn'>`

scrapy (https://github.com/scrapy/scrapy)
- error[invalid-base] tests/test_utils_deprecate.py:41:29: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
- error[invalid-base] tests/test_utils_deprecate.py:65:29: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
- error[invalid-base] tests/test_utils_deprecate.py:84:29: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
- error[invalid-base] tests/test_utils_deprecate.py:101:29: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
- error[invalid-base] tests/test_utils_deprecate.py:104:28: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
- error[invalid-base] tests/test_utils_deprecate.py:107:28: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
- error[invalid-base] tests/test_utils_deprecate.py:123:29: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] tests/test_utils_deprecate.py:41:29: Unsupported class base with type `type`
+ warning[unsupported-base] tests/test_utils_deprecate.py:65:29: Unsupported class base with type `type`
+ warning[unsupported-base] tests/test_utils_deprecate.py:84:29: Unsupported class base with type `type`
+ warning[unsupported-base] tests/test_utils_deprecate.py:101:29: Unsupported class base with type `type`
+ warning[unsupported-base] tests/test_utils_deprecate.py:104:28: Unsupported class base with type `type`
+ warning[unsupported-base] tests/test_utils_deprecate.py:107:28: Unsupported class base with type `type`
+ warning[unsupported-base] tests/test_utils_deprecate.py:123:29: Unsupported class base with type `type`
- error[invalid-base] tests/test_utils_deprecate.py:142:30: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
- error[invalid-base] tests/test_utils_deprecate.py:160:38: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
- error[invalid-base] tests/test_utils_deprecate.py:163:39: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] tests/test_utils_deprecate.py:142:30: Unsupported class base with type `type`
+ warning[unsupported-base] tests/test_utils_deprecate.py:160:38: Unsupported class base with type `type`
+ warning[unsupported-base] tests/test_utils_deprecate.py:163:39: Unsupported class base with type `type`
- error[invalid-base] tests/test_utils_deprecate.py:197:38: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
- error[invalid-base] tests/test_utils_deprecate.py:200:39: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] tests/test_utils_deprecate.py:197:38: Unsupported class base with type `type`
+ warning[unsupported-base] tests/test_utils_deprecate.py:200:39: Unsupported class base with type `type`
- error[invalid-base] tests/test_utils_deprecate.py:251:29: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
- error[invalid-base] tests/test_utils_deprecate.py:268:28: Invalid class base with type `type` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] tests/test_utils_deprecate.py:251:29: Unsupported class base with type `type`
+ warning[unsupported-base] tests/test_utils_deprecate.py:268:28: Unsupported class base with type `type`

psycopg (https://github.com/psycopg/psycopg)
- error[invalid-base] tests/test_copy.py:303:23: Invalid class base with type `<class 'StrDumper'> | <class 'StrBinaryDumper'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] tests/test_copy.py:303:23: Unsupported class base with type `<class 'StrDumper'> | <class 'StrBinaryDumper'>`
- error[invalid-base] tests/test_copy_async.py:313:23: Invalid class base with type `<class 'StrDumper'> | <class 'StrBinaryDumper'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] tests/test_copy_async.py:313:23: Unsupported class base with type `<class 'StrDumper'> | <class 'StrBinaryDumper'>`

meson (https://github.com/mesonbuild/meson)
- error[invalid-base] mesonbuild/_pathlib.py:42:16: Invalid class base with type `type[Path]` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] mesonbuild/_pathlib.py:42:16: Unsupported class base with type `type[Path]`

pwndbg (https://github.com/pwndbg/pwndbg)
- error[invalid-base] pwndbg/aglib/heap/structs.py:65:15: Invalid class base with type `<class 'c_uint32'> | <class 'c_uint64'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] pwndbg/aglib/heap/structs.py:65:15: Unsupported class base with type `<class 'c_uint32'> | <class 'c_uint64'>`
- error[invalid-base] pwndbg/aglib/heap/structs.py:71:16: Invalid class base with type `<class 'c_uint32'> | <class 'c_uint64'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] pwndbg/aglib/heap/structs.py:71:16: Unsupported class base with type `<class 'c_uint32'> | <class 'c_uint64'>`

mypy (https://github.com/python/mypy)
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:403:19: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:403:19: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:408:21: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:408:21: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:413:23: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:413:23: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:418:21: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:418:21: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:423:21: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:423:21: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:428:19: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:428:19: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:433:21: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:433:21: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:442:13: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:442:13: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:447:16: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:447:16: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:455:16: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:455:16: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:460:33: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:460:33: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:466:35: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:466:35: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:475:39: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:475:39: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:501:70: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:501:70: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:504:80: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:504:80: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:507:17: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:507:17: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:515:44: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:515:44: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:538:5: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:538:5: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:543:21: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:543:21: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:548:43: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:548:43: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:554:49: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:554:49: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:571:17: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:571:17: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:577:53: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:577:53: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:660:66: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:660:66: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:691:32: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:691:32: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing.pyi:764:10: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing.pyi:764:10: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:416:23: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:416:23: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:421:25: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:421:25: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:426:27: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:426:27: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:431:25: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:431:25: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:436:25: Invalid class base with type `_SpecialForm` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:436:25: Invalid class base with type `_SpecialForm`
- error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:441:23: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:441:23: Invalid class base with type `object`
- error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:446:25: Invalid class base with type `object` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypy/typeshed/stdlib/typing_extensions.pyi:446:25: Invalid class base with type `object`
- error[invalid-base] mypyc/test-data/fixtures/typing-full.pyi:54:13: Invalid class base with type `Literal[0]` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypyc/test-data/fixtures/typing-full.pyi:54:13: Invalid class base with type `Literal[0]`
- error[invalid-base] mypyc/test-data/fixtures/typing-full.pyi:64:32: Invalid class base with type `Literal[0]` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypyc/test-data/fixtures/typing-full.pyi:64:32: Invalid class base with type `Literal[0]`
- error[invalid-base] mypyc/test-data/fixtures/typing-full.pyi:121:39: Invalid class base with type `Literal[0]` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypyc/test-data/fixtures/typing-full.pyi:121:39: Invalid class base with type `Literal[0]`
- error[invalid-base] mypyc/test-data/fixtures/typing-full.pyi:145:19: Invalid class base with type `Literal[0]` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypyc/test-data/fixtures/typing-full.pyi:145:19: Invalid class base with type `Literal[0]`
- error[invalid-base] mypyc/test-data/fixtures/typing-full.pyi:148:21: Invalid class base with type `Literal[0]` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ error[invalid-base] mypyc/test-data/fixtures/typing-full.pyi:148:21: Invalid class base with type `Literal[0]`

cloud-init (https://github.com/canonical/cloud-init)
- error[invalid-base] cloudinit/templater.py:89:30: Invalid class base with type `<class 'DebugUndefined'> | <class 'object'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] cloudinit/templater.py:89:30: Unsupported class base with type `<class 'DebugUndefined'> | <class 'object'>`

dd-trace-py (https://github.com/DataDog/dd-trace-py)
- error[invalid-base] ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/setup_helpers.py:89:25: Invalid class base with type `<class 'Extension'> | <class 'Extension'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/setup_helpers.py:89:25: Unsupported class base with type `<class 'Extension'> | <class 'Extension'>`
- error[invalid-base] ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/setup_helpers.py:271:17: Invalid class base with type `<class 'build_ext'> | <class 'build_ext'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/setup_helpers.py:271:17: Unsupported class base with type `<class 'build_ext'> | <class 'build_ext'>`

scipy (https://github.com/scipy/scipy)
- error[invalid-base] scipy/stats/_distribution_infrastructure.py:4139:30: Invalid class base with type `<class 'ContinuousDistribution'> | <class 'DiscreteDistribution'>` (all bases must be a class, `Any`, `Unknown` or `Todo`)
+ warning[unsupported-base] scipy/stats/_distribution_infrastructure.py:4139:30: Unsupported class base with type `<class 'ContinuousDistribution'> | <class 'DiscreteDistribution'>`

Comment on lines -906 to -911
if base_ty.is_never() {
// A class base of type `Never` can appear in unreachable code. It
// does not indicate a problem, since the actual construction of the
// class will never happen.
continue;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

I moved this check into ClassBase::try_from_ty so that we don't create the MroError in the first place for classes that inherit from Never

Copy link
Member Author

Choose a reason for hiding this comment

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

The changes in this file are a drive-by fix to try to solve the indeterminacy we've seen in the order of some of our duplicate-base diagnostics. See e.g. #18226 (comment) -- it's been changing randomly on several PRs that touch mro.rs

@AlexWaygood AlexWaygood marked this pull request as ready for review May 21, 2025 20:19
@AlexWaygood AlexWaygood changed the title [ty] Split invalid-base diagnostic into two diagnostics [ty] Split invalid-base error code into two error codes May 21, 2025
@AlexWaygood AlexWaygood force-pushed the alex/split-invalid-base branch 2 times, most recently from 65d51c9 to ba769c1 Compare May 21, 2025 20:45
@AlexWaygood AlexWaygood force-pushed the alex/split-invalid-base branch from ba769c1 to 04472b4 Compare May 21, 2025 20:46
@AlexWaygood AlexWaygood force-pushed the alex/split-invalid-base branch from 419f3dd to eb3e7d5 Compare May 21, 2025 21:49
@AlexWaygood AlexWaygood requested a review from carljm May 21, 2025 21:51
Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

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

Nice!

@AlexWaygood AlexWaygood merged commit cb04343 into main May 21, 2025
35 checks passed
@AlexWaygood AlexWaygood deleted the alex/split-invalid-base branch May 21, 2025 22:02
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.

2 participants