Skip to content
This repository was archived by the owner on Nov 4, 2024. It is now read-only.

Commit 8b049da

Browse files
author
Mikhail Arkhipov
authored
Fix goto definition in stubbed modules (microsoft#659)
* Merge part I * Merge part II * Fix nav to builtins
1 parent 310471c commit 8b049da

File tree

11 files changed

+180
-61
lines changed

11 files changed

+180
-61
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,11 @@ public IMember LookupNameInScopes(string name, out IScope scope, LookupOptions o
7878
scope = scopes.FirstOrDefault(s => s.Variables.Contains(name));
7979
var value = scope?.Variables[name].Value;
8080
if (value == null) {
81-
if (Module != Interpreter.ModuleResolution.BuiltinsModule && options.HasFlag(LookupOptions.Builtins)) {
82-
value = Interpreter.ModuleResolution.BuiltinsModule.GetMember(name);
81+
var builtins = Interpreter.ModuleResolution.BuiltinsModule;
82+
value = Interpreter.ModuleResolution.BuiltinsModule.GetMember(name);
83+
if (Module != builtins && options.HasFlag(LookupOptions.Builtins)) {
84+
value = builtins.GetMember(name);
85+
scope = builtins.GlobalScope;
8386
}
8487
}
8588

@@ -100,7 +103,7 @@ public IPythonType GetTypeFromAnnotation(Expression expr, LookupOptions options
100103
// Try generics
101104
var target = GetValueFromExpression(indexExpr.Target);
102105
var result = GetValueFromGeneric(target, indexExpr);
103-
if(result != null) {
106+
if (result != null) {
104107
return result.GetPythonType();
105108
}
106109
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,15 @@ private void MergeStub() {
108108

109109
// Get documentation from the current type, if any, since stubs
110110
// typically do not contain documentation while scraped code does.
111-
member?.GetPythonType()?.TransferDocumentation(stubMember.GetPythonType());
111+
member?.GetPythonType()?.TransferDocumentationAndLocation(stubMember.GetPythonType());
112112
cls.AddMember(name, stubMember, overwrite: true);
113113
}
114114
} else {
115115
// Re-declare variable with the data from the stub unless member is a module.
116116
// Modules members that are modules should remain as they are, i.e. os.path
117117
// should remain library with its own stub attached.
118118
if (!stubType.IsUnknown() && !(stubType is IPythonModule)) {
119-
sourceType.TransferDocumentation(stubType);
119+
sourceType.TransferDocumentationAndLocation(stubType);
120120
// TODO: choose best type between the scrape and the stub. Stub probably should always win.
121121
var source = Eval.CurrentScope.Variables[v.Name]?.Source ?? VariableSource.Declaration;
122122
Eval.DeclareVariable(v.Name, v.Value, source, LocationInfo.Empty, overwrite: true);

src/Analysis/Ast/Impl/Extensions/PythonTypeExtensions.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,23 @@ public static class PythonTypeExtensions {
2121
public static bool IsUnknown(this IPythonType value) =>
2222
value == null || (value.TypeId == BuiltinTypeId.Unknown && value.MemberType == PythonMemberType.Unknown && value.Name.Equals("Unknown"));
2323

24-
public static bool IsGenericParameter(this IPythonType value)
24+
public static bool IsGenericParameter(this IPythonType value)
2525
=> value is IGenericTypeParameter;
2626

2727
public static bool IsGeneric(this IPythonType value)
2828
=> value is IGenericTypeParameter || value is IGenericType || (value is IPythonClassType c && c.IsGeneric());
2929

30-
public static void TransferDocumentation(this IPythonType src, IPythonType dst) {
30+
public static void TransferDocumentationAndLocation(this IPythonType src, IPythonType dst) {
3131
if (src != null && dst is PythonType pt) {
3232
pt.TrySetTypeId(dst.TypeId);
3333
var documentation = src.Documentation;
34-
if (string.IsNullOrEmpty(pt.Documentation) && !string.IsNullOrEmpty(documentation)) {
35-
pt.SetDocumentationProvider(_ => documentation);
34+
if (!string.IsNullOrEmpty(documentation)) {
35+
if (!string.IsNullOrEmpty(documentation)) {
36+
pt.SetDocumentation(documentation);
37+
}
38+
}
39+
if (src is ILocatedMember lm) {
40+
pt.SetLocation(lm.Location);
3641
}
3742
}
3843
}

src/Analysis/Ast/Impl/Modules/PythonModule.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ internal PythonModule(ModuleCreationOptions creationOptions, IServiceContainer s
103103
Uri = uri;
104104
FilePath = creationOptions.FilePath ?? uri?.LocalPath;
105105
Stub = creationOptions.Stub;
106+
if (Stub is PythonModule stub && ModuleType != ModuleType.Stub) {
107+
stub.PrimaryModule = this;
108+
}
106109

107110
if (ModuleType == ModuleType.Specialized || ModuleType == ModuleType.Unresolved) {
108111
ContentState = State.Analyzed;
@@ -199,6 +202,13 @@ public async Task LoadAndAnalyzeAsync(CancellationToken cancellationToken = defa
199202
await Services.GetService<IPythonAnalyzer>().GetAnalysisAsync(this, -1, cancellationToken);
200203
}
201204

205+
/// <summary>
206+
/// If module is a stub points to the primary module.
207+
/// Typically used in code navigation scenarios when user
208+
/// wants to see library code and not a stub.
209+
/// </summary>
210+
public IPythonModule PrimaryModule { get; internal set; }
211+
202212
protected virtual string LoadContent() {
203213
if (ContentState < State.Loading) {
204214
ContentState = State.Loading;

src/Analysis/Ast/Impl/Types/Definitions/IPythonClassMember.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace Microsoft.Python.Analysis.Types {
1717
/// <summary>
1818
/// Represents member of a class.
1919
/// </summary>
20-
public interface IPythonClassMember : IPythonType {
20+
public interface IPythonClassMember : IPythonType, ILocatedMember {
2121
IPythonType DeclaringType { get; }
2222
}
2323
}

src/Analysis/Ast/Impl/Types/Definitions/IPythonClassType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace Microsoft.Python.Analysis.Types {
2020
/// <summary>
2121
/// Represents Python class type definition.
2222
/// </summary>
23-
public interface IPythonClassType : IPythonType {
23+
public interface IPythonClassType : IPythonType, ILocatedMember {
2424
/// <summary>
2525
/// Class definition node in the AST.
2626
/// </summary>

src/Analysis/Ast/Impl/Types/Definitions/IPythonModule.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,12 @@ public interface IPythonModule : IPythonType, IPythonFile, ILocatedMember {
5555
/// analysis until later time, when module members are actually needed.
5656
/// </summary>
5757
Task LoadAndAnalyzeAsync(CancellationToken cancellationToken = default);
58+
59+
/// <summary>
60+
/// If module is a stub points to the primary module.
61+
/// Typically used in code navigation scenarios when user
62+
/// wants to see library code and not a stub.
63+
/// </summary>
64+
IPythonModule PrimaryModule { get; }
5865
}
5966
}

src/Analysis/Ast/Impl/Types/PythonFunctionType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ internal override void SetDocumentationProvider(Func<string, string> provider) {
116116
#region IPythonFunction
117117
public FunctionDefinition FunctionDefinition { get; }
118118
public IPythonType DeclaringType { get; }
119-
public override string Documentation => _doc ?? _overloads.FirstOrDefault()?.Documentation;
119+
public override string Documentation => _doc ?? base.Documentation ?? _overloads.FirstOrDefault()?.Documentation;
120120
public virtual bool IsClassMethod { get; private set; }
121121
public virtual bool IsStatic { get; private set; }
122122
public override bool IsAbstract => _isAbstract;

src/Analysis/Ast/Impl/Types/PythonType.cs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ namespace Microsoft.Python.Analysis.Types {
2323
[DebuggerDisplay("{Name}")]
2424
internal class PythonType : IPythonType, ILocatedMember, IHasQualifiedName, IEquatable<IPythonType> {
2525
private readonly object _lock = new object();
26-
private readonly Func<string, LocationInfo> _locationProvider;
2726
private readonly string _name;
2827
private Func<string, string> _documentationProvider;
28+
private Func<string, LocationInfo> _locationProvider;
29+
private string _documentation;
30+
private LocationInfo _location;
2931
private Dictionary<string, IMember> _members;
3032
private BuiltinTypeId _typeId;
3133
private bool _readonly;
@@ -41,26 +43,32 @@ public PythonType(
4143
string documentation,
4244
LocationInfo location,
4345
BuiltinTypeId typeId = BuiltinTypeId.Unknown
44-
) : this(name, declaringModule, _ => documentation, _ => location ?? LocationInfo.Empty, typeId) { }
46+
) : this(name, declaringModule, typeId) {
47+
_documentation = documentation;
48+
_location = location;
49+
}
4550

4651
public PythonType(
47-
string name,
48-
IPythonModule declaringModule,
49-
Func<string, string> documentationProvider,
50-
Func<string, LocationInfo> locationProvider,
51-
BuiltinTypeId typeId = BuiltinTypeId.Unknown
52-
) {
53-
_name = name ?? throw new ArgumentNullException(nameof(name));
54-
DeclaringModule = declaringModule;
52+
string name,
53+
IPythonModule declaringModule,
54+
Func<string, string> documentationProvider,
55+
Func<string, LocationInfo> locationProvider,
56+
BuiltinTypeId typeId = BuiltinTypeId.Unknown
57+
) : this(name, declaringModule, typeId) {
5558
_documentationProvider = documentationProvider;
5659
_locationProvider = locationProvider;
57-
_typeId = typeId;
60+
}
61+
62+
private PythonType(string name, IPythonModule declaringModule, BuiltinTypeId typeId = BuiltinTypeId.Unknown) {
63+
_name = name ?? throw new ArgumentNullException(nameof(name));
64+
DeclaringModule = declaringModule;
65+
_typeId = typeId; _typeId = typeId;
5866
}
5967

6068
#region IPythonType
6169

6270
public virtual string Name => TypeId == BuiltinTypeId.Ellipsis ? "..." : _name;
63-
public virtual string Documentation => _documentationProvider?.Invoke(Name);
71+
public virtual string Documentation => _documentationProvider != null ? _documentationProvider.Invoke(Name) : _documentation;
6472
public IPythonModule DeclaringModule { get; }
6573
public virtual PythonMemberType MemberType => _typeId.GetMemberId();
6674
public virtual BuiltinTypeId TypeId => _typeId;
@@ -96,7 +104,8 @@ public virtual IMember Call(IPythonInstance instance, string memberName, IArgume
96104
#endregion
97105

98106
#region ILocatedMember
99-
public virtual LocationInfo Location => _locationProvider?.Invoke(Name) ?? LocationInfo.Empty;
107+
public virtual LocationInfo Location => _locationProvider != null
108+
? _locationProvider?.Invoke(Name) : _location ?? LocationInfo.Empty;
100109
#endregion
101110

102111
#region IHasQualifiedName
@@ -119,6 +128,8 @@ internal bool TrySetTypeId(BuiltinTypeId typeId) {
119128
}
120129

121130
internal virtual void SetDocumentationProvider(Func<string, string> provider) => _documentationProvider = provider;
131+
internal virtual void SetDocumentation(string documentation) => _documentation = documentation;
132+
internal virtual void SetLocation(LocationInfo location) => _location = location;
122133

123134
internal void AddMembers(IEnumerable<IVariable> variables, bool overwrite) {
124135
lock (_lock) {

src/LanguageServer/Impl/Sources/DefinitionSource.cs

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

1616
using Microsoft.Python.Analysis;
17-
using Microsoft.Python.Analysis.Analyzer;
1817
using Microsoft.Python.Analysis.Analyzer.Expressions;
1918
using Microsoft.Python.Analysis.Documents;
2019
using Microsoft.Python.Analysis.Modules;
@@ -40,80 +39,106 @@ public Reference FindDefinition(IDocumentAnalysis analysis, SourceLocation locat
4039

4140
var eval = analysis.ExpressionEvaluator;
4241
using (eval.OpenScope(analysis.Document, exprScope)) {
42+
43+
// First try variables, except in imports
44+
if (expr is NameExpression nex && !string.IsNullOrEmpty(nex.Name) &&
45+
!(statement is ImportStatement) && !(statement is FromImportStatement)) {
46+
var m = eval.LookupNameInScopes(nex.Name, out var scope);
47+
if (m != null && scope.Variables[nex.Name] is IVariable v) {
48+
if (CanNavigateToModule(v.Value.GetPythonType()?.DeclaringModule, analysis)) {
49+
return new Reference { range = v.Location.Span, uri = v.Location.DocumentUri };
50+
}
51+
}
52+
}
53+
4354
var value = eval.GetValueFromExpression(expr);
44-
return FromMember(value, expr, eval);
55+
return FromMember(value, expr, statement, analysis);
4556
}
4657
}
4758

48-
private Reference FromMember(IMember value, Expression expr, IExpressionEvaluator eval) {
59+
private Reference FromMember(IMember value, Expression expr, Node statement, IDocumentAnalysis analysis) {
4960
Node node = null;
5061
IPythonModule module = null;
62+
LocationInfo location = null;
63+
var eval = analysis.ExpressionEvaluator;
5164

5265
switch (value) {
5366
case IPythonClassType cls:
5467
node = cls.ClassDefinition;
5568
module = cls.DeclaringModule;
69+
location = cls.Location;
5670
break;
5771
case IPythonFunctionType fn:
5872
node = fn.FunctionDefinition;
5973
module = fn.DeclaringModule;
74+
location = fn.Location;
6075
break;
6176
case IPythonPropertyType prop:
6277
node = prop.FunctionDefinition;
6378
module = prop.DeclaringModule;
79+
location = prop.Location;
6480
break;
65-
case IPythonModule mod: {
66-
var member = eval.LookupNameInScopes(mod.Name, out var scope);
67-
if (member != null && scope != null) {
68-
var v = scope.Variables[mod.Name];
69-
if (v != null) {
70-
return new Reference { range = v.Location.Span, uri = v.Location.DocumentUri };
71-
}
72-
}
73-
break;
74-
}
75-
case IPythonInstance instance when instance.Type is IPythonFunctionType ft: {
76-
node = ft.FunctionDefinition;
77-
module = ft.DeclaringModule;
78-
break;
79-
}
80-
case IPythonInstance _ when expr is NameExpression nex: {
81-
var member = eval.LookupNameInScopes(nex.Name, out var scope);
82-
if (member != null && scope != null) {
83-
var v = scope.Variables[nex.Name];
84-
if (v != null) {
85-
return new Reference { range = v.Location.Span, uri = v.Location.DocumentUri };
86-
}
81+
case IPythonModule mod:
82+
return HandleModule(mod, analysis, statement);
83+
case IPythonInstance instance when instance.Type is IPythonFunctionType ft:
84+
node = ft.FunctionDefinition;
85+
module = ft.DeclaringModule;
86+
location = ft.Location;
87+
break;
88+
case IPythonInstance instance when instance.Type is IPythonFunctionType ft:
89+
node = ft.FunctionDefinition;
90+
module = ft.DeclaringModule;
91+
break;
92+
case IPythonInstance _ when expr is NameExpression nex:
93+
var m1 = eval.LookupNameInScopes(nex.Name, out var scope);
94+
if (m1 != null && scope != null) {
95+
var v = scope.Variables[nex.Name];
96+
if (v != null) {
97+
return new Reference { range = v.Location.Span, uri = v.Location.DocumentUri };
8798
}
88-
break;
8999
}
100+
break;
90101
case IPythonInstance _ when expr is MemberExpression mex: {
91102
var target = eval.GetValueFromExpression(mex.Target);
92103
var type = target?.GetPythonType();
93-
var member = type?.GetMember(mex.Name);
94-
if (member is IPythonInstance v) {
104+
var m2 = type?.GetMember(mex.Name);
105+
if (m2 is IPythonInstance v) {
95106
return new Reference { range = v.Location.Span, uri = v.Location.DocumentUri };
96107
}
97-
return FromMember(member, null, eval);
108+
return FromMember(m2, null, statement, analysis);
98109
}
99110
}
100111

101-
if (node != null && CanNavigateToModule(module) && module is IDocument doc) {
112+
module = module?.ModuleType == ModuleType.Stub ? module.PrimaryModule : module;
113+
if (node != null && module is IDocument doc && CanNavigateToModule(module, analysis)) {
102114
return new Reference {
103-
range = node.GetSpan(doc.GetAnyAst()), uri = doc.Uri
115+
range = location?.Span ?? node.GetSpan(doc.GetAnyAst()), uri = doc.Uri
104116
};
105117
}
106118

107119
return null;
108120
}
109121

110-
private static bool CanNavigateToModule(IPythonModule m) {
122+
private static Reference HandleModule(IPythonModule module, IDocumentAnalysis analysis, Node statement) {
123+
// If we are in import statement, open the module source if available.
124+
if (statement is ImportStatement || statement is FromImportStatement) {
125+
if (module.Uri != null && CanNavigateToModule(module, analysis)) {
126+
return new Reference { range = default, uri = module.Uri };
127+
}
128+
}
129+
return null;
130+
}
131+
132+
private static bool CanNavigateToModule(IPythonModule m, IDocumentAnalysis analysis) {
133+
if (m == null) {
134+
return false;
135+
}
136+
var canNavigate = m.ModuleType == ModuleType.User || m.ModuleType == ModuleType.Package || m.ModuleType == ModuleType.Library;
111137
#if DEBUG
112138
// Allow navigation anywhere in debug.
113-
return m.ModuleType != ModuleType.Specialized && m.ModuleType != ModuleType.Unresolved;
114-
#else
115-
return m.ModuleType == ModuleType.User || m.ModuleType == ModuleType.Package || m.ModuleType == ModuleType.Library;
139+
canNavigate |= m.ModuleType == ModuleType.Stub || m.ModuleType == ModuleType.Compiled;
116140
#endif
141+
return canNavigate;
117142
}
118143
}
119144
}

0 commit comments

Comments
 (0)