Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit 119e1b2

Browse files
author
Mikhail Arkhipov
authored
Improve goto def in import and fix class base processing (#1529)
* Update signing project to 3.0 * Upgrade to .NET 3.0 (#1521) * Upgrade to 3.0 * Get back to 2.0 * Add global.json for .NET SDK * Ensure FileInfo is not relative in DirectoryInfoProxy (#1519) * Ensure FileInfo is not relative in DirectoryInfoProxy * Add tests * Hadle import * Update dependencies (#1526) * Handle from import * Add test * Fix bad find and replace in AssertionsUtilities (#1527) * Merge issues * Add test (fails) * using * Fix merge of classes * Fix regression in bases Split merging into separate class * Fix null refs * Make common CanUpdateAnalysis check
1 parent c20485b commit 119e1b2

File tree

19 files changed

+654
-332
lines changed

19 files changed

+654
-332
lines changed

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

Lines changed: 5 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,18 @@
1313
// See the Apache Version 2.0 License for specific language governing
1414
// permissions and limitations under the License.
1515

16-
using System.Collections.Generic;
17-
using System.Diagnostics;
18-
using System.Linq;
19-
using System.Threading;
2016
using Microsoft.Python.Analysis.Analyzer.Evaluation;
2117
using Microsoft.Python.Analysis.Documents;
22-
using Microsoft.Python.Analysis.Modules;
23-
using Microsoft.Python.Analysis.Specializations.Typing;
2418
using Microsoft.Python.Analysis.Types;
2519
using Microsoft.Python.Analysis.Types.Collections;
2620
using Microsoft.Python.Analysis.Values;
2721
using Microsoft.Python.Core;
2822
using Microsoft.Python.Core.Diagnostics;
2923
using Microsoft.Python.Parsing.Ast;
24+
using System.Collections.Generic;
25+
using System.Diagnostics;
26+
using System.Linq;
27+
using System.Threading;
3028

3129
namespace Microsoft.Python.Analysis.Analyzer {
3230
[DebuggerDisplay("{Module.Name} : {Module.ModuleType}")]
@@ -205,7 +203,7 @@ public void Complete() {
205203

206204
SymbolTable.EvaluateAll();
207205
SymbolTable.ReplacedByStubs.Clear();
208-
MergeStub();
206+
new StubMerger(Eval).MergeStub(_stubAnalysis, _cancellationToken);
209207

210208
if (_allIsUsable && _allReferencesCount >= 1 && GlobalScope.Variables.TryGetVariable(AllVariableName, out var variable)
211209
&& variable?.Value is IPythonCollection collection && collection.IsExact) {
@@ -221,217 +219,5 @@ public void Complete() {
221219

222220
public GlobalScope GlobalScope => Eval.GlobalScope;
223221
public IReadOnlyList<string> StarImportMemberNames { get; private set; }
224-
225-
/// <summary>
226-
/// Merges data from stub with the data from the module.
227-
/// </summary>
228-
/// <remarks>
229-
/// Functions are taken from the stub by the function walker since
230-
/// information on the return type is needed during the analysis walk.
231-
/// However, if the module is compiled (scraped), it often lacks some
232-
/// of the definitions. Stub may contains those so we need to merge it in.
233-
/// </remarks>
234-
private void MergeStub() {
235-
_cancellationToken.ThrowIfCancellationRequested();
236-
237-
if (Module.ModuleType == ModuleType.User || Module.ModuleType == ModuleType.Stub) {
238-
return;
239-
}
240-
// No stub, no merge.
241-
if (_stubAnalysis.IsEmpty()) {
242-
return;
243-
}
244-
// TODO: figure out why module is getting analyzed before stub.
245-
// https://github.com/microsoft/python-language-server/issues/907
246-
// Debug.Assert(!(_stubAnalysis is EmptyAnalysis));
247-
248-
// Note that scrape can pick up more functions than the stub contains
249-
// Or the stub can have definitions that scraping had missed. Therefore
250-
// merge is the combination of the two with the documentation coming
251-
// from the library source of from the scraped module.
252-
foreach (var v in _stubAnalysis.GlobalScope.Variables) {
253-
var stubType = v.Value.GetPythonType();
254-
if (stubType.IsUnknown()) {
255-
continue;
256-
}
257-
258-
var sourceVar = Eval.GlobalScope.Variables[v.Name];
259-
var sourceType = sourceVar?.Value.GetPythonType();
260-
261-
// If stub says 'Any' but we have better type, keep the current type.
262-
if (stubType.DeclaringModule is TypingModule && stubType.Name == "Any") {
263-
continue;
264-
}
265-
266-
if (sourceVar?.Source == VariableSource.Import &&
267-
sourceVar.GetPythonType()?.DeclaringModule.Stub != null) {
268-
// Keep imported types as they are defined in the library. For example,
269-
// 'requests' imports NullHandler as 'from logging import NullHandler'.
270-
// But 'requests' also declares NullHandler in its stub (but not in the main code)
271-
// and that declaration does not have documentation or location. Therefore avoid
272-
// taking types that are stub-only when similar type is imported from another
273-
// module that also has a stub.
274-
continue;
275-
}
276-
277-
TryReplaceMember(v, sourceType, stubType);
278-
}
279-
280-
UpdateVariables();
281-
}
282-
283-
private void TryReplaceMember(IVariable v, IPythonType sourceType, IPythonType stubType) {
284-
// If type does not exist in module, but exists in stub, declare it unless it is an import.
285-
// If types are the classes, take class from the stub, then add missing members.
286-
// Otherwise, replace type by one from the stub.
287-
switch (sourceType) {
288-
case null:
289-
// Nothing in sources, but there is type in the stub. Declare it.
290-
if (v.Source == VariableSource.Declaration || v.Source == VariableSource.Generic) {
291-
Eval.DeclareVariable(v.Name, v.Value, v.Source);
292-
}
293-
break;
294-
295-
case PythonClassType sourceClass when Module.Equals(sourceClass.DeclaringModule):
296-
// Transfer documentation first so we get class documentation
297-
// that came from class definition win over one that may
298-
// come from __init__ during the member merge below.
299-
TransferDocumentationAndLocation(sourceClass, stubType);
300-
301-
// Replace the class entirely since stub members may use generic types
302-
// and the class definition is important. We transfer missing members
303-
// from the original class to the stub.
304-
Eval.DeclareVariable(v.Name, v.Value, v.Source);
305-
306-
// Go through source class members and pick those that are
307-
// not present in the stub class.
308-
foreach (var name in sourceClass.GetMemberNames()) {
309-
310-
var sourceMember = sourceClass.GetMember(name);
311-
if (sourceMember.IsUnknown()) {
312-
continue; // Anything is better than unknowns.
313-
}
314-
var sourceMemberType = sourceMember?.GetPythonType();
315-
316-
var stubMember = stubType.GetMember(name);
317-
var stubMemberType = stubMember.GetPythonType();
318-
319-
// Don't augment types that do not come from this module.
320-
if (sourceType.IsBuiltin || stubType.IsBuiltin) {
321-
// If source type does not have an immediate member such as __init__() and
322-
// rather have it inherited from object, we do not want to use the inherited
323-
// since stub class may either have its own of inherits it from the object.
324-
continue;
325-
}
326-
327-
if (stubMemberType?.MemberType == PythonMemberType.Method && stubMemberType?.DeclaringModule.ModuleType == ModuleType.Builtins) {
328-
// Leave methods coming from object at the object and don't copy them into the derived class.
329-
}
330-
331-
// Get documentation from the current type, if any, since stubs
332-
// typically do not contain documentation while scraped code does.
333-
TransferDocumentationAndLocation(sourceMemberType, stubMemberType);
334-
335-
// If stub says 'Any' but we have better type, use member from the original class.
336-
if (stubMember != null && !(stubType.DeclaringModule is TypingModule && stubType.Name == "Any")) {
337-
continue; // Stub already have the member, don't replace.
338-
}
339-
340-
(stubType as PythonType)?.AddMember(name, stubMember, overwrite: true);
341-
}
342-
break;
343-
344-
case IPythonModule _:
345-
// We do not re-declare modules.
346-
break;
347-
348-
default:
349-
var stubModule = stubType.DeclaringModule;
350-
if (stubType is IPythonModule || stubModule.ModuleType == ModuleType.Builtins) {
351-
// Modules members that are modules should remain as they are, i.e. os.path
352-
// should remain library with its own stub attached.
353-
break;
354-
}
355-
// We do not re-declaring variables that are imported.
356-
if (v.Source == VariableSource.Declaration) {
357-
// Re-declare variable with the data from the stub.
358-
TransferDocumentationAndLocation(sourceType, stubType);
359-
// TODO: choose best type between the scrape and the stub. Stub probably should always win.
360-
var source = Eval.CurrentScope.Variables[v.Name]?.Source ?? v.Source;
361-
Eval.DeclareVariable(v.Name, v.Value, source);
362-
}
363-
364-
break;
365-
}
366-
}
367-
368-
private void UpdateVariables() {
369-
// Second pass: if we replaced any classes by new from the stub, we need to update
370-
// variables that may still be holding old content. For example, ctypes
371-
// declares 'c_voidp = c_void_p' so when we replace 'class c_void_p'
372-
// by class from the stub, we need to go and update 'c_voidp' variable.
373-
foreach (var v in GlobalScope.Variables) {
374-
var variableType = v.Value.GetPythonType();
375-
if (!variableType.DeclaringModule.Equals(Module) && !variableType.DeclaringModule.Equals(Module.Stub)) {
376-
continue;
377-
}
378-
// Check if type that the variable references actually declared here.
379-
var typeInScope = GlobalScope.Variables[variableType.Name].GetPythonType();
380-
if (typeInScope == null || variableType == typeInScope) {
381-
continue;
382-
}
383-
384-
if (v.Value == variableType) {
385-
Eval.DeclareVariable(v.Name, typeInScope, v.Source);
386-
} else if (v.Value is IPythonInstance) {
387-
Eval.DeclareVariable(v.Name, new PythonInstance(typeInScope), v.Source);
388-
}
389-
}
390-
}
391-
392-
private static void TransferDocumentationAndLocation(IPythonType s, IPythonType d) {
393-
if (s.IsUnknown() || s.IsBuiltin || d == null || d.IsBuiltin) {
394-
return; // Do not transfer location of unknowns or builtins
395-
}
396-
397-
if (d.DeclaringModule != s.DeclaringModule.Stub) {
398-
return; // Do not change unrelated types.
399-
}
400-
401-
// Documentation and location are always get transferred from module type
402-
// to the stub type and never the other way around. This makes sure that
403-
// we show documentation from the original module and goto definition
404-
// navigates to the module source and not to the stub.
405-
if (s != d && s is PythonType src && d is PythonType dst) {
406-
// If type is a class, then doc can either come from class definition node of from __init__.
407-
// If class has doc from the class definition, don't stomp on it.
408-
if (src is PythonClassType srcClass && dst is PythonClassType dstClass) {
409-
// Higher lever source wins
410-
if (srcClass.DocumentationSource == PythonClassType.ClassDocumentationSource.Class ||
411-
(srcClass.DocumentationSource == PythonClassType.ClassDocumentationSource.Init && dstClass.DocumentationSource == PythonClassType.ClassDocumentationSource.Base)) {
412-
dstClass.SetDocumentation(srcClass.Documentation);
413-
}
414-
} else {
415-
// Sometimes destination (stub type) already has documentation. This happens when stub type
416-
// is used to augment more than one type. For example, in threading module RLock stub class
417-
// replaces both RLock function and _RLock class making 'factory' function RLock to look
418-
// like a class constructor. Effectively a single stub type is used for more than one type
419-
// in the source and two source types may have different documentation. Thus transferring doc
420-
// from one source type affects documentation of another type. It may be better to clone stub
421-
// type and separate instances for separate source type, but for now we'll just avoid stomping
422-
// on the existing documentation.
423-
if (string.IsNullOrEmpty(dst.Documentation)) {
424-
var srcDocumentation = src.Documentation;
425-
if (!string.IsNullOrEmpty(srcDocumentation)) {
426-
dst.SetDocumentation(srcDocumentation);
427-
}
428-
}
429-
}
430-
431-
if (src.Location.IsValid) {
432-
dst.Location = src.Location;
433-
}
434-
}
435-
}
436222
}
437223
}

0 commit comments

Comments
 (0)