Skip to content

Commit 206fff8

Browse files
committed
fixup! flag abstract base classes with no abstract methods
1 parent 48e9a0d commit 206fff8

9 files changed

Lines changed: 67 additions & 7 deletions

File tree

packages/pyright-internal/src/analyzer/typeEvaluator.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10796,8 +10796,9 @@ export function createTypeEvaluator(
1079610796
isInstantiableClass(expandedCallType.shared.declaredMetaclass) &&
1079710797
expandedCallType.shared.declaredMetaclass.shared.fullName === 'abc.ABCMeta';
1079810798

10799+
// Handle abstract classes with abstract methods (reportAbstractUsage)
1079910800
if (
10800-
(abstractSymbols.length > 0 || derivesDirectlyFromABC || hasABCMetaMetaclass) &&
10801+
abstractSymbols.length > 0 &&
1080110802
!expandedCallType.priv.includeSubclasses &&
1080210803
!isTypeVar(unexpandedCallType)
1080310804
) {
@@ -10833,6 +10834,20 @@ export function createTypeEvaluator(
1083310834
errorNode
1083410835
);
1083510836
}
10837+
// Handle abstract classes with no abstract methods (reportEmptyAbstractClass)
10838+
else if (
10839+
(derivesDirectlyFromABC || hasABCMetaMetaclass) &&
10840+
!expandedCallType.priv.includeSubclasses &&
10841+
!isTypeVar(unexpandedCallType)
10842+
) {
10843+
addDiagnostic(
10844+
DiagnosticRule.reportEmptyAbstractClass,
10845+
LocMessage.instantiateAbstract().format({
10846+
type: expandedCallType.shared.name,
10847+
}),
10848+
errorNode
10849+
);
10850+
}
1083610851
}
1083710852

