Skip to content
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

mypy infers attrs.fields(type(attrs_instance)) as Any #1297

Closed
injust opened this issue Jun 22, 2024 · 3 comments
Closed

mypy infers attrs.fields(type(attrs_instance)) as Any #1297

injust opened this issue Jun 22, 2024 · 3 comments

Comments

@injust
Copy link

injust commented Jun 22, 2024

import attrs
from attrs import define


@define
class Foo:
    bar: int


foo = Foo(1)

mypy infers attrs.fields(type(foo)) as Any

fields = attrs.fields(type(foo))
reveal_type(fields)
reveal_type(fields.bar)
reveal_type(fields.not_bar)

"""
demo.py:13: note: Revealed type is "Any"
demo.py:14: note: Revealed type is "Any"
demo.py:15: note: Revealed type is "Any"
Success: no issues found in 1 source file
"""

But mypy infers attrs.fields(Foo) just fine

other_fields = attrs.fields(Foo)
reveal_type(other_fields)
reveal_type(other_fields.bar)
reveal_type(other_fields.not_bar)

"""
demo.py:13: note: Revealed type is "tuple[attr.Attribute[builtins.int], fallback=demo.Foo.__demo_Foo_AttrsAttributes__]"
demo.py:14: note: Revealed type is "attr.Attribute[builtins.int]"
demo.py:15: error: "__demo_Foo_AttrsAttributes__" has no attribute "not_bar"  [attr-defined]
demo.py:15: note: Revealed type is "Any"
Found 1 error in 1 file (checked 1 source file)
"""

Is it possible to have mypy infer the type of attrs.fields(type(foo))?

@injust injust changed the title mypy infers attrs.fields(type(foo)) as Any mypy infers attrs.fields(type(attrs_instance)) as Any Jun 22, 2024
@Tinche
Copy link
Member

Tinche commented Jun 22, 2024

There's probably a difference between Foo and type(foo) - the latter can be any arbitrary subclass of Foo. I would reopen this over at the mypy tracker though; that's a better place for it.

Will close this here.

@Tinche Tinche closed this as not planned Won't fix, can't repro, duplicate, stale Jun 22, 2024
@finite-state-machine
Copy link

finite-state-machine commented Dec 19, 2024

@Tinche I was just about to file a report about pyright believing fields(type(……)) returns Any, when my search for any existing reports landed me here.

FWIW, as an end user of attrs encountering unexpected behaviour when type checking, it didn't occur to me that a type checker might effectively ignore the __init__.pyi stubs based on its own, built-in, attrs-specific plugin (as mypy apparently does). I inferred from the stubs that fields() always returns Any, and, after git blame helped me find #890, it wasn't at all clear to me why Any replaced class _Field(tuple[Attribute[Any], ...]): ....

This leads to two questions:

Q1: Why do the stubs record the return type of fields() as Any rather something more specific like _Fields, which was the case until #890 was merged? Pyright has no specific support for attrs, although they did draft PEP 681 (@dataclass_transform) as a non-library specific workaround. Since pyright views fields() no differently than any garden variety function, changing the return type of fields() to something more specific would be a considerable improvement for pyright users. (The pyright team are firmly unwilling to tailor that tool for specific, 3rd-party libraries, unless they've reconsidered.)

Q2: Is there value in systematically marking each place in the __init__.pyis where stubs are likely to be overruled by a type checker's built-in attrs support? This might take the form of a comment referencing a single, shared footnote; but I suspect using typing.Annotated to mark affected values, e.g., Annotated[<old_type>, TypeCheckerMayOverride] (where TypeCheckerMayOverride is a sentinel value of some sort), that might be more literate, especially where an editor is showing parsed type/function annotations as quick reference (e.g., pyright-python-langserver + vim-lsp). [Edit: if type checkers are guaranteed to only override with more restrictive types (e.g., narrower for a return value) it may be that line-by-line annotations aren't worthwhile.)

I'd consider putting in the legwork to add those annotations as a PR, but I may need some guidance depending on how literate the mypy+plugin code is.

I'm happy to file separate issues if you feel that's best.

@Tinche
Copy link
Member

Tinche commented Dec 20, 2024

Like you mentioned, the Pyright folks aren't amenable to bespoke attrs support. I don't think there's much point bending our type hints to accomodate non-Mypy use cases at this time. Pretty sure Pyright will accept fields(int) and stuff like that. I wouldn't want to give Pyright users a false sense of security here.

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

No branches or pull requests

3 participants