From f330161f68252271ea64a92c5ee21487aea0319f Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 11 Sep 2023 15:55:34 -0700 Subject: [PATCH] Fixed a bug that led to a false positive error when a frozen dataclass has an explicit `__eq__` method and is used in way that requires it to be `Hashable`. This addresses #5928. --- .../pyright-internal/src/analyzer/dataClasses.ts | 3 +-- .../pyright-internal/src/analyzer/typeEvaluator.ts | 5 ++++- .../pyright-internal/src/analyzer/typeGuards.ts | 14 ++++++++++++++ .../src/tests/samples/dataclassHash1.py | 11 +++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/pyright-internal/src/analyzer/dataClasses.ts b/packages/pyright-internal/src/analyzer/dataClasses.ts index bd5947e8d908..b9a0bfc3e9e8 100644 --- a/packages/pyright-internal/src/analyzer/dataClasses.ts +++ b/packages/pyright-internal/src/analyzer/dataClasses.ts @@ -623,8 +623,7 @@ export function synthesizeDataClassMethods( }); } - let synthesizeHashFunction = - !ClassType.isSkipSynthesizedDataClassEq(classType) && ClassType.isFrozenDataClass(classType); + let synthesizeHashFunction = ClassType.isFrozenDataClass(classType); const synthesizeHashNone = !ClassType.isSkipSynthesizedDataClassEq(classType) && !ClassType.isFrozenDataClass(classType); diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 33409f27a7f7..b4de3ded5484 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -16409,7 +16409,10 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions let skipSynthesizeHash = false; const hashSymbol = lookUpClassMember(classType, '__hash__', ClassMemberLookupFlags.SkipBaseClasses); - if (hashSymbol) { + + // If there is a hash symbol defined in the class (i.e. one that we didn't + // synthesize above), then we shouldn't synthesize a new one for the dataclass. + if (hashSymbol && !hashSymbol.symbol.getSynthesizedType()) { skipSynthesizeHash = true; } diff --git a/packages/pyright-internal/src/analyzer/typeGuards.ts b/packages/pyright-internal/src/analyzer/typeGuards.ts index 8685d3e9486d..0b2ac47498d7 100644 --- a/packages/pyright-internal/src/analyzer/typeGuards.ts +++ b/packages/pyright-internal/src/analyzer/typeGuards.ts @@ -2456,3 +2456,17 @@ function narrowTypeForCallable( } }); } + +export class Animal {} +export class Dog extends Animal {} + +export class Plant {} +export class Tree extends Plant {} + +export function func1(val: Animal) { + if (val instanceof Tree) { + console.log(val); + } else { + console.log(val); + } +} diff --git a/packages/pyright-internal/src/tests/samples/dataclassHash1.py b/packages/pyright-internal/src/tests/samples/dataclassHash1.py index 0ce0a11a3483..18d741f0815d 100644 --- a/packages/pyright-internal/src/tests/samples/dataclassHash1.py +++ b/packages/pyright-internal/src/tests/samples/dataclassHash1.py @@ -55,3 +55,14 @@ def __hash__(self) -> int: v6: Hashable = DC6(0) + + +@dataclass(frozen=True) +class DC7: + a: int + + def __eq__(self, other) -> bool: + return self.a == other.a + + +v7: Hashable = DC7(0)