diff --git a/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs b/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs index fbf5162c9..e0a524199 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs @@ -42,12 +42,8 @@ public async Task HandleFromImportAsync(FromImportStatement node, Cancella var imports = ModuleResolution.CurrentPathResolver.FindImports(Module.FilePath, node); switch (imports) { - case ModuleImport moduleImport when moduleImport.FullName == Module.Name && Module.ModuleType == ModuleType.Stub: - // If we are processing stub, ignore imports of the original module. - // For example, typeshed stub for 'sys' imports sys. - break; case ModuleImport moduleImport when moduleImport.FullName == Module.Name: - ImportMembersFromSelf(node); + await ImportMembersFromSelfAsync(node, cancellationToken); break; case ModuleImport moduleImport: await ImportMembersFromModuleAsync(node, moduleImport.FullName, cancellationToken); @@ -65,7 +61,7 @@ public async Task HandleFromImportAsync(FromImportStatement node, Cancella return false; } - private void ImportMembersFromSelf(FromImportStatement node) { + private async Task ImportMembersFromSelfAsync(FromImportStatement node, CancellationToken cancellationToken = default) { var names = node.Names; var asNames = node.AsNames; @@ -84,6 +80,14 @@ private void ImportMembersFromSelf(FromImportStatement node) { var memberName = memberReference.Name; var member = Module.GetMember(importName); + if (member == null && Eval.Module == Module) { + // We are still evaluating this module so members are not complete yet. + // Consider 'from . import path as path' in os.pyi in typeshed. + var import = ModuleResolution.CurrentPathResolver.GetModuleImportFromModuleName($"{Module.Name}.{importName}"); + if (!string.IsNullOrEmpty(import?.FullName)) { + member = await ModuleResolution.ImportModuleAsync(import.FullName, cancellationToken); + } + } Eval.DeclareVariable(memberName, member ?? Eval.UnknownType, VariableSource.Declaration, Eval.GetLoc(names[i])); } } diff --git a/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs b/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs index 56c86decf..6f773a0f3 100644 --- a/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs +++ b/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs @@ -71,12 +71,14 @@ private PathResolverSnapshot(PythonLanguageVersion pythonLanguageVersion, string public IEnumerable GetAllModuleNames() => GetModuleNames(_roots.Prepend(_nonRooted).Append(_builtins)); public IEnumerable GetInterpreterModuleNames() => GetModuleNames(_roots.Skip(_userRootsCount).Append(_builtins)); - private static IEnumerable GetModuleNames(IEnumerable roots) => roots - .SelectMany(r => r.TraverseBreadthFirst(n => n.IsModule ? Enumerable.Empty() : n.Children)) - .Where(n => n.IsModule) + private IEnumerable GetModuleNames(IEnumerable roots) { + var builtins = new HashSet(_builtins.Children); + return roots.SelectMany(r => r.TraverseBreadthFirst(n => n.IsModule? Enumerable.Empty() : n.Children)) + .Where(n => n.IsModule || builtins.Contains(n)) .Select(n => n.FullModuleName); + } - public ModuleImport GetModuleImportFromModuleName(in string fullModuleName) { + public ModuleImport GetModuleImportFromModuleName(in string fullModuleName) { foreach (var root in _roots) { var node = root; var matched = true; diff --git a/src/LanguageServer/Impl/Completion/CompletionSource.cs b/src/LanguageServer/Impl/Completion/CompletionSource.cs index 5ab711280..532f27a8f 100644 --- a/src/LanguageServer/Impl/Completion/CompletionSource.cs +++ b/src/LanguageServer/Impl/Completion/CompletionSource.cs @@ -38,14 +38,11 @@ public async Task GetCompletionsAsync(IDocumentAnalysis analys switch (expression) { case MemberExpression me when me.Target != null && me.DotIndex > me.StartIndex && context.Position > me.DotIndex: return new CompletionResult(await ExpressionCompletion.GetCompletionsFromMembersAsync(me.Target, scope, context, cancellationToken)); - case ConstantExpression ce when ce.Value is double || ce.Value is float: + case ConstantExpression ce1 when ce1.Value is double || ce1.Value is float: // no completions on integer ., the user is typing a float - return CompletionResult.Empty; - case ConstantExpression ce when ce.Value is string: + case ConstantExpression ce2 when ce2.Value is string: // no completions in strings - return CompletionResult.Empty; case null when context.Ast.IsInsideComment(context.Location): - return CompletionResult.Empty; case null when context.Ast.IsInsideString(context.Location): return CompletionResult.Empty; } diff --git a/src/LanguageServer/Impl/Completion/ImportCompletion.cs b/src/LanguageServer/Impl/Completion/ImportCompletion.cs index 3b458dc68..171df274f 100644 --- a/src/LanguageServer/Impl/Completion/ImportCompletion.cs +++ b/src/LanguageServer/Impl/Completion/ImportCompletion.cs @@ -116,16 +116,8 @@ public static CompletionResult GetCompletionsInFromImport(FromImportStatement fr } private static IEnumerable GetImportsFromModuleName(IEnumerable nameExpressions, CompletionContext context) { - IReadOnlyList items; var names = nameExpressions.TakeWhile(n => n.StartIndex <= context.Position).Select(n => n.Name).ToArray(); - if (names.Length <= 1) { - var mres = context.Analysis.Document.Interpreter.ModuleResolution; - var importable = mres.CurrentPathResolver.GetAllModuleNames(); - items = importable.Select(m => CompletionItemSource.CreateCompletionItem(m, CompletionItemKind.Module)).ToArray(); - } else { - items = GetChildModules(names, context); - } - return items; + return names.Length <= 1 ? GetAllImportableModules(context) : GetChildModules(names, context); } private static IEnumerable GetModuleMembers(IEnumerable nameExpressions, CompletionContext context) { @@ -140,7 +132,7 @@ private static IEnumerable GetModuleMembers(IEnumerable GetAllImportableModules(CompletionContext context) { var mres = context.Analysis.Document.Interpreter.ModuleResolution; - var modules = mres.CurrentPathResolver.GetAllModuleNames(); + var modules = mres.CurrentPathResolver.GetAllModuleNames().Distinct(); return modules.Select(n => CompletionItemSource.CreateCompletionItem(n, CompletionItemKind.Module)); } diff --git a/src/LanguageServer/Impl/Implementation/Server.Editor.cs b/src/LanguageServer/Impl/Implementation/Server.Editor.cs index 10720c56f..e83214514 100644 --- a/src/LanguageServer/Impl/Implementation/Server.Editor.cs +++ b/src/LanguageServer/Impl/Implementation/Server.Editor.cs @@ -44,11 +44,6 @@ public async Task Completion(CompletionParams @params, Cancellat return res; } - public Task CompletionItemResolve(CompletionItem item, CancellationToken token) { - // TODO: Fill out missing values in item - return Task.FromResult(item); - } - public async Task Hover(TextDocumentPositionParams @params, CancellationToken cancellationToken) { var uri = @params.textDocument.uri; _log?.Log(TraceEventType.Verbose, $"Hover in {uri} at {@params.position}"); diff --git a/src/LanguageServer/Impl/LanguageServer.cs b/src/LanguageServer/Impl/LanguageServer.cs index 71153f0f4..ebc7a909e 100644 --- a/src/LanguageServer/Impl/LanguageServer.cs +++ b/src/LanguageServer/Impl/LanguageServer.cs @@ -238,10 +238,6 @@ public async Task Completion(JToken token, CancellationToken can return await _server.Completion(ToObject(token), GetToken(cancellationToken)); } - [JsonRpcMethod("completionItem/resolve")] - public Task CompletionItemResolve(JToken token, CancellationToken cancellationToken) - => _server.CompletionItemResolve(ToObject(token), GetToken(cancellationToken)); - [JsonRpcMethod("textDocument/hover")] public async Task Hover(JToken token, CancellationToken cancellationToken) { await _prioritizer.DefaultPriorityAsync(cancellationToken); diff --git a/src/LanguageServer/Test/CompletionTests.cs b/src/LanguageServer/Test/CompletionTests.cs index f5a83b82a..14e1b6246 100644 --- a/src/LanguageServer/Test/CompletionTests.cs +++ b/src/LanguageServer/Test/CompletionTests.cs @@ -17,7 +17,9 @@ using System.Linq; using System.Threading.Tasks; using FluentAssertions; +using FluentAssertions.Common; using Microsoft.Python.Analysis.Types; +using Microsoft.Python.Core; using Microsoft.Python.Core.Text; using Microsoft.Python.LanguageServer.Completion; using Microsoft.Python.LanguageServer.Protocol; @@ -791,7 +793,7 @@ import os result.Should().HaveLabels("path", @"devnull", "SEEK_SET", @"curdir"); } - [DataRow(false), Ignore("https://github.com/Microsoft/python-language-server/issues/574")] + [DataRow(false)] [DataRow(true)] [DataTestMethod, Priority(0)] public async Task OsPathMembers(bool is3x) { @@ -805,5 +807,16 @@ import os var result = await cs.GetCompletionsAsync(analysis, new SourceLocation(3, 9)); result.Should().HaveLabels("split", @"getsize", @"islink", @"abspath"); } + + [TestMethod, Priority(0)] + public async Task NoDuplicateMembers() { + const string code = @"import sy"; + var analysis = await GetAnalysisAsync(code); + var cs = new CompletionSource(new PlainTextDocumentationSource(), ServerSettings.completion); + + var result = await cs.GetCompletionsAsync(analysis, new SourceLocation(1, 10)); + result.Completions.Count(c => c.label.EqualsOrdinal(@"sys")).Should().Be(1); + result.Completions.Count(c => c.label.EqualsOrdinal(@"sysconfig")).Should().Be(1); + } } }