diff --git a/src/Analysis/Ast/Impl/Analyzer/AnalysisModuleKey.cs b/src/Analysis/Ast/Impl/Analyzer/AnalysisModuleKey.cs
index 3de3ac411..a2d508cd0 100644
--- a/src/Analysis/Ast/Impl/Analyzer/AnalysisModuleKey.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/AnalysisModuleKey.cs
@@ -40,6 +40,7 @@ private AnalysisModuleKey(string name, string filePath, bool isTypeshed, bool is
Name = name;
FilePath = filePath;
IsTypeshed = isTypeshed;
+ IsNonUserAsDocument = isNonUserAsDocument;
}
public AnalysisModuleKey GetNonUserAsDocumentKey() => new AnalysisModuleKey(Name, FilePath, IsTypeshed, true);
@@ -70,6 +71,8 @@ public void Deconstruct(out string moduleName, out string filePath, out bool isT
public override string ToString() => $"{Name}({FilePath})";
+ public bool IsNonUserAsDocument { get; }
+
private static bool IsNonUserAsDocumentModule(IPythonModule module)
=> (module.IsNonUserFile() || module.IsCompiled()) && module is IDocument document && document.IsOpen;
}
diff --git a/src/Analysis/Ast/Impl/Analyzer/AnalysisWalker.cs b/src/Analysis/Ast/Impl/Analyzer/AnalysisWalker.cs
index f85643fba..ed80d9018 100644
--- a/src/Analysis/Ast/Impl/Analyzer/AnalysisWalker.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/AnalysisWalker.cs
@@ -40,9 +40,9 @@ internal abstract class AnalysisWalker : PythonWalker {
public PythonAst Ast => Eval.Ast;
protected ModuleSymbolTable SymbolTable => Eval.SymbolTable;
- protected AnalysisWalker(ExpressionEval eval, IImportedVariableHandler importedVariableHandler) {
+ protected AnalysisWalker(ExpressionEval eval) {
Eval = eval;
- ImportHandler = new ImportHandler(this, importedVariableHandler);
+ ImportHandler = new ImportHandler(this);
AssignmentHandler = new AssignmentHandler(this);
LoopHandler = new LoopHandler(this);
ConditionalHandler = new ConditionalHandler(this);
diff --git a/src/Analysis/Ast/Impl/Analyzer/Definitions/IAnalyzable.cs b/src/Analysis/Ast/Impl/Analyzer/Definitions/IAnalyzable.cs
index 9a3b9007d..b13891861 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Definitions/IAnalyzable.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/Definitions/IAnalyzable.cs
@@ -21,6 +21,11 @@ namespace Microsoft.Python.Analysis.Analyzer {
/// Represents document that can be analyzed asynchronously.
///
internal interface IAnalyzable {
+ ///
+ /// Returns object that can calculate dependencies of this entry.
+ ///
+ IDependencyProvider DependencyProvider { get; }
+
///
/// Notifies document that analysis is about to begin.
///
diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs
index f55abb0f6..03a37a1c2 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs
@@ -60,8 +60,8 @@ public ExpressionEval(IServiceContainer services, IPythonModule module, PythonAs
public LocationInfo GetLocationInfo(Node node) => node?.GetLocation(this) ?? LocationInfo.Empty;
public Location GetLocationOfName(Node node) {
- if (node == null ||
- Module.ModuleType == ModuleType.Specialized || Module.ModuleType == ModuleType.Compiled ||
+ if (node == null ||
+ Module.ModuleType == ModuleType.Specialized || Module.ModuleType == ModuleType.Compiled ||
Module.ModuleType == ModuleType.CompiledBuiltin || Module.ModuleType == ModuleType.Builtins) {
return DefaultLocation;
}
diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/FunctionCallEvaluator.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/FunctionCallEvaluator.cs
index ca1931259..2790dee88 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/FunctionCallEvaluator.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/FunctionCallEvaluator.cs
@@ -14,7 +14,6 @@
// permissions and limitations under the License.
using System;
-using Microsoft.Python.Analysis.Analyzer.Handlers;
using Microsoft.Python.Analysis.Analyzer.Symbols;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Parsing.Ast;
@@ -29,7 +28,7 @@ internal sealed class FunctionCallEvaluator: AnalysisWalker {
private readonly FunctionDefinition _function;
private IMember _result;
- public FunctionCallEvaluator(IPythonModule declaringModule, FunctionDefinition fd, ExpressionEval eval): base(eval, SimpleImportedVariableHandler.Instance) {
+ public FunctionCallEvaluator(IPythonModule declaringModule, FunctionDefinition fd, ExpressionEval eval): base(eval) {
_declaringModule = declaringModule ?? throw new ArgumentNullException(nameof(declaringModule));
_eval = eval ?? throw new ArgumentNullException(nameof(eval));
_function = fd ?? throw new ArgumentNullException(nameof(fd));
diff --git a/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs b/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs
index f57244f9e..3ea2f5411 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs
@@ -68,13 +68,6 @@ private void AssignVariables(FromImportStatement node, IImportSearchResult impor
if (!string.IsNullOrEmpty(variableName)) {
DeclareVariable(variableModule, memberName, imports, variableName, node.StartIndex, nameExpression);
}
-
- if (imports is IImportChildrenSource cs
- && cs.TryGetChildImport(memberName, out var csr)
- && HandleImportSearchResult(csr, variableModule, null, names[i], out var childModule)) {
-
- _importedVariableHandler.EnsureModule(childModule);
- }
}
}
@@ -83,11 +76,11 @@ private void HandleModuleImportStar(PythonVariableModule variableModule, IImport
// from self import * won't define any new members
return;
}
-
// If __all__ is present, take it, otherwise declare all members from the module that do not begin with an underscore.
var memberNames = imports is ImplicitPackageImport
? variableModule.GetMemberNames()
- : _importedVariableHandler.GetMemberNames(variableModule).ToArray();
+ : variableModule.Analysis.StarImportMemberNames
+ ?? variableModule.GetMemberNames().Where(s => !s.StartsWithOrdinal("_")).ToArray();
foreach (var memberName in memberNames) {
DeclareVariable(variableModule, memberName, imports, memberName, importPosition, nameExpression);
@@ -114,27 +107,14 @@ private void DeclareVariable(PythonVariableModule variableModule, string memberN
value = GetValueFromImports(variableModule, imports as IImportChildrenSource, memberName);
// First try exported or child submodules.
- var member = variableModule.GetMember(memberName);
+ value = value ?? variableModule.GetMember(memberName);
// Value may be variable or submodule. If it is variable, we need it in order to add reference.
- var variable = _importedVariableHandler.GetVariable(variableModule, memberName);
-
- if (member is PythonVariableModule vm && vm.Equals(variable?.Value)) {
- // If member is submodule, use actual variable so it can be linked through for goto definition.
- value = variable;
- } else if (value == null) {
- if (member is PythonVariableModule) {
- // If member is submodule, use it.
- value = member;
- } else if (variable?.Value != null) {
- // Otherwise use variable, if available so references can be linked.
- value = variable;
- } else if (member != null) {
- value = member;
- } else {
- value = Eval.UnknownType;
- }
- }
+ var variable = variableModule.Analysis?.GlobalScope?.Variables[memberName];
+ value = variable?.Value?.Equals(value) == true ? variable : value;
+
+ // If nothing is exported, variables are still accessible.
+ value = value ?? variableModule.Analysis?.GlobalScope?.Variables[memberName]?.Value ?? Eval.UnknownType;
}
// Do not allow imported variables to override local declarations
diff --git a/src/Analysis/Ast/Impl/Analyzer/Handlers/ImportHandler.cs b/src/Analysis/Ast/Impl/Analyzer/Handlers/ImportHandler.cs
index 66e0cdc0b..4cdf23d8b 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Handlers/ImportHandler.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/Handlers/ImportHandler.cs
@@ -29,12 +29,9 @@
namespace Microsoft.Python.Analysis.Analyzer.Handlers {
internal sealed partial class ImportHandler : StatementHandler {
- private readonly IImportedVariableHandler _importedVariableHandler;
private readonly Dictionary _variableModules = new Dictionary();
- public ImportHandler(AnalysisWalker walker, in IImportedVariableHandler importedVariableHandler) : base(walker) {
- _importedVariableHandler = importedVariableHandler;
- }
+ public ImportHandler(AnalysisWalker walker) : base(walker) { }
public bool HandleImport(ImportStatement node) {
if (Module.ModuleType == ModuleType.Specialized) {
@@ -68,9 +65,7 @@ private void HandleImport(ModuleName moduleImportExpression, NameExpression asNa
lastModule = default;
break;
}
-
resolvedModules[i] = (nameExpression.Name, lastModule);
- _importedVariableHandler.EnsureModule(lastModule);
}
// "import fob.oar.baz as baz" is handled as baz = import_module('fob.oar.baz')
@@ -94,6 +89,15 @@ private void HandleImport(ModuleName moduleImportExpression, NameExpression asNa
Eval.DeclareVariable(importNames[0], firstModule, VariableSource.Import, moduleImportExpression.Names[0]);
}
}
+
+ // import a.b.c.d => declares a, b in the current module, c in b, d in c.
+ for (var i = 1; i < resolvedModules.Length - 1; i++) {
+ var (childName, childModule) = resolvedModules[i + 1];
+ if (!string.IsNullOrEmpty(childName) && childModule != null) {
+ var parent = resolvedModules[i].module;
+ parent?.AddChildModule(childName, childModule);
+ }
+ }
}
private bool HandleImportSearchResult(in IImportSearchResult imports, in PythonVariableModule parent, in NameExpression asNameExpression, in Node location, out PythonVariableModule variableModule) {
@@ -108,7 +112,8 @@ private bool HandleImportSearchResult(in IImportSearchResult imports, in PythonV
return TryGetPackageFromImport(packageImport, parent, out variableModule);
case RelativeImportBeyondTopLevel importBeyondTopLevel:
var message = Resources.ErrorRelativeImportBeyondTopLevel.FormatInvariant(importBeyondTopLevel.RelativeImportName);
- Eval.ReportDiagnostics(Eval.Module.Uri, new DiagnosticsEntry(message, location.GetLocation(Eval).Span, ErrorCodes.UnresolvedImport, Severity.Warning, DiagnosticSource.Analysis));
+ Eval.ReportDiagnostics(Eval.Module.Uri,
+ new DiagnosticsEntry(message, location.GetLocation(Eval).Span, ErrorCodes.UnresolvedImport, Severity.Warning, DiagnosticSource.Analysis));
variableModule = default;
return false;
case ImportNotFound importNotFound:
@@ -172,7 +177,7 @@ private bool TryGetModulePossibleImport(PossibleModuleImport possibleModuleImpor
return false;
}
}
-
+
return true;
}
@@ -195,7 +200,7 @@ private PythonVariableModule GetOrCreateVariableModule(in string fullName, in Py
variableModule = new PythonVariableModule(fullName, Eval.Interpreter);
_variableModules[fullName] = variableModule;
}
-
+
parentModule?.AddChildModule(memberName, variableModule);
return variableModule;
}
diff --git a/src/Analysis/Ast/Impl/Analyzer/Handlers/LoopImportedVariableHandler.cs b/src/Analysis/Ast/Impl/Analyzer/Handlers/LoopImportedVariableHandler.cs
deleted file mode 100644
index ea93c71cf..000000000
--- a/src/Analysis/Ast/Impl/Analyzer/Handlers/LoopImportedVariableHandler.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the License); you may not use
-// this file except in compliance with the License. You may obtain a copy of the
-// License at http://www.apache.org/licenses/LICENSE-2.0
-//
-// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
-// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
-// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
-// MERCHANTABILITY OR NON-INFRINGEMENT.
-//
-// See the Apache Version 2.0 License for specific language governing
-// permissions and limitations under the License.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Python.Analysis.Analyzer.Evaluation;
-using Microsoft.Python.Analysis.Modules;
-using Microsoft.Python.Analysis.Types;
-using Microsoft.Python.Analysis.Values;
-using Microsoft.Python.Core;
-using Microsoft.Python.Parsing.Ast;
-
-namespace Microsoft.Python.Analysis.Analyzer.Handlers {
- internal sealed class LoopImportedVariableHandler : IImportedVariableHandler {
- private readonly Dictionary _walkers = new Dictionary();
- private readonly IReadOnlyDictionary _asts;
- private readonly IReadOnlyDictionary _cachedVariables;
- private readonly IServiceContainer _services;
- private readonly Func _isCanceled;
-
- public IReadOnlyCollection Walkers => _walkers.Values;
-
- public LoopImportedVariableHandler(in IServiceContainer services,
- in IReadOnlyDictionary asts,
- in IReadOnlyDictionary cachedVariables,
- in Func isCanceled) {
-
- _services = services;
- _isCanceled = isCanceled;
- _asts = asts;
- _cachedVariables = cachedVariables;
- }
-
- public IEnumerable GetMemberNames(PythonVariableModule variableModule) {
- var module = variableModule.Module;
- if (module == null || _isCanceled()) {
- return Enumerable.Empty();
- }
-
- var key = new AnalysisModuleKey(module);
- if (_walkers.TryGetValue(key, out var walker)) {
- return GetMemberNames(walker, variableModule);
- }
-
- if (!_asts.TryGetValue(key, out var ast)) {
- return _cachedVariables.TryGetValue(key, out var variables)
- ? variables.Names
- : variableModule.GetMemberNames().Where(s => !s.StartsWithOrdinal("_"));
- }
-
- walker = WalkModule(module, ast);
- return walker != null ? GetMemberNames(walker, variableModule) : module.GetMemberNames();
- }
-
- public IVariable GetVariable(in PythonVariableModule variableModule, in string name) {
- var module = variableModule.Module;
- if (module == null || _isCanceled()) {
- return default;
- }
-
- var key = new AnalysisModuleKey(module);
- if (_walkers.TryGetValue(key, out var walker)) {
- return walker.Eval.GlobalScope?.Variables[name];
- }
-
- if (!_asts.TryGetValue(key, out var ast)) {
- return _cachedVariables.TryGetValue(key, out var variables) ? variables[name] : default;
- }
-
- _walkers[key] = walker = WalkModule(module, ast);
- var gs = walker != null ? walker.Eval.GlobalScope : module.GlobalScope;
- return gs?.Variables[name];
- }
-
- public void EnsureModule(in PythonVariableModule variableModule) {
- var module = variableModule.Module;
- if (module == null || _isCanceled()) {
- return;
- }
- EnsureModule(module);
- }
-
- private void EnsureModule(in IPythonModule module) {
- if (module == null || _isCanceled()) {
- return;
- }
- var key = new AnalysisModuleKey(module);
- if (!_walkers.ContainsKey(key) && _asts.TryGetValue(key, out var ast)) {
- WalkModule(module, ast);
- }
- }
-
- public ModuleWalker WalkModule(IPythonModule module, PythonAst ast) {
- // If module has stub, make sure it is processed too.
- if (module.Stub?.Analysis is EmptyAnalysis && module.Stub.GetAst() != null) {
- WalkModule(module.Stub, module.Stub.GetAst());
- }
-
- var eval = new ExpressionEval(_services, module, ast);
- var walker = new ModuleWalker(eval, this);
-
- _walkers[new AnalysisModuleKey(module)] = walker;
- ast.Walk(walker);
- walker.Complete();
- return walker;
- }
-
- private static IEnumerable GetMemberNames(ModuleWalker walker, PythonVariableModule variableModule)
- => walker.StarImportMemberNames ?? walker.GlobalScope.GetExportableVariableNames().Concat(variableModule.ChildrenNames).Distinct().Where(s => !s.StartsWithOrdinal("_"));
- }
-}
diff --git a/src/Analysis/Ast/Impl/Analyzer/Handlers/SimpleImportedVariableHandler.cs b/src/Analysis/Ast/Impl/Analyzer/Handlers/SimpleImportedVariableHandler.cs
deleted file mode 100644
index 86d08832e..000000000
--- a/src/Analysis/Ast/Impl/Analyzer/Handlers/SimpleImportedVariableHandler.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the License); you may not use
-// this file except in compliance with the License. You may obtain a copy of the
-// License at http://www.apache.org/licenses/LICENSE-2.0
-//
-// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
-// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
-// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
-// MERCHANTABILITY OR NON-INFRINGEMENT.
-//
-// See the Apache Version 2.0 License for specific language governing
-// permissions and limitations under the License.
-
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Python.Analysis.Modules;
-using Microsoft.Python.Analysis.Values;
-using Microsoft.Python.Core;
-
-namespace Microsoft.Python.Analysis.Analyzer.Handlers {
- internal sealed class SimpleImportedVariableHandler : IImportedVariableHandler {
- public static IImportedVariableHandler Instance { get; } = new SimpleImportedVariableHandler();
-
- private SimpleImportedVariableHandler() {}
-
- public IEnumerable GetMemberNames(PythonVariableModule variableModule)
- => variableModule.Analysis.StarImportMemberNames ?? variableModule.GetMemberNames().Where(s => !s.StartsWithOrdinal("_"));
-
- public IVariable GetVariable(in PythonVariableModule module, in string memberName)
- => module.Analysis?.GlobalScope?.Variables[memberName];
-
- public void EnsureModule(in PythonVariableModule module) { }
- }
-}
diff --git a/src/Analysis/Ast/Impl/Analyzer/ModuleWalker.cs b/src/Analysis/Ast/Impl/Analyzer/ModuleWalker.cs
index 201f7660c..68f7a1cb1 100644
--- a/src/Analysis/Ast/Impl/Analyzer/ModuleWalker.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/ModuleWalker.cs
@@ -18,7 +18,6 @@
using System.Linq;
using System.Threading;
using Microsoft.Python.Analysis.Analyzer.Evaluation;
-using Microsoft.Python.Analysis.Analyzer.Handlers;
using Microsoft.Python.Analysis.Documents;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Analysis.Types.Collections;
@@ -38,7 +37,8 @@ internal class ModuleWalker : AnalysisWalker {
private int _allReferencesCount;
private bool _allIsUsable = true;
- public ModuleWalker(ExpressionEval eval, IImportedVariableHandler importedVariableHandler) : base(eval, importedVariableHandler) {
+ public ModuleWalker(IServiceContainer services, IPythonModule module, PythonAst ast, CancellationToken cancellationToken)
+ : base(new ExpressionEval(services, module, ast)) {
_stubAnalysis = Module.Stub is IDocument doc ? doc.GetAnyAnalysis() : null;
_cancellationToken = CancellationToken.None;
}
@@ -206,7 +206,7 @@ public void Complete() {
new StubMerger(Eval).MergeStub(_stubAnalysis, _cancellationToken);
if (_allIsUsable && _allReferencesCount >= 1 && GlobalScope.Variables.TryGetVariable(AllVariableName, out var variable)
- && variable.Value is IPythonCollection collection && collection.IsExact) {
+ && variable?.Value is IPythonCollection collection && collection.IsExact) {
StarImportMemberNames = collection.Contents
.OfType()
.Select(c => c.GetString())
diff --git a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs
index 9822c74c8..c784d77b8 100644
--- a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs
@@ -155,7 +155,7 @@ public void EnqueueDocumentForAnalysis(IPythonModule module, PythonAst ast, int
}
}
- var invalidate = entry.Invalidate(module, ast, bufferVersion, version, out var dependencies) || module.Analysis is EmptyAnalysis;
+ var invalidate = entry.Invalidate(module, ast, bufferVersion, version, out var dependencies);
if (invalidate) {
AnalyzeDocument(key, entry, dependencies);
}
@@ -186,13 +186,13 @@ public async Task ResetAnalyzer() {
lock (_syncObj) {
_forceGCOnNextSession = true;
- _analysisEntries.Split(kvp => kvp.Value.Module is IBuiltinsPythonModule, out var entriesToPreserve, out _);
+ _analysisEntries.Split(kvp => kvp.Value.Module is IBuiltinsPythonModule, out var entriesToPreserve, out var entriesToRemove);
_analysisEntries.Clear();
foreach (var (key, entry) in entriesToPreserve) {
_analysisEntries.Add(key, entry);
}
- _dependencyResolver.Reset();
+ _dependencyResolver.RemoveKeys(entriesToRemove.Select(e => e.Key));
}
_services.GetService().ReloadAll();
@@ -212,30 +212,31 @@ public IReadOnlyList LoadedModules {
internal async Task RaiseAnalysisCompleteAsync(int moduleCount, double msElapsed) {
var notAllAnalyzed = false;
lock (_syncObj) {
- if (_nextSession != null || (_currentSession != null && !_currentSession.IsCompleted)) {
- return false; // There are active or pending sessions.
- }
- if (_analysisEntries.Values.ExcludeDefault().Any(e => e.Module.ModuleState < ModuleState.Analyzing)) {
- return false; // There are modules that are still being parsed.
+ if (_nextSession != null || _currentSession?.IsCompleted == false) {
+ return false; // There are active or pending sessions.
}
- var notAnalyzed = _analysisEntries.Values.ExcludeDefault().Where(e => e.NotAnalyzed).ToArray();
+ var notAnalyzed = _analysisEntries.Values
+ .ExcludeDefault()
+ .Where(e => e.NotAnalyzed)
+ .ToArray();
+
notAllAnalyzed = notAnalyzed.Length > 0;
}
if (notAllAnalyzed) {
- // Attempt to see if within reasonable time new session starts
- // This is a workaround since there may still be concurrency issues
- // When module analysis session gets canceled and module never re-queued.
- // We don't want to prevent event from firing when this [rarely] happens.
+ // Attempt to see if within reasonable time new session starts
+ // This is a workaround since there may still be concurrency issues
+ // When module analysis session gets canceled and module never re-queued.
+ // We don't want to prevent event from firing when this [rarely] happens.
for (var i = 0; i < 20; i++) {
await Task.Delay(20);
lock (_syncObj) {
- if(_analysisEntries.Values.ExcludeDefault().All(e => !e.NotAnalyzed)) {
- break; // Now all modules are analyzed.
+ if (_analysisEntries.Values.ExcludeDefault().All(e => !e.NotAnalyzed)) {
+ break; // Now all modules are analyzed.
}
- if (_currentSession != null || _nextSession != null) {
- return false; // New sessions were created
+ if (_nextSession != null || _currentSession?.IsCompleted == false) {
+ return false; // New sessions were created
}
}
}
@@ -243,7 +244,6 @@ internal async Task RaiseAnalysisCompleteAsync(int moduleCount, double msE
_analysisCompleteEvent.Set();
AnalysisComplete?.Invoke(this, new AnalysisCompleteEventArgs(moduleCount, msElapsed));
-
return true;
}
@@ -253,10 +253,12 @@ private void AnalyzeDocument(in AnalysisModuleKey key, in PythonAnalyzerEntry en
_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))}");
var graphVersion = _dependencyResolver.ChangeValue(key, entry, entry.IsUserOrBuiltin, dependencies);
+
lock (_syncObj) {
if (_version >= graphVersion) {
return;
}
+
_version = graphVersion;
_currentSession?.Cancel();
}
@@ -375,14 +377,10 @@ private PythonAnalyzerEntry GetOrCreateAnalysisEntry(IPythonModule module, Analy
return entry;
}
- if (module.ModuleType == ModuleType.Specialized) {
- entry = new PythonAnalyzerEntry(new CachedAnalysis((IDocument)module, _services));
- } else {
- entry = new PythonAnalyzerEntry(new EmptyAnalysis(_services, (IDocument)module));
- _analysisCompleteEvent.Reset();
- }
-
+ var emptyAnalysis = new EmptyAnalysis(_services, (IDocument)module);
+ entry = new PythonAnalyzerEntry(emptyAnalysis);
_analysisEntries[key] = entry;
+ _analysisCompleteEvent.Reset();
return entry;
}
}
diff --git a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerEntry.cs b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerEntry.cs
index 90ddb0cb9..16ffd9b90 100644
--- a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerEntry.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerEntry.cs
@@ -19,7 +19,6 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Python.Analysis.Dependencies;
using Microsoft.Python.Analysis.Modules;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Core;
@@ -82,27 +81,18 @@ public int AnalysisVersion {
}
}
- public bool NotAnalyzed => PreviousAnalysis is EmptyAnalysis;
+ public bool NotAnalyzed => PreviousAnalysis is EmptyAnalysis && Module.ModuleType != ModuleType.Specialized && Module.ModuleType != ModuleType.Builtins;
- public PythonAnalyzerEntry(EmptyAnalysis emptyAnalysis) : this(emptyAnalysis.Document, emptyAnalysis) {
- _bufferVersion = -1;
- }
-
- public PythonAnalyzerEntry(CachedAnalysis analysis) : this(analysis.Document, analysis) {
- _bufferVersion = 0;
- _analysisTcs.SetResult(analysis);
- }
-
- private PythonAnalyzerEntry(IPythonModule module, IDocumentAnalysis analysis) {
- _previousAnalysis = analysis;
- _module = module;
+ public PythonAnalyzerEntry(EmptyAnalysis emptyAnalysis) {
+ _previousAnalysis = emptyAnalysis;
+ _module = emptyAnalysis.Document;
_moduleType = _module.ModuleType;
+ _bufferVersion = -1;
_analysisVersion = 0;
_analysisTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
}
-
public Task GetAnalysisAsync(CancellationToken cancellationToken)
=> _analysisTcs.Task.WaitAsync(cancellationToken);
@@ -197,9 +187,8 @@ public bool Invalidate(ImmutableArray analysisDependencies, int a
}
var dependenciesHashSet = new HashSet();
- foreach (var dependency in analysisDependencies.ExcludeDefault().Where(d => d.ModuleType != ModuleType.Specialized)) {
- if (!dependency.Equals(module) &&
- (dependency.ModuleType == ModuleType.User && dependency.Analysis.Version < version || dependency.Analysis is EmptyAnalysis)) {
+ foreach (var dependency in analysisDependencies) {
+ if (dependency != module && (dependency.ModuleType == ModuleType.User && dependency.Analysis.Version < version || dependency.Analysis is EmptyAnalysis)) {
dependenciesHashSet.Add(new AnalysisModuleKey(dependency));
}
}
@@ -259,12 +248,15 @@ public bool Invalidate(IPythonModule module, PythonAst ast, int bufferVersion, i
private HashSet FindDependencies(IPythonModule module, PythonAst ast, int bufferVersion) {
var dependencies = new HashSet();
- if (_bufferVersion > bufferVersion || module.ModuleType == ModuleType.Specialized) {
+ if (_bufferVersion > bufferVersion) {
return dependencies;
}
- var dw = new DependencyWalker(_module, ast);
- dependencies.UnionWith(dw.Dependencies);
+ var dependencyProvider = (module as IAnalyzable)?.DependencyProvider;
+ var moduleDeps = dependencyProvider?.GetDependencies(ast);
+ if (moduleDeps != null) {
+ dependencies.UnionWith(moduleDeps);
+ }
dependencies.Remove(new AnalysisModuleKey(module));
return dependencies;
diff --git a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerSession.cs b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerSession.cs
index f532f2037..989a66c7b 100644
--- a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerSession.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerSession.cs
@@ -14,7 +14,6 @@
// permissions and limitations under the License.
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime;
@@ -22,18 +21,14 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Python.Analysis.Analyzer.Evaluation;
-using Microsoft.Python.Analysis.Analyzer.Handlers;
using Microsoft.Python.Analysis.Caching;
-using Microsoft.Python.Analysis.Core.DependencyResolution;
using Microsoft.Python.Analysis.Dependencies;
using Microsoft.Python.Analysis.Diagnostics;
using Microsoft.Python.Analysis.Documents;
using Microsoft.Python.Analysis.Modules;
using Microsoft.Python.Analysis.Types;
-using Microsoft.Python.Analysis.Values;
using Microsoft.Python.Core;
using Microsoft.Python.Core.Logging;
-using Microsoft.Python.Core.OS;
using Microsoft.Python.Core.Testing;
using Microsoft.Python.Parsing.Ast;
@@ -48,17 +43,15 @@ internal sealed class PythonAnalyzerSession {
private readonly CancellationToken _analyzerCancellationToken;
private readonly IServiceContainer _services;
private readonly IDiagnosticsService _diagnosticsService;
- private readonly IOSPlatform _platformService;
private readonly IProgressReporter _progress;
private readonly PythonAnalyzer _analyzer;
private readonly ILogger _log;
private readonly bool _forceGC;
- private readonly PathResolverSnapshot _modulesPathResolver;
- private readonly PathResolverSnapshot _typeshedPathResolver;
- private readonly AsyncCountdownEvent _ace = new AsyncCountdownEvent(0);
+ private readonly IModuleDatabaseService _moduleDatabaseService;
private State _state;
private bool _isCanceled;
+ private int _runningTasks;
public bool IsCompleted {
get {
@@ -82,7 +75,6 @@ public PythonAnalyzerSession(IServiceContainer services,
_services = services;
_startNextSession = startNextSession;
-
_analyzerCancellationToken = analyzerCancellationToken;
Version = version;
AffectedEntriesCount = walker?.AffectedValues.Count ?? 1;
@@ -92,24 +84,18 @@ public PythonAnalyzerSession(IServiceContainer services,
_forceGC = forceGC;
_diagnosticsService = _services.GetService();
- _platformService = _services.GetService();
_analyzer = _services.GetService();
_log = _services.GetService();
+ _moduleDatabaseService = _services.GetService();
_progress = progress;
-
- var interpreter = _services.GetService();
- _modulesPathResolver = interpreter.ModuleResolution.CurrentPathResolver;
- _typeshedPathResolver = interpreter.TypeshedResolution.CurrentPathResolver;
}
public void Start(bool analyzeEntry) {
lock (_syncObj) {
- if (_state == State.Completed) {
- return;
- }
-
if (_state != State.NotStarted) {
analyzeEntry = false;
+ } else if (_state == State.Completed) {
+ return;
} else {
_state = State.Started;
}
@@ -146,26 +132,26 @@ private async Task StartAsync() {
try {
_log?.Log(TraceEventType.Verbose, $"Analysis version {Version} of {originalRemaining} entries has started.");
remaining = await AnalyzeAffectedEntriesAsync(stopWatch);
- Debug.Assert(_ace.Count == 0);
} finally {
stopWatch.Stop();
- var isFinal = false;
+ bool isCanceled;
+ bool isFinal;
lock (_syncObj) {
- if (!_isCanceled) {
- _progress.ReportRemaining(remaining);
- }
-
+ isCanceled = _isCanceled;
_state = State.Completed;
isFinal = _walker.MissingKeys.Count == 0 && !_isCanceled && remaining == 0;
_walker = null;
}
- if (isFinal) {
- var (modulesCount, totalMilliseconds) = ActivityTracker.EndTracking();
- totalMilliseconds = Math.Round(totalMilliseconds, 2);
- if (await _analyzer.RaiseAnalysisCompleteAsync(modulesCount, totalMilliseconds)) {
- _log?.Log(TraceEventType.Verbose, $"Analysis complete: {modulesCount} modules in {totalMilliseconds} ms.");
+ if (!isCanceled) {
+ _progress.ReportRemaining(remaining);
+ if (isFinal) {
+ var (modulesCount, totalMilliseconds) = ActivityTracker.EndTracking();
+ totalMilliseconds = Math.Round(totalMilliseconds, 2);
+ if (await _analyzer.RaiseAnalysisCompleteAsync(modulesCount, totalMilliseconds)) {
+ _log?.Log(TraceEventType.Verbose, $"Analysis complete: {modulesCount} modules in {totalMilliseconds} ms.");
+ }
}
}
}
@@ -200,42 +186,43 @@ private static void LogResults(ILogger logger, double elapsed, int originalRemai
}
private async Task AnalyzeAffectedEntriesAsync(Stopwatch stopWatch) {
- IDependencyChainNode node;
+ IDependencyChainNode node;
var remaining = 0;
+ var ace = new AsyncCountdownEvent(0);
+ bool isCanceled;
while ((node = await _walker.GetNextAsync(_analyzerCancellationToken)) != null) {
- var taskLimitReached = false;
lock (_syncObj) {
- if (_isCanceled) {
- switch (node) {
- case IDependencyChainLoopNode loop:
- // Loop analysis is not cancellable or else small
- // inner loops of a larger loop will not be analyzed
- // correctly as large loop may cancel inner loop pass.
- break;
- case IDependencyChainSingleNode single when !single.Value.NotAnalyzed:
- remaining++;
- node.MoveNext();
- continue;
- }
- }
+ isCanceled = _isCanceled;
+ }
+
+ if (isCanceled && !node.Value.NotAnalyzed) {
+ remaining++;
+ node.MoveNext();
+ continue;
+ }
- taskLimitReached = _ace.Count >= _maxTaskRunning || _walker.Remaining == 1;
- _ace.AddOne();
+ ActivityTracker.OnEnqueueModule(node.Value.Module.FilePath);
+
+ var taskLimitReached = false;
+ lock (_syncObj) {
+ _runningTasks++;
+ taskLimitReached = _runningTasks >= _maxTaskRunning || _walker.Remaining == 1;
}
if (taskLimitReached) {
RunAnalysis(node, stopWatch);
} else {
- StartAnalysis(node, stopWatch).DoNotWait();
+ ace.AddOne();
+ StartAnalysis(node, ace, stopWatch).DoNotWait();
}
}
- await _ace.WaitAsync(_analyzerCancellationToken);
+ await ace.WaitAsync(_analyzerCancellationToken);
lock (_syncObj) {
if (_walker.MissingKeys.Count == 0 || _walker.MissingKeys.All(k => k.IsTypeshed)) {
- Debug.Assert(_ace.Count == 0);
+ Debug.Assert(_runningTasks == 0);
} else if (!_isCanceled && _log != null && _log.LogLevel >= TraceEventType.Verbose) {
_log?.Log(TraceEventType.Verbose, $"Missing keys: {string.Join(", ", _walker.MissingKeys)}");
}
@@ -244,240 +231,128 @@ private async Task AnalyzeAffectedEntriesAsync(Stopwatch stopWatch) {
return remaining;
}
- private void RunAnalysis(IDependencyChainNode node, Stopwatch stopWatch)
- => ExecutionContext.Run(ExecutionContext.Capture(), s => Analyze(node, stopWatch), null);
- private Task StartAnalysis(IDependencyChainNode node, Stopwatch stopWatch)
- => Task.Run(() => Analyze(node, stopWatch));
+ private bool IsAnalyzedLibraryInLoop(IDependencyChainNode node, IDocumentAnalysis currentAnalysis)
+ => !node.HasMissingDependencies && currentAnalysis is LibraryAnalysis && node.IsWalkedWithDependencies && node.IsValidVersion;
+
+ private void RunAnalysis(IDependencyChainNode node, Stopwatch stopWatch)
+ => ExecutionContext.Run(ExecutionContext.Capture(), s => Analyze(node, null, stopWatch), null);
+
+ private Task StartAnalysis(IDependencyChainNode node, AsyncCountdownEvent ace, Stopwatch stopWatch)
+ => Task.Run(() => Analyze(node, ace, stopWatch));
- private void Analyze(IDependencyChainNode node, Stopwatch stopWatch) {
- var loopAnalysis = false;
+ ///
+ /// Performs analysis of the document. Returns document global scope
+ /// with declared variables and inner scopes. Does not analyze chain
+ /// of dependencies, it is intended for the single file analysis.
+ ///
+ private void Analyze(IDependencyChainNode node, AsyncCountdownEvent ace, Stopwatch stopWatch) {
try {
- switch (node) {
- case IDependencyChainSingleNode single:
- try {
- Analyze(single, stopWatch);
- } catch (OperationCanceledException oce) {
- single.Value.TryCancel(oce, _walker.Version);
- LogCanceled(single.Value.Module);
- } catch (Exception exception) {
- single.Value.TrySetException(exception, _walker.Version);
- node.MarkWalked();
- LogException(single.Value, exception);
- }
- break;
- case IDependencyChainLoopNode loop:
- try {
- loopAnalysis = true;
- AnalyzeLoop(loop, stopWatch);
- } catch (OperationCanceledException) {
- } catch (Exception exception) {
- node.MarkWalked();
- LogException(loop, exception);
- }
- break;
+ var entry = node.Value;
+ if (!CanUpdateAnalysis(entry, node, _walker.Version, out var module, out var ast, out var currentAnalysis)) {
+ return;
}
+ var startTime = stopWatch.Elapsed;
+ AnalyzeEntry(node, entry, module, ast, _walker.Version);
+
+ LogCompleted(node, module, stopWatch, startTime);
+ } catch (OperationCanceledException oce) {
+ node.Value.TryCancel(oce, _walker.Version);
+ LogCanceled(node.Value.Module);
+ } catch (Exception exception) {
+ node.Value.TrySetException(exception, _walker.Version);
+ node.MarkWalked();
+ LogException(node.Value.Module, exception);
} finally {
+ node.MoveNext();
+
lock (_syncObj) {
- node.MoveNext();
- if (!_isCanceled || loopAnalysis) {
+ if (!_isCanceled) {
_progress.ReportRemaining(_walker.Remaining);
}
- _ace.Signal();
+ _runningTasks--;
+ ace?.Signal();
}
}
}
- ///
- /// Performs analysis of the document. Returns document global scope
- /// with declared variables and inner scopes. Does not analyze chain
- /// of dependencies, it is intended for the single file analysis.
- ///
- private void Analyze(IDependencyChainSingleNode node, Stopwatch stopWatch) {
- ActivityTracker.OnEnqueueModule(node.Value.Module.FilePath);
- var entry = node.Value;
- if (!CanUpdateAnalysis(entry, _walker.Version, out var module, out var ast)) {
- return;
- }
- var startTime = stopWatch.Elapsed;
- AnalyzeEntry(node, entry, module, ast, _walker.Version);
-
- LogCompleted(node, module, stopWatch, startTime);
- }
-
private void AnalyzeEntry() {
var stopWatch = _log != null ? Stopwatch.StartNew() : null;
try {
- if (!CanUpdateAnalysis(_entry, Version, out var module, out var ast)) {
+ if (!CanUpdateAnalysis(_entry, null, Version, out var module, out var ast, out var currentAnalysis)) {
return;
}
var startTime = stopWatch?.Elapsed ?? TimeSpan.Zero;
AnalyzeEntry(null, _entry, module, ast, Version);
- LogCompleted(module, stopWatch, startTime);
+ LogCompleted(null, module, stopWatch, startTime);
} catch (OperationCanceledException oce) {
_entry.TryCancel(oce, Version);
LogCanceled(_entry.Module);
} catch (Exception exception) {
_entry.TrySetException(exception, Version);
- LogException(_entry, exception);
+ LogException(_entry.Module, exception);
} finally {
stopWatch?.Stop();
}
}
- private void AnalyzeLoop(IDependencyChainLoopNode loopNode, Stopwatch stopWatch) {
- var version = _walker.Version;
- var entries = new Dictionary();
- var variables = new Dictionary<(AnalysisModuleKey Module, string Name), int>();
- var importNames = new List<(AnalysisModuleKey From, int FromPosition, AnalysisModuleKey To, string ToName)>();
- var cachedVariables = new Dictionary();
- var asts = new Dictionary();
- var startTime = stopWatch.Elapsed;
-
- // Note: loop analysis is not cancellable. The reason is that when smaller loop
- // appears inside a larger loop gets canceled, it will not be re-walked during
- // the outer loop analysis. For example, functools <=> _functools loop and
- // the related CircularDependencyFunctools test.
- foreach (var entry in loopNode.Values) {
- ActivityTracker.OnEnqueueModule(entry.Module.FilePath);
- if (!CanUpdateAnalysis(entry, Version, out var module, out var ast)) {
- continue;
- }
-
- var moduleKey = new AnalysisModuleKey(module);
- entries[moduleKey] = (module, entry);
- AddLoopImportsFromAst(importNames, variables, moduleKey, ast);
- asts.Add(moduleKey, ast);
- }
-
- if (asts.Count == 0) {
- // Fully cached loop
- if (_log != null && _log.LogLevel == TraceEventType.Verbose) {
- var names = string.Join(", ", cachedVariables.Select(v => v.Key.Name));
- _log?.Log(TraceEventType.Verbose, $"Fully cached modules cycle: {names}");
- }
- return;
- }
-
- var imports = new List<(AnalysisModuleKey From, int FromPosition, AnalysisModuleKey To, int ToPosition)>();
- foreach (var (fromModule, fromPosition, toModule, toName) in importNames) {
- if (!entries.ContainsKey(toModule)) {
- continue;
- }
-
- if (toName == null) {
- imports.Add((fromModule, fromPosition, toModule, 0));
- } else if (variables.TryGetValue((toModule, toName), out var toPosition)) {
- imports.Add((fromModule, fromPosition, toModule, toPosition));
- }
- }
-
- var startingKeys = LocationLoopResolver.FindStartingItems(imports);
- var variableHandler = new LoopImportedVariableHandler(_services, asts, cachedVariables, () => false);
- foreach (var key in startingKeys) {
- if (asts.TryGetValue(key, out var startingAst) && entries.TryGetValue(key, out var me)) {
- variableHandler.WalkModule(me.Module, startingAst);
- }
- }
-
- foreach (var walker in variableHandler.Walkers) {
- asts.Remove(new AnalysisModuleKey(walker.Module));
- }
-
- while (asts.Count > 0) {
- var (moduleKey, ast) = asts.First();
- variableHandler.WalkModule(entries[moduleKey].Module, ast);
-
- foreach (var walker in variableHandler.Walkers) {
- asts.Remove(new AnalysisModuleKey(walker.Module));
- }
- }
-
- foreach (var walker in variableHandler.Walkers) {
- var module = (IDocument)walker.Module;
- var moduleKey = new AnalysisModuleKey(module);
- if (entries.TryGetValue(moduleKey, out var e)) {
- var analysis = CreateAnalysis(null, module, walker.Ast, version, walker);
- CompleteAnalysis(e.Entry, module, version, analysis);
- }
- }
-
- loopNode.MarkWalked();
- LogCompleted(loopNode, entries.Values.Select(v => v.Module), stopWatch, startTime);
- }
+ private bool CanUpdateAnalysis(
+ PythonAnalyzerEntry entry,
+ IDependencyChainNode node,
+ int version,
+ out IPythonModule module,
+ out PythonAst ast,
+ out IDocumentAnalysis currentAnalysis) {
- private void AddLoopImportsFromAst(
- in List<(AnalysisModuleKey From, int FromPosition, AnalysisModuleKey To, string ToName)> imports,
- in Dictionary<(AnalysisModuleKey Module, string Name), int> variables,
- in AnalysisModuleKey moduleKey,
- in PythonAst ast) {
-
- var pathResolver = moduleKey.IsTypeshed ? _typeshedPathResolver : _modulesPathResolver;
- var walker = new ImportExportWalker(ast, _platformService, pathResolver, moduleKey.FilePath, moduleKey.IsTypeshed);
- walker.Walk();
-
- foreach (var export in walker.Exports) {
- var key = (moduleKey, export.Name);
- var location = export.Location.Start;
- if (!variables.TryGetValue(key, out var currentLocation) || currentLocation > location) {
- variables[key] = location;
+ if (!entry.CanUpdateAnalysis(version, out module, out ast, out currentAnalysis)) {
+ if (IsAnalyzedLibraryInLoop(node, currentAnalysis)) {
+ // Library analysis exists, don't analyze again
+ return false;
}
- }
-
- foreach (var (toModule, name, location) in walker.Imports) {
- imports.Add((moduleKey, location.Start, toModule, name));
- }
- }
-
- private bool CanUpdateAnalysis(PythonAnalyzerEntry entry, int version, out IPythonModule module, out PythonAst ast) {
- if (entry.CanUpdateAnalysis(version, out module, out ast, out var currentAnalysis)) {
- return true;
- }
-
- if (ast == null) {
- if (currentAnalysis == null) {
- // Entry doesn't have ast yet. There should be at least one more session.
- Cancel();
- _log?.Log(TraceEventType.Verbose, $"Analysis of {module.Name}({module.ModuleType}) canceled (no AST yet).");
+ if (ast == null) {
+ if (currentAnalysis == null) {
+ // Entry doesn't have ast yet. There should be at least one more session.
+ Cancel();
+ _log?.Log(TraceEventType.Verbose, $"Analysis of {module.Name}({module.ModuleType}) canceled (no AST yet).");
+ return false;
+ }
+ //Debug.Fail($"Library module {module.Name} of type {module.ModuleType} has been analyzed already!");
return false;
}
- //Debug.Fail($"Library module {module.Name} of type {module.ModuleType} has been analyzed already!");
+
+ _log?.Log(TraceEventType.Verbose, $"Analysis of {module.Name}({module.ModuleType}) canceled. Version: {version}, current: {module.Analysis.Version}.");
return false;
}
-
- _log?.Log(TraceEventType.Verbose, $"Analysis of {module.Name}({module.ModuleType}) canceled. Version: {version}, current: {module.Analysis.Version}.");
- return false;
+ return true;
}
- private void AnalyzeEntry(IDependencyChainSingleNode node, PythonAnalyzerEntry entry, IPythonModule module, PythonAst ast, int version) {
+ private void AnalyzeEntry(IDependencyChainNode node, PythonAnalyzerEntry entry, IPythonModule module, PythonAst ast, int version) {
// Now run the analysis.
var analyzable = module as IAnalyzable;
analyzable?.NotifyAnalysisBegins();
Debug.Assert(ast != null);
- var analysis = AnalyzeModule(node, module, ast, version);
+ var analysis = DoAnalyzeEntry(node, module, ast, version);
_analyzerCancellationToken.ThrowIfCancellationRequested();
if (analysis != null) {
- CompleteAnalysis(entry, module, version, analysis);
+ analyzable?.NotifyAnalysisComplete(analysis);
+ entry.TrySetAnalysis(analysis, version);
+
+ if (module.ModuleType == ModuleType.User) {
+ var linterDiagnostics = _analyzer.LintModule(module);
+ _diagnosticsService?.Replace(entry.Module.Uri, linterDiagnostics, DiagnosticSource.Linter);
+ }
}
}
- private void CompleteAnalysis(PythonAnalyzerEntry entry, IPythonModule module, int version, IDocumentAnalysis analysis) {
- var analyzable = module as IAnalyzable;
- analyzable?.NotifyAnalysisComplete(analysis);
- entry.TrySetAnalysis(analysis, version);
-
- if (module.ModuleType != ModuleType.User) {
- return;
+ private IDocumentAnalysis DoAnalyzeEntry(IDependencyChainNode node, IPythonModule module, PythonAst ast, int version) {
+ var analysis = TryRestoreCachedAnalysis(node, module);
+ if (analysis != null) {
+ return analysis;
}
- var linterDiagnostics = _analyzer.LintModule(module);
- _diagnosticsService?.Replace(entry.Module.Uri, linterDiagnostics, DiagnosticSource.Linter);
- }
-
- private IDocumentAnalysis AnalyzeModule(IDependencyChainSingleNode node, IPythonModule module, PythonAst ast, int version) {
if (module is IAnalyzable analyzable) {
var walker = analyzable.Analyze(ast);
return CreateAnalysis(node, (IDocument)module, ast, version, walker);
@@ -485,7 +360,38 @@ private IDocumentAnalysis AnalyzeModule(IDependencyChainSingleNode node, IDocument document, PythonAst ast, int version, ModuleWalker walker) {
+ private bool MarkNodeWalked(IDependencyChainNode node) {
+ bool isCanceled;
+ lock (_syncObj) {
+ isCanceled = _isCanceled;
+ }
+ if (!isCanceled) {
+ node?.MarkWalked();
+ }
+ return isCanceled;
+ }
+
+ private IDocumentAnalysis TryRestoreCachedAnalysis(IDependencyChainNode node, IPythonModule module) {
+ var moduleType = module.ModuleType;
+ if (moduleType.CanBeCached() && _moduleDatabaseService?.ModuleExistsInStorage(module.Name, module.FilePath) == true) {
+ if (_moduleDatabaseService.TryRestoreGlobalScope(module, out var gs)) {
+ if (_log != null) {
+ _log.Log(TraceEventType.Verbose, "Restored from database: ", module.Name);
+ }
+ var analysis = new DocumentAnalysis((IDocument)module, 1, gs, new ExpressionEval(_services, module, module.GetAst()), Array.Empty());
+ gs.ReconstructVariables();
+ MarkNodeWalked(node);
+ return analysis;
+ } else {
+ if (_log != null) {
+ _log.Log(TraceEventType.Verbose, "Restore from database failed for module ", module.Name);
+ }
+ }
+ }
+ return null;
+ }
+
+ private IDocumentAnalysis CreateAnalysis(IDependencyChainNode node, IDocument document, PythonAst ast, int version, ModuleWalker walker) {
var canHaveLibraryAnalysis = false;
// Don't try to drop builtins; it causes issues elsewhere.
@@ -494,73 +400,48 @@ private IDocumentAnalysis CreateAnalysis(IDependencyChainSingleNode();
- if (optionsProvider?.Options.KeepLibraryAst == true) {
- createLibraryAnalysis = false;
- }
+ var optionsProvider = _services.GetService();
+ if (optionsProvider?.Options.KeepLibraryAst == true) {
+ createLibraryAnalysis = false;
+ }
- if (!createLibraryAnalysis) {
- return new DocumentAnalysis(document, version, walker.GlobalScope, walker.Eval, walker.StarImportMemberNames);
- }
+ if (!createLibraryAnalysis) {
+ return new DocumentAnalysis(document, version, walker.GlobalScope, walker.Eval, walker.StarImportMemberNames);
+ }
- if (document.ModuleType != ModuleType.Stub && !_isCanceled) {
- ast.ReduceToImports();
- document.SetAst(ast);
- }
+ ast.Reduce(x => x is ImportStatement || x is FromImportStatement);
+ document.SetAst(ast);
- var eval = new ExpressionEval(walker.Eval.Services, document, ast);
- var analysis = new LibraryAnalysis(document, version, walker.GlobalScope, eval, walker.StarImportMemberNames);
+ var eval = new ExpressionEval(walker.Eval.Services, document, ast);
+ var analysis = new LibraryAnalysis(document, version, walker.GlobalScope, eval, walker.StarImportMemberNames);
- var dbs = _services.GetService();
- dbs?.StoreModuleAnalysisAsync(analysis, immediate: false, _analyzerCancellationToken).DoNotWait();
+ var dbs = _services.GetService();
+ dbs?.StoreModuleAnalysisAsync(analysis, CancellationToken.None).DoNotWait();
- return analysis;
- }
+ return analysis;
}
- private void LogCompleted(IDependencyChainLoopNode node, IEnumerable modules, Stopwatch stopWatch, TimeSpan startTime) {
- if (_log != null) {
- var moduleNames = modules.Select(m => "{0}({1})".FormatInvariant(m.Name, m.Analysis is LibraryAnalysis ? "Library" : m.ModuleType.ToString()));
- var elapsed = Math.Round((stopWatch.Elapsed - startTime).TotalMilliseconds, 2);
- var message = $"Analysis of modules loop on depth {node.VertexDepth} in {elapsed} ms:";
- _log.Log(TraceEventType.Verbose, message);
- foreach (var name in moduleNames) {
- _log.Log(TraceEventType.Verbose, $" {name}");
- }
- }
- }
- private void LogCompleted(IDependencyChainSingleNode node, IPythonModule module, Stopwatch stopWatch, TimeSpan startTime) {
+ private void LogCompleted(IDependencyChainNode node, IPythonModule module, Stopwatch stopWatch, TimeSpan startTime) {
if (_log != null) {
- var completed = module.Analysis is LibraryAnalysis ? "completed for library" : "completed";
+ var completed = node != null && module.Analysis is LibraryAnalysis ? "completed for library" : "completed";
var elapsed = Math.Round((stopWatch.Elapsed - startTime).TotalMilliseconds, 2);
- var message = $"Analysis of {module.Name} ({module.ModuleType}) on depth {node.VertexDepth} {completed} in {elapsed} ms.";
- _log.Log(TraceEventType.Verbose, message);
- }
- }
-
- private void LogCompleted(IPythonModule module, Stopwatch stopWatch, TimeSpan startTime) {
- if (_log != null) {
- var elapsed = Math.Round((stopWatch.Elapsed - startTime).TotalMilliseconds, 2);
- var message = $"Out of order analysis of {module.Name}({module.ModuleType}) completed in {elapsed} ms.";
+ var message = node != null
+ ? $"Analysis of {module.Name} ({module.ModuleType}) on depth {node.VertexDepth} {completed} in {elapsed} ms."
+ : $"Out of order analysis of {module.Name}({module.ModuleType}) completed in {elapsed} ms.";
_log.Log(TraceEventType.Verbose, message);
}
}
@@ -571,21 +452,9 @@ private void LogCanceled(IPythonModule module) {
}
}
- private void LogException(PythonAnalyzerEntry entry, Exception exception) {
+ private void LogException(IPythonModule module, Exception exception) {
if (_log != null) {
- _log.Log(TraceEventType.Verbose, $"Analysis of {entry.Module.Name}({entry.Module.ModuleType}) failed. {exception}");
- }
-
- if (TestEnvironment.Current != null) {
- ExceptionDispatchInfo.Capture(exception).Throw();
- }
- }
-
- private void LogException(IDependencyChainLoopNode node, Exception exception) {
- if (_log != null) {
- var moduleNames = string.Join(", ", node.Values.Select(e => $"{e.Module.Name}({e.Module.ModuleType})"));
- var message = $"Analysis of modules loop [{moduleNames}] failed. {exception}";
- _log.Log(TraceEventType.Verbose, message);
+ _log.Log(TraceEventType.Verbose, $"Analysis of {module.Name}({module.ModuleType}) failed. {exception}");
}
if (TestEnvironment.Current != null) {
diff --git a/src/Analysis/Ast/Impl/Analyzer/StubMerger.cs b/src/Analysis/Ast/Impl/Analyzer/StubMerger.cs
index 2636a5691..188d41c0a 100644
--- a/src/Analysis/Ast/Impl/Analyzer/StubMerger.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/StubMerger.cs
@@ -75,7 +75,6 @@ private void TransferTypesFromStub(IDocumentAnalysis stubAnalysis, CancellationT
if (stubType.IsUnknown()) {
continue;
}
-
// If stub says 'Any' but we have better type, keep the current type.
if (stubType.DeclaringModule is TypingModule && stubType.Name == "Any") {
continue;
@@ -85,7 +84,7 @@ private void TransferTypesFromStub(IDocumentAnalysis stubAnalysis, CancellationT
var sourceType = sourceVar?.Value.GetPythonType();
if (sourceVar?.Source == VariableSource.Import &&
- sourceVar.GetPythonType()?.DeclaringModule.Stub != null) {
+ sourceVar.GetPythonType()?.DeclaringModule.Stub != null) {
// Keep imported types as they are defined in the library. For example,
// 'requests' imports NullHandler as 'from logging import NullHandler'.
// But 'requests' also declares NullHandler in its stub (but not in the main code)
@@ -298,7 +297,7 @@ private void TransferDocumentationAndLocation(IPythonType sourceType, IPythonTyp
// Consider that 'email.headregistry' stub has DataHeader declaring 'datetime'
// property of type 'datetime' from 'datetime' module. We don't want to modify
// datetime type and change it's location to 'email.headregistry'.
- if (stubType.DeclaringModule.ModuleType != ModuleType.Stub || stubType.DeclaringModule != _eval.Module.Stub) {
+ if(stubType.DeclaringModule.ModuleType != ModuleType.Stub || stubType.DeclaringModule != _eval.Module.Stub) {
return;
}
@@ -348,7 +347,7 @@ private void TransferDocumentationAndLocation(IPythonType sourceType, IPythonTyp
/// or location of unrelated types such as coming from the base object type.
///
private bool IsFromThisModuleOrSubmodules(IPythonType type) {
- if (type.IsUnknown()) {
+ if(type.IsUnknown()) {
return false;
}
var thisModule = _eval.Module;
diff --git a/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs b/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs
index 39c7b128a..5151a1a68 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs
@@ -73,7 +73,7 @@ public override void Evaluate() {
v => v.GetPythonType() == null &&
v.GetPythonType() == null)
) {
- ((VariableCollection)Eval.CurrentScope.Variables).Clear();
+ ((VariableCollection)Eval.CurrentScope.Variables).Clear();
}
}
}
@@ -99,7 +99,6 @@ public static IMember GetReturnValueFromAnnotation(ExpressionEval eval, Expressi
var instance = t.IsUnknown() ? (IMember)annotationType : t;
return instance;
}
-
private IMember TryDetermineReturnValue() {
var returnType = GetReturnValueFromAnnotation(Eval, FunctionDefinition.ReturnAnnotation);
if (returnType != null) {
diff --git a/src/Analysis/Ast/Impl/Analyzer/Symbols/MemberEvaluator.cs b/src/Analysis/Ast/Impl/Analyzer/Symbols/MemberEvaluator.cs
index 6cc3c00f3..b2f63639c 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Symbols/MemberEvaluator.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/Symbols/MemberEvaluator.cs
@@ -16,14 +16,13 @@
using System;
using System.Diagnostics;
using Microsoft.Python.Analysis.Analyzer.Evaluation;
-using Microsoft.Python.Analysis.Analyzer.Handlers;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Parsing.Ast;
namespace Microsoft.Python.Analysis.Analyzer.Symbols {
[DebuggerDisplay("{Target.Name}")]
internal abstract class MemberEvaluator : AnalysisWalker {
- protected MemberEvaluator(ExpressionEval eval, ScopeStatement target) : base(eval, SimpleImportedVariableHandler.Instance) {
+ protected MemberEvaluator(ExpressionEval eval, ScopeStatement target) : base(eval) {
Target = target ?? throw new ArgumentNullException(nameof(target));
}
diff --git a/src/Analysis/Ast/Impl/Analyzer/Symbols/SymbolCollector.cs b/src/Analysis/Ast/Impl/Analyzer/Symbols/SymbolCollector.cs
index b383758d1..07c1afb18 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Symbols/SymbolCollector.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/Symbols/SymbolCollector.cs
@@ -112,6 +112,7 @@ private PythonClassType CreateClass(ClassDefinition cd) {
return cls;
}
+
private void AddFunctionOrProperty(FunctionDefinition fd) {
var declaringType = fd.Parent != null && _typeMap.TryGetValue(fd.Parent, out var t) ? t : null;
if (!TryAddProperty(fd, declaringType)) {
@@ -135,7 +136,7 @@ private void AddFunction(FunctionDefinition fd, PythonType declaringType) {
AddOverload(fd, f, o => f.AddOverload(o));
}
- private void AddOverload(FunctionDefinition fd, IPythonClassMember function, Action addOverload) {
+ private void AddOverload(FunctionDefinition fd, IPythonClassMember function, Action addOverload) {
// Check if function exists in stubs. If so, take overload from stub
// and the documentation from this actual module.
if (!_table.ReplacedByStubs.Contains(fd)) {
@@ -165,7 +166,7 @@ private PythonFunctionOverload GetOverloadFromStub(FunctionDefinition node) {
if (t is IPythonFunctionType f) {
return f.Overloads
.OfType()
- .FirstOrDefault(o => o.Parameters.Count == node.Parameters.Count(p => !p.IsPositionalOnlyMarker));
+ .FirstOrDefault(o => o.Parameters.Count == node.Parameters.Where(p => !p.IsPositionalOnlyMarker).Count());
}
return null;
}
diff --git a/src/Analysis/Ast/Impl/Caching/Definitions/IModuleDatabaseService.cs b/src/Analysis/Ast/Impl/Caching/Definitions/IModuleDatabaseService.cs
index e04965a97..fb40a5bfc 100644
--- a/src/Analysis/Ast/Impl/Caching/Definitions/IModuleDatabaseService.cs
+++ b/src/Analysis/Ast/Impl/Caching/Definitions/IModuleDatabaseService.cs
@@ -15,36 +15,34 @@
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Python.Analysis.Analyzer;
-using Microsoft.Python.Analysis.Modules;
+using Microsoft.Python.Analysis.Dependencies;
using Microsoft.Python.Analysis.Types;
namespace Microsoft.Python.Analysis.Caching {
internal interface IModuleDatabaseService: IModuleDatabaseCache {
///
- /// Restores module from database.
+ /// Creates global scope from module persistent state.
+ /// Global scope is then can be used to construct module analysis.
///
- IPythonModule RestoreModule(string moduleName, string modulePath, ModuleType moduleType);
+ /// Python module to restore analysis for.
+ /// Python module global scope.
+ bool TryRestoreGlobalScope(IPythonModule module, out IRestoredGlobalScope gs);
+
+ ///
+ /// Retrieves dependencies from the module persistent state.
+ ///
+ /// Python module to restore analysis for.
+ /// Python module dependency provider.
+ bool TryRestoreDependencies(IPythonModule module, out IDependencyProvider dp);
///
/// Writes module data to the database.
///
- /// Document analysis
- ///
- /// True if database should be written to disk immediately
- /// as opposed to delaying writing until complete analysis event from the
- ///
- /// Cancellation token
- Task StoreModuleAnalysisAsync(IDocumentAnalysis analysis, bool immediate = false, CancellationToken cancellationToken = default);
+ Task StoreModuleAnalysisAsync(IDocumentAnalysis analysis, CancellationToken cancellationToken = default);
///
/// Determines if module analysis exists in the storage.
///
- bool ModuleExistsInStorage(string name, string filePath, ModuleType moduleType);
- }
-
- internal static class ModuleDatabaseExtensions {
- public static bool ModuleExistsInStorage(this IModuleDatabaseService dbs, IPythonModule module)
- => dbs.ModuleExistsInStorage(module.Name, module.FilePath, module.ModuleType);
+ bool ModuleExistsInStorage(string moduleName, string filePath);
}
}
diff --git a/src/Analysis/Ast/Impl/Analyzer/Handlers/IImportedVariableHandler.cs b/src/Analysis/Ast/Impl/Caching/Definitions/IRestoredGlobalScope.cs
similarity index 60%
rename from src/Analysis/Ast/Impl/Analyzer/Handlers/IImportedVariableHandler.cs
rename to src/Analysis/Ast/Impl/Caching/Definitions/IRestoredGlobalScope.cs
index b269186d6..71bf8e130 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Handlers/IImportedVariableHandler.cs
+++ b/src/Analysis/Ast/Impl/Caching/Definitions/IRestoredGlobalScope.cs
@@ -1,4 +1,4 @@
-// Copyright(c) Microsoft Corporation
+// Copyright(c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the License); you may not use
@@ -13,14 +13,16 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
-using System.Collections.Generic;
-using Microsoft.Python.Analysis.Modules;
using Microsoft.Python.Analysis.Values;
-namespace Microsoft.Python.Analysis.Analyzer.Handlers {
- internal interface IImportedVariableHandler {
- IEnumerable GetMemberNames(PythonVariableModule variableModule);
- IVariable GetVariable(in PythonVariableModule module, in string name);
- void EnsureModule(in PythonVariableModule module);
+namespace Microsoft.Python.Analysis.Caching {
+ ///
+ /// Represents global scope that has been restored from
+ /// the database but has not been fully populated yet.
+ /// Used to attach to analysis so variables can be
+ /// accessed during classes and methods restoration.
+ ///
+ internal interface IRestoredGlobalScope : IGlobalScope {
+ void ReconstructVariables();
}
}
diff --git a/src/Analysis/Ast/Impl/Dependencies/DependencyCollector.cs b/src/Analysis/Ast/Impl/Dependencies/DependencyCollector.cs
index 381fc40f9..a0c3e4cca 100644
--- a/src/Analysis/Ast/Impl/Dependencies/DependencyCollector.cs
+++ b/src/Analysis/Ast/Impl/Dependencies/DependencyCollector.cs
@@ -29,9 +29,9 @@ internal sealed class DependencyCollector {
public ISet Dependencies { get; } = new HashSet();
- public DependencyCollector(IPythonModule module, bool? isTypeshed = null) {
+ public DependencyCollector(IPythonModule module, bool? isTypeShed = null) {
_module = module;
- _isTypeshed = isTypeshed ?? module.IsTypeshed;
+ _isTypeshed = isTypeShed ?? module.IsTypeshed;
_moduleResolution = module.Interpreter.ModuleResolution;
_pathResolver = _isTypeshed
? module.Interpreter.TypeshedResolution.CurrentPathResolver
diff --git a/src/Analysis/Ast/Impl/Dependencies/DependencyResolver.cs b/src/Analysis/Ast/Impl/Dependencies/DependencyResolver.cs
index ca8be84fc..0813a6303 100644
--- a/src/Analysis/Ast/Impl/Dependencies/DependencyResolver.cs
+++ b/src/Analysis/Ast/Impl/Dependencies/DependencyResolver.cs
@@ -14,7 +14,6 @@
// permissions and limitations under the License.
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -102,28 +101,51 @@ public int Remove(in TKey key) {
}
}
- ///
- /// Removes everything but builtins.
- ///
- public void Reset() {
+ public int RemoveKeys(params TKey[] keys) => RemoveKeys(ImmutableArray.Create(keys));
+
+ public int RemoveKeys(in ImmutableArray keys) {
lock (_syncObj) {
- if (_vertices.Count > 1) {
- _vertices.RemoveRange(1, _vertices.Count - 1);
+ foreach (var key in keys) {
+ if (_keys.TryGetValue(key, out var index)) {
+ _vertices[index] = default;
+ }
}
- var kvp = _keys.Count > 0 ? _keys.FirstOrDefault(k => k.Value == 0) : (KeyValuePair?)null;
+ var oldKeysReversed = _keys.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
+ var oldVertices = new DependencyVertex[_vertices.Count];
+ _vertices.CopyTo(oldVertices);
+
_keys.Clear();
- if(kvp != null) {
- _keys[kvp.Value.Key] = 0;
+ _vertices.Clear();
+
+ foreach (var oldVertex in oldVertices) {
+ if (oldVertex == null) {
+ continue;
+ }
+
+ var incomingKeys = oldVertex.Incoming.Select(i => oldKeysReversed[i]);
+ var key = oldVertex.Key;
+ var value = oldVertex.Value;
+ var isRoot = oldVertex.IsRoot;
+
+ if (!_keys.TryGetValue(key, out var index)) {
+ index = _keys.Count;
+ _keys[key] = index;
+ _vertices.Add(default);
+ }
+
+ Update(key, value, isRoot, incomingKeys, index);
}
- _version++;
+ return _version;
}
}
private void Update(in TKey key, in TValue value, in bool isRoot, in ImmutableArray incomingKeys, in int index) {
var version = Interlocked.Increment(ref _version);
+
var incoming = EnsureKeys(index, incomingKeys, version);
+
_vertices[index] = new DependencyVertex(key, value, isRoot, incoming, version, index);
_keys[key] = index;
}
@@ -185,7 +207,7 @@ public bool TryCreateWalker(in int version, in int walkerDepthLimit, out IDepend
return false;
}
- if (!TryResolveLoops(walkingGraph, loopsCount, version, out var loopNodes)) {
+ if (!TryResolveLoops(walkingGraph, loopsCount, version, out var totalNodesCount)) {
walker = default;
return false;
}
@@ -196,15 +218,14 @@ public bool TryCreateWalker(in int version, in int walkerDepthLimit, out IDepend
return false;
}
- var affectedValues = walkingGraph.Select(v => v.DependencyVertex.Value);
-
- walkingGraph = walkingGraph.AddRange(loopNodes);
foreach (var vertex in walkingGraph) {
vertex.Seal();
+ vertex.SecondPass?.Seal();
}
+ var affectedValues = walkingGraph.Select(v => v.DependencyVertex.Value);
var startingVertices = walkingGraph.Where(v => !v.HasIncoming);
- walker = new DependencyChainWalker(this, startingVertices, affectedValues, depths, missingKeys, version);
+ walker = new DependencyChainWalker(this, startingVertices, affectedValues, depths, missingKeys, totalNodesCount, version);
return version == _version;
}
@@ -222,7 +243,11 @@ private bool TryBuildReverseGraph(in ImmutableArray[vertices.Count];
- foreach (var vertex in vertices.Where(vertex => vertex != null)) {
+ foreach (var vertex in vertices) {
+ if (vertex == null) {
+ continue;
+ }
+
if (version != _version) {
return false;
}
@@ -245,8 +270,10 @@ private bool TryBuildReverseGraph(in ImmutableArray vertex != null && !vertex.IsSealed)) {
- vertex.Seal(outgoingVertices[vertex.Index]);
+ foreach (var vertex in vertices) {
+ if (vertex != null && !vertex.IsSealed) {
+ vertex.Seal(outgoingVertices[vertex.Index]);
+ }
}
return true;
@@ -407,62 +434,109 @@ private static bool SetLoopNumber(WalkingVertex vertex, Stack> graph, int loopsCount, int version, out ImmutableArray> loopVertices) {
- loopVertices = ImmutableArray>.Empty;
+ private bool TryResolveLoops(in ImmutableArray> graph, int loopsCount, int version, out int totalNodesCount) {
if (loopsCount == 0) {
+ totalNodesCount = graph.Count;
return true;
}
- // Create independent walking vertices for vertex loops
- for (var i = 0; i < loopsCount; i++) {
- loopVertices = loopVertices.Add(new WalkingVertex(i));
- }
-
- // Break internal loop connections
+ // Create vertices for second pass
+ var inLoopsCount = 0;
+ var secondPassLoops = new List>[loopsCount];
foreach (var vertex in graph) {
if (vertex.IsInLoop) {
+ var secondPassVertex = vertex.CreateSecondPassVertex();
var loopNumber = vertex.LoopNumber;
- var loopVertex = loopVertices[loopNumber];
-
- for (var i = vertex.Outgoing.Count - 1; i >= 0; i--) {
- if (vertex.Outgoing[i].LoopNumber == loopNumber) {
- vertex.RemoveOutgoingAt(i);
- }
+ if (secondPassLoops[loopNumber] == null) {
+ secondPassLoops[loopNumber] = new List> { secondPassVertex };
+ } else {
+ secondPassLoops[loopNumber].Add(secondPassVertex);
}
- loopVertex.AddOutgoing(vertex);
+ inLoopsCount++;
}
if (version != _version) {
+ totalNodesCount = default;
return false;
}
+
+ vertex.Index = -1; // Reset index, will use later
}
- // Connect dependencies to loop vertex
- var outgoingLoopVertices = new HashSet>();
- foreach (var vertex in graph) {
- outgoingLoopVertices.Clear();
- for (var i = vertex.Outgoing.Count - 1; i >= 0; i--) {
- var outgoing = vertex.Outgoing[i];
- if (outgoing.IsInLoop && outgoing.LoopNumber != vertex.LoopNumber) {
- var loopVertex = loopVertices[outgoing.LoopNumber];
- vertex.RemoveOutgoingAt(i);
- outgoingLoopVertices.Add(loopVertex);
+ // Break the loops so that its items can be iterated
+ foreach (var loop in secondPassLoops) {
+ // Sort loop items by amount of incoming connections
+ loop.Sort(WalkingVertex.FirstPassIncomingComparison);
+
+ var counter = 0;
+ foreach (var secondPassVertex in loop) {
+ var vertex = secondPassVertex.FirstPass;
+ if (vertex.Index == -1) {
+ RemoveOutgoingLoopEdges(vertex, ref counter);
+ }
+
+ if (version != _version) {
+ totalNodesCount = default;
+ return false;
}
}
+ }
+
+ // Make first vertex from second pass loop (loop is sorted at this point) have incoming edges from vertices from first pass loop and set unique loop numbers
+ var outgoingVertices = new HashSet>();
+ foreach (var loop in secondPassLoops) {
+ outgoingVertices.Clear();
+ var startVertex = loop[0];
- if (outgoingLoopVertices.Count > 0) {
- vertex.AddOutgoing(outgoingLoopVertices);
+ foreach (var secondPassVertex in loop) {
+ var firstPassVertex = secondPassVertex.FirstPass;
+ firstPassVertex.AddOutgoing(startVertex);
+
+ foreach (var outgoingVertex in firstPassVertex.Outgoing) {
+ if (outgoingVertex.LoopNumber != firstPassVertex.LoopNumber) {
+ // Collect outgoing vertices to reference them from loop
+ outgoingVertices.Add(outgoingVertex);
+ } else if (outgoingVertex.SecondPass != null) {
+ // Copy outgoing edges to the second pass vertex
+ secondPassVertex.AddOutgoing(outgoingVertex.SecondPass);
+ }
+ }
+ }
+
+ // Add outgoing edges to all second pass vertices to ensure that further analysis won't start until loop is fully analyzed
+ foreach (var secondPassVertex in loop) {
+ secondPassVertex.AddOutgoing(outgoingVertices);
}
if (version != _version) {
+ totalNodesCount = default;
return false;
}
+
+ loopsCount++;
}
+ totalNodesCount = graph.Count + inLoopsCount;
return true;
}
+ private static void RemoveOutgoingLoopEdges(WalkingVertex vertex, ref int counter) {
+ vertex.Index = counter++;
+ for (var i = vertex.Outgoing.Count - 1; i >= 0; i--) {
+ var outgoing = vertex.Outgoing[i];
+ if (outgoing.LoopNumber != vertex.LoopNumber) {
+ continue;
+ }
+
+ if (outgoing.Index == -1) {
+ RemoveOutgoingLoopEdges(outgoing, ref counter);
+ } else if (outgoing.Index < vertex.Index) {
+ vertex.RemoveOutgoingAt(i);
+ }
+ }
+ }
+
private bool TryFindMissingDependencies(in ImmutableArray> vertices, in ImmutableArray> walkingGraph, int version, out ImmutableArray missingKeys) {
var haveMissingDependencies = new bool[vertices.Count];
var queue = new Queue>();
@@ -507,8 +581,9 @@ private bool TryFindMissingDependencies(in ImmutableArray _dependencyResolver;
private readonly ImmutableArray> _startingVertices;
private readonly ImmutableArray _depths;
- private readonly object _syncObj = new object();
+ private readonly object _syncObj;
private int _remaining;
- private PriorityProducerConsumer _ppc;
+ private PriorityProducerConsumer> _ppc;
public ImmutableArray MissingKeys { get; set; }
public ImmutableArray AffectedValues { get; }
@@ -548,8 +623,10 @@ public DependencyChainWalker(in DependencyResolver dependencyResol
in ImmutableArray affectedValues,
in ImmutableArray depths,
in ImmutableArray missingKeys,
+ in int totalNodesCount,
in int version) {
+ _syncObj = new object();
_dependencyResolver = dependencyResolver;
_startingVertices = startingVertices;
_depths = depths;
@@ -557,17 +634,17 @@ public DependencyChainWalker(in DependencyResolver dependencyResol
Version = version;
MissingKeys = missingKeys;
- _remaining = affectedValues.Count;
+ _remaining = totalNodesCount;
}
- public Task GetNextAsync(CancellationToken cancellationToken) {
- PriorityProducerConsumer ppc;
+ public Task> GetNextAsync(CancellationToken cancellationToken) {
+ PriorityProducerConsumer> ppc;
lock (_syncObj) {
if (_ppc == null) {
- _ppc = new PriorityProducerConsumer();
+ _ppc = new PriorityProducerConsumer>();
foreach (var vertex in _startingVertices) {
- _ppc.Produce(CreateNode(vertex));
+ _ppc.Produce(new DependencyChainNode(this, vertex, _depths[vertex.DependencyVertex.Index]));
}
}
@@ -577,7 +654,7 @@ public Task GetNextAsync(CancellationToken cancellationTok
return ppc.ConsumeAsync(cancellationToken);
}
- public void MoveNext(WalkingVertex vertex, bool loopAnalysis) {
+ public void MoveNext(WalkingVertex vertex) {
var verticesToProduce = new List>();
var isCompleted = false;
lock (_syncObj) {
@@ -587,7 +664,7 @@ public void MoveNext(WalkingVertex vertex, bool loopAnalysis) {
continue;
}
- outgoing.DecrementIncoming(vertex.HasOnlyWalkedIncoming || loopAnalysis);
+ outgoing.DecrementIncoming(vertex.HasOnlyWalkedIncoming);
if (outgoing.HasIncoming) {
continue;
}
@@ -604,7 +681,7 @@ public void MoveNext(WalkingVertex vertex, bool loopAnalysis) {
_ppc.Produce(null);
} else {
foreach (var toProduce in verticesToProduce) {
- _ppc.Produce(CreateNode(toProduce));
+ _ppc.Produce(new DependencyChainNode(this, toProduce, _depths[toProduce.DependencyVertex.Index]));
}
}
}
@@ -616,68 +693,31 @@ public bool IsValidVersion {
}
}
}
-
- private IDependencyChainNode CreateNode(WalkingVertex vertex) {
- if (vertex.DependencyVertex != null) {
- return new SingleNode(this, vertex, _depths[vertex.DependencyVertex.Index]);
- }
-
- var vertices = vertex.Outgoing;
- var values = vertices.Select(v => v.DependencyVertex.Value).ToImmutableArray();
- var depth = vertices.Min(v => _depths[v.DependencyVertex.Index]);
- var hasMissingDependencies = vertices.Any(v => v.HasMissingDependencies);
- return new LoopNode(this, vertices, values, depth, hasMissingDependencies);
- }
}
- [DebuggerDisplay("{" + nameof(Value) + "}")]
- private sealed class SingleNode : IDependencyChainSingleNode {
+ private sealed class DependencyChainNode : IDependencyChainNode {
private readonly WalkingVertex _vertex;
private DependencyChainWalker _walker;
public TValue Value => _vertex.DependencyVertex.Value;
public int VertexDepth { get; }
public bool HasMissingDependencies => _vertex.HasMissingDependencies;
- public bool HasOnlyWalkedDependencies => _vertex.HasOnlyWalkedIncoming;
+ public bool HasOnlyWalkedDependencies => _vertex.HasOnlyWalkedIncoming && _vertex.SecondPass == null;
public bool IsWalkedWithDependencies => _vertex.HasOnlyWalkedIncoming && _vertex.DependencyVertex.IsWalked;
public bool IsValidVersion => _walker.IsValidVersion;
- public SingleNode(DependencyChainWalker walker, WalkingVertex vertex, int depth) => (_walker, _vertex, VertexDepth) = (walker, vertex, depth);
-
- public void MarkWalked() => _vertex.DependencyVertex.MarkWalked();
-
- public void MoveNext() => Interlocked.Exchange(ref _walker, null)?.MoveNext(_vertex, loopAnalysis: false);
- }
-
- [DebuggerDisplay("Loop: {_vertices.Count} nodes")]
- private sealed class LoopNode : IDependencyChainLoopNode {
- private readonly IReadOnlyList> _vertices;
- private DependencyChainWalker _walker;
-
- public int VertexDepth { get; }
- public bool HasMissingDependencies { get; }
- public bool HasOnlyWalkedDependencies => _vertices.All(v => v.HasOnlyWalkedIncoming);
- public bool IsWalkedWithDependencies => _vertices.All(v => v.HasOnlyWalkedIncoming && v.DependencyVertex.IsWalked);
- public bool IsValidVersion => _walker.IsValidVersion;
-
- public ImmutableArray Values { get; }
-
- public LoopNode(DependencyChainWalker walker, IReadOnlyList> vertices, ImmutableArray values, int depth, bool hasMissingDependencies)
- => (_walker, _vertices, Values, VertexDepth, HasMissingDependencies) = (walker, vertices, values, depth, hasMissingDependencies);
+ public DependencyChainNode(DependencyChainWalker walker, WalkingVertex vertex, int depth) {
+ _walker = walker;
+ _vertex = vertex;
+ VertexDepth = depth;
+ }
public void MarkWalked() {
- foreach (var vertex in _vertices) {
- vertex.DependencyVertex.MarkWalked();
+ if (_vertex.SecondPass == null) {
+ _vertex.DependencyVertex.MarkWalked();
}
}
- public void MoveNext() {
- var walker = Interlocked.Exchange(ref _walker, null);
- if (walker != null) {
- foreach (var vertex in _vertices) {
- walker.MoveNext(vertex, loopAnalysis: true);
- }
- }
- }
+ public void MoveNext() => Interlocked.Exchange(ref _walker, null)?.MoveNext(_vertex);
}
}
}
diff --git a/src/Analysis/Ast/Impl/Dependencies/DependencyVertex.cs b/src/Analysis/Ast/Impl/Dependencies/DependencyVertex.cs
index 09c1a1cf5..ddbf9160d 100644
--- a/src/Analysis/Ast/Impl/Dependencies/DependencyVertex.cs
+++ b/src/Analysis/Ast/Impl/Dependencies/DependencyVertex.cs
@@ -36,7 +36,7 @@ internal sealed class DependencyVertex {
private int _state;
private HashSet _outgoing;
- private static readonly HashSet _empty = new HashSet();
+ private static HashSet _empty = new HashSet();
public DependencyVertex(DependencyVertex oldVertex, int version, bool isNew) {
Key = oldVertex.Key;
diff --git a/src/Analysis/Ast/Impl/Dependencies/DependencyWalker.cs b/src/Analysis/Ast/Impl/Dependencies/DependencyWalker.cs
index f0ef93f98..2f2270251 100644
--- a/src/Analysis/Ast/Impl/Dependencies/DependencyWalker.cs
+++ b/src/Analysis/Ast/Impl/Dependencies/DependencyWalker.cs
@@ -14,6 +14,7 @@
// permissions and limitations under the License.
using System.Collections.Generic;
+using System.Linq;
using Microsoft.Python.Analysis.Analyzer;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Core.Collections;
diff --git a/src/Analysis/Ast/Impl/Dependencies/IDependencyChainNode.cs b/src/Analysis/Ast/Impl/Dependencies/IDependencyChainNode.cs
index 2532cb6e6..a7fca9a45 100644
--- a/src/Analysis/Ast/Impl/Dependencies/IDependencyChainNode.cs
+++ b/src/Analysis/Ast/Impl/Dependencies/IDependencyChainNode.cs
@@ -13,10 +13,8 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
-using Microsoft.Python.Core.Collections;
-
namespace Microsoft.Python.Analysis.Dependencies {
- internal interface IDependencyChainNode {
+ internal interface IDependencyChainNode {
int VertexDepth { get; }
///
@@ -35,15 +33,8 @@ internal interface IDependencyChainNode {
/// Returns true if node version matches version of the walked graph
///
bool IsValidVersion { get; }
+ TValue Value { get; }
void MarkWalked();
void MoveNext();
}
-
- internal interface IDependencyChainSingleNode : IDependencyChainNode {
- TValue Value { get; }
- }
-
- internal interface IDependencyChainLoopNode : IDependencyChainNode {
- ImmutableArray Values { get; }
- }
}
diff --git a/src/Analysis/Ast/Impl/Dependencies/IDependencyChainWalker.cs b/src/Analysis/Ast/Impl/Dependencies/IDependencyChainWalker.cs
index 4a34f0087..324aadc5f 100644
--- a/src/Analysis/Ast/Impl/Dependencies/IDependencyChainWalker.cs
+++ b/src/Analysis/Ast/Impl/Dependencies/IDependencyChainWalker.cs
@@ -23,6 +23,6 @@ internal interface IDependencyChainWalker {
ImmutableArray AffectedValues { get; }
int Version { get; }
int Remaining { get; }
- Task GetNextAsync(CancellationToken cancellationToken);
+ Task> GetNextAsync(CancellationToken cancellationToken);
}
}
diff --git a/src/Analysis/Ast/Impl/Dependencies/IDependencyResolver.cs b/src/Analysis/Ast/Impl/Dependencies/IDependencyResolver.cs
index 203ea5a71..3816803e3 100644
--- a/src/Analysis/Ast/Impl/Dependencies/IDependencyResolver.cs
+++ b/src/Analysis/Ast/Impl/Dependencies/IDependencyResolver.cs
@@ -26,7 +26,7 @@ internal interface IDependencyResolver {
int TryAddValue(in TKey key, in TValue value, in bool isRoot, in ImmutableArray incomingKeys);
int ChangeValue(in TKey key, in TValue value, in bool isRoot, in ImmutableArray incomingKeys);
int Remove(in TKey key);
- void Reset();
+ int RemoveKeys(in ImmutableArray keys);
IDependencyChainWalker CreateWalker();
bool TryCreateWalker(in int version, in int walkerDepthLimit, out IDependencyChainWalker walker);
diff --git a/src/Analysis/Ast/Impl/Dependencies/ImportExportWalker.cs b/src/Analysis/Ast/Impl/Dependencies/ImportExportWalker.cs
deleted file mode 100644
index 4710d0ff8..000000000
--- a/src/Analysis/Ast/Impl/Dependencies/ImportExportWalker.cs
+++ /dev/null
@@ -1,256 +0,0 @@
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the License); you may not use
-// this file except in compliance with the License. You may obtain a copy of the
-// License at http://www.apache.org/licenses/LICENSE-2.0
-//
-// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
-// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
-// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
-// MERCHANTABILITY OR NON-INFRINGEMENT.
-//
-// See the Apache Version 2.0 License for specific language governing
-// permissions and limitations under the License.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Python.Analysis.Analyzer;
-using Microsoft.Python.Analysis.Core.DependencyResolution;
-using Microsoft.Python.Core;
-using Microsoft.Python.Core.Collections;
-using Microsoft.Python.Core.IO;
-using Microsoft.Python.Core.OS;
-using Microsoft.Python.Core.Text;
-using Microsoft.Python.Parsing.Ast;
-
-namespace Microsoft.Python.Analysis.Dependencies {
- internal sealed class ImportExportWalker : PythonWalker {
- private readonly Dictionary<(AnalysisModuleKey Module, string Name), IndexSpan> _imports;
- private readonly Dictionary _exports;
- private readonly PythonAst _ast;
- private readonly IOSPlatform _platformService;
- private readonly PathResolverSnapshot _pathResolver;
- private readonly string _filePath;
- private readonly bool _isTypeshed;
- private int _depth;
-
- public IEnumerable<(AnalysisModuleKey Module, string Name, IndexSpan Location)> Imports
- => _imports.Select(kvp => (kvp.Key.Module, kvp.Key.Name, kvp.Value));
- public IEnumerable<(string Name, IndexSpan Location)> Exports
- => _exports.Select(kvp => (kvp.Key, kvp.Value));
-
- public ImportExportWalker(PythonAst ast, IOSPlatform platformService, PathResolverSnapshot pathResolver, string filePath, bool isTypeshed) {
- _imports = new Dictionary<(AnalysisModuleKey Module, string Name), IndexSpan>();
- _exports = new Dictionary();
- _ast = ast;
- _platformService = platformService;
- _pathResolver = pathResolver;
- _isTypeshed = isTypeshed;
- _filePath = filePath;
- }
-
- public void Walk() => _ast.Walk(this);
-
- public override bool Walk(AssignmentStatement node) {
- if (_depth == 0) {
- HandleAssignment(node);
- }
- return base.Walk(node);
- }
-
- private void HandleAssignment(AssignmentStatement node) {
- foreach (var expr in node.Left.Select(s => s.RemoveParenthesis()).OfType()) {
- AddExport(expr.Name, expr.IndexSpan);
- }
-
- if (node.Right is MemberExpression me) {
- AddImportIfModule(me);
- }
- }
-
- public override bool Walk(IfStatement node) => node.WalkIfWithSystemConditions(this, _ast.LanguageVersion, _platformService);
-
- public override bool Walk(ImportStatement expr) {
- if (_depth > 0) {
- return false;
- }
-
- var len = Math.Min(expr.Names.Count, expr.AsNames.Count);
- var forceAbsolute = expr.ForceAbsolute;
- for (var i = 0; i < len; i++) {
- var moduleImportExpression = expr.Names[i];
- var asNameExpression = expr.AsNames[i];
-
- if (!string.IsNullOrEmpty(asNameExpression?.Name)) {
- AddLastModuleImport(moduleImportExpression, asNameExpression, forceAbsolute);
- } else {
- AddAllImports(moduleImportExpression, forceAbsolute);
- }
- }
-
- return false;
- }
-
- private void AddLastModuleImport(ModuleName importExpression, NameExpression importName, bool forceAbsolute) {
- var result = _pathResolver.GetImportsFromAbsoluteName(_filePath, importExpression.Names.Select(n => n.Name), forceAbsolute);
- if (result is ModuleImport mi) {
- AddImport(mi, default, importName.IndexSpan);
- }
- }
-
- private void AddAllImports(ModuleName moduleImportExpression, bool forceAbsolute) {
- var importNames = ImmutableArray.Empty;
-
- for (var i = 0; i < moduleImportExpression.Names.Count; i++) {
- var nameExpression = moduleImportExpression.Names[i];
- importNames = importNames.Add(nameExpression.Name);
- var result = _pathResolver.GetImportsFromAbsoluteName(_filePath, importNames, forceAbsolute);
- if (result is ModuleImport mi && !mi.ModulePath.PathEquals(_filePath)) {
- AddImport(mi, default, nameExpression.IndexSpan);
- if (i == 0) {
- AddExport(nameExpression.Name, nameExpression.IndexSpan);
- }
- }
- }
- }
-
- public override bool Walk(FromImportStatement expr) {
- if (_depth > 0) {
- return base.Walk(expr);
- }
-
- var rootNames = expr.Root.Names;
- if (rootNames.Count == 1 && rootNames[0].Name.EqualsOrdinal("__future__")) {
- return base.Walk(expr);
- }
-
- var imports = _pathResolver.FindImports(_filePath, expr);
- if (!(imports is ModuleImport mi)) {
- return base.Walk(expr);
- }
-
- var names = expr.Names;
- var asNames = expr.AsNames;
- if (names.Count == 1 && names[0].Name == "*") {
- AddImport(mi, default, names[0].IndexSpan);
- return base.Walk(expr);
- }
-
- for (var i = 0; i < names.Count; i++) {
- var memberName = names[i].Name;
- if (string.IsNullOrEmpty(memberName)) {
- continue;
- }
-
- var nameExpression = asNames[i] ?? names[i];
- if (mi.TryGetChildImport(nameExpression.Name, out var child) && child is ModuleImport childMi) {
- AddImport(childMi, default, nameExpression.IndexSpan);
- } else {
- AddImport(mi, names[i].Name, nameExpression.IndexSpan);
- }
-
- AddExport(nameExpression.Name, nameExpression.IndexSpan);
- }
-
- return base.Walk(expr);
- }
-
- public override bool Walk(MemberExpression expr) {
- if (_depth == 0) {
- AddImportIfModule(expr);
- }
-
- return base.Walk(expr);
- }
-
- public override bool Walk(ClassDefinition cd) {
- if (_depth == 0 && !string.IsNullOrEmpty(cd.Name)) {
- AddExport(cd.Name, cd.NameExpression.IndexSpan);
- }
- _depth++;
- return base.Walk(cd);
- }
-
- public override void PostWalk(ClassDefinition cd) {
- _depth--;
- base.PostWalk(cd);
- }
-
- public override bool Walk(FunctionDefinition fd) {
- if (_depth == 0 && !string.IsNullOrEmpty(fd.Name)) {
- AddExport(fd.Name, fd.NameExpression.IndexSpan);
- }
- _depth++;
- return base.Walk(fd);
- }
-
- public override void PostWalk(FunctionDefinition fd) {
- _depth--;
- base.PostWalk(fd);
- }
-
- private void AddExport(in string name, IndexSpan location) {
- if (!_exports.TryGetValue(name, out var current) || current.Start > location.Start) {
- _exports[name] = location;
- }
- }
-
- private void AddImportIfModule(in MemberExpression expr) {
- var currentExpression = expr;
- var memberExpressions = new Stack();
- memberExpressions.Push(currentExpression);
-
- while (currentExpression.Target is MemberExpression me) {
- memberExpressions.Push(me);
- currentExpression = me;
- }
-
- if (!(currentExpression.Target is NameExpression ne)) {
- return;
- }
-
- var import = _pathResolver.GetModuleImportFromModuleName(ne.Name);
- if (import == null) {
- return;
- }
-
- var moduleKey = new AnalysisModuleKey(import.Name, import.ModulePath, _isTypeshed);
- IImportChildrenSource childrenSource = _pathResolver.GetModuleImportFromModuleName(moduleKey.Name);
- if (childrenSource == null) {
- return;
- }
-
- while (memberExpressions.Count > 0) {
- var expression = memberExpressions.Pop();
-
- if (!childrenSource.TryGetChildImport(expression.Name, out var child)) {
- AddImport(moduleKey, expression.Name, expression.IndexSpan);
- return;
- }
-
- if (child is IImportChildrenSource cs) {
- childrenSource = cs;
- } else {
- return;
- }
- }
- }
-
- private void AddImport(in ModuleImport moduleImport, in string name, in IndexSpan location)
- => AddImport(new AnalysisModuleKey(moduleImport.FullName, moduleImport.ModulePath, _isTypeshed), name, location);
-
- private void AddImport(in AnalysisModuleKey key, in string name, in IndexSpan location) {
- if (key.FilePath.PathEquals(_filePath)) {
- return;
- }
-
- if (_imports.TryGetValue((key, name), out var current) && current.Start <= location.Start) {
- return;
- }
-
- _imports[(key, name)] = location;
- }
- }
-}
diff --git a/src/Analysis/Ast/Impl/Dependencies/LocationLoopResolver.cs b/src/Analysis/Ast/Impl/Dependencies/LocationLoopResolver.cs
deleted file mode 100644
index 8ce8bc6c2..000000000
--- a/src/Analysis/Ast/Impl/Dependencies/LocationLoopResolver.cs
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the License); you may not use
-// this file except in compliance with the License. You may obtain a copy of the
-// License at http://www.apache.org/licenses/LICENSE-2.0
-//
-// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
-// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
-// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
-// MERCHANTABILITY OR NON-INFRINGEMENT.
-//
-// See the Apache Version 2.0 License for specific language governing
-// permissions and limitations under the License.
-
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Python.Core;
-using Microsoft.Python.Core.Collections;
-
-namespace Microsoft.Python.Analysis.Dependencies {
- internal static class LocationLoopResolver {
- public static ImmutableArray FindStartingItems(IEnumerable<(T From, int FromLocation, T To, int ToLocation)> edges) {
- var itemToIndex = new Dictionary();
- var groupedEdges = new List>();
- var index = 0;
- foreach (var (fromItem, fromLocation, toItem, toLocation) in edges) {
- if (!itemToIndex.TryGetValue(fromItem, out var fromIndex)) {
- fromIndex = index++;
- groupedEdges.Add(new List<(int, int, int)>());
- itemToIndex[fromItem] = fromIndex;
- }
-
- if (!itemToIndex.TryGetValue(toItem, out var toIndex)) {
- toIndex = index++;
- groupedEdges.Add(new List<(int, int, int)>());
- itemToIndex[toItem] = toIndex;
- }
-
- groupedEdges[fromIndex].Add((fromLocation, toIndex, toLocation));
- }
-
- foreach (var group in groupedEdges) {
- group.Sort(SortByFromLocation);
- }
-
- var startingIndices = FindStartingIndices(groupedEdges);
- return startingIndices.Select(i => itemToIndex.First(j => j.Value == i).Key).ToImmutableArray();
-
- int SortByFromLocation((int FromLocation, int, int) x, (int FromLocation, int, int) y) => x.FromLocation.CompareTo(y.FromLocation);
- }
-
- private static IEnumerable FindStartingIndices(List> groupedEdges) {
- var walkedIndices = new int[groupedEdges.Count];
- var visited = new bool[groupedEdges.Count];
- var path = new Stack();
- var startingIndex = 0;
- var allVisitedBeforeIndex = 0;
-
- while (startingIndex < groupedEdges.Count) {
- if (visited[startingIndex]) {
- if (startingIndex == allVisitedBeforeIndex) {
- allVisitedBeforeIndex++;
- }
-
- startingIndex++;
- continue;
- }
-
- for (var i = 0; i < walkedIndices.Length; i++) {
- walkedIndices[i] = -1;
- }
-
- path.Clear();
-
- if (!IsWalkable(groupedEdges, startingIndex, walkedIndices, visited, path)) {
- startingIndex++;
- continue;
- }
-
- for (var i = 0; i < walkedIndices.Length; i++) {
- if (walkedIndices[i] != -1) {
- visited[i] = true;
- }
- }
-
- yield return startingIndex;
- startingIndex = allVisitedBeforeIndex;
- }
- }
-
- private static bool IsWalkable(in List> groupedEdges, in int startGroupIndex, in int[] walkedIndices, in bool[] visited, in Stack path) {
- const int notVisited = -1;
- var fromGroupIndex = startGroupIndex;
-
- while (true) {
- var indexInFromGroup = ++walkedIndices[fromGroupIndex];
- var fromGroup = groupedEdges[fromGroupIndex];
- if (fromGroup.Count == indexInFromGroup) {
- if (path.Count == 0) {
- return true;
- }
-
- fromGroupIndex = path.Pop();
- continue;
- }
-
- var edge = fromGroup[indexInFromGroup];
- var toGroupIndex = edge.ToIndex;
- if (visited[toGroupIndex]) {
- continue;
- }
-
- var indexInToGroup = walkedIndices[toGroupIndex];
- if (indexInToGroup == notVisited) {
- path.Push(fromGroupIndex);
- fromGroupIndex = toGroupIndex;
- continue;
- }
-
- var toGroup = groupedEdges[toGroupIndex];
- if (toGroup.Count == indexInToGroup) {
- continue;
- }
-
- var requiredPosition = edge.ToLocation;
- var currentPosition = toGroup[indexInToGroup].FromLocation;
- if (requiredPosition > currentPosition) {
- return false;
- }
- }
- }
- }
-}
diff --git a/src/Analysis/Ast/Impl/Dependencies/WalkingVertex.cs b/src/Analysis/Ast/Impl/Dependencies/WalkingVertex.cs
index 041709891..d3debee4f 100644
--- a/src/Analysis/Ast/Impl/Dependencies/WalkingVertex.cs
+++ b/src/Analysis/Ast/Impl/Dependencies/WalkingVertex.cs
@@ -13,6 +13,7 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Python.Core.Diagnostics;
@@ -20,6 +21,8 @@
namespace Microsoft.Python.Analysis.Dependencies {
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
internal sealed class WalkingVertex {
+ public static Comparison> FirstPassIncomingComparison { get; } = (v1, v2) => v1.FirstPass._incomingCount.CompareTo(v2.FirstPass._incomingCount);
+
private readonly List> _outgoing;
private bool _isSealed;
private int _incomingCount;
@@ -33,21 +36,18 @@ internal sealed class WalkingVertex {
public bool HasOnlyWalkedIncoming => _walkedIncomingCount == 0;
public bool HasMissingDependencies { get; private set; }
+ public WalkingVertex FirstPass { get; }
+ public WalkingVertex SecondPass { get; private set; }
+
public bool IsInLoop => LoopNumber >= 0;
- public string DebuggerDisplay => DependencyVertex?.DebuggerDisplay ?? "Loop node";
+ public string DebuggerDisplay => DependencyVertex.DebuggerDisplay;
- public WalkingVertex(DependencyVertex vertex) {
+ public WalkingVertex(DependencyVertex vertex, WalkingVertex firstPass = null) {
DependencyVertex = vertex;
+ FirstPass = firstPass;
Index = -1;
- LoopNumber = -1;
- _outgoing = new List>();
- }
-
- public WalkingVertex(int loopNumber) {
- DependencyVertex = default;
- Index = -1;
- LoopNumber = loopNumber;
+ LoopNumber = firstPass?.LoopNumber ?? -1;
_outgoing = new List>();
}
@@ -83,6 +83,13 @@ public void RemoveOutgoingAt(int index) {
outgoingVertex._walkedIncomingCount--;
}
+ public WalkingVertex CreateSecondPassVertex() {
+ CheckNotSealed();
+
+ SecondPass = new WalkingVertex(DependencyVertex, this);
+ return SecondPass;
+ }
+
public void Seal() => _isSealed = true;
public void DecrementIncoming(bool isWalkedIncoming) {
diff --git a/src/Analysis/Ast/Impl/Documents/RunningDocumentTable.cs b/src/Analysis/Ast/Impl/Documents/RunningDocumentTable.cs
index 0bd2136a2..9b2e259a0 100644
--- a/src/Analysis/Ast/Impl/Documents/RunningDocumentTable.cs
+++ b/src/Analysis/Ast/Impl/Documents/RunningDocumentTable.cs
@@ -85,7 +85,6 @@ public int DocumentCount {
/// Optional file path, if different from the URI.
public IDocument OpenDocument(Uri uri, string content, string filePath = null) {
bool justOpened;
- var created = false;
IDocument document;
lock (_lock) {
var entry = FindDocument(uri);
@@ -106,16 +105,11 @@ public IDocument OpenDocument(Uri uri, string content, string filePath = null) {
ModuleType = moduleType
};
entry = CreateDocument(mco);
- created = true;
}
justOpened = TryOpenDocument(entry, content);
document = entry.Document;
}
- if (created) {
- _services.GetService().InvalidateAnalysis(document);
- }
-
if (justOpened) {
Opened?.Invoke(this, new DocumentEventArgs(document));
}
@@ -247,10 +241,10 @@ private DocumentEntry CreateDocument(ModuleCreationOptions mco) {
IDocument document;
switch (mco.ModuleType) {
case ModuleType.Compiled when TryAddModulePath(mco):
- document = new CompiledPythonModule(mco.ModuleName, ModuleType.Compiled, mco.FilePath, mco.Stub, mco.IsTypeshed, _services);
+ document = new CompiledPythonModule(mco.ModuleName, ModuleType.Compiled, mco.FilePath, mco.Stub, mco.IsPersistent, mco.IsTypeshed, _services);
break;
case ModuleType.CompiledBuiltin:
- document = new CompiledBuiltinPythonModule(mco.ModuleName, mco.Stub, _services);
+ document = new CompiledBuiltinPythonModule(mco.ModuleName, mco.Stub, mco.IsPersistent, _services);
break;
case ModuleType.User:
TryAddModulePath(mco);
diff --git a/src/Analysis/Ast/Impl/Extensions/ScopeExtensions.cs b/src/Analysis/Ast/Impl/Extensions/ScopeExtensions.cs
index e5ce15f5a..f3ed567cb 100644
--- a/src/Analysis/Ast/Impl/Extensions/ScopeExtensions.cs
+++ b/src/Analysis/Ast/Impl/Extensions/ScopeExtensions.cs
@@ -13,11 +13,8 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
-using System.Collections.Generic;
using System.Linq;
using Microsoft.Python.Analysis.Documents;
-using Microsoft.Python.Analysis.Modules;
-using Microsoft.Python.Analysis.Specializations.Typing;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Analysis.Values;
using Microsoft.Python.Core.Text;
@@ -25,35 +22,6 @@
namespace Microsoft.Python.Analysis.Analyzer {
public static class ScopeExtensions {
- public static IEnumerable GetExportableVariableNames(this IGlobalScope scope)
- // drop imported modules and typing
- => scope.Variables
- .Where(v => {
- // Instances are always fine.
- if (v.Value is IPythonInstance) {
- return true;
- }
-
- var valueType = v.Value?.GetPythonType();
- switch (valueType) {
- case PythonModule _:
- case IPythonFunctionType f when f.IsLambda():
- return false; // Do not re-export modules.
- }
-
- if (scope.Module is TypingModule) {
- return true; // Let typing module behave normally.
- }
-
- // Do not re-export types from typing. However, do export variables
- // assigned with types from typing. Example:
- // from typing import Any # do NOT export Any
- // x = Union[int, str] # DO export x
- return !(valueType?.DeclaringModule is TypingModule) || v.Name != valueType.Name;
- })
- .Select(v => v.Name)
- .ToArray();
-
public static IMember LookupNameInScopes(this IScope currentScope, string name, out IScope scope) {
scope = null;
foreach (var s in currentScope.EnumerateTowardsGlobal) {
diff --git a/src/Analysis/Ast/Impl/Extensions/VariableExtensions.cs b/src/Analysis/Ast/Impl/Extensions/VariableExtensions.cs
index 065a8c1c0..54ea7c16e 100644
--- a/src/Analysis/Ast/Impl/Extensions/VariableExtensions.cs
+++ b/src/Analysis/Ast/Impl/Extensions/VariableExtensions.cs
@@ -13,10 +13,17 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
+using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Analysis.Values;
namespace Microsoft.Python.Analysis {
public static class VariableExtensions {
public static T GetValue(this IVariable v) where T : class => v.Value as T;
+
+ public static bool IsTypeInfo(this IVariable v) => v.Value is IPythonType;
+ public static bool IsTypeInfoOf(this IVariable v) where T : class, IPythonType => v.Value is T;
+
+ public static bool IsInstance(this IVariable v) => v.Value is IPythonInstance;
+ public static bool IsInstanceOf(this IVariable v) where T: class, IPythonInstance => v.Value is T;
}
}
diff --git a/src/Analysis/Ast/Impl/Microsoft.Python.Analysis.csproj b/src/Analysis/Ast/Impl/Microsoft.Python.Analysis.csproj
index 4dfae34ee..a244ea8fb 100644
--- a/src/Analysis/Ast/Impl/Microsoft.Python.Analysis.csproj
+++ b/src/Analysis/Ast/Impl/Microsoft.Python.Analysis.csproj
@@ -1025,4 +1025,4 @@
-
\ No newline at end of file
+
diff --git a/src/Analysis/Ast/Impl/Modules/BuiltinsPythonModule.cs b/src/Analysis/Ast/Impl/Modules/BuiltinsPythonModule.cs
index 5edae0a92..c8caf615c 100644
--- a/src/Analysis/Ast/Impl/Modules/BuiltinsPythonModule.cs
+++ b/src/Analysis/Ast/Impl/Modules/BuiltinsPythonModule.cs
@@ -40,7 +40,7 @@ internal sealed class BuiltinsPythonModule : CompiledPythonModule, IBuiltinsPyth
private IPythonType _boolType;
public BuiltinsPythonModule(string moduleName, string filePath, IServiceContainer services)
- : base(moduleName, ModuleType.Builtins, filePath, null, false, services) { } // TODO: builtins stub & persistence
+ : base(moduleName, ModuleType.Builtins, filePath, null, false, false, services) { } // TODO: builtins stub & persistence
#region IMemberContainer
public override IMember GetMember(string name) => _hiddenNames.Contains(name) ? null : base.GetMember(name);
@@ -67,6 +67,9 @@ protected override void Analyze(PythonAst ast, int version) {
protected override void OnAnalysisComplete() {
SpecializeTypes();
SpecializeFunctions();
+ foreach (var n in GetMemberNames()) {
+ GetMember(n).GetPythonType()?.MakeReadOnly();
+ }
base.OnAnalysisComplete();
}
diff --git a/src/Analysis/Ast/Impl/Modules/CompiledBuiltinPythonModule.cs b/src/Analysis/Ast/Impl/Modules/CompiledBuiltinPythonModule.cs
index 451cdbaea..bf1e3d0c5 100644
--- a/src/Analysis/Ast/Impl/Modules/CompiledBuiltinPythonModule.cs
+++ b/src/Analysis/Ast/Impl/Modules/CompiledBuiltinPythonModule.cs
@@ -23,8 +23,8 @@ namespace Microsoft.Python.Analysis.Modules {
/// Represents compiled module that is built into the language.
///
internal sealed class CompiledBuiltinPythonModule : CompiledPythonModule {
- public CompiledBuiltinPythonModule(string moduleName, IPythonModule stub, IServiceContainer services)
- : base(moduleName, ModuleType.CompiledBuiltin, MakeFakeFilePath(moduleName, services), stub, false, services) { }
+ public CompiledBuiltinPythonModule(string moduleName, IPythonModule stub, bool isPersistent, IServiceContainer services)
+ : base(moduleName, ModuleType.CompiledBuiltin, MakeFakeFilePath(moduleName, services), stub, isPersistent, false, services) { }
protected override string[] GetScrapeArguments(IPythonInterpreter interpreter)
=> !InstallPath.TryGetFile("scrape_module.py", out var sm) ? null : new [] { "-W", "ignore", "-B", "-E", sm, "-u8", Name };
diff --git a/src/Analysis/Ast/Impl/Modules/CompiledPythonModule.cs b/src/Analysis/Ast/Impl/Modules/CompiledPythonModule.cs
index 345d29754..a5ddf50db 100644
--- a/src/Analysis/Ast/Impl/Modules/CompiledPythonModule.cs
+++ b/src/Analysis/Ast/Impl/Modules/CompiledPythonModule.cs
@@ -26,8 +26,8 @@ namespace Microsoft.Python.Analysis.Modules {
internal class CompiledPythonModule : PythonModule {
protected IStubCache StubCache => Interpreter.ModuleResolution.StubCache;
- public CompiledPythonModule(string moduleName, ModuleType moduleType, string filePath, IPythonModule stub, bool isTypeshed, IServiceContainer services)
- : base(moduleName, filePath, moduleType, stub, isTypeshed, services) { }
+ public CompiledPythonModule(string moduleName, ModuleType moduleType, string filePath, IPythonModule stub, bool isPersistent, bool isTypeshed, IServiceContainer services)
+ : base(moduleName, filePath, moduleType, stub, isPersistent, isTypeshed, services) { }
public override string Documentation
=> GetMember("__doc__").TryGetConstant(out var s) ? s : string.Empty;
diff --git a/src/Analysis/Ast/Impl/Modules/Definitions/IModuleCache.cs b/src/Analysis/Ast/Impl/Modules/Definitions/IModuleCache.cs
new file mode 100644
index 000000000..40ea4c0a6
--- /dev/null
+++ b/src/Analysis/Ast/Impl/Modules/Definitions/IModuleCache.cs
@@ -0,0 +1,22 @@
+// Copyright(c) Microsoft Corporation
+// All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the License); you may not use
+// this file except in compliance with the License. You may obtain a copy of the
+// License at http://www.apache.org/licenses/LICENSE-2.0
+//
+// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
+// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+// MERCHANTABILITY OR NON-INFRINGEMENT.
+//
+// See the Apache Version 2.0 License for specific language governing
+// permissions and limitations under the License.
+
+namespace Microsoft.Python.Analysis.Modules {
+ public interface IModuleCache {
+ string GetCacheFilePath(string filePath);
+ string ReadCachedModule(string filePath);
+ void WriteCachedModule(string filePath, string code);
+ }
+}
diff --git a/src/Analysis/Ast/Impl/Modules/Definitions/IModuleManagement.cs b/src/Analysis/Ast/Impl/Modules/Definitions/IModuleManagement.cs
index a74e221c3..91a034f98 100644
--- a/src/Analysis/Ast/Impl/Modules/Definitions/IModuleManagement.cs
+++ b/src/Analysis/Ast/Impl/Modules/Definitions/IModuleManagement.cs
@@ -14,6 +14,8 @@
// permissions and limitations under the License.
using System;
+using System.Collections.Generic;
+using System.Threading;
using Microsoft.Python.Analysis.Caching;
using Microsoft.Python.Analysis.Core.Interpreter;
using Microsoft.Python.Analysis.Types;
@@ -86,5 +88,7 @@ public interface IModuleManagement : IModuleResolution {
ImmutableArray LibraryPaths { get; }
bool SetUserConfiguredPaths(ImmutableArray paths);
+
+ IEnumerable GetImportedModules(CancellationToken cancellationToken);
}
}
diff --git a/src/Analysis/Ast/Impl/Modules/Definitions/IModuleResolution.cs b/src/Analysis/Ast/Impl/Modules/Definitions/IModuleResolution.cs
index 493919b30..4b8ce7744 100644
--- a/src/Analysis/Ast/Impl/Modules/Definitions/IModuleResolution.cs
+++ b/src/Analysis/Ast/Impl/Modules/Definitions/IModuleResolution.cs
@@ -13,7 +13,6 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
-using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Python.Analysis.Core.DependencyResolution;
@@ -45,11 +44,5 @@ public interface IModuleResolution {
/// Reloads all modules. Typically after installation or removal of packages.
///
Task ReloadAsync(CancellationToken token = default);
-
- ///
- /// Returns collection of all currently imported modules.
- ///
- ///
- IEnumerable GetImportedModules(CancellationToken cancellationToken = default);
}
}
diff --git a/src/Analysis/Ast/Impl/Modules/Definitions/ModuleCreationOptions.cs b/src/Analysis/Ast/Impl/Modules/Definitions/ModuleCreationOptions.cs
index 876e8ba47..ff57426cc 100644
--- a/src/Analysis/Ast/Impl/Modules/Definitions/ModuleCreationOptions.cs
+++ b/src/Analysis/Ast/Impl/Modules/Definitions/ModuleCreationOptions.cs
@@ -48,6 +48,11 @@ public sealed class ModuleCreationOptions {
///
public IPythonModule Stub { get; set; }
+ ///
+ /// Indicates if module is restored from database.
+ ///
+ public bool IsPersistent { get; set; }
+
///
/// Defines if module belongs to Typeshed and hence resolved
/// via typeshed module resolution service.
diff --git a/src/Analysis/Ast/Impl/Modules/DependencyProvider.cs b/src/Analysis/Ast/Impl/Modules/DependencyProvider.cs
new file mode 100644
index 000000000..1e0e4752a
--- /dev/null
+++ b/src/Analysis/Ast/Impl/Modules/DependencyProvider.cs
@@ -0,0 +1,52 @@
+// Copyright(c) Microsoft Corporation
+// All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the License); you may not use
+// this file except in compliance with the License. You may obtain a copy of the
+// License at http://www.apache.org/licenses/LICENSE-2.0
+//
+// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
+// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
+// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+// MERCHANTABILITY OR NON-INFRINGEMENT.
+//
+// See the Apache Version 2.0 License for specific language governing
+// permissions and limitations under the License.
+
+using System.Collections.Generic;
+using Microsoft.Python.Analysis.Analyzer;
+using Microsoft.Python.Analysis.Caching;
+using Microsoft.Python.Analysis.Dependencies;
+using Microsoft.Python.Analysis.Types;
+using Microsoft.Python.Core;
+using Microsoft.Python.Parsing.Ast;
+
+namespace Microsoft.Python.Analysis.Modules {
+ internal sealed class DependencyProvider: IDependencyProvider {
+ private readonly IPythonModule _module;
+ private readonly IModuleDatabaseService _dbService;
+
+ public static IDependencyProvider Empty { get; } = new EmptyDependencyProvider();
+
+ public DependencyProvider(IPythonModule module, IServiceContainer services) {
+ _dbService = services.GetService();
+ _module = module;
+ }
+
+ #region IDependencyProvider
+ public ISet GetDependencies(PythonAst ast) {
+ if (_dbService != null && _dbService.TryRestoreDependencies(_module, out var dp)) {
+ return dp.GetDependencies(ast);
+ }
+
+ // TODO: try and handle LoadFunctionDependencyModules functionality here.
+ var dw = new DependencyWalker(_module, ast);
+ return dw.Dependencies;
+ }
+ #endregion
+
+ private sealed class EmptyDependencyProvider: IDependencyProvider {
+ public ISet GetDependencies(PythonAst ast) => new HashSet();
+ }
+ }
+}
diff --git a/src/Analysis/Ast/Impl/Modules/PythonModule.cs b/src/Analysis/Ast/Impl/Modules/PythonModule.cs
index dba50e7e7..031cef760 100644
--- a/src/Analysis/Ast/Impl/Modules/PythonModule.cs
+++ b/src/Analysis/Ast/Impl/Modules/PythonModule.cs
@@ -27,6 +27,7 @@
using Microsoft.Python.Analysis.Dependencies;
using Microsoft.Python.Analysis.Diagnostics;
using Microsoft.Python.Analysis.Documents;
+using Microsoft.Python.Analysis.Specializations.Typing;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Analysis.Values;
using Microsoft.Python.Core;
@@ -76,13 +77,14 @@ protected PythonModule(string name, ModuleType moduleType, IServiceContainer ser
SetDeclaringModule(this);
}
- protected PythonModule(string moduleName, string filePath, ModuleType moduleType, IPythonModule stub, bool isTypeshed, IServiceContainer services) :
+ protected PythonModule(string moduleName, string filePath, ModuleType moduleType, IPythonModule stub, bool isPersistent, bool isTypeshed, IServiceContainer services) :
this(new ModuleCreationOptions {
ModuleName = moduleName,
FilePath = filePath,
ModuleType = moduleType,
Stub = stub,
- IsTypeshed = isTypeshed
+ IsTypeshed = isTypeshed,
+ IsPersistent = isPersistent
}, services) { }
internal PythonModule(ModuleCreationOptions creationOptions, IServiceContainer services)
@@ -108,6 +110,7 @@ internal PythonModule(ModuleCreationOptions creationOptions, IServiceContainer s
ModuleState = ModuleState.Analyzed;
}
+ IsPersistent = creationOptions.IsPersistent;
IsTypeshed = creationOptions.IsTypeshed;
InitializeContent(creationOptions.Content, 0);
@@ -150,8 +153,35 @@ public virtual string Documentation {
#region IMemberContainer
public virtual IMember GetMember(string name) => GlobalScope.Variables[name]?.Value;
- public virtual IEnumerable GetMemberNames() => GlobalScope.GetExportableVariableNames();
+ public virtual IEnumerable GetMemberNames() {
+ // drop imported modules and typing.
+ return GlobalScope.Variables
+ .Where(v => {
+ // Instances are always fine.
+ if (v.Value is IPythonInstance) {
+ return true;
+ }
+
+ var valueType = v.Value?.GetPythonType();
+ switch (valueType) {
+ case PythonModule _:
+ case IPythonFunctionType f when f.IsLambda():
+ return false; // Do not re-export modules.
+ }
+ if (this is TypingModule) {
+ return true; // Let typing module behave normally.
+ }
+
+ // Do not re-export types from typing. However, do export variables
+ // assigned with types from typing. Example:
+ // from typing import Any # do NOT export Any
+ // x = Union[int, str] # DO export x
+ return !(valueType?.DeclaringModule is TypingModule) || v.Name != valueType.Name;
+ })
+ .Select(v => v.Name)
+ .ToArray();
+ }
#endregion
#region ILocatedMember
@@ -183,6 +213,11 @@ public virtual string Documentation {
///
public IPythonModule PrimaryModule { get; private set; }
+ ///
+ /// Indicates if module is restored from database.
+ ///
+ public bool IsPersistent { get; }
+
///
/// Defines if module belongs to Typeshed and hence resolved
/// via typeshed module resolution service.
@@ -323,7 +358,7 @@ private void Parse(CancellationToken cancellationToken) {
int version;
Parser parser;
- // Log?.Log(TraceEventType.Verbose, $"Parse begins: {Name} ({ModuleType})");
+ //Log?.Log(TraceEventType.Verbose, $"Parse begins: {Name}");
lock (_syncObj) {
version = _buffer.Version;
@@ -379,6 +414,8 @@ public override void Add(string message, SourceSpan span, int errorCode, Severit
#endregion
#region IAnalyzable
+ public virtual IDependencyProvider DependencyProvider => new DependencyProvider(this, Services);
+
public void NotifyAnalysisBegins() {
lock (_syncObj) {
if (_updated) {
@@ -408,8 +445,7 @@ public void NotifyAnalysisBegins() {
}
public ModuleWalker Analyze(PythonAst ast) {
- var eval = new ExpressionEval(Services, this, ast);
- var walker = new ModuleWalker(eval, SimpleImportedVariableHandler.Instance);
+ var walker = new ModuleWalker(Services, this, ast, CancellationToken.None);
ast.Walk(walker);
walker.Complete();
return walker;
@@ -502,12 +538,17 @@ private void InitializeContent(string content, int version) {
Parse();
}
}
+ Services.GetService().InvalidateAnalysis(this);
}
private void SetOrLoadContent(string content) {
if (ModuleState < ModuleState.Loading) {
try {
- content = content ?? LoadContent();
+ if (IsPersistent) {
+ content = string.Empty;
+ } else {
+ content = content ?? LoadContent();
+ }
_buffer.SetContent(content);
ModuleState = ModuleState.Loaded;
} catch (IOException) { } catch (UnauthorizedAccessException) { }
diff --git a/src/Analysis/Ast/Impl/Modules/PythonVariableModule.cs b/src/Analysis/Ast/Impl/Modules/PythonVariableModule.cs
index 94215dd5d..51550546f 100644
--- a/src/Analysis/Ast/Impl/Modules/PythonVariableModule.cs
+++ b/src/Analysis/Ast/Impl/Modules/PythonVariableModule.cs
@@ -37,6 +37,7 @@ internal sealed class PythonVariableModule : LocatedMember, IPythonModule, IEqua
public IPythonModule Module { get; }
public IPythonInterpreter Interpreter { get; }
+ public ModuleState ModuleState => Module?.ModuleState ?? ModuleState.None;
public IDocumentAnalysis Analysis => Module?.Analysis;
public string Documentation => Module?.Documentation ?? string.Empty;
@@ -51,9 +52,8 @@ internal sealed class PythonVariableModule : LocatedMember, IPythonModule, IEqua
public BuiltinTypeId TypeId => BuiltinTypeId.Module;
public Uri Uri => Module?.Uri;
public override PythonMemberType MemberType => PythonMemberType.Module;
+ public bool IsPersistent => Module?.IsPersistent == true;
public bool IsTypeshed => Module?.IsTypeshed == true;
- public ModuleState ModuleState => Module?.ModuleState ?? ModuleState.None;
- public IEnumerable ChildrenNames => _children.Keys;
public PythonVariableModule(string name, IPythonInterpreter interpreter) : base(null) {
Name = name;
@@ -70,7 +70,7 @@ public PythonVariableModule(IPythonModule module): base(module) {
public void AddChildModule(string memberName, PythonVariableModule module) => _children[memberName] = module;
public IMember GetMember(string name) => _children.TryGetValue(name, out var module) ? module : Module?.GetMember(name);
- public IEnumerable GetMemberNames() => Module != null ? Module.GetMemberNames().Concat(ChildrenNames).Distinct() : ChildrenNames;
+ public IEnumerable GetMemberNames() => Module != null ? Module.GetMemberNames().Concat(_children.Keys).Distinct() : _children.Keys;
public IMember Call(IPythonInstance instance, string memberName, IArgumentSet args) => GetMember(memberName);
public IMember Index(IPythonInstance instance, IArgumentSet args) => Interpreter.UnknownType;
@@ -79,11 +79,15 @@ public PythonVariableModule(IPythonModule module): base(module) {
public bool Equals(IPythonModule other) => other is PythonVariableModule module && Name.EqualsOrdinal(module.Name);
public override bool Equals(object obj) => Equals(obj as IPythonModule);
- public override int GetHashCode() => Name.GetHashCode();
+ public override int GetHashCode() => 0;
#region ILocationConverter
public SourceLocation IndexToLocation(int index) => (Module as ILocationConverter)?.IndexToLocation(index) ?? default;
public int LocationToIndex(SourceLocation location) => (Module as ILocationConverter)?.LocationToIndex(location) ?? default;
#endregion
+
+ #region IDependencyProvider
+ public IDependencyProvider DependencyProvider => (Module as IAnalyzable)?.DependencyProvider;
+ #endregion
}
}
diff --git a/src/Analysis/Ast/Impl/Modules/Resolution/MainModuleResolution.cs b/src/Analysis/Ast/Impl/Modules/Resolution/MainModuleResolution.cs
index 03913c364..22dafdadd 100644
--- a/src/Analysis/Ast/Impl/Modules/Resolution/MainModuleResolution.cs
+++ b/src/Analysis/Ast/Impl/Modules/Resolution/MainModuleResolution.cs
@@ -56,7 +56,7 @@ public MainModuleResolution(string root, IServiceContainer services, ImmutableAr
public IBuiltinsPythonModule BuiltinsModule => _builtins;
- public IEnumerable GetImportedModules(CancellationToken cancellationToken = default) {
+ public IEnumerable GetImportedModules(CancellationToken cancellationToken) {
foreach (var module in _specialized.Values) {
cancellationToken.ThrowIfCancellationRequested();
yield return module;
@@ -77,54 +77,40 @@ protected override IPythonModule CreateModule(string name) {
return null;
}
- IPythonModule module;
if (moduleImport.ModulePath != null) {
- module = GetRdt().GetDocument(new Uri(moduleImport.ModulePath));
+ var module = GetRdt().GetDocument(new Uri(moduleImport.ModulePath));
if (module != null) {
GetRdt().LockDocument(module.Uri);
return module;
}
}
- var moduleType = moduleImport.IsBuiltin ? ModuleType.CompiledBuiltin
- : moduleImport.IsCompiled ? ModuleType.Compiled
- : moduleImport.IsLibrary ? ModuleType.Library
- : ModuleType.User;
-
var dbs = GetDbService();
- if (dbs != null) {
- var sw = Stopwatch.StartNew();
- module = dbs.RestoreModule(name, moduleImport.ModulePath, moduleType);
- sw.Stop();
- if (module != null) {
- Log?.Log(TraceEventType.Verbose, $"Restored from database: {name} in {sw.ElapsedMilliseconds} ms.");
- Interpreter.ModuleResolution.SpecializeModule(name, x => module, true);
- return module;
+ moduleImport.IsPersistent = dbs != null && dbs.ModuleExistsInStorage(name, moduleImport.ModulePath);
+
+ IPythonModule stub = null;
+ if (!moduleImport.IsPersistent) {
+ // If there is a stub, make sure it is loaded and attached
+ // First check stub next to the module.
+ if (!TryCreateModuleStub(name, moduleImport.ModulePath, out stub)) {
+ // If nothing found, try Typeshed.
+ stub = Interpreter.TypeshedResolution.GetOrLoadModule(moduleImport.IsBuiltin ? name : moduleImport.FullName);
}
- }
- // If there is a stub, make sure it is loaded and attached
- // First check stub next to the module.
- if (TryCreateModuleStub(name, moduleImport.ModulePath, out var stub)) {
- Analyzer.InvalidateAnalysis(stub);
- } else {
- // If nothing found, try Typeshed.
- stub = Interpreter.TypeshedResolution.GetOrLoadModule(moduleImport.IsBuiltin ? name : moduleImport.FullName);
- }
-
- // If stub is created and its path equals to module, return that stub as module
- if (stub != null && stub.FilePath.PathEquals(moduleImport.ModulePath)) {
- return stub;
+ // If stub is created and its path equals to module, return that stub as module
+ if (stub != null && stub.FilePath.PathEquals(moduleImport.ModulePath)) {
+ return stub;
+ }
}
if (moduleImport.IsBuiltin) {
Log?.Log(TraceEventType.Verbose, "Create built-in compiled (scraped) module: ", name, Configuration.InterpreterPath);
- return new CompiledBuiltinPythonModule(name, stub, Services);
+ return new CompiledBuiltinPythonModule(name, stub, moduleImport.IsPersistent, Services);
}
if (moduleImport.IsCompiled) {
Log?.Log(TraceEventType.Verbose, "Create compiled (scraped): ", moduleImport.FullName, moduleImport.ModulePath, moduleImport.RootPath);
- return new CompiledPythonModule(moduleImport.FullName, ModuleType.Compiled, moduleImport.ModulePath, stub, false, Services);
+ return new CompiledPythonModule(moduleImport.FullName, ModuleType.Compiled, moduleImport.ModulePath, stub, moduleImport.IsPersistent, false, Services);
}
Log?.Log(TraceEventType.Verbose, "Import: ", moduleImport.FullName, moduleImport.ModulePath);
@@ -132,9 +118,10 @@ protected override IPythonModule CreateModule(string name) {
var mco = new ModuleCreationOptions {
ModuleName = moduleImport.FullName,
- ModuleType = moduleType,
+ ModuleType = moduleImport.IsLibrary ? ModuleType.Library : ModuleType.User,
FilePath = moduleImport.ModulePath,
- Stub = stub
+ Stub = stub,
+ IsPersistent = moduleImport.IsPersistent
};
return GetRdt().AddModule(mco);
@@ -196,7 +183,7 @@ private void AddBuiltinTypesToPathResolver() {
Check.InvalidOperation(!(BuiltinsModule.Analysis is EmptyAnalysis), "Builtins analysis did not complete correctly.");
// Add built-in module names
var builtinModuleNamesMember = BuiltinsModule.GetAnyMember("__builtin_module_names__");
- var value = builtinModuleNamesMember is IVariable variable ? variable.Value : builtinModuleNamesMember;
+ var value = (builtinModuleNamesMember as IVariable)?.Value ?? builtinModuleNamesMember;
if (value.TryGetConstant(out var s)) {
var builtinModuleNames = s.Split(',').Select(n => n.Trim());
PathResolver.SetBuiltins(builtinModuleNames);
diff --git a/src/Analysis/Ast/Impl/Modules/Resolution/ModuleResolutionBase.cs b/src/Analysis/Ast/Impl/Modules/Resolution/ModuleResolutionBase.cs
index 52112e7bf..0a3782b24 100644
--- a/src/Analysis/Ast/Impl/Modules/Resolution/ModuleResolutionBase.cs
+++ b/src/Analysis/Ast/Impl/Modules/Resolution/ModuleResolutionBase.cs
@@ -18,7 +18,6 @@
using System.IO;
using System.Linq;
using System.Threading;
-using Microsoft.Python.Analysis.Analyzer;
using Microsoft.Python.Analysis.Caching;
using Microsoft.Python.Analysis.Core.DependencyResolution;
using Microsoft.Python.Analysis.Core.Interpreter;
@@ -27,13 +26,13 @@
using Microsoft.Python.Core.Collections;
using Microsoft.Python.Core.IO;
using Microsoft.Python.Core.Logging;
+using Microsoft.Python.Core.Services;
namespace Microsoft.Python.Analysis.Modules.Resolution {
internal abstract class ModuleResolutionBase {
protected IServiceContainer Services { get; }
protected IFileSystem FileSystem { get; }
protected IPythonInterpreter Interpreter { get; }
- protected IPythonAnalyzer Analyzer { get; }
protected ILogger Log { get; }
protected ConcurrentDictionary Modules { get; } = new ConcurrentDictionary();
@@ -50,7 +49,6 @@ protected ModuleResolutionBase(string root, IServiceContainer services) {
FileSystem = services.GetService();
Interpreter = services.GetService();
- Analyzer = services.GetService();
StubCache = services.GetService();
Log = services.GetService();
}
diff --git a/src/Analysis/Ast/Impl/Modules/Resolution/TypeshedResolution.cs b/src/Analysis/Ast/Impl/Modules/Resolution/TypeshedResolution.cs
index 91576a8dc..971fe86c5 100644
--- a/src/Analysis/Ast/Impl/Modules/Resolution/TypeshedResolution.cs
+++ b/src/Analysis/Ast/Impl/Modules/Resolution/TypeshedResolution.cs
@@ -48,49 +48,23 @@ public TypeshedResolution(string root, IServiceContainer services) : base(root,
}
protected override IPythonModule CreateModule(string name) {
- if (!TryCreateStubModule(name, out var module)) {
- return null;
- }
-
- Analyzer.InvalidateAnalysis(module);
- return module;
-
- }
-
- private bool TryCreateStubModule(string name, out IPythonModule module) {
- module = null;
var moduleImport = CurrentPathResolver.GetModuleImportFromModuleName(name);
if (moduleImport != null) {
if (moduleImport.IsCompiled) {
Log?.Log(TraceEventType.Warning, "Unsupported native module in stubs", moduleImport.FullName, moduleImport.ModulePath);
- return false;
+ return null;
}
-
- module = new StubPythonModule(moduleImport.FullName, moduleImport.ModulePath, true, Services);
- return true;
+ return new StubPythonModule(moduleImport.FullName, moduleImport.ModulePath, true, Services);
}
var i = name.IndexOf('.');
if (i == 0) {
Debug.Fail("Invalid module name");
- return false;
+ return null;
}
var stubPath = CurrentPathResolver.GetPossibleModuleStubPaths(name).FirstOrDefault(p => FileSystem.FileExists(p));
- if (stubPath != null) {
- module = new StubPythonModule(name, stubPath, true, Services);
- return true;
- }
- return false;
- }
-
- public IEnumerable GetImportedModules(CancellationToken cancellationToken = default) {
- foreach (var moduleRef in Modules.Values) {
- cancellationToken.ThrowIfCancellationRequested();
- if (moduleRef.Value != null) {
- yield return moduleRef.Value;
- }
- }
+ return stubPath != null ? new StubPythonModule(name, stubPath, true, Services) : null;
}
public Task ReloadAsync(CancellationToken cancellationToken = default) {
diff --git a/src/Analysis/Ast/Impl/Modules/SpecializedModule.cs b/src/Analysis/Ast/Impl/Modules/SpecializedModule.cs
index 41bd338b2..f9f545348 100644
--- a/src/Analysis/Ast/Impl/Modules/SpecializedModule.cs
+++ b/src/Analysis/Ast/Impl/Modules/SpecializedModule.cs
@@ -13,6 +13,7 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
+using Microsoft.Python.Analysis.Dependencies;
using Microsoft.Python.Analysis.Specializations.Typing;
using Microsoft.Python.Core;
using Microsoft.Python.Core.IO;
@@ -31,11 +32,15 @@ namespace Microsoft.Python.Analysis.Modules {
///
internal abstract class SpecializedModule : PythonModule {
protected SpecializedModule(string name, string modulePath, IServiceContainer services)
- : base(name, modulePath, ModuleType.Specialized, null, false, services) { }
+ : base(name, modulePath, ModuleType.Specialized, null, false, false, services) { }
protected override string LoadContent() {
// Exceptions are handled in the base
return FileSystem.FileExists(FilePath) ? FileSystem.ReadTextWithRetry(FilePath) : string.Empty;
}
+
+ #region IAnalyzable
+ public override IDependencyProvider DependencyProvider => Modules.DependencyProvider.Empty;
+ #endregion
}
}
diff --git a/src/Analysis/Ast/Impl/Modules/StubPythonModule.cs b/src/Analysis/Ast/Impl/Modules/StubPythonModule.cs
index d01eaadfe..7709c36c0 100644
--- a/src/Analysis/Ast/Impl/Modules/StubPythonModule.cs
+++ b/src/Analysis/Ast/Impl/Modules/StubPythonModule.cs
@@ -23,7 +23,7 @@ namespace Microsoft.Python.Analysis.Modules {
///
internal class StubPythonModule : CompiledPythonModule {
public StubPythonModule(string moduleName, string stubPath, bool isTypeshed, IServiceContainer services)
- : base(moduleName, ModuleType.Stub, stubPath, null, isTypeshed, services) {
+ : base(moduleName, ModuleType.Stub, stubPath, null, false, isTypeshed, services) {
}
protected override string LoadContent() {
diff --git a/src/Analysis/Ast/Impl/Specializations/Typing/Definitions/ITypingNamedTupleType.cs b/src/Analysis/Ast/Impl/Specializations/Typing/Definitions/ITypingNamedTupleType.cs
index 00bf93bae..61f35325a 100644
--- a/src/Analysis/Ast/Impl/Specializations/Typing/Definitions/ITypingNamedTupleType.cs
+++ b/src/Analysis/Ast/Impl/Specializations/Typing/Definitions/ITypingNamedTupleType.cs
@@ -21,16 +21,16 @@ namespace Microsoft.Python.Analysis.Specializations.Typing {
///
public interface ITypingNamedTupleType : ITypingTupleType {
IReadOnlyList ItemNames { get; }
- ///
- /// Allows setting alternative name to the tuple at the variable assignment time.
- ///
- ///
- /// Named tuple may get assigned to variables that have name different from the tuple itself.
- /// Then the name may conflict with other types in module or its persistent model. For example,
- /// 'tokenize' stub declares _TokenInfo = NamedTuple('TokenInfo', ...) but there is also
- /// 'class TokenInfo(_TokenInfo)'' so we have to use the variable name in order to avoid type conflicts.
- ///
- ///
+ ///
+ /// Allows setting alternative name to the tuple at the variable assignment time.
+ ///
+ ///
+ /// Named tuple may get assigned to variables that have name different from the tuple itself.
+ /// Then the name may conflict with other types in module or its persistent model. For example,
+ /// 'tokenize' stub declares _TokenInfo = NamedTuple('TokenInfo', ...) but there is also
+ /// 'class TokenInfo(_TokenInfo)'' so we have to use the variable name in order to avoid type conflicts.
+ ///
+ ///
void SetName(string name);
}
}
diff --git a/src/Analysis/Ast/Impl/Specializations/Typing/Types/NamedTupleType.cs b/src/Analysis/Ast/Impl/Specializations/Typing/Types/NamedTupleType.cs
index 98be2ad23..f568fd9fe 100644
--- a/src/Analysis/Ast/Impl/Specializations/Typing/Types/NamedTupleType.cs
+++ b/src/Analysis/Ast/Impl/Specializations/Typing/Types/NamedTupleType.cs
@@ -58,7 +58,7 @@ public NamedTupleType(string tupleName, IReadOnlyList itemNames, IReadOn
#region IPythonType
public override string Name => _name;
- public override string QualifiedName => $"{DeclaringModule.Name}:{Name}";
+ public override string QualifiedName => $"{DeclaringModule.Name}:{Name}"; // Named tuple name is a type name as class.
public override bool IsSpecialized => true;
public override string Documentation { get; }
#endregion
diff --git a/src/Analysis/Ast/Impl/Types/Definitions/IPythonModule.cs b/src/Analysis/Ast/Impl/Types/Definitions/IPythonModule.cs
index 7453c869c..9b438e082 100644
--- a/src/Analysis/Ast/Impl/Types/Definitions/IPythonModule.cs
+++ b/src/Analysis/Ast/Impl/Types/Definitions/IPythonModule.cs
@@ -64,6 +64,11 @@ public interface IPythonModule : IPythonType {
///
IPythonModule PrimaryModule { get; }
+ ///
+ /// Indicates if module is restored from database.
+ ///
+ bool IsPersistent { get; }
+
///
/// Defines if module belongs to Typeshed and hence resolved
/// via typeshed module resolution service.
diff --git a/src/Analysis/Ast/Impl/Types/Definitions/IPythonPropertyType.cs b/src/Analysis/Ast/Impl/Types/Definitions/IPythonPropertyType.cs
index a6efd3d4f..badcecb6b 100644
--- a/src/Analysis/Ast/Impl/Types/Definitions/IPythonPropertyType.cs
+++ b/src/Analysis/Ast/Impl/Types/Definitions/IPythonPropertyType.cs
@@ -25,6 +25,11 @@ public interface IPythonPropertyType : IPythonClassMember {
///
FunctionDefinition FunctionDefinition { get; }
+ ///
+ /// A user readable description of the property.
+ ///
+ string Description { get; }
+
///
/// True if the property is read-only.
///
diff --git a/src/Analysis/Ast/Impl/Types/LocatedMember.cs b/src/Analysis/Ast/Impl/Types/LocatedMember.cs
index 504af4be9..288a9fe35 100644
--- a/src/Analysis/Ast/Impl/Types/LocatedMember.cs
+++ b/src/Analysis/Ast/Impl/Types/LocatedMember.cs
@@ -91,18 +91,16 @@ public virtual void RemoveReferences(IPythonModule module) {
}
internal abstract class EmptyLocatedMember : ILocatedMember {
- protected EmptyLocatedMember(IPythonModule declaringModule, PythonMemberType memberType) {
- DeclaringModule = declaringModule;
+ protected EmptyLocatedMember(PythonMemberType memberType) {
MemberType = memberType;
- Location = new Location(DeclaringModule);
}
public PythonMemberType MemberType { get; }
- public IPythonModule DeclaringModule { get; }
+ public IPythonModule DeclaringModule => null;
public LocationInfo Definition => LocationInfo.Empty;
public IReadOnlyList References => Array.Empty();
public void AddReference(Location location) { }
public void RemoveReferences(IPythonModule module) { }
- public Location Location { get; }
+ public Location Location { get; internal set; }
}
}
diff --git a/src/Analysis/Ast/Impl/Types/Location.cs b/src/Analysis/Ast/Impl/Types/Location.cs
index 88a867020..53060d859 100644
--- a/src/Analysis/Ast/Impl/Types/Location.cs
+++ b/src/Analysis/Ast/Impl/Types/Location.cs
@@ -23,8 +23,6 @@ public Location(IPythonModule module, IndexSpan indexSpan = default) {
IndexSpan = indexSpan;
}
- public void Deconstruct(out IPythonModule module, out IndexSpan indexSpan) => (module, indexSpan) = (Module, IndexSpan);
-
public IPythonModule Module { get; }
public IndexSpan IndexSpan { get; }
diff --git a/src/Analysis/Ast/Impl/Types/PythonClassType.Generics.cs b/src/Analysis/Ast/Impl/Types/PythonClassType.Generics.cs
index b31a42712..bb58c303a 100644
--- a/src/Analysis/Ast/Impl/Types/PythonClassType.Generics.cs
+++ b/src/Analysis/Ast/Impl/Types/PythonClassType.Generics.cs
@@ -59,7 +59,7 @@ internal partial class PythonClassType {
/// B[int] inherits from A[int, str]
///
public virtual IPythonType CreateSpecificType(IArgumentSet args) {
- lock (MembersLock) {
+ lock (_membersLock) {
var newGenericTypeParameters = GetTypeParameters();
var newBases = new List();
diff --git a/src/Analysis/Ast/Impl/Types/PythonClassType.cs b/src/Analysis/Ast/Impl/Types/PythonClassType.cs
index e6f805482..3ba89e2a0 100644
--- a/src/Analysis/Ast/Impl/Types/PythonClassType.cs
+++ b/src/Analysis/Ast/Impl/Types/PythonClassType.cs
@@ -37,7 +37,9 @@ internal enum ClassDocumentationSource {
Base
}
private static readonly string[] _classMethods = { "mro", "__dict__", @"__weakref__" };
+
private readonly ReentrancyGuard _memberGuard = new ReentrancyGuard();
+ private readonly object _membersLock = new object();
private List _bases;
private IReadOnlyList _mro;
@@ -67,7 +69,7 @@ public PythonClassType(
public override PythonMemberType MemberType => PythonMemberType.Class;
public override IEnumerable GetMemberNames() {
- lock (MembersLock) {
+ lock (_membersLock) {
var names = new HashSet(Members.Keys);
foreach (var m in Mro.Skip(1)) {
names.UnionWith(m.GetMemberNames());
@@ -77,7 +79,7 @@ public override IEnumerable GetMemberNames() {
}
public override IMember GetMember(string name) {
- lock (MembersLock) {
+ lock (_membersLock) {
if (Members.TryGetValue(name, out var member)) {
return member;
}
@@ -185,7 +187,7 @@ public override IMember Index(IPythonInstance instance, IArgumentSet args) {
public ClassDefinition ClassDefinition => DeclaringModule.GetAstNode(this);
public IReadOnlyList Bases {
get {
- lock (MembersLock) {
+ lock (_membersLock) {
return _bases?.ToArray();
}
}
diff --git a/src/Analysis/Ast/Impl/Types/PythonFunctionOverload.cs b/src/Analysis/Ast/Impl/Types/PythonFunctionOverload.cs
index b287b7c1f..e394de6a2 100644
--- a/src/Analysis/Ast/Impl/Types/PythonFunctionOverload.cs
+++ b/src/Analysis/Ast/Impl/Types/PythonFunctionOverload.cs
@@ -17,7 +17,6 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Python.Analysis.Analyzer.Evaluation;
-using Microsoft.Python.Analysis.Analyzer.Symbols;
using Microsoft.Python.Analysis.Specializations.Typing;
using Microsoft.Python.Analysis.Values;
using Microsoft.Python.Core;
@@ -125,20 +124,10 @@ public string GetReturnDocumentation(IPythonType self = null) {
public IMember Call(IArgumentSet args, IPythonType self) {
if (!_fromAnnotation) {
// First try supplied specialization callback.
- var rt = _returnValueProvider?.Invoke(args.Eval?.Module, this, args, default);
+ var rt = _returnValueProvider?.Invoke(args.Eval.Module, this, args, default);
if (!rt.IsUnknown()) {
return rt;
}
- if (StaticReturnValue == null && !string.IsNullOrEmpty(_returnDocumentation) && FunctionDefinition?.ReturnAnnotation != null) {
- // There is return documentation but no static return value.
- // This may happen if function is inside module circular
- // dependency loop. Try and re-evaluate now.
- var returnValue = FunctionEvaluator.GetReturnValueFromAnnotation(args.Eval as ExpressionEval, FunctionDefinition.ReturnAnnotation);
- if (returnValue != null) {
- SetReturnValue(returnValue, true);
- return returnValue;
- }
- }
}
return GetSpecificReturnType(self as IPythonClassType, args);
diff --git a/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs b/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs
index 33d75e15b..12297dace 100644
--- a/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs
+++ b/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs
@@ -14,10 +14,13 @@
// permissions and limitations under the License.
using Microsoft.Python.Analysis.Values;
+using Microsoft.Python.Core;
using Microsoft.Python.Parsing.Ast;
namespace Microsoft.Python.Analysis.Types {
internal sealed class PythonPropertyType : PythonType, IPythonPropertyType {
+ private IPythonFunctionOverload _getter;
+
public PythonPropertyType(FunctionDefinition fd, Location location, IPythonType declaringType, bool isAbstract)
: this(fd.Name, location, fd.GetDocumentation(), declaringType, isAbstract) {
declaringType.DeclaringModule.AddAstNode(this, fd);
@@ -40,13 +43,19 @@ public PythonPropertyType(string name, Location location, string documentation,
public bool IsReadOnly => true;
public IPythonType DeclaringType { get; }
+ public string Description {
+ get {
+ var typeName = ReturnType?.GetPythonType()?.Name;
+ return typeName != null ? Resources.PropertyOfType.FormatUI(typeName) : Resources.PropertyOfUnknownType;
+ }
+ }
+
public override IMember Call(IPythonInstance instance, string memberName, IArgumentSet args)
- => Getter.Call(args, instance?.GetPythonType() ?? DeclaringType);
+ => _getter.Call(args, instance?.GetPythonType() ?? DeclaringType);
- public IMember ReturnType => Getter?.Call(ArgumentSet.WithoutContext, DeclaringType);
+ public IMember ReturnType => _getter?.Call(ArgumentSet.WithoutContext, DeclaringType);
#endregion
- internal void AddOverload(PythonFunctionOverload overload) => Getter = overload;
- internal PythonFunctionOverload Getter { get; private set; }
+ internal void AddOverload(IPythonFunctionOverload overload) => _getter = _getter ?? overload;
}
}
diff --git a/src/Analysis/Ast/Impl/Types/PythonType.cs b/src/Analysis/Ast/Impl/Types/PythonType.cs
index cdfe7fb81..11770248a 100644
--- a/src/Analysis/Ast/Impl/Types/PythonType.cs
+++ b/src/Analysis/Ast/Impl/Types/PythonType.cs
@@ -19,20 +19,22 @@
using System.Linq;
using Microsoft.Python.Analysis.Modules;
using Microsoft.Python.Analysis.Values;
+using Microsoft.Python.Core.Diagnostics;
namespace Microsoft.Python.Analysis.Types {
[DebuggerDisplay("{" + nameof(Name) + "}")]
internal class PythonType : LocatedMember, IPythonType {
+ private readonly object _lock = new object();
private Dictionary _members;
private BuiltinTypeId _typeId;
+ private bool _readonly;
- protected object MembersLock { get; } = new object();
protected IReadOnlyDictionary Members => WritableMembers;
private Dictionary WritableMembers =>
_members ?? (_members = new Dictionary());
- public PythonType(string name, Location location, string documentation, BuiltinTypeId typeId = BuiltinTypeId.Unknown)
+ public PythonType(string name, Location location, string documentation, BuiltinTypeId typeId = BuiltinTypeId.Unknown)
: this(name, location, typeId) {
BaseName = name ?? throw new ArgumentNullException(nameof(name));
Documentation = documentation;
@@ -86,19 +88,8 @@ public virtual IMember Call(IPythonInstance instance, string memberName, IArgume
#endregion
#region IMemberContainer
-
- public virtual IMember GetMember(string name) {
- lock (MembersLock) {
- return Members.TryGetValue(name, out var member) ? member : null;
- }
- }
-
- public virtual IEnumerable GetMemberNames() {
- lock (MembersLock) {
- return Members.Keys.ToArray();
- }
- }
-
+ public virtual IMember GetMember(string name) => Members.TryGetValue(name, out var member) ? member : null;
+ public virtual IEnumerable GetMemberNames() => Members.Keys;
#endregion
internal bool TrySetTypeId(BuiltinTypeId typeId) {
@@ -112,25 +103,29 @@ internal bool TrySetTypeId(BuiltinTypeId typeId) {
internal virtual void SetDocumentation(string documentation) => Documentation = documentation;
internal void AddMembers(IEnumerable variables, bool overwrite) {
- lock (MembersLock) {
- foreach (var v in variables.OfType()) {
- var hasMember = Members.ContainsKey(v.Name);
- if (overwrite || !hasMember) {
- // If variable holds function or a class, use value as member.
- // If it holds an instance, use the variable itself (i.e. it is a data member).
- WritableMembers[v.Name] = v.Value;
- }
- if (hasMember) {
- v.IsClassMember = true;
+ lock (_lock) {
+ if (!_readonly) {
+ foreach (var v in variables.OfType()) {
+ var hasMember = Members.ContainsKey(v.Name);
+ if (overwrite || !hasMember) {
+ // If variable holds function or a class, use value as member.
+ // If it holds an instance, use the variable itself (i.e. it is a data member).
+ WritableMembers[v.Name] = v.Value;
+ }
+ if (hasMember) {
+ v.IsClassMember = true;
+ }
}
}
}
}
internal void AddMembers(IEnumerable> members, bool overwrite) {
- lock (MembersLock) {
- foreach (var kv in members.Where(m => overwrite || !Members.ContainsKey(m.Key))) {
- WritableMembers[kv.Key] = kv.Value;
+ lock (_lock) {
+ if (!_readonly) {
+ foreach (var kv in members.Where(m => overwrite || !Members.ContainsKey(m.Key))) {
+ WritableMembers[kv.Key] = kv.Value;
+ }
}
}
}
@@ -144,22 +139,24 @@ internal void AddMembers(IPythonClassType cls, bool overwrite) {
}
internal IMember AddMember(string name, IMember member, bool overwrite) {
- lock (MembersLock) {
- if (overwrite || !Members.ContainsKey(name)) {
- WritableMembers[name] = member is IVariable v ? v.Value : member;
+ lock (_lock) {
+ if (!_readonly) {
+ if (overwrite || !Members.ContainsKey(name)) {
+ WritableMembers[name] = member;
+ }
}
return member;
}
}
- internal bool IsHidden => ContainsMember("__hidden__");
-
- protected bool ContainsMember(string name) {
- lock (MembersLock) {
- return Members.ContainsKey(name);
+ internal void MakeReadOnly() {
+ lock (_lock) {
+ _readonly = true;
}
}
+ internal bool IsHidden => ContainsMember("__hidden__");
+ protected bool ContainsMember(string name) => Members.ContainsKey(name);
protected IPythonType UnknownType => DeclaringModule.Interpreter.UnknownType;
}
}
diff --git a/src/Analysis/Ast/Impl/Types/PythonTypeWrapper.cs b/src/Analysis/Ast/Impl/Types/PythonTypeWrapper.cs
index d84b87a3c..5b129ac91 100644
--- a/src/Analysis/Ast/Impl/Types/PythonTypeWrapper.cs
+++ b/src/Analysis/Ast/Impl/Types/PythonTypeWrapper.cs
@@ -60,16 +60,10 @@ protected PythonTypeWrapper(BuiltinTypeId builtinTypeId, IPythonModule declaring
_builtinTypeId = builtinTypeId;
}
- protected PythonTypeWrapper() { }
- protected void SetInnerType(IPythonType innerType) {
- _innerType = innerType;
- DeclaringModule = _innerType.DeclaringModule;
- }
-
#region IPythonType
public virtual string Name => _typeName ?? InnerType.Name;
public virtual string QualifiedName => _typeName != null ? $"{DeclaringModule.Name}:{_typeName}" : InnerType.QualifiedName;
- public IPythonModule DeclaringModule { get; private set; }
+ public IPythonModule DeclaringModule { get; }
public virtual string Documentation => _documentation ?? InnerType.Documentation;
public virtual BuiltinTypeId TypeId => InnerType.TypeId;
public virtual PythonMemberType MemberType => InnerType.MemberType;
diff --git a/src/Analysis/Ast/Impl/Values/Collections/PythonDictionary.cs b/src/Analysis/Ast/Impl/Values/Collections/PythonDictionary.cs
index ae38c78f3..53fc8175d 100644
--- a/src/Analysis/Ast/Impl/Values/Collections/PythonDictionary.cs
+++ b/src/Analysis/Ast/Impl/Values/Collections/PythonDictionary.cs
@@ -62,7 +62,7 @@ public IReadOnlyList Items
_contents.TryGetValue(key, out var value) ? value : UnknownType;
public override IPythonIterator GetIterator() =>
- Call(@"iterkeys", ArgumentSet.WithoutContext) as IPythonIterator ?? new EmptyIterator(Type.DeclaringModule);
+ Call(@"iterkeys", ArgumentSet.WithoutContext) as IPythonIterator ?? new EmptyIterator(Type.DeclaringModule.Interpreter.UnknownType);
public override IMember Index(IArgumentSet args) {
if (args.Arguments.Count == 1) {
diff --git a/src/Analysis/Ast/Impl/Values/Collections/PythonInstanceIterator.cs b/src/Analysis/Ast/Impl/Values/Collections/PythonInstanceIterator.cs
index 06f895779..2cc207822 100644
--- a/src/Analysis/Ast/Impl/Values/Collections/PythonInstanceIterator.cs
+++ b/src/Analysis/Ast/Impl/Values/Collections/PythonInstanceIterator.cs
@@ -46,8 +46,8 @@ public override IMember Call(string memberName, IArgumentSet args) {
/// Empty iterator
///
internal sealed class EmptyIterator : EmptyLocatedMember, IPythonIterator {
- public EmptyIterator(IPythonModule declaringModule): base(declaringModule, PythonMemberType.Class) {
- Type = declaringModule.Interpreter.UnknownType;
+ public EmptyIterator(IPythonType unknownType): base(PythonMemberType.Class) {
+ Type = unknownType;
}
public IPythonIterator GetIterator() => this;
diff --git a/src/Analysis/Ast/Impl/Values/Definitions/IScope.cs b/src/Analysis/Ast/Impl/Values/Definitions/IScope.cs
index 7e2bf7695..6b350178d 100644
--- a/src/Analysis/Ast/Impl/Values/Definitions/IScope.cs
+++ b/src/Analysis/Ast/Impl/Values/Definitions/IScope.cs
@@ -48,9 +48,9 @@ public interface IScope {
///
IReadOnlyList Children { get; }
- ///
- /// Locates child scope by name.
- ///
+ ///
+ /// Locates child scope by name.
+ ///
IScope GetChildScope(ScopeStatement node);
///
diff --git a/src/Analysis/Ast/Impl/Values/GlobalScope.cs b/src/Analysis/Ast/Impl/Values/GlobalScope.cs
index ed323da8d..0316402cf 100644
--- a/src/Analysis/Ast/Impl/Values/GlobalScope.cs
+++ b/src/Analysis/Ast/Impl/Values/GlobalScope.cs
@@ -15,9 +15,6 @@
using Microsoft.Python.Analysis.Modules;
using Microsoft.Python.Analysis.Types;
-using Microsoft.Python.Core.Collections;
-using Microsoft.Python.Core.Diagnostics;
-using Microsoft.Python.Core.Text;
using Microsoft.Python.Parsing.Ast;
namespace Microsoft.Python.Analysis.Values {
@@ -44,14 +41,14 @@ private void DeclareBuiltinVariables() {
var dictType = Module.Interpreter.GetBuiltinType(BuiltinTypeId.Dict);
var objectType = Module.Interpreter.GetBuiltinType(BuiltinTypeId.Object);
- DeclareBuiltinVariable("__debug__", boolType, location);
- DeclareBuiltinVariable("__doc__", strType, location);
- DeclareBuiltinVariable("__file__", strType, location);
- DeclareBuiltinVariable("__name__", strType, location);
- DeclareBuiltinVariable("__package__", strType, location);
- DeclareBuiltinVariable("__path__", listType, location);
- DeclareBuiltinVariable("__dict__", dictType, location);
- DeclareBuiltinVariable("__spec__", objectType, location);
+ DeclareVariable("__debug__", boolType, VariableSource.Builtin, location);
+ DeclareVariable("__doc__", strType, VariableSource.Builtin, location);
+ DeclareVariable("__file__", strType, VariableSource.Builtin, location);
+ DeclareVariable("__name__", strType, VariableSource.Builtin, location);
+ DeclareVariable("__package__", strType, VariableSource.Builtin, location);
+ DeclareVariable("__path__", listType, VariableSource.Builtin, location);
+ DeclareVariable("__dict__", dictType, VariableSource.Builtin, location);
+ DeclareVariable("__spec__", objectType, VariableSource.Builtin, location);
}
}
}
diff --git a/src/Analysis/Ast/Impl/Values/PythonInstance.cs b/src/Analysis/Ast/Impl/Values/PythonInstance.cs
index 43ef4969e..52ae31aee 100644
--- a/src/Analysis/Ast/Impl/Values/PythonInstance.cs
+++ b/src/Analysis/Ast/Impl/Values/PythonInstance.cs
@@ -61,7 +61,7 @@ public virtual IPythonIterator GetIterator() {
return new PythonInstanceIterator(instance, Type.DeclaringModule.Interpreter);
}
- return new EmptyIterator(Type.DeclaringModule);
+ return new EmptyIterator(Type.DeclaringModule.Interpreter.UnknownType);
}
public bool Equals(IPythonInstance other) => Type?.Equals(other?.Type) == true;
diff --git a/src/Analysis/Ast/Impl/Values/PythonNone.cs b/src/Analysis/Ast/Impl/Values/PythonNone.cs
index 78112f8f5..3af3360d8 100644
--- a/src/Analysis/Ast/Impl/Values/PythonNone.cs
+++ b/src/Analysis/Ast/Impl/Values/PythonNone.cs
@@ -26,7 +26,7 @@ internal sealed class PythonNone : PythonType, IPythonInstance {
public IMember Call(string memberName, IArgumentSet args) => DeclaringModule.Interpreter.UnknownType;
- public IPythonIterator GetIterator() => new EmptyIterator(DeclaringModule);
+ public IPythonIterator GetIterator() => new EmptyIterator(DeclaringModule.Interpreter.UnknownType);
public IMember Index(IArgumentSet args) => DeclaringModule.Interpreter.UnknownType;
}
diff --git a/src/Analysis/Ast/Impl/Values/Scope.cs b/src/Analysis/Ast/Impl/Values/Scope.cs
index 08df8a957..7ef4408fd 100644
--- a/src/Analysis/Ast/Impl/Values/Scope.cs
+++ b/src/Analysis/Ast/Impl/Values/Scope.cs
@@ -19,7 +19,6 @@
using System.Linq;
using Microsoft.Python.Analysis.Modules;
using Microsoft.Python.Analysis.Types;
-using Microsoft.Python.Core.Diagnostics;
using Microsoft.Python.Parsing.Ast;
namespace Microsoft.Python.Analysis.Values {
@@ -34,8 +33,6 @@ internal class Scope : IScope {
private Dictionary _childScopes;
public Scope(ScopeStatement node, IScope outerScope, IPythonModule module) {
- Check.ArgumentNotNull(nameof(module), module);
-
OuterScope = outerScope;
Module = module;
if (node != null) {
@@ -52,7 +49,6 @@ public Scope(ScopeStatement node, IScope outerScope, IPythonModule module) {
public IReadOnlyList Children => _childScopes?.Values.ToArray() ?? Array.Empty();
public IScope GetChildScope(ScopeStatement node) => _childScopes != null && _childScopes.TryGetValue(node, out var s) ? s : null;
-
public IVariableCollection Variables => _variables ?? VariableCollection.Empty;
public IVariableCollection NonLocals => _nonLocals ?? VariableCollection.Empty;
public IVariableCollection Globals => _globals ?? VariableCollection.Empty;
@@ -96,7 +92,8 @@ public void DeclareImported(string name, IMember value, Location location = defa
=> (_imported ?? (_imported = new VariableCollection())).DeclareVariable(name, value, VariableSource.Import, location);
#endregion
- internal void AddChildScope(Scope s) => (_childScopes ?? (_childScopes = new Dictionary()))[s.Node] = s;
+ internal void AddChildScope(Scope s)
+ => (_childScopes ?? (_childScopes = new Dictionary()))[s.Node] = s;
internal void ReplaceVariable(IVariable v) {
VariableCollection.RemoveVariable(v.Name);
@@ -114,7 +111,7 @@ private void DeclareBuiltinVariables() {
var strType = Module.Interpreter.GetBuiltinType(BuiltinTypeId.Str);
var objType = Module.Interpreter.GetBuiltinType(BuiltinTypeId.Object);
- DeclareBuiltinVariable("__name__", strType, location);
+ VariableCollection.DeclareVariable("__name__", strType, VariableSource.Builtin, location);
if (Node is FunctionDefinition) {
var dictType = Module.Interpreter.GetBuiltinType(BuiltinTypeId.Dict);
@@ -131,8 +128,7 @@ private void DeclareBuiltinVariables() {
DeclareBuiltinVariable("__self__", objType, location);
}
}
-
- protected void DeclareBuiltinVariable(string name, IPythonType type, Location location)
+ protected void DeclareBuiltinVariable(string name, IPythonType type, Location location)
=> VariableCollection.DeclareVariable(name, type, VariableSource.Builtin, location);
}
}
diff --git a/src/Analysis/Ast/Test/AnalysisTestBase.cs b/src/Analysis/Ast/Test/AnalysisTestBase.cs
index 5ad5426de..db915ae7f 100644
--- a/src/Analysis/Ast/Test/AnalysisTestBase.cs
+++ b/src/Analysis/Ast/Test/AnalysisTestBase.cs
@@ -37,26 +37,18 @@
using TestUtilities;
namespace Microsoft.Python.Analysis.Tests {
- public abstract class AnalysisTestBase: IDisposable {
- private readonly CancellationTokenSource _testCts;
+ public abstract class AnalysisTestBase {
+ protected const int AnalysisTimeoutInMS = 1000 * 60;
- protected TimeSpan AnalysisTimeout { get; set; } = TimeSpan.FromMilliseconds(3000 * 60);
+ protected TimeSpan AnalysisTimeout { get; set; } = TimeSpan.FromMilliseconds(AnalysisTimeoutInMS);
+
+ private TimeSpan GetAnalysisTimeout() => Debugger.IsAttached ? Timeout.InfiniteTimeSpan : AnalysisTimeout;
protected TestLogger TestLogger { get; } = new TestLogger();
protected ServiceManager Services { get; private set; }
protected virtual IDiagnosticsService GetDiagnosticsService(IServiceContainer s) => null;
- protected CancellationToken TestCancellationToken => _testCts.Token;
-
- protected AnalysisTestBase() {
- _testCts = new CancellationTokenSource(Debugger.IsAttached ? Timeout.InfiniteTimeSpan : AnalysisTimeout);
- }
-
- public void Dispose() {
- _testCts.Dispose();
- }
-
protected ServiceManager CreateServiceManager() {
Services = new ServiceManager();
@@ -167,10 +159,14 @@ protected async Task GetAnalysisAsync(
var ast = await doc.GetAstAsync(CancellationToken.None);
ast.Should().NotBeNull();
TestLogger.Log(TraceEventType.Information, "Test: AST end.");
+
TestLogger.Log(TraceEventType.Information, "Test: Analysis begin.");
- await services.GetService().WaitForCompleteAnalysisAsync(TestCancellationToken);
- var analysis = await doc.GetAnalysisAsync(-1, TestCancellationToken);
+ IDocumentAnalysis analysis;
+ using (var cts = new CancellationTokenSource(GetAnalysisTimeout())) {
+ await services.GetService().WaitForCompleteAnalysisAsync(cts.Token);
+ analysis = await doc.GetAnalysisAsync(-1, cts.Token);
+ }
analysis.Should().NotBeNull();
TestLogger.Log(TraceEventType.Information, "Test: Analysis end.");
@@ -180,8 +176,10 @@ protected async Task GetAnalysisAsync(
protected async Task GetDocumentAnalysisAsync(IDocument document) {
var analyzer = Services.GetService();
- await analyzer.WaitForCompleteAnalysisAsync(TestCancellationToken);
- return await document.GetAnalysisAsync(Timeout.Infinite, TestCancellationToken);
+ using (var cts = new CancellationTokenSource(GetAnalysisTimeout())) {
+ await analyzer.WaitForCompleteAnalysisAsync(cts.Token);
+ return await document.GetAnalysisAsync(Timeout.Infinite, cts.Token);
+ }
}
}
}
diff --git a/src/Analysis/Ast/Test/DependencyResolverTests.cs b/src/Analysis/Ast/Test/DependencyResolverTests.cs
index 7a5693747..92effbbca 100644
--- a/src/Analysis/Ast/Test/DependencyResolverTests.cs
+++ b/src/Analysis/Ast/Test/DependencyResolverTests.cs
@@ -13,14 +13,12 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
-using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Python.Analysis.Dependencies;
-using Microsoft.Python.Analysis.Tests.FluentAssertions;
using Microsoft.Python.Core.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TestUtilities;
@@ -38,17 +36,16 @@ public void TestInitialize()
public void Cleanup() => TestEnvironmentImpl.TestCleanup();
// ReSharper disable StringLiteralTypo
- // Square brackets mean that nodes can be walked in parallel. Parentheses mean that nodes are in loop.
[DataRow("A:BC|B:C|C", "CBA", "A")]
[DataRow("C|A:BC|B:C", "CBA", "A")]
- [DataRow("C|B:AC|A:BC", "C(BA)", "A")]
- [DataRow("A:CE|B:A|C:B|D:B|E", "E(ACB)D", "D")]
- [DataRow("A:D|B:DA|C:BA|D:AE|E", "E(AD)BC", "C")]
- [DataRow("A:C|C:B|B:A|D:AF|F:CE|E:BD", "(ACB)(DFE)", "F")]
- [DataRow("A:BC|B:AC|C:BA|D:BC", "(ABC)D", "D")]
+ [DataRow("C|B:AC|A:BC", "CBABA", "A")]
+ [DataRow("A:CE|B:A|C:B|D:B|E", "[CE]ABCABD", "D")]
+ [DataRow("A:D|B:DA|C:BA|D:AE|E", "[AE]DADBC", "C")]
+ [DataRow("A:C|C:B|B:A|D:AF|F:CE|E:BD", "ABCABCDEFDEF", "F")]
+ [DataRow("A:BC|B:AC|C:BA|D:BC", "ACBACBD", "D")]
[DataRow("A|B|C|D:AB|E:BC", "[ABC][DE]", "D|E")]
- [DataRow("A:CE|B:A|C:B|D:BC|E|F:C", "E(ACB)[FD]", "D|F")]
- [DataRow("A:D|B:E|C:F|D:E|E:F|F:D", "(DEF)[ABC]", "A|B|C")]
+ [DataRow("A:CE|B:A|C:B|D:BC|E|F:C", "[CE]ABCAB[DF]", "D|F")]
+ [DataRow("A:D|B:E|C:F|D:E|E:F|F:D", "DFEDFE[ABC]", "A|B|C")]
// ReSharper restore StringLiteralTypo
[DataTestMethod]
public void ChangeValue(string input, string output, string root) {
@@ -64,7 +61,7 @@ public void ChangeValue(string input, string output, string root) {
var walker = resolver.CreateWalker();
var result = new StringBuilder();
- var tasks = new List>();
+ var tasks = new List>>();
while (walker.Remaining > 0) {
var nodeTask = walker.GetNextAsync(default);
if (!nodeTask.IsCompleted) {
@@ -73,7 +70,7 @@ public void ChangeValue(string input, string output, string root) {
}
foreach (var task in tasks) {
- AppendFirstChar(result, task.Result);
+ result.Append(task.Result.Value[0]);
task.Result.MarkWalked();
task.Result.MoveNext();
}
@@ -89,7 +86,7 @@ public void ChangeValue(string input, string output, string root) {
result.ToString().Should().Be(output);
}
-
+
[TestMethod]
public async Task ChangeValue_ChangeToIdentical() {
var resolver = new DependencyResolver();
@@ -101,8 +98,8 @@ public async Task ChangeValue_ChangeToIdentical() {
var result = new StringBuilder();
while (walker.Remaining > 0) {
var node = await walker.GetNextAsync(default);
- AppendFirstChar(result, node);
- node.Should().HaveOnlyWalkedDependencies();
+ result.Append(node.Value[0]);
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
}
@@ -115,8 +112,8 @@ public async Task ChangeValue_ChangeToIdentical() {
result = new StringBuilder();
while (walker.Remaining > 0) {
var node = await walker.GetNextAsync(default);
- AppendFirstChar(result, node);
- node.Should().HaveOnlyWalkedDependencies();
+ result.Append(node.Value[0]);
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
}
@@ -136,8 +133,8 @@ public async Task ChangeValue_TwoChanges() {
var result = new StringBuilder();
while (walker.Remaining > 0) {
var node = await walker.GetNextAsync(default);
- AppendFirstChar(result, node);
- node.Should().HaveOnlyWalkedDependencies();
+ result.Append(node.Value[0]);
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
}
@@ -151,8 +148,8 @@ public async Task ChangeValue_TwoChanges() {
result = new StringBuilder();
while (walker.Remaining > 0) {
var node = await walker.GetNextAsync(default);
- AppendFirstChar(result, node);
- node.Should().HaveOnlyWalkedDependencies();
+ result.Append(node.Value[0]);
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
}
@@ -167,30 +164,31 @@ public async Task ChangeValue_MissingKeys() {
resolver.ChangeValue("B", "B", false);
resolver.ChangeValue("C", "C:D", true, "D");
var walker = resolver.CreateWalker();
-
+
+ var result = new StringBuilder();
var node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("B")
- .And.HaveOnlyWalkedDependencies();
+ result.Append(node.Value[0]);
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("C:D")
- .And.HaveOnlyWalkedDependencies();
+ result.Append(node.Value[0]);
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
walker.MissingKeys.Should().Equal("D");
+ result.ToString().Should().Be("BC");
resolver.ChangeValue("D", "D", false);
walker = resolver.CreateWalker();
- walker.MissingKeys.Should().BeEmpty();
-
- node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("A:B");
+ result = new StringBuilder();
+ result.Append((await walker.GetNextAsync(default)).Value[0]);
+ result.Append((await walker.GetNextAsync(default)).Value[0]);
- node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("D");
+ walker.MissingKeys.Should().BeEmpty();
+ result.ToString().Should().Be("AD");
}
[TestMethod]
@@ -202,9 +200,9 @@ public async Task ChangeValue_Add() {
var walker = resolver.CreateWalker();
walker.MissingKeys.Should().Equal("B", "D");
var node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("A:BD")
- .And.HaveMissingDependencies()
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("A:BD");
+ node.HasMissingDependencies.Should().BeTrue();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
@@ -214,46 +212,46 @@ public async Task ChangeValue_Add() {
resolver.ChangeValue("B", "B", false);
walker = resolver.CreateWalker();
walker.MissingKeys.Should().Equal("D");
-
+
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("B")
- .And.HaveNoMissingDependencies()
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("B");
+ node.HasMissingDependencies.Should().BeFalse();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
- node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("A:BD")
- .And.HaveMissingDependencies()
- .And.HaveOnlyWalkedDependencies();
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("A:BD");
+ node.HasMissingDependencies.Should().BeTrue();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
walker.Remaining.Should().Be(0);
-
+
// Add D
resolver.ChangeValue("D", "D:C", false, "C");
walker = resolver.CreateWalker();
walker.MissingKeys.Should().BeEmpty();
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("C")
- .And.HaveNoMissingDependencies()
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("C");
+ node.HasMissingDependencies.Should().BeFalse();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
- node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("D:C")
- .And.HaveNoMissingDependencies()
- .And.HaveOnlyWalkedDependencies();
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("D:C");
+ node.HasMissingDependencies.Should().BeFalse();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("A:BD")
- .And.HaveNoMissingDependencies()
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("A:BD");
+ node.HasMissingDependencies.Should().BeFalse();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
@@ -261,7 +259,7 @@ public async Task ChangeValue_Add() {
}
[TestMethod]
- public async Task ChangeValue_Add_ParallelWalkers() {
+ public async Task ChangeValue_Add_ParallelWalkers() {
var resolver = new DependencyResolver();
resolver.ChangeValue("A", "A:BD", true, "B", "D");
resolver.ChangeValue("B", "B:C", false, "C");
@@ -271,9 +269,9 @@ public async Task ChangeValue_Add_ParallelWalkers() {
walker.MissingKeys.Should().Equal("D");
var node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("C")
- .And.HaveNoMissingDependencies()
- .And.HaveValidVersion();
+ node.Value.Should().Be("C");
+ node.HasMissingDependencies.Should().BeFalse();
+ node.IsValidVersion.Should().BeTrue();
// Add D
resolver.ChangeValue("D", "D:C", false, "C");
@@ -281,24 +279,24 @@ public async Task ChangeValue_Add_ParallelWalkers() {
newWalker.MissingKeys.Should().BeEmpty();
// MarkWalked node from old walker
- node.Should().HaveOnlyWalkedDependencies()
- .And.HaveInvalidVersion();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsValidVersion.Should().BeFalse();
node.MarkWalked();
node.MoveNext();
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("B:C")
- .And.HaveNoMissingDependencies()
- .And.HaveOnlyWalkedDependencies()
- .And.HaveInvalidVersion();
+ node.Value.Should().Be("B:C");
+ node.HasMissingDependencies.Should().BeFalse();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsValidVersion.Should().BeFalse();
node.MarkWalked();
node.MoveNext();
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("A:BD")
- .And.HaveMissingDependencies()
- .And.HaveOnlyWalkedDependencies()
- .And.HaveInvalidVersion();
+ node.Value.Should().Be("A:BD");
+ node.HasMissingDependencies.Should().BeTrue();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsValidVersion.Should().BeFalse();
node.MarkWalked();
node.MoveNext();
@@ -306,34 +304,34 @@ public async Task ChangeValue_Add_ParallelWalkers() {
// Walk new walker
node = await newWalker.GetNextAsync(default);
- node.Should().HaveSingleValue("C")
- .And.HaveNoMissingDependencies()
- .And.HaveOnlyWalkedDependencies()
- .And.HaveValidVersion();
+ node.Value.Should().Be("C");
+ node.HasMissingDependencies.Should().BeFalse();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsValidVersion.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
node = await newWalker.GetNextAsync(default);
- node.Should().HaveSingleValue("B:C")
- .And.HaveNoMissingDependencies()
- .And.HaveOnlyWalkedDependencies()
- .And.HaveValidVersion();
+ node.Value.Should().Be("B:C");
+ node.HasMissingDependencies.Should().BeFalse();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsValidVersion.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
node = await newWalker.GetNextAsync(default);
- node.Should().HaveSingleValue("D:C")
- .And.HaveNoMissingDependencies()
- .And.HaveOnlyWalkedDependencies()
- .And.HaveValidVersion();
+ node.Value.Should().Be("D:C");
+ node.HasMissingDependencies.Should().BeFalse();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsValidVersion.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
node = await newWalker.GetNextAsync(default);
- node.Should().HaveSingleValue("A:BD")
- .And.HaveNoMissingDependencies()
- .And.HaveOnlyWalkedDependencies()
- .And.HaveValidVersion();
+ node.Value.Should().Be("A:BD");
+ node.HasMissingDependencies.Should().BeFalse();
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsValidVersion.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
@@ -353,34 +351,87 @@ public async Task ChangeValue_PartiallyWalkLoop() {
walker.MissingKeys.Should().BeEmpty();
var node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("E")
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("E");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("B:CE");
+ node.HasOnlyWalkedDependencies.Should().BeFalse();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("D:BE");
+ node.HasOnlyWalkedDependencies.Should().BeFalse();
node.MarkWalked();
node.MoveNext();
node = await walker.GetNextAsync(default);
- node.Should().HaveLoopValues("B:CE", "C:DE", "D:BE")
- .And.HaveNonWalkedDependencies();
+ node.Value.Should().Be("C:DE");
+ node.HasOnlyWalkedDependencies.Should().BeFalse();
node.MarkWalked();
node.MoveNext();
// Create new walker
var newWalker = resolver.CreateWalker();
- // Mark vertex walked as it would've been in parallel
- // Loops are always walked fully.
+ // Mark vertex walked as it would've in parallel
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("A:B")
- .And.HaveOnlyWalkedDependencies()
- .And.NotBeWalkedWithDependencies();
+ node.Value.Should().Be("B:CE");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsWalkedWithDependencies.Should().BeFalse();
node.MarkWalked();
node.MoveNext();
// Now iterate with new walker
node = await newWalker.GetNextAsync(default);
- node.Should().HaveSingleValue("A:B")
- .And.HaveOnlyWalkedDependencies()
- .And.BeWalkedWithDependencies();
+ node.Value.Should().Be("B:CE");
+ node.HasOnlyWalkedDependencies.Should().BeFalse();
+ node.IsWalkedWithDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await newWalker.GetNextAsync(default);
+ node.Value.Should().Be("D:BE");
+ node.HasOnlyWalkedDependencies.Should().BeFalse();
+ node.IsWalkedWithDependencies.Should().BeFalse();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await newWalker.GetNextAsync(default);
+ node.Value.Should().Be("C:DE");
+ node.HasOnlyWalkedDependencies.Should().BeFalse();
+ node.IsWalkedWithDependencies.Should().BeFalse();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await newWalker.GetNextAsync(default);
+ node.Value.Should().Be("B:CE");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsWalkedWithDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await newWalker.GetNextAsync(default);
+ node.Value.Should().Be("D:BE");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsWalkedWithDependencies.Should().BeFalse();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await newWalker.GetNextAsync(default);
+ node.Value.Should().Be("C:DE");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsWalkedWithDependencies.Should().BeFalse();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await newWalker.GetNextAsync(default);
+ node.Value.Should().Be("A:B");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.IsWalkedWithDependencies.Should().BeFalse();
node.MarkWalked();
node.MoveNext();
@@ -397,20 +448,20 @@ public async Task ChangeValue_Remove() {
var walker = resolver.CreateWalker();
walker.MissingKeys.Should().BeEmpty();
var node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("C")
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("C");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("B:C")
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("B:C");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("A:BC")
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("A:BC");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
@@ -419,8 +470,8 @@ public async Task ChangeValue_Remove() {
walker.MissingKeys.Should().Equal("B");
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("A:BC")
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("A:BC");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
@@ -437,7 +488,7 @@ public async Task ChangeValue_ChangeChangeRemove() {
var walker = resolver.CreateWalker();
walker.MissingKeys.Should().Equal("D");
walker.AffectedValues.Should().Equal("A:B", "B:C", "C:AD");
- walker.Remaining.Should().Be(3);
+ walker.Remaining.Should().Be(6);
//resolver.ChangeValue("D", "D:B", true, "B");
resolver.ChangeValue("A", "A", true);
@@ -449,14 +500,14 @@ public async Task ChangeValue_ChangeChangeRemove() {
walker.Remaining.Should().Be(2);
var node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("A")
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("A");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("C:AD")
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("C:AD");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
@@ -474,11 +525,41 @@ public async Task ChangeValue_RemoveFromLoop() {
walker.MissingKeys.Should().BeEmpty();
var node = await walker.GetNextAsync(default);
- node.Should().HaveLoopValues("A:B", "B:C", "C:A")
- .And.HaveNonWalkedDependencies();
+ node.Value.Should().Be("A:B");
+ node.HasOnlyWalkedDependencies.Should().BeFalse();
node.MarkWalked();
node.MoveNext();
-
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("C:A");
+ node.HasOnlyWalkedDependencies.Should().BeFalse();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("B:C");
+ node.HasOnlyWalkedDependencies.Should().BeFalse();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("A:B");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("C:A");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("B:C");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
walker.Remaining.Should().Be(0);
resolver.Remove("B");
@@ -486,8 +567,61 @@ public async Task ChangeValue_RemoveFromLoop() {
walker.MissingKeys.Should().Equal("B");
node = await walker.GetNextAsync(default);
- node.Should().HaveSingleValue("A:B")
- .And.HaveOnlyWalkedDependencies();
+ node.Value.Should().Be("A:B");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
+ walker.Remaining.Should().Be(0);
+ }
+
+ [TestMethod]
+ public async Task ChangeValue_RemoveKeys() {
+ var resolver = new DependencyResolver();
+ resolver.ChangeValue("A", "A:BC", true, "B", "C");
+ resolver.ChangeValue("B", "B:C", false, "C");
+ resolver.ChangeValue("C", "C:D", false, "D");
+ resolver.ChangeValue("D", "D", false);
+
+ var walker = resolver.CreateWalker();
+ walker.MissingKeys.Should().BeEmpty();
+ var node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("D");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("C:D");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("B:C");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("A:BC");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
+ resolver.RemoveKeys("B", "D");
+ walker = resolver.CreateWalker();
+ walker.MissingKeys.Should().Equal("B", "D");
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("C:D");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
+ node.MarkWalked();
+ node.MoveNext();
+
+ node = await walker.GetNextAsync(default);
+ node.Value.Should().Be("A:BC");
+ node.HasOnlyWalkedDependencies.Should().BeTrue();
node.MarkWalked();
node.MoveNext();
@@ -505,33 +639,22 @@ public async Task ChangeValue_Skip() {
var walker = resolver.CreateWalker();
var result = new StringBuilder();
var node = await walker.GetNextAsync(default);
- AppendFirstChar(result, node);
+ result.Append(node.Value[0]);
node.MoveNext();
-
+
node = await walker.GetNextAsync(default);
- AppendFirstChar(result, node);
+ result.Append(node.Value[0]);
node.MoveNext();
-
+
result.ToString().Should().Be("BD");
resolver.ChangeValue("D", "D", false);
walker = resolver.CreateWalker();
result = new StringBuilder();
- AppendFirstChar(result, await walker.GetNextAsync(default));
- AppendFirstChar(result, await walker.GetNextAsync(default));
+ result.Append((await walker.GetNextAsync(default)).Value[0]);
+ result.Append((await walker.GetNextAsync(default)).Value[0]);
result.ToString().Should().Be("BD");
}
-
- private static StringBuilder AppendFirstChar(StringBuilder sb, IDependencyChainNode node) {
- switch (node) {
- case IDependencyChainSingleNode single:
- return sb.Append(single.Value[0]);
- case IDependencyChainLoopNode loop:
- return sb.Append($"({new string(loop.Values.Select(v => v[0]).ToArray())})");
- default:
- throw new InvalidOperationException();
- }
- }
}
}
diff --git a/src/Analysis/Ast/Test/FluentAssertions/AssertionsFactory.cs b/src/Analysis/Ast/Test/FluentAssertions/AssertionsFactory.cs
index 70de139c9..9e4d86965 100644
--- a/src/Analysis/Ast/Test/FluentAssertions/AssertionsFactory.cs
+++ b/src/Analysis/Ast/Test/FluentAssertions/AssertionsFactory.cs
@@ -14,7 +14,6 @@
// permissions and limitations under the License.
using System.Diagnostics.CodeAnalysis;
-using Microsoft.Python.Analysis.Dependencies;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Analysis.Values;
using Microsoft.Python.Core.Text;
@@ -22,7 +21,7 @@
namespace Microsoft.Python.Analysis.Tests.FluentAssertions {
[ExcludeFromCodeCoverage]
internal static class AssertionsFactory {
- public static DependencyChainNodeAssertions Should(this IDependencyChainNode node) => new DependencyChainNodeAssertions(node);
+ public static ScopeAssertions Should(this IScope scope) => new ScopeAssertions(scope);
public static MemberAssertions Should(this IMember member) => new MemberAssertions(member);
public static PythonFunctionAssertions Should(this IPythonFunctionType f) => new PythonFunctionAssertions(f);
@@ -34,7 +33,6 @@ internal static class AssertionsFactory {
public static RangeAssertions Should(this Range? range) => new RangeAssertions(range);
- public static ScopeAssertions Should(this IScope scope) => new ScopeAssertions(scope);
public static SourceSpanAssertions Should(this SourceSpan span) => new SourceSpanAssertions(span);
public static SourceSpanAssertions Should(this SourceSpan? span) => new SourceSpanAssertions(span.Value);
}
diff --git a/src/Analysis/Ast/Test/FluentAssertions/DependencyChainNodeAssertions.cs b/src/Analysis/Ast/Test/FluentAssertions/DependencyChainNodeAssertions.cs
deleted file mode 100644
index d628ac178..000000000
--- a/src/Analysis/Ast/Test/FluentAssertions/DependencyChainNodeAssertions.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the License); you may not use
-// this file except in compliance with the License. You may obtain a copy of the
-// License at http://www.apache.org/licenses/LICENSE-2.0
-//
-// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
-// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
-// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
-// MERCHANTABILITY OR NON-INFRINGEMENT.
-//
-// See the Apache Version 2.0 License for specific language governing
-// permissions and limitations under the License.
-
-using System.Linq;
-using FluentAssertions;
-using FluentAssertions.Execution;
-using FluentAssertions.Primitives;
-using Microsoft.Python.Analysis.Dependencies;
-using static Microsoft.Python.Analysis.Tests.FluentAssertions.AssertionsUtilities;
-
-namespace Microsoft.Python.Analysis.Tests.FluentAssertions {
- internal sealed class DependencyChainNodeAssertions : ReferenceTypeAssertions {
- public DependencyChainNodeAssertions(IDependencyChainNode node) {
- Subject = node;
- }
-
- protected override string Identifier => nameof(IDependencyChainNode);
-
- [CustomAssertion]
- public AndConstraint HaveSingleValue(T value, string because = "", params object[] reasonArgs) {
- var currentStateMessage = Subject == null ? "null" : "loop node";
-
- Execute.Assertion.ForCondition(Subject is IDependencyChainSingleNode)
- .BecauseOf(because, reasonArgs)
- .FailWith($"Expected [{typeof(T)}] node to be single node{{reason}}, but it is {currentStateMessage}");
-
- var actual = ((IDependencyChainSingleNode)Subject).Value;
- Execute.Assertion.ForCondition(Equals(actual, value))
- .BecauseOf(because, reasonArgs)
- .FailWith($"Expected [{typeof(T)}] node to have value {value}{{reason}}, but it has {actual}");
-
- return new AndConstraint(this);
- }
-
- [CustomAssertion]
- public AndConstraint HaveLoopValues(params T[] values) => HaveLoopValues(values, string.Empty);
-
- [CustomAssertion]
- public AndConstraint HaveLoopValues(T[] values, string because = "", params object[] reasonArgs) {
- var currentStateMessage = Subject == null ? "null" : "loop node";
-
- Execute.Assertion.ForCondition(Subject is IDependencyChainLoopNode)
- .BecauseOf(because, reasonArgs)
- .FailWith($"Expected [{typeof(T)}] node to be loop node{{reason}}, but it is {currentStateMessage}");
-
- var actual = ((IDependencyChainLoopNode)Subject).Values.ToArray();
- var errorMessage = GetAssertCollectionOnlyContainsMessage(actual, values, "loop node", "value", "values");
-
- Execute.Assertion.ForCondition(errorMessage == null)
- .BecauseOf(string.Empty, string.Empty)
- .FailWith(errorMessage);
-
- return new AndConstraint(this);
- }
-
- [CustomAssertion]
- public AndConstraint HaveOnlyWalkedDependencies(string because = "", params object[] reasonArgs) {
- Execute.Assertion.ForCondition(Subject.HasOnlyWalkedDependencies)
- .BecauseOf(because, reasonArgs)
- .FailWith($"Expected {Subject} to have only walked dependencies{{reason}}");
-
- return new AndConstraint(this);
- }
-
- [CustomAssertion]
- public AndConstraint