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

Commit e7f29d7

Browse files
author
Mikhail Arkhipov
authored
Handling dependencies in module persistence (#1471)
* Partial * Models and views * Restore log null checks * Fix merge conflict * Fix merge issue * Null check * Partial * Partial * Partial * Fix test * Partial * Partial * First test * Baseline comparison * Builtins * Partial * Type fixes * Fix type names, part I * Qualified name * Properly write variables * Partial * Construct module from model * Test * Variable creations * Factories * Factories * Split construction * Restore * Save builtins * Test passes * Qualified name * Better export detection * Test fixes * More consistent qualified names * Sys test * Demo * Complete sys write/read * Partial * Partial * Test staility * Perf bug * Baseline, remove debug code, deactivate db * Test fixes * Test fix * Simplify a bit * Baselines and use : separator * Baselines * PR feedback * Merge master * Remove registry reference * PR feedback * PR feedback * Restore persistence + update test * Better handle persistent module in dependencies * Undo * Add location converter abstraction * Store member location * Fix merge issue * Basic locations test * Navigation * Add test * Update baselines * Type restore - initial * Remove debug code * Partial * Fix stub merge * Various model fixes * Improve module handling * Qualified name improvements * Fix unbound case * Improve stub merge * Fix qualified names of typing * Handle stub-only modules * Add tests for io, re and sys * Better handle named tuple * Handle module circular references * Handle else in platform and version clauses + handle it in symbol collector * Formatting * Fix walk of multi-level if statement * Unify package search in imports * Fix tests * Undo change * Port changes from dbtype * Partial * Named tuple support * Baseline updates * Debug code * Support types * Properly compare class member declaring type * Nested classes and functions persistence * Undo debug * Fix numpy restore * Baselines * Fix tests * Update AnyStr test reflecting changes to AnyStr behavior * Exclude baselines from git diff * Fix gitattr * Move git setting to root * Try no path * Test fixes * Undo change * Additional stub merge fixes * Baseline updates * Fix goto def * Protect specific type creation * Track documentaton source * More reliable tests + simplification * Typo * Cleanup * Basic classification * Fix merge error * Module unique id fixes * Stricted check to save analysis * Revert "Fix tests" This reverts commit 247a8c3. * Revert "Unify package search in imports" This reverts commit 67fed10. * Don't clear scope variables with inner classes * Fix typo * Many new tests * Fix collections test * Fix CTypes * Initial * Update test * Merge issues * Fix CTypes again * Fix null bases * Tell between class members with/without self better * TypeVar support * Add bound/covariant * Fix Enum reassignments * Fix Random * Fix import * over local declarations (Socket) * Move interface * Fix reference search * Enable asyncio test * More tests * Enable dataclasses * Add inspect and gzip * More tests * Add setting * Add handling of import position relative to the variable * Caching level changes * Partial * Rework * Fix null * Update baselines * Functools pass * Reverse stub merge * Partial * Partial ctypes * Update variables post merge * Fix ctypes * Merge issues * Fix typevar * Add caching on construction * Assorted fixes * Named tuples * Work around os stub oddness * Fix generic qualified names * Ignore lambdas * Support named tuples as bases * Baselines * Test updates * Named tuple tests * PR feedback * Using * Separate creation and population for better handling of forward references. * Add dependency provider * Merge issues * Better class creation * Partial * Dependency providers * Add IsPersistent flag * Make special global scope * Move dependency fetch earlier * Replace AST walk by interface * Make analysis sequence closer to master * Undo some * Undo debug * Show dependencies in log * null check * Update tests * Make sure factory looks in correct scope Enable assertions in tests * Separate stub dependencies * Don't try and merge from empty stub * Match generic parameters by name * Use qualified names in generics Minor tests cleanup * Back to names * Update tests * usings * Fix dependencies for stub * Null check * PR feedback * PR feedback + fix merge issue in generics * Test fixes + PR feedback * Baseline updates * PR feedback * Restore original order * Order * Merge issues * Make cancellation closer to original * Pass cancellation to module walker * Simplify
1 parent 28ce8e3 commit e7f29d7

File tree

73 files changed

+1378
-885
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1378
-885
lines changed

src/Analysis/Ast/Impl/Analyzer/Definitions/IAnalyzable.cs

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

16+
using Microsoft.Python.Analysis.Dependencies;
17+
1618
namespace Microsoft.Python.Analysis.Analyzer {
1719
/// <summary>
1820
/// Represents document that can be analyzed asynchronously.
1921
/// </summary>
2022
internal interface IAnalyzable {
23+
/// <summary>
24+
/// Returns object that can calculate dependencies of this entry.
25+
/// </summary>
26+
IDependencyProvider DependencyProvider { get; }
27+
2128
/// <summary>
2229
/// Notifies document that analysis is about to begin.
2330
/// </summary>

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

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System.Collections.Generic;
1717
using System.Diagnostics;
1818
using System.Linq;
19+
using System.Threading;
1920
using Microsoft.Python.Analysis.Analyzer.Evaluation;
2021
using Microsoft.Python.Analysis.Documents;
2122
using Microsoft.Python.Analysis.Modules;
@@ -32,30 +33,36 @@ namespace Microsoft.Python.Analysis.Analyzer {
3233
internal class ModuleWalker : AnalysisWalker {
3334
private const string AllVariableName = "__all__";
3435
private readonly IDocumentAnalysis _stubAnalysis;
36+
private readonly CancellationToken _cancellationToken;
3537

3638
// A hack to use __all__ export in the most simple case.
3739
private int _allReferencesCount;
3840
private bool _allIsUsable = true;
3941

40-
public ModuleWalker(IServiceContainer services, IPythonModule module, PythonAst ast)
42+
public ModuleWalker(IServiceContainer services, IPythonModule module, PythonAst ast, CancellationToken cancellationToken)
4143
: base(new ExpressionEval(services, module, ast)) {
4244
_stubAnalysis = Module.Stub is IDocument doc ? doc.GetAnyAnalysis() : null;
45+
_cancellationToken = cancellationToken;
4346
}
4447

4548
public override bool Walk(NameExpression node) {
4649
if (Eval.CurrentScope == Eval.GlobalScope && node.Name == AllVariableName) {
4750
_allReferencesCount++;
4851
}
52+
53+
_cancellationToken.ThrowIfCancellationRequested();
4954
return base.Walk(node);
5055
}
5156

5257
public override bool Walk(AugmentedAssignStatement node) {
5358
HandleAugmentedAllAssign(node);
59+
_cancellationToken.ThrowIfCancellationRequested();
5460
return base.Walk(node);
5561
}
5662

5763
public override bool Walk(CallExpression node) {
5864
HandleAllAppendExtend(node);
65+
_cancellationToken.ThrowIfCancellationRequested();
5966
return base.Walk(node);
6067
}
6168

@@ -146,6 +153,7 @@ private bool IsHandleableAll(Node node) {
146153

147154
public override bool Walk(PythonAst node) {
148155
Check.InvalidOperation(() => Ast == node, "walking wrong AST");
156+
_cancellationToken.ThrowIfCancellationRequested();
149157

150158
// Collect basic information about classes and functions in order
151159
// to correctly process forward references. Does not determine
@@ -181,16 +189,20 @@ public override bool Walk(PythonAst node) {
181189

182190
// Classes and functions are walked by their respective evaluators
183191
public override bool Walk(ClassDefinition node) {
192+
_cancellationToken.ThrowIfCancellationRequested();
184193
SymbolTable.Evaluate(node);
185194
return false;
186195
}
187196

188197
public override bool Walk(FunctionDefinition node) {
198+
_cancellationToken.ThrowIfCancellationRequested();
189199
SymbolTable.Evaluate(node);
190200
return false;
191201
}
192202

193203
public void Complete() {
204+
_cancellationToken.ThrowIfCancellationRequested();
205+
194206
SymbolTable.EvaluateAll();
195207
SymbolTable.ReplacedByStubs.Clear();
196208
MergeStub();
@@ -220,11 +232,13 @@ public void Complete() {
220232
/// of the definitions. Stub may contains those so we need to merge it in.
221233
/// </remarks>
222234
private void MergeStub() {
223-
if (Module.ModuleType == ModuleType.User) {
235+
_cancellationToken.ThrowIfCancellationRequested();
236+
237+
if (Module.ModuleType == ModuleType.User || Module.ModuleType == ModuleType.Stub) {
224238
return;
225239
}
226240
// No stub, no merge.
227-
if (_stubAnalysis == null) {
241+
if (_stubAnalysis.IsEmpty()) {
228242
return;
229243
}
230244
// TODO: figure out why module is getting analyzed before stub.
@@ -248,6 +262,18 @@ private void MergeStub() {
248262
if (stubType.DeclaringModule is TypingModule && stubType.Name == "Any") {
249263
continue;
250264
}
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+
251277
TryReplaceMember(v, sourceType, stubType);
252278
}
253279

@@ -257,7 +283,7 @@ private void MergeStub() {
257283
private void TryReplaceMember(IVariable v, IPythonType sourceType, IPythonType stubType) {
258284
// If type does not exist in module, but exists in stub, declare it unless it is an import.
259285
// If types are the classes, take class from the stub, then add missing members.
260-
// Otherwise, replace type from one from the stub.
286+
// Otherwise, replace type by one from the stub.
261287
switch (sourceType) {
262288
case null:
263289
// Nothing in sources, but there is type in the stub. Declare it.
@@ -379,28 +405,26 @@ private static void TransferDocumentationAndLocation(IPythonType s, IPythonType
379405
if (s != d && s is PythonType src && d is PythonType dst) {
380406
// If type is a class, then doc can either come from class definition node of from __init__.
381407
// If class has doc from the class definition, don't stomp on it.
382-
var transferDoc = true;
383408
if (src is PythonClassType srcClass && dst is PythonClassType dstClass) {
384409
// Higher lever source wins
385410
if (srcClass.DocumentationSource == PythonClassType.ClassDocumentationSource.Class ||
386411
(srcClass.DocumentationSource == PythonClassType.ClassDocumentationSource.Init && dstClass.DocumentationSource == PythonClassType.ClassDocumentationSource.Base)) {
387412
dstClass.SetDocumentation(srcClass.Documentation);
388-
transferDoc = false;
389413
}
390-
}
391-
392-
// Sometimes destination (stub type) already has documentation. This happens when stub type
393-
// is used to augment more than one type. For example, in threading module RLock stub class
394-
// replaces both RLock function and _RLock class making 'factory' function RLock to look
395-
// like a class constructor. Effectively a single stub type is used for more than one type
396-
// in the source and two source types may have different documentation. Thus transferring doc
397-
// from one source type affects documentation of another type. It may be better to clone stub
398-
// type and separate instances for separate source type, but for now we'll just avoid stomping
399-
// on the existing documentation.
400-
if (transferDoc && string.IsNullOrEmpty(dst.Documentation)) {
401-
var srcDocumentation = src.Documentation;
402-
if (!string.IsNullOrEmpty(srcDocumentation)) {
403-
dst.SetDocumentation(srcDocumentation);
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+
}
404428
}
405429
}
406430

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ internal void RaiseAnalysisComplete(int moduleCount, double msElapsed)
238238
private void AnalyzeDocument(in AnalysisModuleKey key, in PythonAnalyzerEntry entry, in ImmutableArray<AnalysisModuleKey> dependencies) {
239239
_analysisCompleteEvent.Reset();
240240
ActivityTracker.StartTracking();
241-
_log?.Log(TraceEventType.Verbose, $"Analysis of {entry.Module.Name}({entry.Module.ModuleType}) queued");
241+
_log?.Log(TraceEventType.Verbose, $"Analysis of {entry.Module.Name} ({entry.Module.ModuleType}) queued. Dependencies: {string.Join(", ", dependencies.Select(d => d.IsTypeshed ? $"{d.Name} (stub)" : d.Name))}");
242242

243243
var graphVersion = _dependencyResolver.ChangeValue(key, entry, entry.IsUserOrBuiltin || key.IsNonUserAsDocument, dependencies);
244244

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

Lines changed: 5 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,13 @@
1919
using System.Linq;
2020
using System.Threading;
2121
using System.Threading.Tasks;
22-
using Microsoft.Python.Analysis.Core.DependencyResolution;
2322
using Microsoft.Python.Analysis.Modules;
2423
using Microsoft.Python.Analysis.Types;
25-
using Microsoft.Python.Core;
2624
using Microsoft.Python.Core.Collections;
2725
using Microsoft.Python.Parsing.Ast;
2826

2927
namespace Microsoft.Python.Analysis.Analyzer {
30-
[DebuggerDisplay("{_module.Name}({_module.ModuleType})")]
28+
[DebuggerDisplay("{_module.Name} : {_module.ModuleType}")]
3129
internal sealed class PythonAnalyzerEntry {
3230
private readonly object _syncObj = new object();
3331
private TaskCompletionSource<IDocumentAnalysis> _analysisTcs;
@@ -248,20 +246,13 @@ public bool Invalidate(IPythonModule module, PythonAst ast, int bufferVersion, i
248246
}
249247

250248
private HashSet<AnalysisModuleKey> FindDependencies(IPythonModule module, PythonAst ast, int bufferVersion) {
251-
if (_bufferVersion > bufferVersion) {
252-
return new HashSet<AnalysisModuleKey>();
253-
}
254-
255-
var walker = new DependencyWalker(module);
256-
ast.Walk(walker);
257-
var dependencies = walker.Dependencies;
258-
dependencies.Remove(new AnalysisModuleKey(module));
249+
var dependencyProvider = (module as IAnalyzable)?.DependencyProvider;
250+
var dependencies = _bufferVersion <= bufferVersion && module is IAnalyzable analyzable && analyzable.DependencyProvider != null
251+
? dependencyProvider.GetDependencies()
252+
: new HashSet<AnalysisModuleKey>();
259253
return dependencies;
260254
}
261255

262-
private static bool Ignore(IModuleManagement moduleResolution, string fullName, string modulePath)
263-
=> moduleResolution.BuiltinModuleName.EqualsOrdinal(fullName) || moduleResolution.IsSpecializedModule(fullName, modulePath);
264-
265256
private void UpdateAnalysisTcs(int analysisVersion) {
266257
_analysisVersion = analysisVersion;
267258
if (_analysisTcs.Task.Status == TaskStatus.RanToCompletion) {
@@ -273,69 +264,6 @@ private void UpdateAnalysisTcs(int analysisVersion) {
273264
}
274265
}
275266

276-
private class DependencyWalker : PythonWalker {
277-
private readonly IPythonModule _module;
278-
private readonly bool _isTypeshed;
279-
private readonly IModuleManagement _moduleResolution;
280-
private readonly PathResolverSnapshot _pathResolver;
281-
282-
public HashSet<AnalysisModuleKey> Dependencies { get; }
283-
284-
public DependencyWalker(IPythonModule module) {
285-
_module = module;
286-
_isTypeshed = module is StubPythonModule stub && stub.IsTypeshed;
287-
_moduleResolution = module.Interpreter.ModuleResolution;
288-
_pathResolver = _isTypeshed
289-
? module.Interpreter.TypeshedResolution.CurrentPathResolver
290-
: _moduleResolution.CurrentPathResolver;
291-
292-
Dependencies = new HashSet<AnalysisModuleKey>();
293-
294-
if (module.Stub != null) {
295-
Dependencies.Add(new AnalysisModuleKey(module.Stub));
296-
}
297-
}
298-
299-
public override bool Walk(ImportStatement import) {
300-
var forceAbsolute = import.ForceAbsolute;
301-
foreach (var moduleName in import.Names) {
302-
var importNames = ImmutableArray<string>.Empty;
303-
foreach (var nameExpression in moduleName.Names) {
304-
importNames = importNames.Add(nameExpression.Name);
305-
var imports = _pathResolver.GetImportsFromAbsoluteName(_module.FilePath, importNames, forceAbsolute);
306-
HandleSearchResults(imports);
307-
}
308-
}
309267

310-
return false;
311-
}
312-
313-
public override bool Walk(FromImportStatement fromImport) {
314-
var imports = _pathResolver.FindImports(_module.FilePath, fromImport);
315-
HandleSearchResults(imports);
316-
if (imports is IImportChildrenSource childrenSource) {
317-
foreach (var name in fromImport.Names) {
318-
if (childrenSource.TryGetChildImport(name.Name, out var childImport)) {
319-
HandleSearchResults(childImport);
320-
}
321-
}
322-
}
323-
324-
return false;
325-
}
326-
327-
private void HandleSearchResults(IImportSearchResult searchResult) {
328-
switch (searchResult) {
329-
case ModuleImport moduleImport when !Ignore(_moduleResolution, moduleImport.FullName, moduleImport.ModulePath):
330-
Dependencies.Add(new AnalysisModuleKey(moduleImport.FullName, moduleImport.ModulePath, _isTypeshed));
331-
return;
332-
case PossibleModuleImport possibleModuleImport when !Ignore(_moduleResolution, possibleModuleImport.PrecedingModuleFullName, possibleModuleImport.PrecedingModulePath):
333-
Dependencies.Add(new AnalysisModuleKey(possibleModuleImport.PrecedingModuleFullName, possibleModuleImport.PrecedingModulePath, _isTypeshed));
334-
return;
335-
default:
336-
return;
337-
}
338-
}
339-
}
340268
}
341269
}

0 commit comments

Comments
 (0)