From 89fd2859c69fd5d684dc54a951e0579cc7fef61a Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 2 May 2022 18:51:52 -0700 Subject: [PATCH] Enhanced the reportUninitializedInstanceVariable check to detect cases where a class variable is declared but not initialized in the class body and no instance variable of the same name is assigned in the `__init__` method. --- .../pyright-internal/src/analyzer/checker.ts | 17 ++++++++++++++++- .../pyright-internal/src/tests/checker.test.ts | 13 +++++++++++++ .../src/tests/samples/uninitializedVariable1.py | 13 +++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 packages/pyright-internal/src/tests/samples/uninitializedVariable1.py diff --git a/packages/pyright-internal/src/analyzer/checker.ts b/packages/pyright-internal/src/analyzer/checker.ts index b270d9018..8ef0b25c0 100644 --- a/packages/pyright-internal/src/analyzer/checker.ts +++ b/packages/pyright-internal/src/analyzer/checker.ts @@ -3743,10 +3743,25 @@ export class Checker extends ParseTreeWalker { if ( decls.find((decl) => { const containingClass = ParseTreeUtils.getEnclosingClassOrFunction(decl.node); - if (!containingClass || containingClass.nodeType === ParseNodeType.Class) { + if (!containingClass) { return true; } + if (containingClass.nodeType === ParseNodeType.Class) { + // If this is part of an assignment statement, assume it has been + // initialized as a class variable. + if (decl.node.parent?.nodeType === ParseNodeType.Assignment) { + return true; + } + + if ( + decl.node.parent?.nodeType === ParseNodeType.TypeAnnotation && + decl.node.parent.parent?.nodeType === ParseNodeType.Assignment + ) { + return true; + } + } + if (containingClass.name.value === '__init__') { return true; } diff --git a/packages/pyright-internal/src/tests/checker.test.ts b/packages/pyright-internal/src/tests/checker.test.ts index 79d174937..4ee5441e0 100644 --- a/packages/pyright-internal/src/tests/checker.test.ts +++ b/packages/pyright-internal/src/tests/checker.test.ts @@ -413,6 +413,19 @@ test('UnusedExpression1', () => { TestUtils.validateResults(analysisResults, 10); }); +test('UninitializedVariable1', () => { + const configOptions = new ConfigOptions('.'); + + // By default, this is off. + let analysisResults = TestUtils.typeAnalyzeSampleFiles(['uninitializedVariable1.py'], configOptions); + TestUtils.validateResults(analysisResults, 0); + + // Enable it as an error. + configOptions.diagnosticRuleSet.reportUninitializedInstanceVariable = 'error'; + analysisResults = TestUtils.typeAnalyzeSampleFiles(['uninitializedVariable1.py'], configOptions); + TestUtils.validateResults(analysisResults, 1); +}); + // For now, this functionality is disabled. // test('Deprecated1', () => { diff --git a/packages/pyright-internal/src/tests/samples/uninitializedVariable1.py b/packages/pyright-internal/src/tests/samples/uninitializedVariable1.py new file mode 100644 index 000000000..ef26681b4 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/uninitializedVariable1.py @@ -0,0 +1,13 @@ +# This sample tests the reportUninitializedInstanceVariable functionality. + +class A: + # This should generate an error if reportUninitializedInstanceVariable + # is enabled. + v1: int + v2: int + v3 = 2 + v4: int = 3 + + def __init__(self) -> None: + self.v2 = 3 + super().__init__()