Skip to content

Make Any a proper class instead of an alias to object() #13520

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 6 commits into from
Mar 21, 2025

Conversation

grievejia
Copy link
Contributor

At runtime, typing.Any is an actual class, not an object. Currently typeshed says Any = object() which seems wrong given it's not valid Python to use object() as type annotations, e.g.

def test0() -> object(): ...  # ERROR! `object()` is not a valid type

MyAny = object()
def test1() -> MyAny:  # ERROR! `MyAny` is not a valid type

from typing import Any
def test2() -> Any: ...  # This should be OK, but it's not if we strictly follow the `Any = object()` alias

I'm guessing that existing type checkers probably had some special cases for typing.Any to make it work. But if we define it right we could probably get rid of the special cases.

We ran into this issue when working on Pyre2.

This comment has been minimized.

@JelleZijlstra
Copy link
Member

This is technically more accurate but as you can see it interacts poorly with the existing special casing in some type checkers. Any is a type system primitive and needs to be special-cased in type checkers anyway; it might make more practical sense to stick with the existing definition.

@hauntsaninja
Copy link
Collaborator

From a mypy perspective, based on primer this is actually probably an improvement (at least on 3.11+, and maybe depending on what you think TypeForm semantics should be).

pyright seems pretty unhappy though.

This comment has been minimized.

@srittau
Copy link
Collaborator

srittau commented Mar 18, 2025

Stubtest errors are unrelated.

This comment has been minimized.

This comment has been minimized.

@grievejia grievejia marked this pull request as ready for review March 18, 2025 21:48
@grievejia
Copy link
Contributor Author

Pyright has been updated and now it's OK with the change.

Copy link
Collaborator

@srittau srittau left a comment

Choose a reason for hiding this comment

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

Seems good to me.

@AlexWaygood
Copy link
Member

For red-knot, I'd honestly prefer for us to be forced to special-case Any explicitly wherever we need to. While it is technically a class at runtime, it behaves differently from every other class in the type system from a type checker's perspective. I'm worried that if it becomes a class in the stubs, it will be easy for us to forget to manually add in some special casing for it somewhere in red-knot where we need to.

We've already implemented the necessary special-casing in red-knot that allows users to inherit from Any even though it's an instance of object in the stubs, so I don't think this change would offer any advantages to red-knot.

@AlexWaygood
Copy link
Member

(Disclaimer: I'm obviously just one developer working on red-knot; I can't claim to speak for everybody on the red-knot team!)

@srittau
Copy link
Collaborator

srittau commented Mar 19, 2025

I prefer annotations to follow implementation if possible. Since this needs to be special-cased by type checkers anyway, this shouldn't be a problem for them. But nowadays type annotations are not only used by type checkers.

@AlexWaygood AlexWaygood reopened this Mar 19, 2025

This comment has been minimized.

@AlexWaygood
Copy link
Member

At runtime, typing.Any is an actual class, not an object.

Note that this is only true on Python 3.11+, but typeshed supports Python 3.8+. It was changed in python/cpython@5a4973e.

I'm still not enthusiastic about this PR, as I don't see it as bringing significant practical benefits to users. But having discussed it with other people on the red-knot team, I no longer want to block it. We'll be able to work around it without too much difficulty in red-knot, and it's true that it is more accurate to the runtime implementation on Python 3.11+. I'm happy for other maintainers to land it.

Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

