diff --git a/server/src/analyzer/scope.ts b/server/src/analyzer/scope.ts index ad0b89214..7e6d33bfc 100644 --- a/server/src/analyzer/scope.ts +++ b/server/src/analyzer/scope.ts @@ -374,7 +374,8 @@ export class Scope { if (this._parent) { // If our recursion is about to take us outside the scope of the current - // module (i.e. into a built-in scope), indicate as such with the second parameter. + // module (i.e. into a built-in scope), indicate as such with the second + // parameter. return this._parent._lookUpSymbolRecursiveInternal(name, isOutsideCallerModule || this._scopeType === ScopeType.Module, isBeyondExecutionScope || this.isIndependentlyExecutable()); diff --git a/server/src/analyzer/typeAnalyzer.ts b/server/src/analyzer/typeAnalyzer.ts index 6bc266cbd..8ffbe57ff 100644 --- a/server/src/analyzer/typeAnalyzer.ts +++ b/server/src/analyzer/typeAnalyzer.ts @@ -3471,6 +3471,15 @@ export class TypeAnalyzer extends ParseTreeWalker { let prevScope = this._currentScope; let newScope = AnalyzerNodeInfo.getScope(node); assert(newScope !== undefined); + + let prevParent: Scope | undefined; + if (!newScope!.isIndependentlyExecutable()) { + // Temporary reparent the scope in case it is contained + // within a temporary scope. + prevParent = newScope!.getParent(); + newScope!.setParent(this._currentScope); + } + this._currentScope = newScope!; // Clear the raises/returns flags in case this wasn't our @@ -3486,6 +3495,9 @@ export class TypeAnalyzer extends ParseTreeWalker { this._currentScope.clearTypeConstraints(); this._currentScope = prevScope; + if (prevParent) { + newScope!.setParent(prevParent); + } return newScope!; } diff --git a/server/src/tests/samples/unbound1.py b/server/src/tests/samples/unbound1.py new file mode 100644 index 000000000..5add43d94 --- /dev/null +++ b/server/src/tests/samples/unbound1.py @@ -0,0 +1,19 @@ +# This sample tests the type checker's ability to determine which +# symbols are potentially unbound. + +if True: + class X: + # This should generate an error because 'X' is not yet declared. + def foo(self) -> X: + return X() + + a: X + + class A: + a: X + b = X + + def fn(self) -> X: + return X() + + diff --git a/server/src/tests/typeAnalyzer.test.ts b/server/src/tests/typeAnalyzer.test.ts index 1da82b0c6..d927125df 100644 --- a/server/src/tests/typeAnalyzer.test.ts +++ b/server/src/tests/typeAnalyzer.test.ts @@ -523,3 +523,9 @@ test('UnnecessaryIsInstance1', () => { analysisResults = TestUtils.typeAnalyzeSampleFiles(['unnecessaryIsInstance1.py'], configOptions); validateResults(analysisResults, 3); }); + +test('Unbound1', () => { + let analysisResults = TestUtils.typeAnalyzeSampleFiles(['unbound1.py']); + + validateResults(analysisResults, 1); +});