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

Linter for undefined variables #693

Merged
merged 41 commits into from
Mar 14, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8bca56b
Fix #668 (partial)
Mar 1, 2019
a731c2a
Merge branch 'master' of https://github.com/Microsoft/python-language…
Mar 1, 2019
ba126e7
Undef variables, first cut
Mar 4, 2019
bde1db1
Reorg
Mar 4, 2019
55cc25f
Tests
Mar 4, 2019
8b79615
Tests
Mar 4, 2019
069910b
Merge branch 'master' of https://github.com/Microsoft/python-language…
Mar 5, 2019
7ffc9db
Tests
Mar 5, 2019
6303c45
Revert "Tests"
Mar 5, 2019
945451c
Merge branch 'master' of https://github.com/Microsoft/python-language…
Mar 5, 2019
6429bfa
Options and tests
Mar 5, 2019
42dc0a0
Don't squiggle builtin-ins
Mar 5, 2019
9b130fd
Test for function arguments
Mar 5, 2019
2490a0a
Fix tuple assignment in analysis
Mar 5, 2019
f9216e3
Don't false positive on functions and classes forward references
Mar 5, 2019
58a0d66
Merge master
Mar 5, 2019
034e437
Merge branch 'master' of https://github.com/Microsoft/python-language…
Mar 5, 2019
3b1682f
Merge branch 'master' of https://github.com/Microsoft/python-language…
Mar 6, 2019
5cea68d
Disable tracking assignment location
Mar 6, 2019
af542a4
Merge branch 'master' of https://github.com/Microsoft/python-language…
Mar 6, 2019
8dfc390
Track multiple locations
Mar 6, 2019
a30eea3
Tests
Mar 6, 2019
8a1c83a
using
Mar 6, 2019
33d10e9
Merge locations
Mar 6, 2019
4404e5a
Properly look at locations
Mar 6, 2019
6c47d09
Merge branch 'master' of https://github.com/Microsoft/python-language…
Mar 11, 2019
13e6e99
Comprehensions etc
Mar 12, 2019
df7cdfd
Merge branch 'master' of https://github.com/Microsoft/python-language…
Mar 12, 2019
4adf172
Test update
Mar 12, 2019
1aaa445
Add support for lambdas
Mar 12, 2019
8861171
Add lambda linting
Mar 12, 2019
e76540c
Handle tuple assignment better
Mar 12, 2019
fe099b5
Test update
Mar 13, 2019
861d487
Merge master
Mar 13, 2019
29e3dd1
Correct comprehension iterator handlint
Mar 13, 2019
aa2a415
Fix race condition at builtins load
Mar 13, 2019
0739ad3
Merge master
Mar 14, 2019
add8358
Merge master
Mar 14, 2019
91a7215
Restore linter
Mar 14, 2019
35c7f0e
Merge branch 'master' of https://github.com/Microsoft/python-language…
Mar 14, 2019
3e18aea
Merge branch 'master' of https://github.com/Microsoft/python-language…
Mar 14, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Scopes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ public IPythonType GetTypeFromAnnotation(Expression expr, out bool isGeneric, Lo
/// as a child of the specified scope. Scope is pushed on the stack
/// and will be removed when returned the disposable is disposed.
/// </summary>
public IDisposable OpenScope(IPythonModule module, ScopeStatement node, out Scope fromScope) {
fromScope = null;
public IDisposable OpenScope(IPythonModule module, ScopeStatement node, out Scope outerScope) {
outerScope = null;
if (node == null) {
return Disposable.Empty;
}
Expand All @@ -156,26 +156,26 @@ public IDisposable OpenScope(IPythonModule module, ScopeStatement node, out Scop
}

if (node.Parent != null) {
if (!_scopeLookupCache.TryGetValue(node.Parent, out fromScope)) {
fromScope = gs
if (!_scopeLookupCache.TryGetValue(node.Parent, out outerScope)) {
outerScope = gs
.TraverseDepthFirst(s => s.Children.OfType<Scope>())
.FirstOrDefault(s => s.Node == node.Parent);
_scopeLookupCache[node.Parent] = fromScope;
_scopeLookupCache[node.Parent] = outerScope;
}
}

fromScope = fromScope ?? gs;
if (fromScope != null) {
outerScope = outerScope ?? gs;
if (outerScope != null) {
Scope scope;
if (node is PythonAst) {
// node points to global scope, it is not a function or a class.
scope = gs;
} else {
scope = fromScope.Children.OfType<Scope>().FirstOrDefault(s => s.Node == node);
scope = outerScope.Children.OfType<Scope>().FirstOrDefault(s => s.Node == node);
if (scope == null) {
scope = new Scope(node, fromScope, true);
fromScope.AddChildScope(scope);
_scopeLookupCache[node] = fromScope;
scope = new Scope(node, outerScope, true);
outerScope.AddChildScope(scope);
_scopeLookupCache[node] = scope;
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/Analysis/Ast/Impl/Analyzer/PythonInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ private async Task LoadBuiltinTypesAsync(string root, IServiceManager sm, Cancel
_moduleResolution = new MainModuleResolution(root, sm);
await _moduleResolution.InitializeAsync(cancellationToken);

_stubResolution = new TypeshedResolution(sm);
await _stubResolution.InitializeAsync(cancellationToken);

var builtinModule = _moduleResolution.BuiltinsModule;
lock (_lock) {
_builtinTypes[BuiltinTypeId.NoneType]
Expand All @@ -60,6 +57,9 @@ private async Task LoadBuiltinTypesAsync(string root, IServiceManager sm, Cancel
= UnknownType = new PythonType("Unknown", builtinModule, string.Empty, LocationInfo.Empty);
}
await _moduleResolution.LoadBuiltinTypesAsync(cancellationToken);

_stubResolution = new TypeshedResolution(sm);
await _stubResolution.InitializeAsync(cancellationToken);
}

public static async Task<IPythonInterpreter> CreateAsync(InterpreterConfiguration configuration, string root, IServiceManager sm, CancellationToken cancellationToken = default) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
namespace Microsoft.Python.Analysis.Linting.UndefinedVariables {
internal sealed class ComprehensionWalker : PythonWalker {
private readonly IDocumentAnalysis _analysis;
private readonly HashSet<string> _names = new HashSet<string>();
private readonly HashSet<NameExpression> _additionalNameNodes = new HashSet<NameExpression>();
private readonly HashSet<string> _localNames = new HashSet<string>();
private readonly HashSet<NameExpression> _localNameNodes = new HashSet<NameExpression>();

public ComprehensionWalker(IDocumentAnalysis analysis) {
_analysis = analysis;
Expand All @@ -43,34 +43,35 @@ public override bool Walk(SetComprehension node) {
}

public override bool Walk(ForStatement node) {
var nc = new NameCollectorWalker(_names, _additionalNameNodes);
var nc = new NameCollectorWalker(_localNames, _localNameNodes);
node.Left?.Walk(nc);
return true;
}

public override bool Walk(DictionaryComprehension node) {
CollectNames(node);
node.Key?.Walk(new ExpressionWalker(_analysis, _names, _additionalNameNodes));
node.Value?.Walk(new ExpressionWalker(_analysis, _names, _additionalNameNodes));
var ew = new ExpressionWalker(_analysis, _localNames, _localNameNodes);
node.Key?.Walk(ew);
node.Value?.Walk(ew);
foreach (var iter in node.Iterators) {
iter?.Walk(new ExpressionWalker(_analysis, null, _additionalNameNodes));
iter?.Walk(ew);
}

return true;
}

private void CollectNames(Comprehension c) {
var nc = new NameCollectorWalker(_names, _additionalNameNodes);
var nc = new NameCollectorWalker(_localNames, _localNameNodes);
foreach (var cfor in c.Iterators.OfType<ComprehensionFor>()) {
cfor.Left?.Walk(nc);
}
}

private void ProcessComprehension(Comprehension c, Node item, IEnumerable<ComprehensionIterator> iterators) {
CollectNames(c);
item?.Walk(new ExpressionWalker(_analysis, _names, _additionalNameNodes));
var ew = new ExpressionWalker(_analysis, _localNames, _localNameNodes);
item?.Walk(ew);
foreach (var iter in iterators) {
iter.Walk(new ExpressionWalker(_analysis, null, _additionalNameNodes));
iter.Walk(ew);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
namespace Microsoft.Python.Analysis.Linting.UndefinedVariables {
internal sealed class ExpressionWalker : PythonWalker {
private readonly IDocumentAnalysis _analysis;
private readonly HashSet<string> _additionalNames;
private readonly HashSet<NameExpression> _additionalNameNodes;
private readonly HashSet<string> _localNames;
private readonly HashSet<NameExpression> _localNameNodes;

public ExpressionWalker(IDocumentAnalysis analysis)
: this(analysis, null, null) { }
Expand All @@ -33,12 +33,12 @@ public ExpressionWalker(IDocumentAnalysis analysis)
/// Creates walker for detection of undefined variables.
/// </summary>
/// <param name="analysis">Document analysis.</param>
/// <param name="additionalNames">Additional defined names.</param>
/// <param name="additionalNameNodes">Name nodes for defined names.</param>
public ExpressionWalker(IDocumentAnalysis analysis, HashSet<string> additionalNames, HashSet<NameExpression> additionalNameNodes) {
/// <param name="localNames">Locally defined names, such as variables in a comprehension.</param>
/// <param name="localNameNodes">Name nodes for local names.</param>
public ExpressionWalker(IDocumentAnalysis analysis, HashSet<string> localNames, HashSet<NameExpression> localNameNodes) {
_analysis = analysis;
_additionalNames = additionalNames;
_additionalNameNodes = additionalNameNodes;
_localNames = localNames;
_localNameNodes = localNameNodes;
}

public override bool Walk(CallExpression node) {
Expand Down Expand Up @@ -73,10 +73,10 @@ public override bool Walk(GeneratorExpression node) {
}

public override bool Walk(NameExpression node) {
if (_additionalNames?.Contains(node.Name) == true) {
if (_localNames?.Contains(node.Name) == true) {
return false;
}
if (_additionalNameNodes?.Contains(node) == true) {
if (_localNameNodes?.Contains(node) == true) {
return false;
}
var m = _analysis.ExpressionEvaluator.LookupNameInScopes(node.Name, out var scope);
Expand Down
27 changes: 27 additions & 0 deletions src/Analysis/Ast/Test/LintUndefinedVarsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ public async Task ForVariables() {
analysis.Diagnostics.Should().BeEmpty();
}

[TestMethod, Priority(0)]
public async Task ForVariablesCondition() {
const string code = @"
c = {}
for a, b in c if a < 0:
x = b
";
var analysis = await GetAnalysisAsync(code);
analysis.Diagnostics.Should().BeEmpty();
}

[TestMethod, Priority(0)]
public async Task ForVariablesList() {
const string code = @"
Expand All @@ -174,6 +185,22 @@ public async Task ForVariablesList() {
analysis.Diagnostics.Should().BeEmpty();
}

[TestMethod, Priority(0)]
public async Task ForExpression() {
const string code = @"
def func1(a):
return a

def func2(a, b):
return a + b

func1(func2(a) for a, b in {} if a < 0)
";
var analysis = await GetAnalysisAsync(code);
analysis.Diagnostics.Should().BeEmpty();
}


[TestMethod, Priority(0)]
public async Task ListComprehension() {
const string code = @"
Expand Down