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

Commit 2ee4d06

Browse files
author
MikhailArkhipov
committed
Better scoping checks
1 parent 104bb43 commit 2ee4d06

3 files changed

Lines changed: 74 additions & 22 deletions

File tree

src/Analysis/Engine/Impl/ModuleAnalysis.cs

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -246,39 +246,66 @@ private static VariablesResult GetVariablesFromCallExpression(AnalysisUnit unit,
246246
return null;
247247
}
248248

249+
private class VariableScopePair : Tuple<IAnalysisVariable, int> {
250+
public VariableScopePair(IAnalysisVariable v, int scopeLevel) : base(v, scopeLevel) { }
251+
public IAnalysisVariable Variable => Item1;
252+
public int ScopeLevel => Item2;
253+
public VariableType VariableType => Variable.Type;
254+
public ILocationInfo Location => Variable.Location;
255+
}
256+
249257
private VariablesResult GetVariablesFromNameExpression(Expression expr, AnalysisUnit unit, IScope scope) {
250258
if (!(expr is NameExpression name)) {
251259
return null;
252260
}
253261

254-
var variables = Enumerable.Empty<IAnalysisVariable>();
255262
if (!scope.EnumerateTowardsGlobal.Any()) {
256-
variables = _unit.State.BuiltinModule.GetDefinitions(name.Name).SelectMany(ToVariables);
257-
return new VariablesResult(variables, unit.Tree);
263+
var v = _unit.State.BuiltinModule.GetDefinitions(name.Name).SelectMany(ToVariables);
264+
return new VariablesResult(v, unit.Tree);
258265
}
259266

267+
var variables = Enumerable.Empty<VariableScopePair>();
268+
VariableScopePair mainDefinition = null;
269+
var scopeLevel = 0;
260270
foreach (var s in scope.EnumerateTowardsGlobal) {
261-
var scopeVariables = GetVariablesInScope(name, s).Distinct();
271+
var scopeVariables = GetVariablesInScope(name, s)
272+
.Distinct()
273+
.Select(v => new VariableScopePair(v, scopeLevel))
274+
.ToArray();
275+
262276
variables = variables.Union(scopeVariables);
263-
var args = scopeVariables.Where(v => IsFunctionArgument(s, v));
264-
if (args.Any()) {
277+
278+
// If we already have definition and it is a function argument, then stop
279+
// since function argument wins over definitions in the outer scope
280+
mainDefinition = scopeVariables.FirstOrDefault(v => v.VariableType == VariableType.Definition && IsFunctionArgument(s, v.Variable));
281+
if (mainDefinition != null) {
265282
break;
266283
}
284+
scopeLevel++;
267285
}
268286

269-
// Now take outermost definition and treat inner ones (such as reassignments) as references
270-
var others = variables.Where(v => v.Type == VariableType.Reference || v.Type == VariableType.Value);
287+
// Now take innermost definition and treat inner ones (such as reassignments) as references
271288
var definitions = variables
272-
.Where(v => v.Type == VariableType.Definition)
289+
.Where(v => v.VariableType == VariableType.Definition)
273290
.OrderBy(v => v.Location.Span.Start)
291+
.Reverse()
274292
.ToArray();
275293

276-
if (definitions.Length > 0) {
277-
var defsToRefs = definitions.Skip(1).Select(v => new AnalysisVariable(v.Variable, VariableType.Reference, v.Location));
278-
variables = definitions.Take(1).Concat(others.Concat(defsToRefs));
294+
mainDefinition = mainDefinition ?? definitions.FirstOrDefault();
295+
if (mainDefinition != null) {
296+
// Drop definitions in outer scopes and convert those in inner scopes to references.
297+
// Scope levels are numbered in reverse (X == main definition level, x+1 == one up).
298+
var defsToRefs = definitions
299+
.Where(d => d != mainDefinition && d.ScopeLevel <= mainDefinition.ScopeLevel)
300+
.Select(v => new VariableScopePair(new AnalysisVariable(v.Variable.Variable, VariableType.Reference, v.Location), v.ScopeLevel));
301+
302+
var others = variables
303+
.Where(v => (v.VariableType == VariableType.Reference || v.VariableType == VariableType.Value) &&
304+
v.ScopeLevel <= mainDefinition.ScopeLevel);
305+
variables = new[] { mainDefinition }.Concat(others.Concat(defsToRefs));
279306
}
280307

281-
return new VariablesResult(variables, unit.Tree);
308+
return new VariablesResult(variables.Select(v => v.Variable), unit.Tree);
282309
}
283310

284311
private VariablesResult GetVariablesFromMemberExpression(Expression expr, AnalysisUnit unit, ExpressionEvaluator eval) {

src/Analysis/Engine/Test/AnalysisTest.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3154,6 +3154,27 @@ assert isinstance(a, int)
31543154
}
31553155
}
31563156

3157+
[TestMethod, Priority(0)]
3158+
public async Task References_ClassLocalVariable() {
3159+
var uri = TestData.GetDefaultModuleUri();
3160+
using (var server = await CreateServerAsync(PythonVersions.LatestAvailable2X)) {
3161+
var text = @"
3162+
a = 1
3163+
class B:
3164+
a = 2
3165+
";
3166+
await server.SendDidOpenTextDocument(uri, text);
3167+
var references = await server.SendFindReferences(uri, 3, 5);
3168+
references.Should().OnlyHaveReferences(
3169+
(uri, (3, 4, 3, 5), ReferenceKind.Definition)
3170+
);
3171+
references = await server.SendFindReferences(uri, 1, 1);
3172+
references.Should().OnlyHaveReferences(
3173+
(uri, (1, 0, 1, 1), ReferenceKind.Definition)
3174+
);
3175+
}
3176+
}
3177+
31573178
[TestMethod, Priority(0)]
31583179
public async Task ListDictArgReferences() {
31593180
var text = @"

src/Analysis/Engine/Test/LanguageServerTests.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,10 @@ public async Task CompletionForOverrideArgs() {
449449
var u = await AddModule(s, "class A:\n def bar(arg=None): pass\n\nclass B(A):\n def b");
450450

451451
await AssertNoCompletion(s, u, new SourceLocation(2, 9));
452-
await AssertCompletion(s, u, new[] { "bar(arg=None):\r\n return super().bar()" }, new[] { "bar(arg = None):\r\n return super().bar()" }, new SourceLocation(5, 10));
452+
await AssertCompletion(s, u,
453+
new[] { "bar(arg=None):\r\n return super(B, arg).bar()" },
454+
new[] { "bar(arg = None):\r\n return super().bar()" },
455+
new SourceLocation(5, 10));
453456
}
454457
}
455458

@@ -762,7 +765,7 @@ class D:
762765
await AssertReferences(s, mod1, new SourceLocation(10, 2), expected, unexpected);
763766
await AssertReferences(s, mod2, new SourceLocation(17, 6), expected, unexpected);
764767

765-
await AssertReferences(s, mod1, new SourceLocation(8, 5), unexpected, expected);
768+
await AssertReferences(s, mod1, new SourceLocation(8, 5), unexpected, Enumerable.Empty<string>());
766769

767770
// a
768771
expected = new[] {
@@ -800,14 +803,11 @@ class D:
800803

801804
// C.real
802805
expected = new[] {
803-
"Reference;(3, 7) - (3, 11)",
804-
"Definition;(7, 5) - (7, 9)"
805-
};
806-
unexpected = new[] {
807806
"Definition;(11, 1) - (11, 5)",
808-
"Definition;(14, 5) - (14, 9)"
807+
"Reference;(3, 7) - (3, 11)",
808+
"Reference;(7, 5) - (7, 9)"
809809
};
810-
await AssertReferences(s, mod1, new SourceLocation(7, 8), expected, unexpected);
810+
await AssertReferences(s, mod1, new SourceLocation(7, 8), expected, Enumerable.Empty<string>());
811811

812812
// D.real
813813
expected = new[] {
@@ -1228,7 +1228,11 @@ public static async Task AssertReferences(Server s, TextDocumentIdentifier docum
12281228
}
12291229
}, CancellationToken.None);
12301230

1231-
refs.Select(r => $"{r._kind ?? ReferenceKind.Reference};{r.range}").Should().Contain(contains).And.NotContain(excludes);
1231+
if (excludes.Any()) {
1232+
refs.Select(r => $"{r._kind ?? ReferenceKind.Reference};{r.range}").Should().Contain(contains).And.NotContain(excludes);
1233+
} else {
1234+
refs.Select(r => $"{r._kind ?? ReferenceKind.Reference};{r.range}").Should().Contain(contains);
1235+
}
12321236
}
12331237

12341238
public Task<ILanguageServerExtension> CreateAsync(IPythonLanguageServer server, IReadOnlyDictionary<string, object> properties, CancellationToken cancellationToken) => throw new NotImplementedException();

0 commit comments

Comments
 (0)