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

Commit

Permalink
Merge pull request #243 from MikhailArkhipov/225
Browse files Browse the repository at this point in the history
Fix Goto Definition with class methods
  • Loading branch information
Mikhail Arkhipov authored Oct 13, 2018
2 parents f204686 + 588f8ec commit 39a17b1
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 43 deletions.
5 changes: 1 addition & 4 deletions src/Analysis/Engine/Impl/AnalysisValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,10 +324,7 @@ public virtual void SetIndex(Node node, AnalysisUnit unit, IAnalysisSet index, I
/// skipped.
/// </returns>
public bool Push() {
if (_processing == null) {
_processing = new HashSet<AnalysisValue>();
}

_processing = _processing ?? new HashSet<AnalysisValue>();
return _processing.Add(this);
}

Expand Down
8 changes: 4 additions & 4 deletions src/Analysis/Engine/Impl/Analyzer/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public ExpressionEvaluator(AnalysisUnit unit, IScope scope, bool mergeScopes = f
/// <summary>
/// Returns possible variable refs associated with the expr in the expression evaluators scope.
/// </summary>
[DebuggerStepThrough]
public IAnalysisSet Evaluate(Expression node) {
var res = EvaluateWorker(node);
Debug.Assert(res != null);
Expand Down Expand Up @@ -84,11 +85,11 @@ internal IAnalysisSet EvaluateNoMemberRecursion(Expression node, HashSet<Analysi
return res;
}

[DebuggerStepThrough]
public IAnalysisSet EvaluateMaybeNull(Expression node) {
if (node == null) {
return null;
}

return Evaluate(node);
}

Expand Down Expand Up @@ -208,12 +209,11 @@ private IAnalysisSet[] Evaluate(IList<Arg> nodes) {
return result;
}

[DebuggerStepThrough]
private IAnalysisSet EvaluateWorker(Node node) {
EvalDelegate eval;
if (_evaluators.TryGetValue(node.GetType(), out eval)) {
if (_evaluators.TryGetValue(node.GetType(), out var eval)) {
return eval(this, node);
}

return AnalysisSet.Empty;
}

Expand Down
17 changes: 3 additions & 14 deletions src/Analysis/Engine/Impl/Values/ClassInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -636,19 +636,8 @@ public IEnumerable<IReferenceable> GetDefinitions(string name) {
return result;
}

private IEnumerable<IReferenceable> GetDefinitions(string name, IEnumerable<AnalysisValue> nses) {
var result = new List<IReferenceable>();
foreach (var subType in nses) {
if (subType.Push()) {
IReferenceableContainer container = subType as IReferenceableContainer;
if (container != null) {
result.AddRange(container.GetDefinitions(name));
}
subType.Pop();
}
}
return result;
}
private IEnumerable<IReferenceable> GetDefinitions(string name, IEnumerable<AnalysisValue> nses)
=> nses.OfType<IReferenceableContainer>().SelectMany(c => c.GetDefinitions(name));

#endregion

Expand Down Expand Up @@ -698,7 +687,7 @@ public Mro(IEnumerable<AnalysisValue> values) {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public void Recompute() {
if(_classInfo == null) {
if (_classInfo == null) {
return;
}
var mroList = new List<AnalysisValue> { _classInfo };
Expand Down
42 changes: 21 additions & 21 deletions src/Analysis/Engine/Test/AnalysisTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ public async Task SpecialArgTypes() {
[TestMethod, Priority(0)]
public async Task TestPackageImportStar() {
using (var server = await CreateServerAsync(PythonVersions.LatestAvailable3X)) {
var fob = await server.AddModuleWithContentAsync("fob", "fob\\__init__.py", "from oar import *");
var oar = await server.AddModuleWithContentAsync("fob.oar", "fob\\oar\\__init__.py", "from .baz import *");
var baz = await server.AddModuleWithContentAsync("fob.oar.baz", "fob\\oar\\baz.py", "import fob.oar.quox as quox\r\nfunc = quox.func");
var quox = await server.AddModuleWithContentAsync("fob.oar.quox", "fob\\oar\\quox.py", "def func(): return 42");
var fob = await server.AddModuleWithContentAsync("fob", Path.Combine("fob", "__init__.py"), "from oar import *");
var oar = await server.AddModuleWithContentAsync("fob.oar", Path.Combine("fob", "oar", "__init__.py"), "from .baz import *");
var baz = await server.AddModuleWithContentAsync("fob.oar.baz", Path.Combine("fob", "oar", "baz.py"), "import fob.oar.quox as quox\r\nfunc = quox.func");
var quox = await server.AddModuleWithContentAsync("fob.oar.quox", Path.Combine("fob", "oar", "quox.py"), "def func(): return 42");

var fobAnalysis = await fob.GetAnalysisAsync();
var oarAnalysis = await oar.GetAnalysisAsync();
Expand Down Expand Up @@ -4061,9 +4061,9 @@ import fob.y as y
";

using (var server = await CreateServerAsync(rootUri: TestData.GetTestSpecificRootUri())) {
var uriSrc1 = TestData.CreateTestSpecificFile(@"fob\__init__.py");
var uriSrc2 = TestData.GetTestSpecificUri(@"fob\x.py");
var uriSrc3 = TestData.GetTestSpecificUri(@"fob\y.py");
var uriSrc1 = TestData.CreateTestSpecificFile(Path.Combine("fob", "__init__.py"));
var uriSrc2 = TestData.GetTestSpecificUri(Path.Combine("fob", "x.py"));
var uriSrc3 = TestData.GetTestSpecificUri(Path.Combine("fob", "y.py"));

await server.SendDidOpenTextDocument(uriSrc1, src1);
await server.SendDidOpenTextDocument(uriSrc2, src2);
Expand All @@ -4085,9 +4085,9 @@ public async Task PackageRelativeImport() {
var src3 = "abc = 42";

using (var server = await CreateServerAsync(rootUri: TestData.GetTestSpecificRootUri())) {
var uriSrc1 = await TestData.CreateTestSpecificFileAsync(@"fob\__init__.py", src1);
var uriSrc2 = await TestData.CreateTestSpecificFileAsync(@"fob\x.py", src2);
var uriSrc3 = await TestData.CreateTestSpecificFileAsync(@"fob\y.py", src3);
var uriSrc1 = await TestData.CreateTestSpecificFileAsync(Path.Combine("fob", "__init__.py"), src1);
var uriSrc2 = await TestData.CreateTestSpecificFileAsync(Path.Combine("fob", "x.py"), src2);
var uriSrc3 = await TestData.CreateTestSpecificFileAsync(Path.Combine("fob", "y.py"), src3);

await server.SendDidOpenTextDocument(uriSrc1, src1);
await server.SendDidOpenTextDocument(uriSrc2, src2);
Expand Down Expand Up @@ -4118,15 +4118,15 @@ public async Task PackageRelativeImportPep328(string subpackageModuleXContent, s
var subpackage2ModuleZContent = "def eggs():\n pass\n";

using (var server = await CreateServerAsync(rootUri: TestData.GetTestSpecificRootUri())) {
var initUri = await TestData.CreateTestSpecificFileAsync(@"package\__init__.py", initContent);
var moduleAUri = await TestData.CreateTestSpecificFileAsync(@"package\moduleA.py", moduleAContent);
var initUri = await TestData.CreateTestSpecificFileAsync(Path.Combine("package", "__init__.py"), initContent);
var moduleAUri = await TestData.CreateTestSpecificFileAsync(Path.Combine("package", "moduleA.py"), moduleAContent);

var subpackageInitUri = await TestData.CreateTestSpecificFileAsync(@"package\subpackage1\__init__.py", string.Empty);
var subpackageModuleXUri = await TestData.CreateTestSpecificFileAsync(@"package\subpackage1\moduleX.py", subpackageModuleXContent);
var subpackageModuleYUri = await TestData.CreateTestSpecificFileAsync(@"package\subpackage1\moduleY.py", subpackageModuleYContent);
var subpackageInitUri = await TestData.CreateTestSpecificFileAsync(Path.Combine("package", "subpackage1", "__init__.py"), string.Empty);
var subpackageModuleXUri = await TestData.CreateTestSpecificFileAsync(Path.Combine("package", "subpackage1", "moduleX.py"), subpackageModuleXContent);
var subpackageModuleYUri = await TestData.CreateTestSpecificFileAsync(Path.Combine("package", "subpackage1", "moduleY.py"), subpackageModuleYContent);

var subpackage2InitUri = await TestData.CreateTestSpecificFileAsync(@"package\subpackage2\__init__.py", string.Empty);
var subpackage2ModuleZUri = await TestData.CreateTestSpecificFileAsync(@"package\subpackage2\moduleZ.py", subpackage2ModuleZContent);
var subpackage2InitUri = await TestData.CreateTestSpecificFileAsync(Path.Combine("package", "subpackage2", "__init__.py"), string.Empty);
var subpackage2ModuleZUri = await TestData.CreateTestSpecificFileAsync(Path.Combine("package", "subpackage2", "moduleZ.py"), subpackage2ModuleZContent);

await server.SendDidOpenTextDocument(initUri, initContent);
await server.SendDidOpenTextDocument(moduleAUri, moduleAContent);
Expand All @@ -4151,8 +4151,8 @@ public async Task PackageRelativeImportAliasedMember() {
var src2 = "def y(): pass";

using (var server = await CreateServerAsync(rootUri: TestData.GetTestSpecificRootUri())) {
var uriSrc1 = await TestData.CreateTestSpecificFileAsync(@"fob\__init__.py", src1);
var uriSrc2 = await TestData.CreateTestSpecificFileAsync(@"fob\y.py", src2);
var uriSrc1 = await TestData.CreateTestSpecificFileAsync(Path.Combine("fob", "__init__.py"), src1);
var uriSrc2 = await TestData.CreateTestSpecificFileAsync(Path.Combine("fob", "y.py"), src2);

await server.SendDidOpenTextDocument(uriSrc1, src1);
await server.SendDidOpenTextDocument(uriSrc2, src2);
Expand Down Expand Up @@ -6051,8 +6051,8 @@ public async Task CrossModuleUnassignedImport() {
using (var server = await CreateServerAsync(PythonVersions.LatestAvailable)) {
// Hack to avoid creation of the real files
// Project entries are explicitly added to the server before DidOpenTextDocument is called
var path1 = TestData.GetTestSpecificPath(@"p\__init__.py");
var path2 = TestData.GetTestSpecificPath(@"p\m.py");
var path1 = TestData.GetTestSpecificPath(Path.Combine("p", "__init__.py"));
var path2 = TestData.GetTestSpecificPath(Path.Combine("p", "m.py"));
var uri1 = new Uri(path1);
var uri2 = new Uri(path2);
server.ProjectFiles.GetOrAddEntry(uri1, server.Analyzer.AddModule("p", path1, uri1));
Expand Down
206 changes: 206 additions & 0 deletions src/Analysis/Engine/Test/FindReferencesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1602,5 +1602,211 @@ public static async Task AssertReferences(Server s, TextDocumentIdentifier docum
refs.Select(r => $"{r._kind ?? ReferenceKind.Reference};{r.range}").Should().Contain(contains);
}
}

[TestMethod, Priority(0)]
public async Task ClassMethod() {
var text = @"
class Base(object):
@classmethod
def fob_base(cls):
pass
class Derived(Base):
@classmethod
def fob_derived(cls):
'x'
Base.fob_base()
Derived.fob_derived()
";

using (var server = await CreateServerAsync()) {
var uri = await server.OpenDefaultDocumentAndGetUriAsync(text);
var references = await server.SendFindReferences(uri, 11, 6);

var expectedReferences = new (Uri, (int, int, int, int), ReferenceKind?)[] {
(uri, (3, 8, 3, 16), ReferenceKind.Definition),
(uri, (2, 4, 4, 12), ReferenceKind.Value),
(uri, (11, 5, 11, 13), ReferenceKind.Reference),
};

references.Should().OnlyHaveReferences(expectedReferences);
}
}

[TestMethod, Priority(0)]
public async Task ClassMethodInBase() {
var text = @"
class Base(object):
@classmethod
def fob_base(cls):
pass
class Derived(Base):
pass
Derived.fob_base()
";

using (var server = await CreateServerAsync()) {
var uri = await server.OpenDefaultDocumentAndGetUriAsync(text);
var references = await server.SendFindReferences(uri, 9, 9);

var expectedReferences = new (Uri, (int, int, int, int), ReferenceKind?)[] {
(uri, (3, 8, 3, 16), ReferenceKind.Definition),
(uri, (2, 4, 4, 12), ReferenceKind.Value),
(uri, (9, 8, 9, 16), ReferenceKind.Reference),
};

references.Should().OnlyHaveReferences(expectedReferences);
}
}

[TestMethod, Priority(0)]
public async Task ClassMethodInImportedBase() {
var text1 = @"
import mod2
class Derived(mod2.Base):
pass
Derived.fob_base()
";

var text2 = @"
class Base(object):
@classmethod
def fob_base(cls):
pass
";

using (var server = await CreateServerAsync()) {
var uri1 = await server.OpenDefaultDocumentAndGetUriAsync(text1);
var uri2 = await TestData.CreateTestSpecificFileAsync("mod2.py", text2);
await server.LoadFileAsync(uri2);

var references = await server.SendFindReferences(uri1, 6, 9);

var expectedReferences = new (Uri, (int, int, int, int), ReferenceKind?)[] {
(uri2, (3, 8, 3, 16), ReferenceKind.Definition),
(uri2, (2, 4, 4, 12), ReferenceKind.Value),
(uri1, (6, 8, 6, 16), ReferenceKind.Reference),
};

references.Should().OnlyHaveReferences(expectedReferences);
}
}

[TestMethod, Priority(0)]
public async Task ClassMethodInRelativeImportedBase() {
var text1 = @"
from .mod2 import Base
class Derived(Base):
pass
Derived.fob_base()
";

var text2 = @"
class Base(object):
@classmethod
def fob_base(cls):
pass
";

using (var server = await CreateServerAsync()) {
var uri1 = await server.OpenDefaultDocumentAndGetUriAsync(text1);
var uri2 = await TestData.CreateTestSpecificFileAsync("mod2.py", text2);
await server.LoadFileAsync(uri2);

var references = await server.SendFindReferences(uri1, 6, 9);

var expectedReferences = new (Uri, (int, int, int, int), ReferenceKind?)[] {
(uri2, (3, 8, 3, 16), ReferenceKind.Definition),
(uri2, (2, 4, 4, 12), ReferenceKind.Value),
(uri1, (6, 8, 6, 16), ReferenceKind.Reference),
};

references.Should().OnlyHaveReferences(expectedReferences);
}
}

[TestMethod, Priority(0)]
public async Task ClassMethodInStarImportedBase() {
var text1 = @"
from mod2 import *
class Derived(Base):
pass
Derived.fob_base()
";

var text2 = @"
class Base(object):
@classmethod
def fob_base(cls):
pass
";

using (var server = await CreateServerAsync()) {
var uri1 = await server.OpenDefaultDocumentAndGetUriAsync(text1);
var uri2 = await TestData.CreateTestSpecificFileAsync("mod2.py", text2);
await server.LoadFileAsync(uri2);

var references = await server.SendFindReferences(uri1, 6, 9);

var expectedReferences = new (Uri, (int, int, int, int), ReferenceKind?)[] {
(uri2, (3, 8, 3, 16), ReferenceKind.Definition),
(uri2, (2, 4, 4, 12), ReferenceKind.Value),
(uri1, (6, 8, 6, 16), ReferenceKind.Reference),
};

references.Should().OnlyHaveReferences(expectedReferences);
}
}

[TestMethod, Priority(0)]
public async Task ClassMethodInRelativeImportedBaseWithCircularReference() {
var text1 = @"
from .mod2 import Derived1
class Base(object):
@classmethod
def fob_base(cls):
pass
class Derived2(Derived1):
pass
Derived2.fob_base()
";

var text2 = @"
from mod1 import *
class Derived1(Base):
pass
";

using (var server = await CreateServerAsync()) {
var uri1 = await TestData.CreateTestSpecificFileAsync("mod1.py", text1);
var uri2 = await TestData.CreateTestSpecificFileAsync("mod2.py", text2);

await server.LoadFileAsync(uri1);
await server.LoadFileAsync(uri2);

var references = await server.SendFindReferences(uri1, 11, 9);

var expectedReferences = new (Uri, (int, int, int, int), ReferenceKind?)[] {
(uri1, (5, 8, 5, 16), ReferenceKind.Definition),
(uri1, (4, 4, 6, 12), ReferenceKind.Value),
(uri1, (11, 9, 11, 17), ReferenceKind.Reference),
};

references.Should().OnlyHaveReferences(expectedReferences);
}
}
}
}

0 comments on commit 39a17b1

Please sign in to comment.