Skip to content

Add signature for attr.evolve - #14526

Merged
JelleZijlstra merged 21 commits into
python:masterfrom
ikonst:2023-01-25-attrs-evolve
Mar 6, 2023
Merged

Add signature for attr.evolve#14526
JelleZijlstra merged 21 commits into
python:masterfrom
ikonst:2023-01-25-attrs-evolve

Conversation

@ikonst

@ikonstikonst commented Jan 26, 2023

Copy link
Copy Markdown
Contributor

Validate attr.evolve calls to specify correct arguments and types.

The implementation makes it so that at every point where attr.evolve is called, the signature is modified to expect the attrs class' initializer's arguments (but so that they're all kw-only and optional).

Notes:

Fixes#14525.

@ikonst
ikonstforce-pushed the 2023-01-25-attrs-evolve branch from e47698c to 042aa94CompareJanuary 26, 2023 04:18
@ikonst
ikonstforce-pushed the 2023-01-25-attrs-evolve branch from 042aa94 to cd832ccCompareJanuary 26, 2023 04:25
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment threadmypy/plugins/default.py Outdated
) -> Callable[[FunctionSigContext], FunctionLike] | None:
from mypy.plugins import attrs

if fullname in ("attr.evolve", "attr.assoc"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There’s also attrs.evolve() - maybe that’s just an alias and will work, but it should be tested. Perhaps these should be moved to a constant with the others at the start of the attrs plug-in module also?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yep, yep. All still WIP.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ready now!

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@ikonst

ikonst commented Jan 27, 2023

Copy link
Copy Markdown
ContributorAuthor

mypy-primer identified an error in home-assistant:

new=self.areas[area_id] =attr.evolve(old, **new_values)

Typing new_values as a typed dict with the appropriate fields can address that. The misleading error is mentioned in #14073.

@github-actions

This comment has been minimized.

@ikonst
ikonst marked this pull request as ready for review January 27, 2023 17:41
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment on lines +1812 to +1813
2: <b>, <b[wildcard]>, __main__
3: <b>, <b[wildcard]>, __main__, a

@ikonstikonstJan 27, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

So.... the addition of dict in fixtures/list.pyi now causes __annotations__ to exist:

mypy/mypy/semanal.py

Lines 636 to 639 in bac9e77

elifname=="__annotations__":
sym=self.lookup_qualified("__builtins__.dict", Context(), suppress_errors=True)
ifnotsym:
continue

which then makes __annotations__ one of the names in the diff (reasonably...):

ifitem.count(".") <=package_nesting_level+1anditem.split(".")[-1] notin (
"__builtins__",
"__file__",
"__name__",
"__package__",
"__doc__",
):
# Activate catch-all wildcard trigger for top-level module changes (used for
# "from m import *"). This also gets triggered by changes to module-private
# entries, but as these unneeded dependencies only result in extra processing,
# it's a minor problem.
#
# TODO: Some __* names cause mistriggers. Fix the underlying issue instead of
# special casing them here.
diff.add(id+WILDCARD_TAG)

I've created #14547 to discuss this more.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Addressed in #14550 and #14575.

@github-actions

This comment has been minimized.

@ikonst
ikonstforce-pushed the 2023-01-25-attrs-evolve branch from 7188141 to cf756d1CompareJanuary 27, 2023 21:31
@ikonst
ikonstforce-pushed the 2023-01-25-attrs-evolve branch from ab8d33b to babf91fCompareJanuary 30, 2023 16:51
@github-actions

This comment has been minimized.

@ikonst

Copy link
Copy Markdown
ContributorAuthor

@AlexWaygood can label with attrs? :)

@github-actions

This comment has been minimized.

@ikonst

Copy link
Copy Markdown
ContributorAuthor

@ikonst

Copy link
Copy Markdown
ContributorAuthor

@JelleZijlstra would love to get some eyes on this (I was waiting for #14575 to merge to reduce the diff)

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

@JelleZijlstra

Copy link
Copy Markdown
Member

I can take a look in the next few days.

This function is basically equivalent to dataclasses.replace, right? Would be nice if we can support that function in a similar way (in a separate PR).

@JelleZijlstra
JelleZijlstra self-requested a review March 3, 2023 18:13

@JelleZijlstraJelleZijlstra left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good, just one minor point.

Comment threadmypy/types.py
arg_types: Bogus[Sequence[Type]] = _dummy,
arg_kinds: Bogus[list[ArgKind]] = _dummy,
arg_names: Bogus[list[str | None]] = _dummy,
arg_names: Bogus[Sequence[str | None]] = _dummy,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is the reason for this change? I'd prefer to avoid touching the mypy core in this PR.

@ikonstikonstMar 6, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Because of list's invariance — otherwise, this fails since it passes list[str]:

arg_names=["inst"] +attrs_init_type.arg_names[1:],

CallableType.__init__ accepts arg_names: Sequence[str | None] but I guess CallableType.copy_modified was not updated in tandem.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Broke out into #14840 — can merge then update this branch.

@JelleZijlstra
JelleZijlstra merged commit bbc9cce into python:masterMar 6, 2023
Comment threadmypy/plugins/attrs.py
Comment on lines +927 to +930
# <hack>
assert isinstance(ctx.api, TypeChecker)
inst_type = ctx.api.expr_checker.accept(inst_arg)
# </hack>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@JelleZijlstra now that we got this merged 😅 what would be the right place to discuss whether this should be promoted to be formal plugin API?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Open an issue, I suppose.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

👉 #14845

@LukasKrocek

LukasKrocek commented Apr 6, 2023

Copy link
Copy Markdown

hey, I have tried to run this in my project, doen't work well when evolving generic classes

class A(Generic[T]):
field: T
a = A("1")
evolve(a, field="2") ```
# 2 errors are raised:
# Argument 1 to "evolve" of "A[str]" has incompatible type "A[str]"; expected "A[T]" [arg-type]
# Argument "field" to "evolve" of "A[str]" has incompatible type "int"; expected "T" [arg-type]

@ikonst

Copy link
Copy Markdown
ContributorAuthor

@LukasKrocek this was just mentioned in the review for #14849 — addressed in c005895, but here it would actually be simpler

@ikonst

Copy link
Copy Markdown
ContributorAuthor

Generics should be addressed in #15016.

@ikonstikonst mentioned this pull request Apr 7, 2023
ilevkivskyi pushed a commit that referenced this pull request Jun 17, 2023
Validate `dataclassses.replace` actual arguments to match the fields:
- Unlike `__init__`, the arguments are always named.
- All arguments are optional except for `InitVar`s without a default
value.
The tricks:
- We're looking up type of the first positional argument ("obj") through
private API. See #10216, #14845.
- We're preparing the signature of "replace" (for that specific
dataclass) during the dataclass transformation and storing it in a
"private" class attribute `__mypy-replace` (obviously not part of
PEP-557 but contains a hyphen so should not conflict with any future
valid identifier). Stashing the signature into the symbol table allows
it to be passed across phases and cached across invocations. The stashed
signature lacks the first argument, which we prepend at function
signature hook time, since it depends on the type that `replace` is
called on.
Based on #14526 but actually simpler.
Partially addresses #5152.
# Remaining tasks
- [x] handle generic dataclasses
- [x] avoid data class transforms
- [x] fine-grained mode tests
---------
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

attrs plugin should support attrs.evolve

5 participants

@ikonst@JelleZijlstra@LukasKrocek@TeamSpen210@AlexWaygood