Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
forked frompython/mypy

Commit00c0ef7

Browse files
HnasarTinche
andcommitted
Fix attrs hashability detection
This commit fixes a couple regressions in 1.9.0 from91be285.Attrs' logic for hashability is slightly complex:*https://www.attrs.org/en/stable/hashing.html*https://github.com/python-attrs/attrs/blob/9e443b18527dc96b194e92805fa751cbf8434ba9/src/attr/_make.py#L1660-L1689Mypy now properly emulates attrs' logic so that custom `__hash__`implementations are preserved, `@frozen` subclasses are always hashable,and classes are only made unhashable based on the values of eq and`unsafe_hash`.Fixespython#17015Fixespython#16556 (comment)Based on a patch inpython#17012Co-Authored-By: Tin Tvrtkovic <tinchester@gmail.com>
1 parentea49e1f commit00c0ef7

File tree

4 files changed

+117
-14
lines changed

4 files changed

+117
-14
lines changed

‎mypy/plugins/attrs.py‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,6 @@ def attr_class_maker_callback(
325325
frozen=_get_frozen(ctx,frozen_default)
326326
order=_determine_eq_order(ctx)
327327
slots=_get_decorator_bool_argument(ctx,"slots",slots_default)
328-
hashable=_get_decorator_bool_argument(ctx,"hash",False)or_get_decorator_bool_argument(
329-
ctx,"unsafe_hash",False
330-
)
331328

332329
auto_attribs=_get_decorator_optional_bool_argument(ctx,"auto_attribs",auto_attribs_default)
333330
kw_only=_get_decorator_bool_argument(ctx,"kw_only",False)
@@ -371,7 +368,25 @@ def attr_class_maker_callback(
371368
_add_order(ctx,adder)
372369
iffrozen:
373370
_make_frozen(ctx,attributes)
374-
elifnothashable:
371+
# Frozen classes are hashable by default, even if inheriting from non-frozen ones.
372+
hash=_get_decorator_bool_argument(
373+
ctx,"hash",True
374+
)and_get_decorator_bool_argument(ctx,"unsafe_hash",True)
375+
else:
376+
hash=_get_decorator_optional_bool_argument(ctx,"unsafe_hash")
377+
ifhashisNone:
378+
hash=_get_decorator_optional_bool_argument(ctx,"hash")
379+
380+
eq=_get_decorator_optional_bool_argument(ctx,"eq")
381+
has_own_hash="__hash__"inctx.cls.info.names
382+
383+
do_nothing=has_own_hashor (hashisNoneandeqisFalse)
384+
ifdo_nothing:
385+
pass
386+
elifhash:
387+
# We copy the `__hash__` signature from `object` to make them hashable.
388+
ctx.cls.info.names["__hash__"]=ctx.cls.info.mro[-1].names["__hash__"]
389+
else:
375390
_remove_hashability(ctx)
376391

377392
returnTrue

‎test-data/unit/check-incremental.test‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3015,7 +3015,7 @@ class NoInit:
30153015
class NoCmp:
30163016
x: int
30173017

3018-
[builtins fixtures/list.pyi]
3018+
[builtins fixtures/plugin_attrs.pyi]
30193019
[rechecked]
30203020
[stale]
30213021
[out1]

‎test-data/unit/check-plugin-attrs.test‎

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,8 @@ class A:
360360

361361
a = A(5)
362362
a.a = 16 # E: Property "a" defined in "A" is read-only
363-
[builtins fixtures/bool.pyi]
363+
[builtins fixtures/plugin_attrs.pyi]
364+
364365
[case testAttrsNextGenFrozen]
365366
from attr import frozen, field
366367

@@ -370,7 +371,7 @@ class A:
370371

371372
a = A(5)
372373
a.a = 16 # E: Property "a" defined in "A" is read-only
373-
[builtins fixtures/bool.pyi]
374+
[builtins fixtures/plugin_attrs.pyi]
374375

375376
[case testAttrsNextGenDetect]
376377
from attr import define, field
@@ -420,7 +421,7 @@ reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.bool) -
420421
reveal_type(B) # N: Revealed type is "def (a: builtins.bool, b: builtins.int) -> __main__.B"
421422
reveal_type(C) # N: Revealed type is "def (a: builtins.int) -> __main__.C"
422423

423-
[builtins fixtures/bool.pyi]
424+
[builtins fixtures/plugin_attrs.pyi]
424425

425426
[case testAttrsDataClass]
426427
import attr
@@ -1155,7 +1156,7 @@ c = NonFrozenFrozen(1, 2)
11551156
c.a = 17 # E: Property "a" defined in "NonFrozenFrozen" is read-only
11561157
c.b = 17 # E: Property "b" defined in "NonFrozenFrozen" is read-only
11571158

1158-
[builtins fixtures/bool.pyi]
1159+
[builtins fixtures/plugin_attrs.pyi]
11591160
[case testAttrsCallableAttributes]
11601161
from typing import Callable
11611162
import attr
@@ -1178,7 +1179,7 @@ class G:
11781179
class FFrozen(F):
11791180
def bar(self) -> bool:
11801181
return self._cb(5, 6)
1181-
[builtins fixtures/callable.pyi]
1182+
[builtins fixtures/plugin_attrs.pyi]
11821183

11831184
[case testAttrsWithFactory]
11841185
from typing import List
@@ -1450,7 +1451,7 @@ class C:
14501451
total = attr.ib(type=Bad) # E: Name "Bad" is not defined
14511452

14521453
C(0).total = 1 # E: Property "total" defined in "C" is read-only
1453-
[builtins fixtures/bool.pyi]
1454+
[builtins fixtures/plugin_attrs.pyi]
14541455

14551456
[case testTypeInAttrDeferredStar]
14561457
import lib
@@ -1941,7 +1942,7 @@ class C:
19411942
default=None, converter=default_if_none(factory=dict) \
19421943
# E: Unsupported converter, only named functions, types and lambdas are currently supported
19431944
)
1944-
[builtins fixtures/dict.pyi]
1945+
[builtins fixtures/plugin_attrs.pyi]
19451946

