Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit 435108d

Browse files
author
Mikhail Arkhipov
authored
Take into account scopes when checking definition order (#1077)
1 parent 6ec38d8 commit 435108d

3 files changed

Lines changed: 56 additions & 12 deletions

File tree

src/Analysis/Ast/Impl/Extensions/ScopeExtensions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// See the Apache Version 2.0 License for specific language governing
1414
// permissions and limitations under the License.
1515

16+
using System.Linq;
1617
using Microsoft.Python.Analysis.Documents;
1718
using Microsoft.Python.Analysis.Values;
1819
using Microsoft.Python.Core.Text;
@@ -31,6 +32,9 @@ public static int GetBodyStartIndex(this IScope scope, PythonAst ast) {
3132
}
3233
}
3334

35+
public static bool IsNestedInScope(this IScope s, IScope outer)
36+
=> s.OuterScope != null && s.OuterScope.EnumerateTowardsGlobal.Any(x => x == outer);
37+
3438
public static IScope FindScope(this IScope parent, IDocument document, SourceLocation location) {
3539
var children = parent.Children;
3640
var ast = document.Analysis.Ast;

src/Analysis/Ast/Impl/Linting/UndefinedVariables/ExpressionWalker.cs

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using System.Collections.Generic;
1717
using System.Linq;
18+
using Microsoft.Python.Analysis.Analyzer;
1819
using Microsoft.Python.Analysis.Types;
1920
using Microsoft.Python.Core;
2021
using Microsoft.Python.Core.Text;
@@ -77,27 +78,42 @@ public override bool Walk(NameExpression node) {
7778
}
7879

7980
var analysis = _walker.Analysis;
80-
var m = analysis.ExpressionEvaluator.LookupNameInScopes(node.Name, out _, out var v);
81+
var m = analysis.ExpressionEvaluator.LookupNameInScopes(node.Name, out var variableDefinitionScope, out var v);
8182
if (m == null) {
8283
_walker.ReportUndefinedVariable(node);
8384
}
8485
v?.AddReference(new Location(analysis.Document, node.IndexSpan));
8586

87+
// Make sure we have definition and the document matches
88+
if (v?.Definition == null || v.Definition.DocumentUri != analysis.Document.Uri) {
89+
return false;
90+
}
91+
// Do not complain about functions and classes that appear later in the file
92+
if (v.Value is IPythonFunctionType || v.Value is IPythonClassType) {
93+
return false;
94+
}
95+
8696
// Take into account where variable is defined so we do detect
8797
// undefined x in
8898
// y = x
8999
// x = 1
90-
if (v?.Definition != null && v.Definition.DocumentUri == analysis.Document.Uri) {
91-
// Do not complain about functions and classes that appear later in the file
92-
if (!(v.Value is IPythonFunctionType || v.Value is IPythonClassType)) {
93-
var span = v.Definition.Span;
94-
var nodeLoc = node.GetLocation(analysis.Document);
95-
// Exclude same-name variables declared within the same statement
96-
// like 'e' that appears before its declaration in '[e in for e in {}]'
97-
if (span.IsAfter(nodeLoc.Span) && !IsSpanInComprehension(nodeLoc.Span)) {
98-
_walker.ReportUndefinedVariable(node);
99-
}
100-
}
100+
var variableDefinitionSpan = v.Definition.Span;
101+
var nameUseLocation = node.GetLocation(analysis.Document);
102+
103+
// Make sure we are in the same scope in order to avoid
104+
// def func():
105+
// y = x
106+
//
107+
// x = 1
108+
var nameUseScope = analysis.ExpressionEvaluator.GlobalScope.FindScope(analysis.Document, nameUseLocation.Span.Start);
109+
if (nameUseScope.IsNestedInScope(variableDefinitionScope)) {
110+
return false;
111+
}
112+
113+
// Exclude same-name variables declared within the same statement
114+
// like 'e' that appears before its declaration in '[e in for e in {}]'
115+
if (variableDefinitionSpan.IsAfter(nameUseLocation.Span) && !IsSpanInComprehension(nameUseLocation.Span)) {
116+
_walker.ReportUndefinedVariable(node);
101117
}
102118
return false;
103119
}

src/Analysis/Ast/Test/LintUndefinedVarsTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,30 @@ def bar(x, y, z):
587587
d.Should().BeEmpty();
588588
}
589589

590+
[TestMethod, Priority(0)]
591+
public async Task DifferentScopes() {
592+
const string code = @"
593+
def func():
594+
var = _CONSTANT
595+
596+
_CONSTANT = 1
597+
";
598+
var d = await LintAsync(code);
599+
d.Should().BeEmpty();
600+
}
601+
602+
[TestMethod, Priority(0)]
603+
public async Task GlobalScope() {
604+
const string code = @"
605+
var = _CONSTANT
606+
_CONSTANT = 1
607+
";
608+
var d = await LintAsync(code);
609+
d.Should().HaveCount(1);
610+
d[0].ErrorCode.Should().Be(ErrorCodes.UndefinedVariable);
611+
d[0].SourceSpan.Should().Be(2, 7, 2, 16);
612+
}
613+
590614
private async Task<IReadOnlyList<DiagnosticsEntry>> LintAsync(string code, InterpreterConfiguration configuration = null) {
591615
var analysis = await GetAnalysisAsync(code, configuration ?? PythonVersions.LatestAvailable3X);
592616
var a = Services.GetService<IPythonAnalyzer>();

0 commit comments

Comments
 (0)