Skip to content

[red-knot] Allow subclasses of Any to be assignable to Callable types #17717

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 1, 2025

Conversation

Kalmaegi
Copy link
Contributor

@Kalmaegi Kalmaegi commented Apr 29, 2025

Summary

Fixes #17701. I'm uncertain whether this Python code needs to be added to the test cases. I'd be happy to supplement it if needed.

Copy link
Contributor

github-actions bot commented Apr 29, 2025

mypy_primer results

Changes were detected when running on open source projects
starlette (https://github.com/encode/starlette)
- error[lint:invalid-argument-type] tests/test_templates.py:191:39: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Unknown | MagicMock`
- error[lint:invalid-argument-type] tests/test_templates.py:267:39: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Unknown | MagicMock`
- error[lint:invalid-argument-type] tests/test_templates.py:388:32: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Unknown | MagicMock`
- error[lint:invalid-argument-type] tests/test_templates.py:420:39: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Unknown | MagicMock`
- Found 181 diagnostics
+ Found 177 diagnostics

cki-lib (https://gitlab.com/cki-project/cki-lib)
- error[lint:invalid-assignment] tests/test_messagequeue.py:238:9: Object of type `Mock` is not assignable to attribute `_connect_no_keepalive` of type `(...) -> Unknown`
- error[lint:invalid-assignment] tests/test_messagequeue.py:239:9: Object of type `Mock` is not assignable to attribute `_connect_and_keepalive` of type `(...) -> Unknown`
- Found 174 diagnostics
+ Found 172 diagnostics

ignite (https://github.com/pytorch/ignite)
- error[lint:invalid-argument-type] tests/ignite/contrib/engines/test_tbptt.py:61:68: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/contrib/engines/test_tbptt.py:63:70: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_create_supervised.py:374:77: Argument to this function is incorrect: Expected `(Any, Any, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_create_supervised.py:410:73: Argument to this function is incorrect: Expected `(Any, Any, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_custom_events.py:89:55: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_custom_events.py:126:72: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:79:25: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:93:25: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:107:25: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:117:25: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:139:25: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:197:25: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:306:25: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:405:25: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:409:60: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Mock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:412:62: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Mock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:431:25: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_engine.py:848:52: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:32:21: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:180:43: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:181:47: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:182:45: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:183:45: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:184:41: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:185:45: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:186:41: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:187:41: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:193:47: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:197:45: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:198:45: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:199:41: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:341:48: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:349:41: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:350:37: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:351:37: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:352:41: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:375:54: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:378:33: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:383:33: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:443:21: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:451:21: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:468:21: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/engine/test_event_handlers.py:478:21: Argument to this function is incorrect: Expected `(Engine, Any, /) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/handlers/test_base_logger.py:238:28: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/handlers/test_base_logger.py:262:32: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/handlers/test_base_logger.py:268:32: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/handlers/test_base_logger.py:289:32: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- error[lint:invalid-argument-type] tests/ignite/handlers/test_base_logger.py:338:32: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `MagicMock`
- Found 2461 diagnostics
+ Found 2413 diagnostics

comtypes (https://github.com/enthought/comtypes)
- error[lint:invalid-argument-type] comtypes/test/test_inout_args.py:38:33: Argument to this function is incorrect: Expected `(...) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] comtypes/test/test_inout_args.py:83:33: Argument to this function is incorrect: Expected `(...) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] comtypes/test/test_inout_args.py:111:33: Argument to this function is incorrect: Expected `(...) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] comtypes/test/test_inout_args.py:138:33: Argument to this function is incorrect: Expected `(...) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] comtypes/test/test_inout_args.py:283:41: Argument to this function is incorrect: Expected `(...) -> Any`, found `MagicMock`
- error[lint:invalid-argument-type] comtypes/test/test_inout_args.py:491:33: Argument to this function is incorrect: Expected `(...) -> Any`, found `MagicMock`
- Found 705 diagnostics
+ Found 699 diagnostics

optuna (https://github.com/optuna/optuna)
- error[lint:invalid-argument-type] tests/samplers_tests/test_nsgaii.py:663:54: Argument to this function is incorrect: Expected `((...) -> @Todo(specialized non-generic class)) | None`, found `MagicMock`
- Found 2136 diagnostics
+ Found 2135 diagnostics

vision (https://github.com/pytorch/vision)
- error[lint:invalid-argument-type] gallery/transforms/plot_cutmix_mixup.py:44:56: Argument to this function is incorrect: Expected `((...) -> Unknown) | None`, found `Compose`
- Found 2251 diagnostics
+ Found 2250 diagnostics

dd-trace-py (https://github.com/DataDog/dd-trace-py)
- error[lint:invalid-argument-type] tests/appsec/iast/secure_marks/test_sanitizers.py:33:39: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Mock`
- error[lint:invalid-argument-type] tests/appsec/iast/secure_marks/test_sanitizers.py:58:29: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Mock`
- error[lint:invalid-argument-type] tests/appsec/iast/secure_marks/test_sanitizers.py:82:29: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Mock`
- error[lint:invalid-argument-type] tests/appsec/iast/secure_marks/test_validators.py:29:30: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Mock`
- error[lint:invalid-argument-type] tests/appsec/iast/secure_marks/test_validators.py:53:20: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Mock`
- error[lint:invalid-argument-type] tests/appsec/iast/secure_marks/test_validators.py:77:20: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Mock`
- error[lint:invalid-argument-type] tests/appsec/iast/taint_sinks/test_command_injection.py:233:29: Argument to this function is incorrect: Expected `(...) -> Unknown`, found `Mock`
- Found 6776 diagnostics
+ Found 6769 diagnostics

@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label Apr 29, 2025
@AlexWaygood AlexWaygood removed their request for review April 29, 2025 17:37
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.

Thanks for the PR!

As @sharkdp mentioned on the issue, there should be some exceptions here. One of those exceptions (subclass of Any can't be a subclass of a final class) we already have a test for, which is causing the tests to fail on this PR. I think all literal types should also be excluded (an instance of an Any subclass can't possibly inhabit Literal[1] or any other literal type).

I am not 100% sure that a separate catchall check at the top of the function will still be the clearest implementation here, once we account for all the exceptions. It depends on the balance of how many types we have to carve out of this catchall, vs how many we'd have to add a dedicated check for inheritance-from-Any to.

And yes, Python code samples should be added to the mdtests in this PR demonstrating the cases we want to handle that were not previously handled.

@Kalmaegi Kalmaegi marked this pull request as draft April 30, 2025 08:18
@Kalmaegi Kalmaegi marked this pull request as ready for review April 30, 2025 10:51
@Kalmaegi
Copy link
Contributor Author

Thanks for the PR!

As @sharkdp mentioned on the issue, there should be some exceptions here. One of those exceptions (subclass of Any can't be a subclass of a final class) we already have a test for, which is causing the tests to fail on this PR. I think all literal types should also be excluded (an instance of an Any subclass can't possibly inhabit Literal[1] or any other literal type).

I am not 100% sure that a separate catchall check at the top of the function will still be the clearest implementation here, once we account for all the exceptions. It depends on the balance of how many types we have to carve out of this catchall, vs how many we'd have to add a dedicated check for inheritance-from-Any to.

And yes, Python code samples should be added to the mdtests in this PR demonstrating the cases we want to handle that were not previously handled.

I checked for existing tests and found that assignments from Any to both Final and Literal are already covered. I just added a test for the Callable case.
I haven’t found other cases that need special handling for now—just these two. Since we’re checking self at the start of the method, I think this placement works reasonably well XD

@sharkdp
Copy link
Contributor

sharkdp commented Apr 30, 2025

I checked for existing tests and found that assignments from Any to both Final and Literal are already covered.

Where do we cover assignments to literals? It looks like it is possibly to assign to a literal on your branch. You will need a test that makes sure that instances of Subclass(Any) are not assignable to something like Literal[1]:

from typing import Any, Literal

class C(Any):
    pass

x: Literal[1] = C()  # this should fail, but does not on your branch.

As @carljm mentioned, it might be better to follow a "opt in" strategy, i.e. to handle special cases to the existing match statement. This way, we make sure that we exhaustively handle all type variants; and that we properly treat union and intersection types. For example:

from typing import final, Any

@final
class F1: ...

@final
class F2: ...

class Subclass(Any): ...

f: F1 | F2 = Subclass()  # this should fail, but does not on your branch.

@Kalmaegi
Copy link
Contributor Author

I checked for existing tests and found that assignments from Any to both Final and Literal are already covered.

Where do we cover assignments to literals? It looks like it is possibly to assign to a literal on your branch. You will need a test that makes sure that instances of Subclass(Any) are not assignable to something like Literal[1]:

from typing import Any, Literal

class C(Any):
    pass

x: Literal[1] = C()  # this should fail, but does not on your branch.

As @carljm mentioned, it might be better to follow a "opt in" strategy, i.e. to handle special cases to the existing match statement. This way, we make sure that we exhaustively handle all type variants; and that we properly treat union and intersection types. For example:

from typing import final, Any

@final
class F1: ...

@final
class F2: ...

class Subclass(Any): ...

f: F1 | F2 = Subclass()  # this should fail, but does not on your branch.

Ah, sorry about that—I misread the test. I’ll update the code accordingly.

@Kalmaegi Kalmaegi marked this pull request as draft April 30, 2025 15:00
@sharkdp sharkdp force-pushed the fix-any-subclass-callable branch from af5630d to 46d6758 Compare May 1, 2025 08:09
@sharkdp sharkdp marked this pull request as ready for review May 1, 2025 08:09
@sharkdp sharkdp force-pushed the fix-any-subclass-callable branch from 46d6758 to 7fa5767 Compare May 1, 2025 08:09
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.

@Kalmaegi I didn't really see any evidence in the newly added tests here that we need the catch-all case. In fact, there were some new ecosystem false-positives that looked incorrect (see below). It might be that we're still missing some cases where we should allow assignability, but until then, I'd rather be conservative and only allow assignability to Callable specifically. I changed the code to reflect this, hope that's fine for you. These false positives are now gone after the update:

pytest-robotframework (https://github.com/detachhead/pytest-robotframework)
+ error[lint:invalid-argument-type] pytest_robotframework/_internal/pytest/plugin.py:374:26: Argument to this function is incorrect: Expected `PytestRuntestProtocolHooks`, found `PytestRuntestProtocolHooks`
- Found 322 diagnostics
+ Found 323 diagnostics

mypy-protobuf (https://github.com/dropbox/mypy-protobuf)
+ error[lint:invalid-argument-type] mypy_protobuf/main.py:134:64: Argument to this function is incorrect: Expected `FileDescriptorProto`, found `FileDescriptorProto`
+ error[lint:invalid-argument-type] mypy_protobuf/main.py:135:59: Argument to this function is incorrect: Expected `FileDescriptorProto`, found `FileDescriptorProto`
+ error[lint:invalid-argument-type] mypy_protobuf/main.py:941:22: Argument to this function is incorrect: Expected `FieldDescriptorProto`, found `FieldDescriptorProto`

Thank you for working on this.

@sharkdp sharkdp changed the title [red-knot] Allow subclasses of Any to be assignable to all types [red-knot] Allow subclasses of Any to be assignable to Callable types May 1, 2025
@sharkdp sharkdp merged commit 76ec64d into astral-sh:main May 1, 2025
35 checks passed
@Kalmaegi
Copy link
Contributor Author

Kalmaegi commented May 1, 2025

@Kalmaegi I didn't really see any evidence in the newly added tests here that we need the catch-all case. In fact, there were some new ecosystem false-positives that looked incorrect (see below). It might be that we're still missing some cases where we should allow assignability, but until then, I'd rather be conservative and only allow assignability to Callable specifically. I changed the code to reflect this, hope that's fine for you. These false positives are now gone after the update:

pytest-robotframework (https://github.com/detachhead/pytest-robotframework)
+ error[lint:invalid-argument-type] pytest_robotframework/_internal/pytest/plugin.py:374:26: Argument to this function is incorrect: Expected `PytestRuntestProtocolHooks`, found `PytestRuntestProtocolHooks`
- Found 322 diagnostics
+ Found 323 diagnostics

mypy-protobuf (https://github.com/dropbox/mypy-protobuf)
+ error[lint:invalid-argument-type] mypy_protobuf/main.py:134:64: Argument to this function is incorrect: Expected `FileDescriptorProto`, found `FileDescriptorProto`
+ error[lint:invalid-argument-type] mypy_protobuf/main.py:135:59: Argument to this function is incorrect: Expected `FileDescriptorProto`, found `FileDescriptorProto`
+ error[lint:invalid-argument-type] mypy_protobuf/main.py:941:22: Argument to this function is incorrect: Expected `FieldDescriptorProto`, found `FieldDescriptorProto`

Thank you for working on this.

@sharkdp Thank you so much for your help. I'm still familiarizing myself with the project structure, so some of my ideas might be a bit rudimentary.

dcreager added a commit that referenced this pull request May 1, 2025
* main:
  Improve messages outputted by py-fuzzer (#17764)
  [`red-knot`] Allow subclasses of Any to be assignable to Callable types (#17717)
  [red-knot] Increase durability of read-only `File` fields (#17757)
  [red-knot] Cache source type during semanic index building (#17756)
  [`flake8-use-pathlib`] Fix `PTH116` false positive when `stat` is passed a file descriptor (#17709)
  Sync vendored typeshed stubs (#17753)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ty Multi-file analysis & type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[red-knot] Subclasses of Any should be assignable to Callable
4 participants