diff --git a/src/Analysis/Ast/Impl/Modules/BuiltinsPythonModule.cs b/src/Analysis/Ast/Impl/Modules/BuiltinsPythonModule.cs index 28de09602..ee31598bc 100644 --- a/src/Analysis/Ast/Impl/Modules/BuiltinsPythonModule.cs +++ b/src/Analysis/Ast/Impl/Modules/BuiltinsPythonModule.cs @@ -65,6 +65,11 @@ private void SpecializeTypes() { continue; } + if (biType.IsUnknown()) { + // Under no circumstances we modify the unknown type. + continue; + } + if (biType.IsHidden) { _hiddenNames.Add(biType.Name); } @@ -98,6 +103,8 @@ private void SpecializeTypes() { biType.AddMember(@"__iter__", BuiltinsSpecializations.__iter__(Interpreter, typeId), true); } break; + case BuiltinTypeId.Unknown: + break; default: biType.TrySetTypeId(typeId); switch (typeId) { diff --git a/src/Analysis/Ast/Impl/Types/PythonType.cs b/src/Analysis/Ast/Impl/Types/PythonType.cs index 3c23c95fa..a18cc7d9e 100644 --- a/src/Analysis/Ast/Impl/Types/PythonType.cs +++ b/src/Analysis/Ast/Impl/Types/PythonType.cs @@ -62,7 +62,7 @@ public PythonType( private PythonType(string name, IPythonModule declaringModule, BuiltinTypeId typeId = BuiltinTypeId.Unknown) { _name = name ?? throw new ArgumentNullException(nameof(name)); DeclaringModule = declaringModule; - _typeId = typeId; _typeId = typeId; + _typeId = typeId; } #region IPythonType diff --git a/src/Analysis/Ast/Test/BasicTests.cs b/src/Analysis/Ast/Test/BasicTests.cs index 2be4d75df..5b451c084 100644 --- a/src/Analysis/Ast/Test/BasicTests.cs +++ b/src/Analysis/Ast/Test/BasicTests.cs @@ -85,6 +85,23 @@ import sys .And.HaveVariable("x").OfType(BuiltinTypeId.List); } + [DataRow(true, true)] + [DataRow(false, true)] + [DataRow(true, false)] + [DataRow(false, false)] + [DataTestMethod, Priority(0)] + public async Task UnknownType(bool isPython3X, bool isAnaconda) { + const string code = @"x = 1"; + + var configuration = isPython3X + ? isAnaconda ? PythonVersions.LatestAnaconda3X : PythonVersions.LatestAvailable3X + : isAnaconda ? PythonVersions.LatestAnaconda2X : PythonVersions.LatestAvailable2X; + var analysis = await GetAnalysisAsync(code, configuration); + + var unkType = analysis.Document.Interpreter.UnknownType; + unkType.TypeId.Should().Be(BuiltinTypeId.Unknown); + } + [DataRow(true, true)] [DataRow(false, true)] [DataRow(true, false)] diff --git a/src/LanguageServer/Impl/Sources/DefinitionSource.cs b/src/LanguageServer/Impl/Sources/DefinitionSource.cs index 9e3e15429..4df412c85 100644 --- a/src/LanguageServer/Impl/Sources/DefinitionSource.cs +++ b/src/LanguageServer/Impl/Sources/DefinitionSource.cs @@ -20,6 +20,7 @@ using Microsoft.Python.Analysis.Modules; using Microsoft.Python.Analysis.Types; using Microsoft.Python.Analysis.Values; +using Microsoft.Python.Core; using Microsoft.Python.Core.Text; using Microsoft.Python.LanguageServer.Completion; using Microsoft.Python.LanguageServer.Protocol; @@ -55,23 +56,10 @@ public Reference FindDefinition(IDocumentAnalysis analysis, SourceLocation locat } var value = eval.GetValueFromExpression(expr); - if (value.IsUnknown()) { - // If this is 'import A as B' A is not declared as a variable, so try modules. - string moduleName = null; - if (!string.IsNullOrEmpty(name)) { - switch (statement) { - case ImportStatement imp when imp.Names.Any(x => x?.MakeString() == name): - case FromImportStatement fimp when fimp.Root.Names.Any(x => x?.Name == name): - moduleName = name; - break; - } - - if (moduleName != null) { - var module = analysis.Document.Interpreter.ModuleResolution.GetImportedModule(moduleName); - if (module != null && CanNavigateToModule(module, analysis)) { - return new Reference {range = default, uri = module.Uri}; - } - } + if (value.IsUnknown() && !string.IsNullOrEmpty(name)) { + var reference = FromImport(statement, name, analysis, out value); + if (reference != null) { + return reference; } } @@ -82,6 +70,48 @@ public Reference FindDefinition(IDocumentAnalysis analysis, SourceLocation locat } } + private Reference FromImport(Node statement, string name, IDocumentAnalysis analysis, out IMember value) { + value = null; + string moduleName = null; + switch (statement) { + // In 'import A as B' A is not declared as a variable, so try locating B. + case ImportStatement imp when imp.Names.Any(x => x?.MakeString() == name): + case FromImportStatement fimp when fimp.Root.Names.Any(x => x?.Name == name): + moduleName = name; + break; + } + + if (moduleName != null) { + var module = analysis.Document.Interpreter.ModuleResolution.GetImportedModule(moduleName); + if (module != null && CanNavigateToModule(module, analysis)) { + return new Reference { range = default, uri = module.Uri }; + } + } + + // Perhaps it is a member such as A in 'from X import A as B' + switch (statement) { + case ImportStatement imp: { + // Import A as B + var index = imp.Names.IndexOf(x => x?.MakeString() == name); + if (index >= 0 && index < imp.AsNames.Count) { + value = analysis.ExpressionEvaluator.GetValueFromExpression(imp.AsNames[index]); + return null; + } + break; + } + case FromImportStatement fimp: { + // From X import A as B + var index = fimp.Names.IndexOf(x => x?.Name == name); + if (index >= 0 && index < fimp.AsNames.Count) { + value = analysis.ExpressionEvaluator.GetValueFromExpression(fimp.AsNames[index]); + return null; + } + break; + } + } + return null; + } + private Reference FromMember(IMember value, Expression expr, Node statement, IDocumentAnalysis analysis) { Node node = null; IPythonModule module = null; diff --git a/src/LanguageServer/Test/GoToDefinitionTests.cs b/src/LanguageServer/Test/GoToDefinitionTests.cs index 25f02508c..21f6ace79 100644 --- a/src/LanguageServer/Test/GoToDefinitionTests.cs +++ b/src/LanguageServer/Test/GoToDefinitionTests.cs @@ -152,6 +152,19 @@ public async Task GotoModuleSourceFromImport() { reference.uri.AbsolutePath.Should().NotContain("pyi"); } + [TestMethod, Priority(0)] + public async Task GotoModuleSourceFromImportAs() { + const string code = @"from logging import RootLogger as rl"; + var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X); + var ds = new DefinitionSource(); + + var reference = ds.FindDefinition(analysis, new SourceLocation(1, 23)); + reference.Should().NotBeNull(); + reference.range.start.line.Should().BeGreaterThan(500); + reference.uri.AbsolutePath.Should().Contain("logging"); + reference.uri.AbsolutePath.Should().NotContain("pyi"); + } + [TestMethod, Priority(0)] public async Task GotoBuiltinObject() { const string code = @"