19461947
[case testAttrsUnannotatedConverter]
19471948
import attr
@@ -2012,7 +2013,7 @@ class Sub(Base):
20122013

20132014
@property
20142015
def name(self) -> str: ...
2015-
[builtins fixtures/property.pyi]
2016+
[builtins fixtures/plugin_attrs.pyi]
20162017

20172018
[case testOverrideWithPropertyInFrozenClassChecked]
20182019
from attrs import frozen
@@ -2035,7 +2036,7 @@ class Sub(Base):
20352036

20362037
# This matches runtime semantics
20372038
reveal_type(Sub) # N: Revealed type is "def (*, name: builtins.str, first_name: builtins.str, last_name: builtins.str) -> __main__.Sub"
2038-
[builtins fixtures/property.pyi]
2039+
[builtins fixtures/plugin_attrs.pyi]
20392040

20402041
[case testFinalInstanceAttribute]
20412042
from attrs import define
@@ -2342,6 +2343,12 @@ class A:
23422343

23432344
reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int"
23442345

2346+
@frozen(hash=False)
2347+
class B:
2348+
b: int
2349+
2350+
reveal_type(B.__hash__) # N: Revealed type is "None"
2351+
23452352
[builtins fixtures/plugin_attrs.pyi]
23462353

23472354
[case testManualHashHashability]
@@ -2380,3 +2387,82 @@ class B(A):
23802387
reveal_type(B.__hash__) # N: Revealed type is "None"
23812388

23822389
[builtins fixtures/plugin_attrs.pyi]
2390+
2391+
[case testManualOwnHashability]
2392+
from attrs import define, frozen
2393+
2394+
@define
2395+
class A:
2396+
a: int
2397+
def __hash__(self) -> int:
2398+
...
2399+
2400+
reveal_type(A.__hash__) # N: Revealed type is "def (self: __main__.A) -> builtins.int"
2401+
2402+
[builtins fixtures/plugin_attrs.pyi]
2403+
2404+
[case testSubclassDefaultLosesHashability]
2405+
from attrs import define, frozen
2406+
2407+
@define
2408+
class A:
2409+
a: int
2410+
def __hash__(self) -> int:
2411+
...
2412+
2413+
@define
2414+
class B(A):
2415+
pass
2416+
2417+
reveal_type(B.__hash__) # N: Revealed type is "None"
2418+
2419+
[builtins fixtures/plugin_attrs.pyi]
2420+
2421+
[case testSubclassEqFalseKeepsHashability]
2422+
from attrs import define, frozen
2423+
2424+
@define
2425+
class A:
2426+
a: int
2427+
def __hash__(self) -> int:
2428+
...
2429+
2430+
@define(eq=False)
2431+
class B(A):
2432+
pass
2433+
2434+
reveal_type(B.__hash__) # N: Revealed type is "def (self: __main__.A) -> builtins.int"
2435+
2436+
[builtins fixtures/plugin_attrs.pyi]
2437+
2438+
[case testSubclassingFrozenHashability]
2439+
from attrs import define, frozen
2440+
2441+
@define
2442+
class A:
2443+
a: int
2444+
2445+
@frozen
2446+
class B(A):
2447+
pass
2448+
2449+
reveal_type(B.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int"
2450+
2451+
[builtins fixtures/plugin_attrs.pyi]
2452+
2453+
[case testSubclassingFrozenHashOffHashability]
2454+
from attrs import define, frozen
2455+
2456+
@define
2457+
class A:
2458+
a: int
2459+
def __hash__(self) -> int:
2460+
...
2461+
2462+
@frozen(unsafe_hash=False)
2463+
class B(A):
2464+
pass
2465+
2466+
reveal_type(B.__hash__) # N: Revealed type is "None"
2467+
2468+
[builtins fixtures/plugin_attrs.pyi]

‎test-data/unit/fixtures/plugin_attrs.pyi‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ class tuple(Sequence[Tco], Generic[Tco]):
3535
def__iter__(self)->Iterator[Tco]:pass
3636
def__contains__(self,item:object)->bool:pass
3737
def__getitem__(self,x:int)->Tco:pass
38+
39+
property=object()# Dummy definition

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp