From 426f294ec8ffc3fb42fe3eb55b3fa1280cf73cbe Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Thu, 23 Jan 2025 09:30:21 -0800 Subject: [PATCH] Added `@override` enforcement for an overloaded method that has no implementation. This addresses #9746. --- .../pyright-internal/src/analyzer/checker.ts | 8 +++ .../src/tests/samples/override1.py | 52 +++++++++++-------- .../src/tests/typeEvaluator5.test.ts | 2 +- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts index ef625e775f17..103ea3b86971 100644 --- a/packages/pyright-internal/src/analyzer/checker.ts +++ b/packages/pyright-internal/src/analyzer/checker.ts @@ -6562,6 +6562,14 @@ export class Checker extends ParseTreeWalker { if (impl && isFunction(impl)) { overrideFunction = impl; } + + // If there is no implementation present, use the first overload. + if (!impl) { + const overloads = OverloadedType.getOverloads(overrideType); + if (overloads.length > 0) { + overrideFunction = overloads[0]; + } + } } else if (isClassInstance(overrideType) && ClassType.isPropertyClass(overrideType)) { if (overrideType.priv.fgetInfo) { overrideFunction = overrideType.priv.fgetInfo.methodType; diff --git a/packages/pyright-internal/src/tests/samples/override1.py b/packages/pyright-internal/src/tests/samples/override1.py index e9366e97ffd0..99ab73d11a1f 100644 --- a/packages/pyright-internal/src/tests/samples/override1.py +++ b/packages/pyright-internal/src/tests/samples/override1.py @@ -1,7 +1,7 @@ # This sample tests the handling of the @override decorator as described # in PEP 698. -from typing import Callable +from typing import Callable, Protocol from typing_extensions import ( # pyright: ignore[reportMissingModuleSource] Any, overload, @@ -19,15 +19,12 @@ def method3(self) -> None: pass @overload - def method5(self, x: int) -> int: - ... + def method5(self, x: int) -> int: ... @overload - def method5(self, x: str) -> str: - ... + def method5(self, x: str) -> str: ... - def method5(self, x: int | str) -> int | str: - ... + def method5(self, x: int | str) -> int | str: ... class ClassC(ClassA, ClassB): @@ -56,34 +53,27 @@ def method4(self) -> None: pass @overload - def method5(self, x: int) -> int: - ... + def method5(self, x: int) -> int: ... @overload - def method5(self, x: str) -> str: - ... + def method5(self, x: str) -> str: ... @override - def method5(self, x: int | str) -> int | str: - ... + def method5(self, x: int | str) -> int | str: ... @overload - def method6(self, x: int) -> int: - ... + def method6(self, x: int) -> int: ... @overload - def method6(self, x: str) -> str: - ... + def method6(self, x: str) -> str: ... @override # This should generate an error because method6 does not # override anything in a base class. - def method6(self, x: int | str) -> int | str: - ... + def method6(self, x: int | str) -> int | str: ... -class ClassD(Any): - ... +class ClassD(Any): ... class ClassE(ClassD): @@ -109,3 +99,23 @@ class G(F): @evil_wrapper def method1(self): pass + + +class H(Protocol): + pass + + +class I(H, Protocol): + @override + # This should generate an error because method1 isn't present + # in the base. + def method1(self): + pass + + @overload + @override + # This should generate an error because method2 isn't present + # in the base. + def method2(self, x: int) -> int: ... + @overload + def method2(self, x: str) -> str: ... diff --git a/packages/pyright-internal/src/tests/typeEvaluator5.test.ts b/packages/pyright-internal/src/tests/typeEvaluator5.test.ts index 246a9e75fa36..ca8dce83fc8e 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator5.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator5.test.ts @@ -179,7 +179,7 @@ test('Hashability3', () => { test('Override1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['override1.py']); - TestUtils.validateResults(analysisResults, 3); + TestUtils.validateResults(analysisResults, 5); }); test('Override2', () => {