1083810853
if (ClassType.isProtocolClass(expandedCallType) && !expandedCallType.priv.includeSubclasses) {

packages/pyright-internal/src/common/configOptions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ export interface DiagnosticRuleSet {
189189
// Report use of abstract method or variable?
190190
reportAbstractUsage: DiagnosticLevel;
191191

192+
// Report instantiation of abstract class with no abstract methods?
193+
reportEmptyAbstractClass: DiagnosticLevel;
194+
192195
// Report argument type incompatibilities?
193196
reportArgumentType: DiagnosticLevel;
194197

@@ -481,6 +484,7 @@ export function getDiagLevelDiagnosticRules() {
481484
DiagnosticRule.reportDuplicateImport,
482485
DiagnosticRule.reportWildcardImportFromLibrary,
483486
DiagnosticRule.reportAbstractUsage,
487+
DiagnosticRule.reportEmptyAbstractClass,
484488
DiagnosticRule.reportArgumentType,
485489
DiagnosticRule.reportAssertTypeFailure,
486490
DiagnosticRule.reportAssignmentType,
@@ -617,6 +621,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet {
617621
reportDuplicateImport: 'none',
618622
reportWildcardImportFromLibrary: 'none',
619623
reportAbstractUsage: 'none',
624+
reportEmptyAbstractClass: 'none',
620625
reportArgumentType: 'none',
621626
reportAssertTypeFailure: 'none',
622627
reportAssignmentType: 'none',
@@ -737,6 +742,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet {
737742
reportDuplicateImport: 'none',
738743
reportWildcardImportFromLibrary: 'warning',
739744
reportAbstractUsage: 'error',
745+
reportEmptyAbstractClass: 'error',
740746
reportArgumentType: 'error',
741747
reportAssertTypeFailure: 'error',
742748
reportAssignmentType: 'error',
@@ -857,6 +863,7 @@ export function getStandardDiagnosticRuleSet(): DiagnosticRuleSet {
857863
reportDuplicateImport: 'none',
858864
reportWildcardImportFromLibrary: 'warning',
859865
reportAbstractUsage: 'error',
866+
reportEmptyAbstractClass: 'error',
860867
reportArgumentType: 'error',
861868
reportAssertTypeFailure: 'error',
862869
reportAssignmentType: 'error',
@@ -976,6 +983,7 @@ export const getRecommendedDiagnosticRuleSet = (): DiagnosticRuleSet => ({
976983
reportDuplicateImport: 'warning',
977984
reportWildcardImportFromLibrary: 'warning',
978985
reportAbstractUsage: 'error',
986+
reportEmptyAbstractClass: 'error',
979987
reportArgumentType: 'error',
980988
reportAssertTypeFailure: 'error',
981989
reportAssignmentType: 'error',
@@ -1092,6 +1100,7 @@ export const getAllDiagnosticRuleSet = (): DiagnosticRuleSet => ({
10921100
reportDuplicateImport: 'error',
10931101
reportWildcardImportFromLibrary: 'error',
10941102
reportAbstractUsage: 'error',
1103+
reportEmptyAbstractClass: 'error',
10951104
reportArgumentType: 'error',
10961105
reportAssertTypeFailure: 'error',
10971106
reportAssignmentType: 'error',
@@ -1209,6 +1218,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet {
12091218
reportDuplicateImport: 'error',
12101219
reportWildcardImportFromLibrary: 'error',
12111220
reportAbstractUsage: 'error',
1221+
reportEmptyAbstractClass: 'error',
12121222
reportArgumentType: 'error',
12131223
reportAssertTypeFailure: 'error',
12141224
reportAssignmentType: 'error',

packages/pyright-internal/src/common/diagnosticRules.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export enum DiagnosticRule {
3838
reportDuplicateImport = 'reportDuplicateImport',
3939
reportWildcardImportFromLibrary = 'reportWildcardImportFromLibrary',
4040
reportAbstractUsage = 'reportAbstractUsage',
41+
reportEmptyAbstractClass = 'reportEmptyAbstractClass',
4142
reportArgumentType = 'reportArgumentType',
4243
reportAssertTypeFailure = 'reportAssertTypeFailure',
4344
reportAssignmentType = 'reportAssignmentType',

packages/pyright-internal/src/tests/checker.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,16 @@ test('AbstractClass11', () => {
131131
test('AbstractClass12', () => {
132132
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['abstractClass12.py']);
133133

134-
TestUtils.validateResultsButBased(analysisResults, {});
134+
TestUtils.validateResultsButBased(analysisResults, {
135+
// one error to validate code and message, the rest use `pyright: ignore`
136+
errors: [
137+
{
138+
line: 14,
139+
message: 'Cannot instantiate abstract class "AbstractFoo"',
140+
code: DiagnosticRule.reportEmptyAbstractClass,
141+
},
142+
],
143+
});
135144
});
136145

137146
test('Constants1', () => {

packages/pyright-internal/src/tests/samples/abstractClass12.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Foo(AbstractFoo):
1212

1313
# this should generate an error because `AbstractFoo`
1414
# is an abstract class even though it has no abstract methods
15-
a = AbstractFoo() # pyright: ignore[reportAbstractUsage]
15+
a = AbstractFoo()
1616

1717
# this should not generate an error because Foo is concrete
1818
b = Foo()
@@ -58,7 +58,7 @@ def static_method():
5858

5959

6060
# This should generate an error even though all methods are concrete
61-
h = AbstractWithMethods() # pyright: ignore[reportAbstractUsage]
61+
h = AbstractWithMethods() # pyright: ignore[reportEmptyAbstractClass]
6262

6363

6464
class AbstractWithMetaclass(metaclass=ABCMeta):
@@ -69,7 +69,7 @@ class ConcreteWithMetaclass(AbstractWithMetaclass):
6969

7070

7171
# this should generate an error because AbstractWithMetaclass uses ABCMeta
72-
i = AbstractWithMetaclass() # pyright: ignore[reportAbstractUsage]
72+
i = AbstractWithMetaclass() # pyright: ignore[reportEmptyAbstractClass]
7373

7474
# this should not generate an error
7575
j = ConcreteWithMetaclass()

packages/pyright-internal/src/tests/samples/async1.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ async def b():
99
yield i
1010

1111

12-
cm = AsyncExitStack()
12+
cm = AsyncExitStack() # pyright: ignore[reportEmptyAbstractClass] # typeshed moment
1313

1414

1515
def func1():

packages/pyright-internal/src/tests/samples/property2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def y(self) -> float:
2121
raise NotImplementedError
2222

2323

24-
a = Foo()
24+
a = Foo() # pyright: ignore[reportEmptyAbstractClass]
2525
requires_int(a.x)
2626

2727
a.x = 3

packages/vscode-pyright/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,23 @@
490490
false
491491
]
492492
},
493+
"reportEmptyAbstractClass": {
494+
"type": [
495+
"string",
496+
"boolean"
497+
],
498+
"description": "Diagnostics for an attempt to instantiate an abstract class that has no abstract members.",
499+
"default": "error",
500+
"enum": [
501+
"none",
502+
"hint",
503+
"information",
504+
"warning",
505+
"error",
506+
true,
507+
false
508+
]
509+
},
493510
"reportArgumentType": {
494511
"type": [
495512
"string",

packages/vscode-pyright/schemas/pyrightconfig.schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@
185185
"title": "Controls reporting of attempted instantiation of abstract class",
186186
"default": "error"
187187
},
188+
"reportEmptyAbstractClass": {
189+
"$ref": "#/definitions/diagnostic",
190+
"title": "Controls reporting of attempted instantiation of empty abstract class",
191+
"default": "error"
192+
},
188193
"reportArgumentType": {
189194
"$ref": "#/definitions/diagnostic",
190195
"title": "Controls reporting of incompatible argument type",
@@ -744,6 +749,9 @@
744749
"reportAbstractUsage": {
745750
"$ref": "#/definitions/reportAbstractUsage"
746751
},
752+
"reportEmptyAbstractClass": {
753+
"$ref": "#/definitions/reportEmptyAbstractClass"
754+
},
747755
"reportArgumentType": {
748756
"$ref": "#/definitions/reportArgumentType"
749757
},

0 commit comments

Comments
 (0)