Skip to content

Commit

Permalink
eliminate some false positives on reportUninitializedInstanceVariable
Browse files Browse the repository at this point in the history
  • Loading branch information
beauxq committed Jan 3, 2025
1 parent aba927d commit 435698e
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 0 deletions.
21 changes: 21 additions & 0 deletions packages/pyright-internal/src/analyzer/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5341,6 +5341,27 @@ export class Checker extends ParseTreeWalker {
return true;
}

// if containingClass (that initializes this symbol) is a method that is called in __init__
// then we consider this symbol initialized
if (containingClass.nodeType === ParseNodeType.Function) {
const initNode = ClassType.getSymbolTable(classType).get('__init__')?.getDeclarations().at(0)
?.node;
if (initNode && initNode.nodeType === ParseNodeType.Function) {
return initNode.d.suite.d.statements.some((maybeStatementList) => {
if (maybeStatementList.nodeType === ParseNodeType.StatementList) {
return maybeStatementList.d.statements.some((maybeCall) => {
return (
maybeCall.nodeType === ParseNodeType.Call &&
maybeCall.d.leftExpr.nodeType === ParseNodeType.MemberAccess &&
maybeCall.d.leftExpr.d.member.d.value === containingClass.d.name.d.value
);
});
}
return false;
});
}
}

return false;
})
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from typing import final

# The objective of this test is to try a bunch of ways
# to trick the checker into thinking C.x is initialized
# when it's not initialized.


class C:
x: int # should be reported as uninitialized

def __init__(self) -> None:
self.a_method_called_by_init()

def foo() -> None: # pyright: ignore[reportUnusedFunction]
self.x = 3

def a_method_not_called_by_init(self) -> None:
self.x = 3

def a_method_called_by_init(self) -> None:
# reference x before initializing it
self.x += 3


def __init__() -> None:
c = C()
c.x = 3


class D:
def __init__(self) -> None:
c = C()
c.x = 3


@final
class E(C):
def __init__(self) -> None:
super().__init__()
self.x = 3


class F(C):
def __init__(self) -> None:
super().__init__()
self.x = 3 # pyright: ignore[reportUnannotatedClassAttribute]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class C:
x: int # should not be reported as uninitialized

def __init__(self) -> None:
self.reset()

def reset(self) -> None:
self.x = 3
17 changes: 17 additions & 0 deletions packages/pyright-internal/src/tests/typeEvaluatorBased.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,20 @@ test('`reportUnusedFunction` on `@final` classes', () => {
],
});
});

describe('uninitialized variable checking with init method calling', () => {
test('uninitialized', () => {
const configOptions = new BasedConfigOptions(Uri.empty());
const analysisResults = typeAnalyzeSampleFiles(['uninitializedVariableBased1.py'], configOptions);
validateResultsButBased(analysisResults, {
errors: [{ line: 8, code: DiagnosticRule.reportUninitializedInstanceVariable }],
});
});
test('initialized', () => {
const configOptions = new BasedConfigOptions(Uri.empty());
const analysisResults = typeAnalyzeSampleFiles(['uninitializedVariableBased2.py'], configOptions);
validateResultsButBased(analysisResults, {
errors: [],
});
});
});

0 comments on commit 435698e

Please sign in to comment.