diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Scopes.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Scopes.cs index 7320e5827..c06d04bc2 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Scopes.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Scopes.cs @@ -176,6 +176,8 @@ public IDisposable OpenScope(IPythonModule module, ScopeStatement node, out Scop return new ScopeTracker(this); } + internal void ReplaceVariable(IVariable v) => CurrentScope.ReplaceVariable(v); + private class ScopeTracker : IDisposable { private readonly ExpressionEval _eval; diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs index 3b67d45d2..f55abb0f6 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs @@ -60,7 +60,9 @@ public ExpressionEval(IServiceContainer services, IPythonModule module, PythonAs public LocationInfo GetLocationInfo(Node node) => node?.GetLocation(this) ?? LocationInfo.Empty; public Location GetLocationOfName(Node node) { - if (node == null || Module.ModuleType != ModuleType.User && Module.ModuleType != ModuleType.Library) { + if (node == null || + Module.ModuleType == ModuleType.Specialized || Module.ModuleType == ModuleType.Compiled || + Module.ModuleType == ModuleType.CompiledBuiltin || Module.ModuleType == ModuleType.Builtins) { return DefaultLocation; } diff --git a/src/Analysis/Ast/Impl/Analyzer/StubMerger.cs b/src/Analysis/Ast/Impl/Analyzer/StubMerger.cs index 54eb2dd29..2636a5691 100644 --- a/src/Analysis/Ast/Impl/Analyzer/StubMerger.cs +++ b/src/Analysis/Ast/Impl/Analyzer/StubMerger.cs @@ -149,6 +149,10 @@ private void TryReplaceMember(IVariable v, IPythonType sourceType, IPythonType s MergeMembers(v, sourceFunction, stubType, cancellationToken); break; + case PythonPropertyType sourceProperty: + MergeMembers(v, sourceProperty, stubType, cancellationToken); + break; + case IPythonModule _: // We do not re-declare modules. break; @@ -181,7 +185,17 @@ private void MergeMembers(IVariable v, IPythonType sourceType, IPythonType stubT // Replace the class entirely since stub members may use generic types // and the class definition is important. We transfer missing members // from the original class to the stub. - _eval.DeclareVariable(v.Name, v.Value, v.Source); + // + // In case module is compiled, it is already a stub and has no locations + // for code navigation.. In this case we replace the entire variable by one + // from the stub rather than just the value since stub variable has location + // and its own root definition/reference chain. + if (sourceType.DeclaringModule.ModuleType == ModuleType.Compiled || + sourceType.DeclaringModule.ModuleType == ModuleType.CompiledBuiltin) { + _eval.ReplaceVariable(v); + } else { + _eval.DeclareVariable(v.Name, v.Value, v.Source); + } // First pass: go through source class members and pick those // that are not present in the stub class. @@ -193,7 +207,7 @@ private void MergeMembers(IVariable v, IPythonType sourceType, IPythonType stubT continue; // Do not add unknowns to the stub. } var sourceMemberType = sourceMember?.GetPythonType(); - if (sourceMemberType is IPythonClassMember cm && cm.DeclaringType != sourceType) { + if (sourceMemberType is IPythonClassMember cm && !cm.DeclaringModule.Equals(sourceType.DeclaringModule)) { continue; // Only take members from this class and not from bases. } if (!IsFromThisModuleOrSubmodules(sourceMemberType)) { @@ -340,7 +354,13 @@ private bool IsFromThisModuleOrSubmodules(IPythonType type) { var thisModule = _eval.Module; var typeModule = type.DeclaringModule; var typeMainModuleName = typeModule.Name.Split('.').FirstOrDefault(); - return typeModule.Equals(thisModule) || typeMainModuleName == thisModule.Name; + if (typeModule.Equals(thisModule) || typeMainModuleName == thisModule.Name) { + return true; + } + // Check if module is explicitly imported by the current one. For example, 'os' + // imports 'nt' and os.pyi specifies functions from 'nt' such as mkdir and so on. + var imported = thisModule.GlobalScope.Variables[typeModule.Name]; + return imported?.Value != null && imported.Source == VariableSource.Import; } } } diff --git a/src/Analysis/Ast/Impl/Modules/Resolution/TypeshedResolution.cs b/src/Analysis/Ast/Impl/Modules/Resolution/TypeshedResolution.cs index a5ad74b30..91576a8dc 100644 --- a/src/Analysis/Ast/Impl/Modules/Resolution/TypeshedResolution.cs +++ b/src/Analysis/Ast/Impl/Modules/Resolution/TypeshedResolution.cs @@ -32,8 +32,11 @@ internal sealed class TypeshedResolution : ModuleResolutionBase, IModuleResoluti public TypeshedResolution(string root, IServiceContainer services) : base(root, services) { // TODO: merge with user-provided stub paths - var stubs = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Stubs"); - _typeStubPaths = GetTypeShedPaths(Root) + var asmLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var stubs = Path.Combine(asmLocation, "Stubs"); + var typeshedRoot = Root ?? Path.Combine(asmLocation, "Typeshed"); + + _typeStubPaths = GetTypeShedPaths(typeshedRoot) .Concat(GetTypeShedPaths(stubs)) .Where(services.GetService().DirectoryExists) .ToImmutableArray(); diff --git a/src/Analysis/Ast/Impl/Values/Scope.cs b/src/Analysis/Ast/Impl/Values/Scope.cs index 2d49f8f69..08df8a957 100644 --- a/src/Analysis/Ast/Impl/Values/Scope.cs +++ b/src/Analysis/Ast/Impl/Values/Scope.cs @@ -98,6 +98,11 @@ public void DeclareImported(string name, IMember value, Location location = defa internal void AddChildScope(Scope s) => (_childScopes ?? (_childScopes = new Dictionary()))[s.Node] = s; + internal void ReplaceVariable(IVariable v) { + VariableCollection.RemoveVariable(v.Name); + VariableCollection.DeclareVariable(v.Name, v.Value, v.Source, v.Location); + } + private VariableCollection VariableCollection => _variables ?? (_variables = new VariableCollection()); private void DeclareBuiltinVariables() { diff --git a/src/LanguageServer/Impl/Implementation/Server.Editor.cs b/src/LanguageServer/Impl/Implementation/Server.Editor.cs index b6133bd84..46de921fa 100644 --- a/src/LanguageServer/Impl/Implementation/Server.Editor.cs +++ b/src/LanguageServer/Impl/Implementation/Server.Editor.cs @@ -89,8 +89,11 @@ public async Task GotoDefinition(TextDocumentPositionParams @params _log?.Log(TraceEventType.Verbose, $"Goto Definition in {uri} at {@params.position}"); var analysis = await Document.GetAnalysisAsync(uri, Services, CompletionAnalysisTimeout, cancellationToken); - var reference = new DefinitionSource(Services).FindDefinition(analysis, @params.position, out _); - return reference != null ? new[] { reference } : Array.Empty(); + var ds = new DefinitionSource(Services); + var reference = ds.FindDefinition(analysis, @params.position, out _); + return reference != null && ds.CanNavigateToModule(reference.uri) + ? new[] { reference } + : Array.Empty(); } public async Task GotoDeclaration(TextDocumentPositionParams @params, CancellationToken cancellationToken) { @@ -98,8 +101,11 @@ public async Task GotoDeclaration(TextDocumentPositionParams @params, _log?.Log(TraceEventType.Verbose, $"Goto Declaration in {uri} at {@params.position}"); var analysis = await Document.GetAnalysisAsync(uri, Services, CompletionAnalysisTimeout, cancellationToken); - var reference = new DeclarationSource(Services).FindDefinition(analysis, @params.position, out _); - return reference != null ? new Location { uri = reference.uri, range = reference.range } : null; + var ds = new DeclarationSource(Services); + var reference = ds.FindDefinition(analysis, @params.position, out _); + return reference != null && ds.CanNavigateToModule(reference.uri) + ? new Location { uri = reference.uri, range = reference.range } + : null; } public Task FindReferences(ReferencesParams @params, CancellationToken cancellationToken) { @@ -137,8 +143,8 @@ public async Task CodeAction(CodeActionParams @params, Cancellatio return codeActions.ToArray(); - static bool AskedFor(CodeActionParams @params, string codeActionKind) { - return @params.context.only == null || @params.context.only.Any(s => s.StartsWith(codeActionKind)); + bool AskedFor(CodeActionParams p, string codeActionKind) { + return p.context.only == null || p.context.only.Any(s => s.StartsWith(codeActionKind)); } } } diff --git a/src/LanguageServer/Impl/Sources/DefinitionSource.cs b/src/LanguageServer/Impl/Sources/DefinitionSource.cs index 6cacb068a..e104afba4 100644 --- a/src/LanguageServer/Impl/Sources/DefinitionSource.cs +++ b/src/LanguageServer/Impl/Sources/DefinitionSource.cs @@ -14,6 +14,7 @@ // permissions and limitations under the License. using System; +using System.IO; using System.Linq; using Microsoft.Python.Analysis; using Microsoft.Python.Analysis.Analyzer; @@ -360,10 +361,14 @@ private Reference FromMember(IMember m) { return null; } - private bool CanNavigateToModule(Uri uri) { + public bool CanNavigateToModule(Uri uri) { if (uri == null) { return false; } + + if (!CanNavigateToPath(uri.LocalPath)) { + return false; + } var rdt = _services.GetService(); var doc = rdt.GetDocument(uri); // Allow navigation to modules not in RDT - most probably @@ -371,11 +376,17 @@ private bool CanNavigateToModule(Uri uri) { return doc == null || CanNavigateToModule(doc); } - private static bool CanNavigateToModule(IPythonModule m) - => m?.ModuleType == ModuleType.User || - m?.ModuleType == ModuleType.Stub || - m?.ModuleType == ModuleType.Package || - m?.ModuleType == ModuleType.Library || - m?.ModuleType == ModuleType.Specialized; + private static bool CanNavigateToModule(IPythonModule m) { + if(m == null || !CanNavigateToPath(m.FilePath)) { + return false; + } + return m.ModuleType == ModuleType.User || + m.ModuleType == ModuleType.Stub || + m.ModuleType == ModuleType.Package || + m.ModuleType == ModuleType.Library || + m.ModuleType == ModuleType.Specialized; + } + + private static bool CanNavigateToPath(string path) => Path.GetExtension(path) != ".exe"; } } diff --git a/src/LanguageServer/Test/GoToDefinitionTests.cs b/src/LanguageServer/Test/GoToDefinitionTests.cs index 58d1b8494..fdd3f3a28 100644 --- a/src/LanguageServer/Test/GoToDefinitionTests.cs +++ b/src/LanguageServer/Test/GoToDefinitionTests.cs @@ -722,5 +722,19 @@ def func(a, b): var reference = ds.FindDefinition(analysis, new SourceLocation(3, 12), out _); reference.range.Should().Be(1, 9, 1, 10); } + + [TestMethod, Priority(0)] + public async Task CompiledCode() { + const string code = @" +import os +os.mkdir() +"; + var analysis = await GetAnalysisAsync(code); + var ds = new DefinitionSource(Services); + var reference = ds.FindDefinition(analysis, new SourceLocation(3, 6), out _); + reference.range.start.line.Should().NotBe(1); + reference.range.end.line.Should().NotBe(1); + reference.uri.AbsolutePath.Should().Contain(".pyi"); + } } } diff --git a/src/LanguageServer/Test/HoverTests.cs b/src/LanguageServer/Test/HoverTests.cs index 0a167b18b..9377e5cc0 100644 --- a/src/LanguageServer/Test/HoverTests.cs +++ b/src/LanguageServer/Test/HoverTests.cs @@ -284,6 +284,18 @@ class A: AssertHover(hs, analysis, new SourceLocation(4, 7), @"x: int", new SourceSpan(4, 7, 4, 8)); } + [TestMethod, Priority(0)] + public async Task CompiledCode() { + const string code = @" +import os +os.mkdir() +"; + var analysis = await GetAnalysisAsync(code); + var hs = new HoverSource(new PlainTextDocumentationSource()); + var hover = hs.GetHover(analysis, new SourceLocation(3, 6)); + hover.contents.value.Should().Contain("Create a directory"); + } + private static void AssertNoHover(HoverSource hs, IDocumentAnalysis analysis, SourceLocation position) { var hover = hs.GetHover(analysis, position); hover.Should().BeNull();