Skip to content

Commit e330f2e

Browse files
author
Mikhail Arkhipov
committed
Merge branch 'stubnav'
2 parents a1c1401 + 695d715 commit e330f2e

File tree

10 files changed

+98
-23
lines changed

10 files changed

+98
-23
lines changed

CONTRIBUTING.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
### Prerequisites
66

7-
1. .NET Core 2.2 SDK
7+
1. .NET Core 3.1 SDK
88
- [Windows](https://www.microsoft.com/net/learn/get-started/windows)
99
- [Mac OS](https://www.microsoft.com/net/learn/get-started/macos)
1010
- [Linux](https://www.microsoft.com/net/learn/get-started/linux/rhel)
1111
2. C# Extension to [VS Code](https://code.visualstudio.com) (all platforms)
1212
3. Python 2.7
13-
4. Python 3.6
13+
4. Python 3.6+
1414

15-
*Alternative:* [Visual Studio 2017 or 2019](https://www.visualstudio.com/downloads/) (Windows only) with .NET Core and C# Workloads. Community Edition is free and is fully functional.
15+
*Alternative:* [Visual Studio 2019](https://www.visualstudio.com/downloads/) (Windows only) with .NET Core and C# Workloads. Community Edition is free and is fully functional.
1616

1717
### Setup
1818

@@ -54,7 +54,7 @@ On Windows you can also attach from Visual Studio (Debug | Attach To Process).
5454
### Unit Tests
5555
To run unit tests, do one of the following:
5656
- Run the Unit Tests in the [VS Code Python Extension](https://github.com/Microsoft/vscode-python) project via *Launch Language Server Tests*.
57-
- On Windows: open the `PLS.sln` solution in Visual Studio 2017 or 2019 and run tests from the Test Explorer.
57+
- On Windows: open the `PLS.sln` solution in Visual Studio 2019 and run tests from the Test Explorer.
5858
- Run `dotnet test` from Terminal in the `src` directory, or in a specific directory like `src/Analysis/Ast/Test` to test a specific suite.
5959
- Install C# extension and .NET Core Test Explorer for VS Code, open src folder in VS Code and run tests.
6060

src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Scopes.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ public IDisposable OpenScope(IPythonModule module, ScopeStatement node, out Scop
176176
return new ScopeTracker(this);
177177
}
178178

179+
internal void ReplaceVariable(IVariable v) => CurrentScope.ReplaceVariable(v);
180+
179181
private class ScopeTracker : IDisposable {
180182
private readonly ExpressionEval _eval;
181183

src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ public ExpressionEval(IServiceContainer services, IPythonModule module, PythonAs
6060
public LocationInfo GetLocationInfo(Node node) => node?.GetLocation(this) ?? LocationInfo.Empty;
6161

6262
public Location GetLocationOfName(Node node) {
63-
if (node == null || Module.ModuleType != ModuleType.User && Module.ModuleType != ModuleType.Library) {
63+
if (node == null ||
64+
Module.ModuleType == ModuleType.Specialized || Module.ModuleType == ModuleType.Compiled ||
65+
Module.ModuleType == ModuleType.CompiledBuiltin || Module.ModuleType == ModuleType.Builtins) {
6466
return DefaultLocation;
6567
}
6668

src/Analysis/Ast/Impl/Analyzer/StubMerger.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ private void TryReplaceMember(IVariable v, IPythonType sourceType, IPythonType s
149149
MergeMembers(v, sourceFunction, stubType, cancellationToken);
150150
break;
151151

152+
case PythonPropertyType sourceProperty:
153+
MergeMembers(v, sourceProperty, stubType, cancellationToken);
154+
break;
155+
152156
case IPythonModule _:
153157
// We do not re-declare modules.
154158
break;
@@ -181,7 +185,17 @@ private void MergeMembers(IVariable v, IPythonType sourceType, IPythonType stubT
181185
// Replace the class entirely since stub members may use generic types
182186
// and the class definition is important. We transfer missing members
183187
// from the original class to the stub.
184-
_eval.DeclareVariable(v.Name, v.Value, v.Source);
188+
//
189+
// In case module is compiled, it is already a stub and has no locations
190+
// for code navigation.. In this case we replace the entire variable by one
191+
// from the stub rather than just the value since stub variable has location
192+
// and its own root definition/reference chain.
193+
if (sourceType.DeclaringModule.ModuleType == ModuleType.Compiled ||
194+
sourceType.DeclaringModule.ModuleType == ModuleType.CompiledBuiltin) {
195+
_eval.ReplaceVariable(v);
196+
} else {
197+
_eval.DeclareVariable(v.Name, v.Value, v.Source);
198+
}
185199

186200
// First pass: go through source class members and pick those
187201
// that are not present in the stub class.
@@ -193,7 +207,7 @@ private void MergeMembers(IVariable v, IPythonType sourceType, IPythonType stubT
193207
continue; // Do not add unknowns to the stub.
194208
}
195209
var sourceMemberType = sourceMember?.GetPythonType();
196-
if (sourceMemberType is IPythonClassMember cm && cm.DeclaringType != sourceType) {
210+
if (sourceMemberType is IPythonClassMember cm && !cm.DeclaringModule.Equals(sourceType.DeclaringModule)) {
197211
continue; // Only take members from this class and not from bases.
198212
}
199213
if (!IsFromThisModuleOrSubmodules(sourceMemberType)) {
@@ -340,7 +354,13 @@ private bool IsFromThisModuleOrSubmodules(IPythonType type) {
340354
var thisModule = _eval.Module;
341355
var typeModule = type.DeclaringModule;
342356
var typeMainModuleName = typeModule.Name.Split('.').FirstOrDefault();
343-
return typeModule.Equals(thisModule) || typeMainModuleName == thisModule.Name;
357+
if (typeModule.Equals(thisModule) || typeMainModuleName == thisModule.Name) {
358+
return true;
359+
}
360+
// Check if module is explicitly imported by the current one. For example, 'os'
361+
// imports 'nt' and os.pyi specifies functions from 'nt' such as mkdir and so on.
362+
var imported = thisModule.GlobalScope.Variables[typeModule.Name];
363+
return imported?.Value != null && imported.Source == VariableSource.Import;
344364
}
345365
}
346366
}

src/Analysis/Ast/Impl/Modules/Resolution/TypeshedResolution.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ internal sealed class TypeshedResolution : ModuleResolutionBase, IModuleResoluti
3232

3333
public TypeshedResolution(string root, IServiceContainer services) : base(root, services) {
3434
// TODO: merge with user-provided stub paths
35-
var stubs = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Stubs");
36-
_typeStubPaths = GetTypeShedPaths(Root)
35+
var asmLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
36+
var stubs = Path.Combine(asmLocation, "Stubs");
37+
var typeshedRoot = Root ?? Path.Combine(asmLocation, "Typeshed");
38+
39+
_typeStubPaths = GetTypeShedPaths(typeshedRoot)
3740
.Concat(GetTypeShedPaths(stubs))
3841
.Where(services.GetService<IFileSystem>().DirectoryExists)
3942
.ToImmutableArray();

src/Analysis/Ast/Impl/Values/Scope.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ public void DeclareImported(string name, IMember value, Location location = defa
9898

9999
internal void AddChildScope(Scope s) => (_childScopes ?? (_childScopes = new Dictionary<ScopeStatement, Scope>()))[s.Node] = s;
100100

101+
internal void ReplaceVariable(IVariable v) {
102+
VariableCollection.RemoveVariable(v.Name);
103+
VariableCollection.DeclareVariable(v.Name, v.Value, v.Source, v.Location);
104+
}
105+
101106
private VariableCollection VariableCollection => _variables ?? (_variables = new VariableCollection());
102107

103108
private void DeclareBuiltinVariables() {

src/LanguageServer/Impl/Implementation/Server.Editor.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,23 @@ public async Task<Reference[]> GotoDefinition(TextDocumentPositionParams @params
8989
_log?.Log(TraceEventType.Verbose, $"Goto Definition in {uri} at {@params.position}");
9090

9191
var analysis = await Document.GetAnalysisAsync(uri, Services, CompletionAnalysisTimeout, cancellationToken);
92-
var reference = new DefinitionSource(Services).FindDefinition(analysis, @params.position, out _);
93-
return reference != null ? new[] { reference } : Array.Empty<Reference>();
92+
var ds = new DefinitionSource(Services);
93+
var reference = ds.FindDefinition(analysis, @params.position, out _);
94+
return reference != null && ds.CanNavigateToModule(reference.uri)
95+
? new[] { reference }
96+
: Array.Empty<Reference>();
9497
}
9598

9699
public async Task<Location> GotoDeclaration(TextDocumentPositionParams @params, CancellationToken cancellationToken) {
97100
var uri = @params.textDocument.uri;
98101
_log?.Log(TraceEventType.Verbose, $"Goto Declaration in {uri} at {@params.position}");
99102

100103
var analysis = await Document.GetAnalysisAsync(uri, Services, CompletionAnalysisTimeout, cancellationToken);
101-
var reference = new DeclarationSource(Services).FindDefinition(analysis, @params.position, out _);
102-
return reference != null ? new Location { uri = reference.uri, range = reference.range } : null;
104+
var ds = new DeclarationSource(Services);
105+
var reference = ds.FindDefinition(analysis, @params.position, out _);
106+
return reference != null && ds.CanNavigateToModule(reference.uri)
107+
? new Location { uri = reference.uri, range = reference.range }
108+
: null;
103109
}
104110

105111
public Task<Reference[]> FindReferences(ReferencesParams @params, CancellationToken cancellationToken) {
@@ -137,8 +143,8 @@ public async Task<CodeAction[]> CodeAction(CodeActionParams @params, Cancellatio
137143

138144
return codeActions.ToArray();
139145

140-
static bool AskedFor(CodeActionParams @params, string codeActionKind) {
141-
return @params.context.only == null || @params.context.only.Any(s => s.StartsWith(codeActionKind));
146+
bool AskedFor(CodeActionParams p, string codeActionKind) {
147+
return p.context.only == null || p.context.only.Any(s => s.StartsWith(codeActionKind));
142148
}
143149
}
144150
}

src/LanguageServer/Impl/Sources/DefinitionSource.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// permissions and limitations under the License.
1515

1616
using System;
17+
using System.IO;
1718
using System.Linq;
1819
using Microsoft.Python.Analysis;
1920
using Microsoft.Python.Analysis.Analyzer;
@@ -360,22 +361,32 @@ private Reference FromMember(IMember m) {
360361
return null;
361362
}
362363

363-
private bool CanNavigateToModule(Uri uri) {
364+
public bool CanNavigateToModule(Uri uri) {
364365
if (uri == null) {
365366
return false;
366367
}
368+
369+
if (!CanNavigateToPath(uri.LocalPath)) {
370+
return false;
371+
}
367372
var rdt = _services.GetService<IRunningDocumentTable>();
368373
var doc = rdt.GetDocument(uri);
369374
// Allow navigation to modules not in RDT - most probably
370375
// it is a module that was restored from database.
371376
return doc == null || CanNavigateToModule(doc);
372377
}
373378

374-
private static bool CanNavigateToModule(IPythonModule m)
375-
=> m?.ModuleType == ModuleType.User ||
376-
m?.ModuleType == ModuleType.Stub ||
377-
m?.ModuleType == ModuleType.Package ||
378-
m?.ModuleType == ModuleType.Library ||
379-
m?.ModuleType == ModuleType.Specialized;
379+
private static bool CanNavigateToModule(IPythonModule m) {
380+
if(m == null || !CanNavigateToPath(m.FilePath)) {
381+
return false;
382+
}
383+
return m.ModuleType == ModuleType.User ||
384+
m.ModuleType == ModuleType.Stub ||
385+
m.ModuleType == ModuleType.Package ||
386+
m.ModuleType == ModuleType.Library ||
387+
m.ModuleType == ModuleType.Specialized;
388+
}
389+
390+
private static bool CanNavigateToPath(string path) => Path.GetExtension(path) != ".exe";
380391
}
381392
}

src/LanguageServer/Test/GoToDefinitionTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,5 +722,19 @@ def func(a, b):
722722
var reference = ds.FindDefinition(analysis, new SourceLocation(3, 12), out _);
723723
reference.range.Should().Be(1, 9, 1, 10);
724724
}
725+
726+
[TestMethod, Priority(0)]
727+
public async Task CompiledCode() {
728+
const string code = @"
729+
import os
730+
os.mkdir()
731+
";
732+
var analysis = await GetAnalysisAsync(code);
733+
var ds = new DefinitionSource(Services);
734+
var reference = ds.FindDefinition(analysis, new SourceLocation(3, 6), out _);
735+
reference.range.start.line.Should().NotBe(1);
736+
reference.range.end.line.Should().NotBe(1);
737+
reference.uri.AbsolutePath.Should().Contain(".pyi");
738+
}
725739
}
726740
}

src/LanguageServer/Test/HoverTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,18 @@ class A:
284284
AssertHover(hs, analysis, new SourceLocation(4, 7), @"x: int", new SourceSpan(4, 7, 4, 8));
285285
}
286286

287+
[TestMethod, Priority(0)]
288+
public async Task CompiledCode() {
289+
const string code = @"
290+
import os
291+
os.mkdir()
292+
";
293+
var analysis = await GetAnalysisAsync(code);
294+
var hs = new HoverSource(new PlainTextDocumentationSource());
295+
var hover = hs.GetHover(analysis, new SourceLocation(3, 6));
296+
hover.contents.value.Should().Contain("Create a directory");
297+
}
298+
287299
private static void AssertNoHover(HoverSource hs, IDocumentAnalysis analysis, SourceLocation position) {
288300
var hover = hs.GetHover(analysis, position);
289301
hover.Should().BeNull();

0 commit comments

Comments
 (0)