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

Commit31dc503

Browse files
HnasarTinche
andauthored
attrs: Fix emulating hash method logic (#17016)
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`.Fixes#17015Fixes#16556 (comment)Based on a patch in#17012Co-Authored-By: Tin Tvrtkovic <tinchester@gmail.com>Co-authored-by: Hashem Nasarat <hashem@hudson-trading.com>
1 parent1741c16 commit31dc503

File tree

4 files changed

+125
-14
lines changed

4 files changed

+125
-14
lines changed

‎mypy/plugins/attrs.py‎

Lines changed: 18 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,24 @@ 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+
hashable:bool|None=_get_decorator_bool_argument(
373+
ctx,"hash",True
374+
)and_get_decorator_bool_argument(ctx,"unsafe_hash",True)
375+
else:
376+
hashable=_get_decorator_optional_bool_argument(ctx,"unsafe_hash")
377+
ifhashableisNone:# unspecified
378+
hashable=_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+
ifhas_own_hashor (hashableisNoneandeqisFalse):
384+
pass# Do nothing.
385+
elifhashable:
386+
# We copy the `__hash__` signature from `object` to make them hashable.
387+
ctx.cls.info.names["__hash__"]=ctx.cls.info.mro[-1].names["__hash__"]
388+
else:
375389
_remove_hashability(ctx)
376390

377391
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: 104 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
@@ -2380,3 +2381,97 @@ class B(A):
23802381
reveal_type(B.__hash__) # N: Revealed type is "None"
23812382

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