Skip to content

Commit

Permalink
Implemented a check for an attempt to invoke a static or class method…
Browse files Browse the repository at this point in the history
… that is marked abstract. This addresses python/mypy#14939.
  • Loading branch information
msfterictraut committed May 13, 2023
1 parent 773b619 commit c51bb4b
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 0 deletions.
24 changes: 24 additions & 0 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7521,6 +7521,30 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
addError(Localizer.Diagnostic.revealLocalsArgs(), node);
}
} else {
// Check for an attempt to invoke an abstract static or class method.
if (
isFunction(baseTypeResult.type) &&
baseTypeResult.type.boundToType &&
isInstantiableClass(baseTypeResult.type.boundToType) &&
!baseTypeResult.type.boundToType.includeSubclasses
) {
if (FunctionType.isAbstractMethod(baseTypeResult.type)) {
if (
FunctionType.isStaticMethod(baseTypeResult.type) ||
FunctionType.isClassMethod(baseTypeResult.type)
) {
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.abstractMethodInvocation().format({
method: baseTypeResult.type.details.name,
}),
node.leftExpression
);
}
}
}

const callResult = validateCallArguments(
node,
argList,
Expand Down
2 changes: 2 additions & 0 deletions packages/pyright-internal/src/localization/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ export function loadStringsForLocale(locale: string, localeMap: Map<string, any>

export namespace Localizer {
export namespace Diagnostic {
export const abstractMethodInvocation = () =>
new ParameterizedString<{ method: string }>(getRawString('Diagnostic.abstractMethodInvocation'));
export const annotatedParamCountMismatch = () =>
new ParameterizedString<{ expected: number; received: number }>(
getRawString('Diagnostic.annotatedParamCountMismatch')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"Diagnostic": {
"abstractMethodInvocation": "Method \"{method}\" cannot be called because it is abstract",
"annotatedParamCountMismatch": "Parameter annotation count mismatch: expected {expected} but received {received}",
"annotatedTypeArgMissing": "Expected one type argument and one or more annotations for \"Annotated\"",
"annotationFormatString": "Type annotations cannot use format string literals (f-strings)",
Expand Down
6 changes: 6 additions & 0 deletions packages/pyright-internal/src/tests/checker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ test('AbstractClass9', () => {
TestUtils.validateResults(analysisResults, 0);
});

test('AbstractClass10', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['abstractClass10.py']);

TestUtils.validateResults(analysisResults, 6);
});

test('Constants1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constants1.py']);

Expand Down
55 changes: 55 additions & 0 deletions packages/pyright-internal/src/tests/samples/abstractClass10.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# This sample tests the detection of static or class method invocations
# where the method is marked abstract.

from abc import ABC, abstractmethod


class A(ABC):
@staticmethod
@abstractmethod
def method1() -> None:
...

@classmethod
@abstractmethod
def method2(cls) -> None:
...


# This should generate an error.
A.method1()

# This should generate an error.
A.method2()


class B(A):
@staticmethod
def method1() -> None:
# This should generate an error.
return super().method1()

@classmethod
def method2(cls) -> None:
# This should generate an error.
return super().method2()


B.method1()
B.method2()


def func1(a: type[A]):
a.method1()
a.method2()


class C(A):
...


# This should generate an error.
C.method1()

# This should generate an error.
C.method2()

0 comments on commit c51bb4b

Please sign in to comment.