sphinx (https://github.com/sphinx-doc/sphinx)
+ sphinx/config.py:631: error: Unused "type: ignore" comment  [unused-ignore]

ibis (https://github.com/ibis-project/ibis)
- ibis/expr/operations/__init__.py:13: error: Incompatible import of "Any" (imported name has type "type[ibis.expr.operations.reductions.Any]", local name has type "<typing special form>")  [assignment]
+ ibis/expr/operations/__init__.py:13: error: Incompatible import of "Any" (imported name has type "type[ibis.expr.operations.reductions.Any]", local name has type "type[typing.Any]")  [assignment]
- ibis/expr/types/logical.py:333: error: "<typing special form>" not callable  [operator]
+ ibis/expr/types/logical.py:333: error: Too many arguments for "Any"  [call-arg]
+ ibis/expr/types/logical.py:333: error: Unexpected keyword argument "where" for "Any"  [call-arg]
+ note: "Any" defined here
+ ibis/expr/types/logical.py:340: error: "Any" has no attribute "to_expr"  [attr-defined]
- ibis/backends/polars/compiler.py:745: error: No overload variant of "register" of "_SingleDispatchCallable" matches argument types "object", "Callable[[Any, KwArg(Any)], Any]"  [call-overload]
- ibis/backends/polars/compiler.py:745: note: Possible overload variants:
- ibis/backends/polars/compiler.py:745: note:     def register(self, cls: type[Any] | UnionType, func: None = ...) -> Callable[[Callable[..., Any]], Callable[..., Any]]
- ibis/backends/polars/compiler.py:745: note:     def register(self, cls: Callable[..., Any], func: None = ...) -> Callable[..., Any]
- ibis/backends/polars/compiler.py:745: note:     def register(self, cls: type[Any] | UnionType, func: Callable[..., Any]) -> Callable[..., Any]

prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/server/events/actions.py:101: error: Unsupported left operand type for | ("<typing special form>")  [operator]
+ src/prefect/server/events/actions.py:101: error: Unsupported left operand type for | ("type[Any]")  [operator]

artigraph (https://github.com/artigraph/artigraph)
- src/arti/internal/type_hints.py:54: error: Non-overlapping identity check (left operand type: "type", right operand type: "<typing special form>")  [comparison-overlap]
- src/arti/internal/type_hints.py:55: error: Non-overlapping identity check (left operand type: "type", right operand type: "<typing special form>")  [comparison-overlap]

hydra-zen (https://github.com/mit-ll-responsible-ai/hydra-zen)
- src/hydra_zen/structured_configs/_implementations.py:3252: error: List comprehension has incompatible type List[tuple[str, Any | <typing special form>, Any | Field[Any]]]; expected List[tuple[str, type] | tuple[str, type, Any]]  [misc]

steam.py (https://github.com/Gobot1234/steam.py)
- steam/ext/commands/converters.py:510: error: Incompatible types in assignment (expression has type "<typing special form>", variable has type "type[T]")  [assignment]
+ steam/ext/commands/converters.py:510: error: Incompatible types in assignment (expression has type "type[Any]", variable has type "type[T]")  [assignment]

mypy (https://github.com/python/mypy)
+ mypy/stubgenc.py:768: error: Unused "type: ignore" comment  [unused-ignore]

Copy link
Collaborator

@rchen152 rchen152 left a comment

Choose a reason for hiding this comment

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

I'm in favor of this change. It better reflects the runtime, which I think is a good goal to strive for in general (although it obviously needs to be balanced against other considerations).

@srittau srittau merged commit 693b66a into python:main Mar 21, 2025
55 checks passed
@grievejia grievejia deleted the fix_any branch March 24, 2025 18:18
carljm added a commit to astral-sh/ruff that referenced this pull request Apr 1, 2025
## Summary

In python/typeshed#13520 the typeshed definition
of `typing.Any` was changed from `Any = object()` to `class Any: ...`.
Our automated typeshed updater pulled down this change in
#17106, with the consequence that
we no longer understand `Any`, which is... not good.

This PR gives us the ability to understand `Any` defined as a class
instead of `object()`. It doesn't remove our ability to understand the
old form. Perhaps at some point we'll want to remove it, but for now we
may as well support both old and new typeshed?

This also directly patches typeshed to use the new form of `Any`; this
is purely to work around our tests that no known class is inferred as
`Unknown`, which otherwise fail with the old typeshed and the changes in
this PR. (All other tests pass.) This patch to typeshed will shortly be
subsumed by #17106 anyway.

## Test Plan

Without the typeshed change in this PR, all tests pass except for the
two `known_class_doesnt_fallback_to_unknown_unexpectedly_*` tests (so we
still support the old form of defining `Any`). With the typeshed change
in this PR, all tests pass, so we now support the new form in a way that
is indistinguishable to our test suite from the old form. And
indistinguishable to the ecosystem check: after rebasing
#17106 on this PR, there's zero
ecosystem impact.
@cdce8p cdce8p mentioned this pull request Apr 4, 2025
mmingyu pushed a commit to mmingyu/typeshed that referenced this pull request May 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants