diff --git a/src/Analysis/Ast/Impl/Analyzer/Definitions/IPythonAnalyzer.cs b/src/Analysis/Ast/Impl/Analyzer/Definitions/IPythonAnalyzer.cs
index 0a4a23ddb..26cfce286 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Definitions/IPythonAnalyzer.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/Definitions/IPythonAnalyzer.cs
@@ -37,7 +37,12 @@ public interface IPythonAnalyzer {
/// Invalidates current analysis for the module, assuming that AST for the new analysis will be provided later.
///
void InvalidateAnalysis(IPythonModule module);
-
+
+ ///
+ /// Removes modules from the analysis.
+ ///
+ void RemoveAnalysis(IPythonModule module);
+
///
/// Get most recent analysis for module. If after specified time analysis isn't available, returns previously calculated analysis.
///
diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Scopes.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Scopes.cs
index ca27a311e..a2877e8a9 100644
--- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Scopes.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Scopes.cs
@@ -175,7 +175,7 @@ public IDisposable OpenScope(IPythonModule module, ScopeStatement node, out Scop
if (scope == null) {
scope = new Scope(node, fromScope, true);
fromScope.AddChildScope(scope);
- _scopeLookupCache[node] = fromScope;
+ _scopeLookupCache[node] = scope;
}
}
diff --git a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs
index d68a5b0cd..9929221af 100644
--- a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs
@@ -112,6 +112,12 @@ public void InvalidateAnalysis(IPythonModule module) {
}
}
+ public void RemoveAnalysis(IPythonModule module) {
+ lock (_syncObj) {
+ _analysisEntries.Remove(new AnalysisModuleKey(module));
+ }
+ }
+
public void EnqueueDocumentForAnalysis(IPythonModule module, ImmutableArray analysisDependencies) {
var key = new AnalysisModuleKey(module);
PythonAnalyzerEntry entry;
@@ -269,7 +275,7 @@ private async Task AnalyzeAffectedEntriesAsync(IDependencyChainWalker missingKeys) {
foreach (var (moduleName, _, isTypeshed) in missingKeys) {
var moduleResolution = isTypeshed ? interpreter.TypeshedResolution : interpreter.ModuleResolution;
@@ -356,7 +362,7 @@ private void AnalyzeEntry(PythonAnalyzerEntry entry, IPythonModule module, Pytho
// Python analyzer to call NotifyAnalysisComplete.
walker.Complete();
cancellationToken.ThrowIfCancellationRequested();
- var analysis = new DocumentAnalysis((IDocument) module, version, walker.GlobalScope, walker.Eval);
+ var analysis = new DocumentAnalysis((IDocument)module, version, walker.GlobalScope, walker.Eval);
(module as IAnalyzable)?.NotifyAnalysisComplete(analysis);
entry.TrySetAnalysis(analysis, version);
diff --git a/src/Analysis/Ast/Impl/Analyzer/PythonInterpreter.cs b/src/Analysis/Ast/Impl/Analyzer/PythonInterpreter.cs
index 3745ce3b5..dd387a924 100644
--- a/src/Analysis/Ast/Impl/Analyzer/PythonInterpreter.cs
+++ b/src/Analysis/Ast/Impl/Analyzer/PythonInterpreter.cs
@@ -47,18 +47,19 @@ private async Task LoadBuiltinTypesAsync(string root, IServiceManager sm, Cancel
sm.AddService(this);
_moduleResolution = new MainModuleResolution(root, sm);
- await _moduleResolution.InitializeAsync(cancellationToken);
- _stubResolution = new TypeshedResolution(sm);
- await _stubResolution.InitializeAsync(cancellationToken);
-
- var builtinModule = _moduleResolution.BuiltinsModule;
lock (_lock) {
+ var builtinModule = _moduleResolution.CreateBuiltinsModule();
_builtinTypes[BuiltinTypeId.NoneType]
= new PythonType("NoneType", builtinModule, string.Empty, LocationInfo.Empty, BuiltinTypeId.NoneType);
_builtinTypes[BuiltinTypeId.Unknown]
= UnknownType = new PythonType("Unknown", builtinModule, string.Empty, LocationInfo.Empty);
}
+ await _moduleResolution.InitializeAsync(cancellationToken);
+
+ _stubResolution = new TypeshedResolution(sm);
+ await _stubResolution.InitializeAsync(cancellationToken);
+
await _moduleResolution.LoadBuiltinTypesAsync(cancellationToken);
}
diff --git a/src/Analysis/Ast/Impl/Dependencies/DependencyResolver.cs b/src/Analysis/Ast/Impl/Dependencies/DependencyResolver.cs
index e10301434..3f50a626f 100644
--- a/src/Analysis/Ast/Impl/Dependencies/DependencyResolver.cs
+++ b/src/Analysis/Ast/Impl/Dependencies/DependencyResolver.cs
@@ -21,7 +21,6 @@
namespace Microsoft.Python.Analysis.Dependencies {
internal sealed class DependencyResolver : IDependencyResolver {
- private readonly IDependencyFinder _dependencyFinder;
private readonly DependencyGraph _vertices = new DependencyGraph();
private readonly Dictionary> _changedVertices = new Dictionary>();
private readonly object _syncObj = new object();
diff --git a/src/Analysis/Ast/Impl/Documents/Definitions/IRunningDocumentTable.cs b/src/Analysis/Ast/Impl/Documents/Definitions/IRunningDocumentTable.cs
index 6164f3125..465998596 100644
--- a/src/Analysis/Ast/Impl/Documents/Definitions/IRunningDocumentTable.cs
+++ b/src/Analysis/Ast/Impl/Documents/Definitions/IRunningDocumentTable.cs
@@ -55,6 +55,20 @@ public interface IRunningDocumentTable: IEnumerable {
///
IDocument GetDocument(string name);
+ ///
+ /// Increase reference count of the document.
+ ///
+ ///
+ /// New lock count or -1 if document was not found.
+ int LockDocument(Uri uri);
+
+ ///
+ /// Decrease reference count of the document.
+ ///
+ ///
+ /// New lock count or -1 if document was not found.
+ int UnlockDocument(Uri uri);
+
///
/// Fires when document is opened.
///
@@ -64,5 +78,10 @@ public interface IRunningDocumentTable: IEnumerable {
/// Fires when document is closed.
///
event EventHandler Closed;
+
+ ///
+ /// Fires when document is removed.
+ ///
+ event EventHandler Removed;
}
}
diff --git a/src/Analysis/Ast/Impl/Documents/RunningDocumentTable.cs b/src/Analysis/Ast/Impl/Documents/RunningDocumentTable.cs
index 389bf6c32..a267be97b 100644
--- a/src/Analysis/Ast/Impl/Documents/RunningDocumentTable.cs
+++ b/src/Analysis/Ast/Impl/Documents/RunningDocumentTable.cs
@@ -19,9 +19,9 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
+using Microsoft.Python.Analysis.Analyzer;
using Microsoft.Python.Analysis.Modules;
using Microsoft.Python.Core;
-using Microsoft.Python.Core.Diagnostics;
namespace Microsoft.Python.Analysis.Documents {
///
@@ -52,6 +52,7 @@ public RunningDocumentTable(string workspaceRoot, IServiceContainer services) {
public event EventHandler Opened;
public event EventHandler Closed;
+ public event EventHandler Removed;
///
/// Adds file to the list of available documents.
@@ -60,17 +61,19 @@ public RunningDocumentTable(string workspaceRoot, IServiceContainer services) {
/// Document content
/// Optional file path, if different from the URI.
public IDocument OpenDocument(Uri uri, string content, string filePath = null) {
- var justOpened = false;
+ bool justOpened;
DocumentEntry entry;
lock (_lock) {
entry = FindDocument(null, uri);
if (entry == null) {
+ var resolver = _services.GetService().ModuleResolution.CurrentPathResolver;
+ var moduleType = resolver.IsLibraryFile(uri.ToAbsolutePath()) ? ModuleType.Library : ModuleType.User;
var mco = new ModuleCreationOptions {
ModuleName = Path.GetFileNameWithoutExtension(uri.LocalPath),
Content = content,
FilePath = filePath,
Uri = uri,
- ModuleType = ModuleType.User
+ ModuleType = moduleType
};
entry = CreateDocument(mco);
}
@@ -114,10 +117,29 @@ public IDocument GetDocument(string name) {
}
}
+ public int LockDocument(Uri uri) {
+ lock (_lock) {
+ if (_documentsByUri.TryGetValue(uri, out var entry)) {
+ return ++entry.LockCount;
+ }
+ return -1;
+ }
+ }
+
+ public int UnlockDocument(Uri uri) {
+ lock (_lock) {
+ if (_documentsByUri.TryGetValue(uri, out var entry)) {
+ return --entry.LockCount;
+ }
+ return -1;
+ }
+ }
+
public IEnumerator GetEnumerator() => _documentsByUri.Values.Select(e => e.Document).GetEnumerator();
public void CloseDocument(Uri documentUri) {
- var justClosed = false;
+ var closed = false;
+ var removed = false;
DocumentEntry entry;
lock (_lock) {
if (_documentsByUri.TryGetValue(documentUri, out entry)) {
@@ -125,7 +147,7 @@ public void CloseDocument(Uri documentUri) {
if (entry.Document.IsOpen) {
entry.Document.IsOpen = false;
- justClosed = true;
+ closed = true;
}
entry.LockCount--;
@@ -133,20 +155,23 @@ public void CloseDocument(Uri documentUri) {
if (entry.LockCount == 0) {
_documentsByUri.Remove(documentUri);
_documentsByName.Remove(entry.Document.Name);
+ removed = true;
entry.Document.Dispose();
}
- // TODO: Remove from module resolution?
}
}
- if(justClosed) {
+ if (closed) {
Closed?.Invoke(this, new DocumentEventArgs(entry.Document));
}
+ if (removed) {
+ Removed?.Invoke(this, new DocumentEventArgs(entry.Document));
+ }
}
IEnumerator IEnumerable.GetEnumerator() => _documentsByUri.Values.GetEnumerator();
public void Dispose() {
- lock(_lock) {
+ lock (_lock) {
foreach (var d in _documentsByUri.Values.OfType()) {
d.Dispose();
}
diff --git a/src/Analysis/Ast/Impl/Modules/Definitions/IModuleCache.cs b/src/Analysis/Ast/Impl/Modules/Definitions/IModuleCache.cs
index b4d4a231a..40ea4c0a6 100644
--- a/src/Analysis/Ast/Impl/Modules/Definitions/IModuleCache.cs
+++ b/src/Analysis/Ast/Impl/Modules/Definitions/IModuleCache.cs
@@ -13,10 +13,6 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Python.Analysis.Documents;
-
namespace Microsoft.Python.Analysis.Modules {
public interface IModuleCache {
string GetCacheFilePath(string filePath);
diff --git a/src/Analysis/Ast/Impl/Modules/Definitions/IModuleResolution.cs b/src/Analysis/Ast/Impl/Modules/Definitions/IModuleResolution.cs
index 67aa559b4..e0959f44d 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;
diff --git a/src/Analysis/Ast/Impl/Modules/ModuleCache.cs b/src/Analysis/Ast/Impl/Modules/ModuleCache.cs
index 3fa18a348..4052283c7 100644
--- a/src/Analysis/Ast/Impl/Modules/ModuleCache.cs
+++ b/src/Analysis/Ast/Impl/Modules/ModuleCache.cs
@@ -18,9 +18,7 @@
using System.IO;
using System.Security.Cryptography;
using System.Text;
-using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Python.Analysis.Documents;
using Microsoft.Python.Core;
using Microsoft.Python.Core.IO;
using Microsoft.Python.Core.Logging;
@@ -43,7 +41,7 @@ public ModuleCache(IPythonInterpreter interpreter, IServiceContainer services) {
_log = services.GetService();
_skipCache = string.IsNullOrEmpty(_interpreter.Configuration.DatabasePath);
}
-
+
public string GetCacheFilePath(string filePath) {
if (string.IsNullOrEmpty(filePath) || !PathEqualityComparer.IsValidPath(ModuleCachePath)) {
if (!_loggedBadDbPath) {
diff --git a/src/Analysis/Ast/Impl/Modules/PythonModule.cs b/src/Analysis/Ast/Impl/Modules/PythonModule.cs
index b5fb1fa60..07824e2b2 100644
--- a/src/Analysis/Ast/Impl/Modules/PythonModule.cs
+++ b/src/Analysis/Ast/Impl/Modules/PythonModule.cs
@@ -29,6 +29,7 @@
using Microsoft.Python.Analysis.Values;
using Microsoft.Python.Core;
using Microsoft.Python.Core.Diagnostics;
+using Microsoft.Python.Core.Disposables;
using Microsoft.Python.Core.IO;
using Microsoft.Python.Core.Logging;
using Microsoft.Python.Core.Text;
@@ -53,7 +54,7 @@ private enum State {
}
private readonly DocumentBuffer _buffer = new DocumentBuffer();
- private readonly CancellationTokenSource _allProcessingCts = new CancellationTokenSource();
+ private readonly DisposeToken _disposeToken = DisposeToken.Create< PythonModule>();
private IReadOnlyList _parseErrors = Array.Empty();
private readonly IDiagnosticsService _diagnosticsService;
@@ -242,8 +243,7 @@ private void LoadContent(string content) {
protected virtual void Dispose(bool disposing) {
_diagnosticsService?.Remove(Uri);
- _allProcessingCts.Cancel();
- _allProcessingCts.Dispose();
+ _disposeToken.TryMarkDisposed();
}
#endregion
@@ -310,7 +310,7 @@ public void Update(IEnumerable changes) {
_parseCts = new CancellationTokenSource();
_linkedParseCts?.Dispose();
- _linkedParseCts = CancellationTokenSource.CreateLinkedTokenSource(_allProcessingCts.Token, _parseCts.Token);
+ _linkedParseCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeToken.CancellationToken, _parseCts.Token);
_buffer.Update(changes);
Parse();
@@ -332,7 +332,7 @@ private void Parse() {
_parseCts = new CancellationTokenSource();
_linkedParseCts?.Dispose();
- _linkedParseCts = CancellationTokenSource.CreateLinkedTokenSource(_allProcessingCts.Token, _parseCts.Token);
+ _linkedParseCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeToken.CancellationToken, _parseCts.Token);
ContentState = State.Parsing;
_parsingTask = Task.Run(() => Parse(_linkedParseCts.Token), _linkedParseCts.Token);
@@ -383,7 +383,7 @@ private void Parse(CancellationToken cancellationToken) {
ContentState = State.Analyzing;
var analyzer = Services.GetService();
- analyzer.EnqueueDocumentForAnalysis(this, ast, version, _allProcessingCts.Token);
+ analyzer.EnqueueDocumentForAnalysis(this, ast, version, _disposeToken.CancellationToken);
}
lock (AnalysisLock) {
diff --git a/src/Analysis/Ast/Impl/Modules/Resolution/MainModuleResolution.cs b/src/Analysis/Ast/Impl/Modules/Resolution/MainModuleResolution.cs
index 9a951daa2..e13d613d6 100644
--- a/src/Analysis/Ast/Impl/Modules/Resolution/MainModuleResolution.cs
+++ b/src/Analysis/Ast/Impl/Modules/Resolution/MainModuleResolution.cs
@@ -34,23 +34,30 @@
namespace Microsoft.Python.Analysis.Modules.Resolution {
internal sealed class MainModuleResolution : ModuleResolutionBase, IModuleManagement {
private readonly ConcurrentDictionary _specialized = new ConcurrentDictionary();
+ private IRunningDocumentTable _rdt;
private IReadOnlyList _searchPaths;
public MainModuleResolution(string root, IServiceContainer services)
: base(root, services) { }
+ internal IBuiltinsPythonModule CreateBuiltinsModule() {
+ if (BuiltinsModule == null) {
+ // Initialize built-in
+ var moduleName = BuiltinTypeId.Unknown.GetModuleName(_interpreter.LanguageVersion);
+
+ ModuleCache = new ModuleCache(_interpreter, _services);
+ var modulePath = ModuleCache.GetCacheFilePath(_interpreter.Configuration.InterpreterPath);
+
+ var b = new BuiltinsPythonModule(moduleName, modulePath, _services);
+ BuiltinsModule = b;
+ Modules[BuiltinModuleName] = new ModuleRef(b);
+ }
+ return BuiltinsModule;
+ }
+
internal async Task InitializeAsync(CancellationToken cancellationToken = default) {
- // Add names from search paths
await ReloadAsync(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
-
- // Initialize built-in
- var moduleName = BuiltinTypeId.Unknown.GetModuleName(_interpreter.LanguageVersion);
- var modulePath = ModuleCache.GetCacheFilePath(_interpreter.Configuration.InterpreterPath);
-
- var b = new BuiltinsPythonModule(moduleName, modulePath, _services);
- BuiltinsModule = b;
- Modules[BuiltinModuleName] = new ModuleRef(b);
}
public async Task> GetSearchPathsAsync(CancellationToken cancellationToken = default) {
@@ -60,16 +67,19 @@ public async Task> GetSearchPathsAsync(CancellationToken c
_searchPaths = await GetInterpreterSearchPathsAsync(cancellationToken);
Debug.Assert(_searchPaths != null, "Should have search paths");
- _searchPaths = _searchPaths != null
- ? Configuration.SearchPaths != null
- ? _searchPaths.Concat(Configuration.SearchPaths).ToArray()
- : _searchPaths
- : Array.Empty();
+ _searchPaths = _searchPaths ?? Array.Empty();
- _log?.Log(TraceEventType.Information, "SearchPaths:");
+ _log?.Log(TraceEventType.Information, "Python search paths:");
foreach (var s in _searchPaths) {
_log?.Log(TraceEventType.Information, $" {s}");
}
+
+ var configurationSearchPaths = Configuration.SearchPaths ?? Array.Empty();
+
+ _log?.Log(TraceEventType.Information, "Configuration search paths:");
+ foreach (var s in configurationSearchPaths) {
+ _log?.Log(TraceEventType.Information, $" {s}");
+ }
return _searchPaths;
}
@@ -80,11 +90,11 @@ protected override IPythonModule CreateModule(string name) {
return null;
}
- var rdt = _services.GetService();
IPythonModule module;
if (!string.IsNullOrEmpty(moduleImport.ModulePath) && Uri.TryCreate(moduleImport.ModulePath, UriKind.Absolute, out var uri)) {
- module = rdt.GetDocument(uri);
+ module = GetRdt().GetDocument(uri);
if (module != null) {
+ GetRdt().LockDocument(uri);
return module;
}
}
@@ -117,7 +127,7 @@ protected override IPythonModule CreateModule(string name) {
FilePath = moduleImport.ModulePath,
Stub = stub
};
- module = rdt.AddModule(mco);
+ module = GetRdt().AddModule(mco);
}
return module;
@@ -176,9 +186,10 @@ internal async Task LoadBuiltinTypesAsync(CancellationToken cancellationToken =
}
public async Task ReloadAsync(CancellationToken cancellationToken = default) {
+ foreach (var m in Modules) {
+ GetRdt()?.UnlockDocument(m.Value.Value.Uri);
+ }
Modules.Clear();
-
- ModuleCache = new ModuleCache(_interpreter, _services);
PathResolver = new PathResolver(_interpreter.LanguageVersion);
var addedRoots = new HashSet();
@@ -187,7 +198,8 @@ public async Task ReloadAsync(CancellationToken cancellationToken = default) {
var interpreterPaths = await GetSearchPathsAsync(cancellationToken);
addedRoots.UnionWith(PathResolver.SetInterpreterSearchPaths(interpreterPaths));
- addedRoots.UnionWith(SetUserSearchPaths(_interpreter.Configuration.SearchPaths));
+ var userSearchPaths = _interpreter.Configuration.SearchPaths.Except(interpreterPaths, StringExtensions.PathsStringComparer);
+ addedRoots.UnionWith(SetUserSearchPaths(userSearchPaths));
ReloadModulePaths(addedRoots);
}
@@ -213,5 +225,8 @@ private bool TryCreateModuleStub(string name, string modulePath, out IPythonModu
module = !string.IsNullOrEmpty(stubPath) ? new StubPythonModule(name, stubPath, false, _services) : null;
return module != null;
}
+
+ private IRunningDocumentTable GetRdt()
+ => _rdt ?? (_rdt = _services.GetService());
}
}
diff --git a/src/Analysis/Ast/Test/FluentAssertions/DocumentAnalysisAssertions.cs b/src/Analysis/Ast/Test/FluentAssertions/DocumentAnalysisAssertions.cs
index 6f04641b6..e2f06b179 100644
--- a/src/Analysis/Ast/Test/FluentAssertions/DocumentAnalysisAssertions.cs
+++ b/src/Analysis/Ast/Test/FluentAssertions/DocumentAnalysisAssertions.cs
@@ -22,7 +22,7 @@
namespace Microsoft.Python.Analysis.Tests.FluentAssertions {
[ExcludeFromCodeCoverage]
- internal sealed class DocumentAnalysisAssertions : ReferenceTypeAssertions {
+ public sealed class DocumentAnalysisAssertions : ReferenceTypeAssertions {
private readonly ScopeAssertions _scopeAssertions;
public DocumentAnalysisAssertions(IDocumentAnalysis analysis) {
diff --git a/src/Analysis/Ast/Test/FluentAssertions/VariableAssertions.cs b/src/Analysis/Ast/Test/FluentAssertions/VariableAssertions.cs
index 269948ba9..6f0aac0c1 100644
--- a/src/Analysis/Ast/Test/FluentAssertions/VariableAssertions.cs
+++ b/src/Analysis/Ast/Test/FluentAssertions/VariableAssertions.cs
@@ -22,7 +22,7 @@
using Microsoft.Python.Analysis.Values;
namespace Microsoft.Python.Analysis.Tests.FluentAssertions {
- internal sealed class VariableAssertions : ReferenceTypeAssertions {
+ public sealed class VariableAssertions : ReferenceTypeAssertions {
public IMember Value { get; }
public VariableAssertions(IVariable v) {
Subject = v;
diff --git a/src/Analysis/Ast/Test/FluentAssertions/VariableAssertionsExtensions.cs b/src/Analysis/Ast/Test/FluentAssertions/VariableAssertionsExtensions.cs
index 055b877e7..5a965b91e 100644
--- a/src/Analysis/Ast/Test/FluentAssertions/VariableAssertionsExtensions.cs
+++ b/src/Analysis/Ast/Test/FluentAssertions/VariableAssertionsExtensions.cs
@@ -21,7 +21,7 @@
namespace Microsoft.Python.Analysis.Tests.FluentAssertions {
[ExcludeFromCodeCoverage]
- internal static class VariableAssertionsExtensions {
+ public static class VariableAssertionsExtensions {
public static AndWhichConstraint OfType(
this AndWhichConstraint andWhichConstraint, BuiltinTypeId typeId, string because = "", params object[] reasonArgs) {
andWhichConstraint.Subject.Value.Should().HaveType(typeId, because, reasonArgs);
diff --git a/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs b/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs
index dfa81d7c2..bed14e386 100644
--- a/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs
+++ b/src/Analysis/Core/Impl/DependencyResolution/PathResolverSnapshot.cs
@@ -73,7 +73,7 @@ private IEnumerable GetModuleNames(IEnumerable roots) => roots
.Where(n => n.IsModule)
.Concat(_builtins.Children)
.Select(n => n.FullModuleName);
-
+
public ModuleImport GetModuleImportFromModuleName(in string fullModuleName) {
for (var rootIndex = 0; rootIndex < _roots.Count; rootIndex++) {
if (TryFindModuleByName(rootIndex, fullModuleName, out var lastEdge) && TryCreateModuleImport(lastEdge, out var moduleImports)) {
@@ -237,6 +237,9 @@ public IImportSearchResult GetImportsFromRelativePath(in string modulePath, in i
return new ImportNotFound(fullName);
}
+ public bool IsLibraryFile(string filePath)
+ => TryFindModule(filePath, out var edge, out _) && IsLibraryPath(edge.FirstEdge.End.Name);
+
private bool TryGetSearchResults(in ImmutableArray matchedEdges, out IImportSearchResult searchResult) {
foreach (var edge in matchedEdges) {
if (TryCreateModuleImport(edge, out var moduleImport)) {
@@ -253,7 +256,7 @@ private bool TryGetSearchResults(in ImmutableArray matchedEdges, out IImpo
return false;
}
- private bool TryCreateModuleImport(Edge lastEdge, out ModuleImport moduleImport) {
+ private bool TryCreateModuleImport(Edge lastEdge, out ModuleImport moduleImport) {
var moduleNode = lastEdge.End;
var rootNode = lastEdge.FirstEdge.End;
@@ -265,7 +268,7 @@ private bool TryCreateModuleImport(Edge lastEdge, out ModuleImport moduleImport)
rootNode.Name,
initPyNode.ModulePath,
false,
- IsLibrary(rootNode.Name));
+ IsLibraryPath(rootNode.Name));
return true;
}
@@ -278,7 +281,7 @@ private bool TryCreateModuleImport(Edge lastEdge, out ModuleImport moduleImport)
rootNode.Name,
moduleNode.ModulePath,
IsPythonCompiled(moduleNode.ModulePath),
- IsLibrary(rootNode.Name));
+ IsLibraryPath(rootNode.Name));
return true;
}
@@ -765,8 +768,9 @@ private static bool IsNotInitPy(in Node node)
private static bool IsInitPyModule(in Node node, out Node initPyNode)
=> node.TryGetChild("__init__", out initPyNode) && initPyNode.IsModule;
- private bool IsLibrary(string rootPath)
- => _interpreterSearchPaths.Contains(rootPath, StringExtensions.PathsStringComparer);
+ private bool IsLibraryPath(string rootPath)
+ => !_userSearchPaths.Contains(rootPath, StringExtensions.PathsStringComparer)
+ && _interpreterSearchPaths.Except(_userSearchPaths).Contains(rootPath, StringExtensions.PathsStringComparer);
private PathResolverSnapshot ReplaceNonRooted(Node nonRooted)
=> new PathResolverSnapshot(_pythonLanguageVersion, _workDirectory, _userSearchPaths, _interpreterSearchPaths, _roots, _userRootsCount, nonRooted, _builtins, Version + 1);
diff --git a/src/Core/Impl/Threading/AsyncManualResetEvent.cs b/src/Core/Impl/Threading/AsyncManualResetEvent.cs
index 00e301ea7..46152e1e2 100644
--- a/src/Core/Impl/Threading/AsyncManualResetEvent.cs
+++ b/src/Core/Impl/Threading/AsyncManualResetEvent.cs
@@ -13,8 +13,6 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
-using System;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
diff --git a/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs b/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs
index 037cc700b..ada637d60 100644
--- a/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs
+++ b/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs
@@ -75,8 +75,12 @@ public DiagnosticsService(IServiceContainer services) {
public IReadOnlyDictionary> Diagnostics {
get {
lock (_lock) {
- return _diagnostics.ToDictionary(kvp => kvp.Key,
- kvp => FilterBySeverityMap(kvp.Value).ToList() as IReadOnlyList);
+ return _diagnostics.ToDictionary(
+ kvp => kvp.Key,
+ kvp => Rdt.GetDocument(kvp.Key)?.IsOpen == true
+ ? FilterBySeverityMap(kvp.Value).ToList() as IReadOnlyList
+ : Array.Empty()
+ );
}
}
}
@@ -205,29 +209,34 @@ private void ConnectToRdt() {
if (_rdt != null) {
_rdt.Opened += OnOpenDocument;
_rdt.Closed += OnCloseDocument;
+ _rdt.Removed += OnRemoveDocument;
_disposables
.Add(() => _rdt.Opened -= OnOpenDocument)
- .Add(() => _rdt.Closed -= OnCloseDocument);
+ .Add(() => _rdt.Closed -= OnCloseDocument)
+ .Add(() => _rdt.Removed -= OnRemoveDocument);
}
}
}
private void OnOpenDocument(object sender, DocumentEventArgs e) {
lock (_lock) {
- if(_diagnostics.TryGetValue(e.Document.Uri, out var d)) {
+ if (_diagnostics.TryGetValue(e.Document.Uri, out var d)) {
d.Changed = d.Entries.Length > 0;
}
}
}
- private void OnCloseDocument(object sender, DocumentEventArgs e) {
+ private void OnCloseDocument(object sender, DocumentEventArgs e) => ClearDiagnostics(e.Document.Uri, false);
+ private void OnRemoveDocument(object sender, DocumentEventArgs e) => ClearDiagnostics(e.Document.Uri, true);
+
+ private void ClearDiagnostics(Uri uri, bool remove) {
lock (_lock) {
- // Before removing the document, make sure we clear its diagnostics.
- if (_diagnostics.TryGetValue(e.Document.Uri, out var d)) {
- d.Clear();
+ if (_diagnostics.TryGetValue(uri, out var _)) {
PublishDiagnostics();
- _diagnostics.Remove(e.Document.Uri);
+ if (remove) {
+ _diagnostics.Remove(uri);
+ }
}
}
}
diff --git a/src/LanguageServer/Impl/Implementation/Server.Documents.cs b/src/LanguageServer/Impl/Implementation/Server.Documents.cs
index 6305645d7..5aac074de 100644
--- a/src/LanguageServer/Impl/Implementation/Server.Documents.cs
+++ b/src/LanguageServer/Impl/Implementation/Server.Documents.cs
@@ -56,9 +56,13 @@ public void DidChangeTextDocument(DidChangeTextDocumentParams @params) {
}
public void DidChangeWatchedFiles(DidChangeWatchedFilesParams @params) {
+ _disposableBag.ThrowIfDisposed();
foreach (var c in @params.changes.MaybeEnumerate()) {
- _disposableBag.ThrowIfDisposed();
- // TODO: handle?
+ switch (c.type) {
+ case FileChangeType.Deleted:
+ _interpreter.ModuleResolution.CurrentPathResolver.RemoveModulePath(c.uri.ToAbsolutePath());
+ break;
+ }
}
}
diff --git a/src/LanguageServer/Test/DiagnosticsTests.cs b/src/LanguageServer/Test/DiagnosticsTests.cs
index bf674f609..9000a8d15 100644
--- a/src/LanguageServer/Test/DiagnosticsTests.cs
+++ b/src/LanguageServer/Test/DiagnosticsTests.cs
@@ -13,13 +13,11 @@
// 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.Threading.Tasks;
using FluentAssertions;
using Microsoft.Python.Analysis.Diagnostics;
using Microsoft.Python.Analysis.Documents;
-using Microsoft.Python.Core.Idle;
using Microsoft.Python.Core.Services;
using Microsoft.Python.Core.Text;
using Microsoft.Python.LanguageServer.Protocol;
@@ -249,17 +247,5 @@ public async Task OnlyPublishChangedFile() {
reported[0].uri.Should().Be(doc1.Uri);
reported[0].diagnostics.Length.Should().Be(1);
}
-
- private IDiagnosticsService GetDiagnosticsService() {
- var ds = Services.GetService();
- ds.PublishingDelay = 0;
- return ds;
- }
- private void PublishDiagnostics() {
- var ds = Services.GetService();
- ds.PublishingDelay = 0;
- var idle = Services.GetService();
- idle.Idle += Raise.EventWith(null, EventArgs.Empty);
- }
}
}
diff --git a/src/LanguageServer/Test/FluentAssertions/AssertionsFactory.cs b/src/LanguageServer/Test/FluentAssertions/AssertionsFactory.cs
index 50c29fb39..5054053aa 100644
--- a/src/LanguageServer/Test/FluentAssertions/AssertionsFactory.cs
+++ b/src/LanguageServer/Test/FluentAssertions/AssertionsFactory.cs
@@ -15,7 +15,9 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using Microsoft.Python.Analysis;
using Microsoft.Python.Analysis.Tests.FluentAssertions;
+using Microsoft.Python.Analysis.Values;
using Microsoft.Python.Core.Text;
using Microsoft.Python.LanguageServer.Completion;
using Microsoft.Python.LanguageServer.Protocol;
@@ -43,5 +45,8 @@ public static SignatureInformationAssertions Should(this SignatureInformation si
public static TextEditCollectionAssertions Should(this IEnumerable textEdits)
=> new TextEditCollectionAssertions(textEdits);
+
+ public static DocumentAnalysisAssertions Should(this IDocumentAnalysis analysis) => new DocumentAnalysisAssertions(analysis);
+ public static VariableAssertions Should(this IVariable v) => new VariableAssertions(v);
}
}
diff --git a/src/LanguageServer/Test/LanguageServerTestBase.cs b/src/LanguageServer/Test/LanguageServerTestBase.cs
index 88c882da8..8537b2d81 100644
--- a/src/LanguageServer/Test/LanguageServerTestBase.cs
+++ b/src/LanguageServer/Test/LanguageServerTestBase.cs
@@ -13,14 +13,33 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
+using System;
using Microsoft.Python.Analysis.Diagnostics;
using Microsoft.Python.Analysis.Tests;
using Microsoft.Python.Core;
+using Microsoft.Python.Core.Idle;
using Microsoft.Python.LanguageServer.Diagnostics;
+using NSubstitute;
namespace Microsoft.Python.LanguageServer.Tests {
public abstract class LanguageServerTestBase : AnalysisTestBase {
protected static readonly ServerSettings ServerSettings = new ServerSettings();
protected override IDiagnosticsService GetDiagnosticsService(IServiceContainer s) => new DiagnosticsService(s);
+
+
+ protected IDiagnosticsService GetDiagnosticsService() {
+ var ds = Services.GetService();
+ ds.PublishingDelay = 0;
+ return ds;
+ }
+ protected void PublishDiagnostics() {
+ GetDiagnosticsService();
+ RaiseIdleEvent();
+ }
+
+ protected void RaiseIdleEvent() {
+ var idle = Services.GetService();
+ idle.Idle += Raise.EventWith(null, EventArgs.Empty);
+ }
}
}
diff --git a/src/LanguageServer/Test/RdtTests.cs b/src/LanguageServer/Test/RdtTests.cs
index a4289944e..7ca457416 100644
--- a/src/LanguageServer/Test/RdtTests.cs
+++ b/src/LanguageServer/Test/RdtTests.cs
@@ -13,12 +13,19 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.
+using System;
+using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
+using Microsoft.Python.Analysis.Analyzer;
using Microsoft.Python.Analysis.Diagnostics;
using Microsoft.Python.Analysis.Documents;
+using Microsoft.Python.Analysis.Tests.FluentAssertions;
+using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Parsing.Tests;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.Python.LanguageServer.Tests.FluentAssertions;
using TestUtilities;
namespace Microsoft.Python.LanguageServer.Tests {
@@ -63,5 +70,91 @@ public async Task OpenCloseDocuments() {
rdt.GetDocument(uri1).Should().BeNull();
ds.Diagnostics.TryGetValue(uri1, out _).Should().BeFalse();
}
+
+ [TestMethod, Priority(0)]
+ public async Task LockCount() {
+ var uri1 = TestData.GetDefaultModuleUri();
+ await CreateServicesAsync(PythonVersions.LatestAvailable3X, uri1.AbsolutePath);
+ var rdt = Services.GetService();
+
+ rdt.OpenDocument(uri1, "from LockCount1 import *");
+ var doc = rdt.GetDocument(uri1);
+ await doc.GetAstAsync(CancellationToken.None);
+ await Services.GetService().WaitForCompleteAnalysisAsync();
+
+ var docLc1 = rdt.First(d => d.Name.Contains("LockCount1"));
+ var docLc2 = rdt.First(d => d.Name.Contains("LockCount2"));
+ var docLc3 = rdt.First(d => d.Name.Contains("LockCount3"));
+
+ VerifyLockCount(rdt, docLc1.Uri, 1);
+ VerifyLockCount(rdt, docLc2.Uri, 1);
+ VerifyLockCount(rdt, docLc3.Uri, 1);
+
+ rdt.OpenDocument(docLc1.Uri, null);
+ VerifyLockCount(rdt, docLc1.Uri, 2);
+
+ rdt.OpenDocument(docLc3.Uri, null);
+ VerifyLockCount(rdt, docLc3.Uri, 2);
+
+ rdt.CloseDocument(docLc1.Uri);
+ VerifyLockCount(rdt, docLc1.Uri, 1);
+
+ rdt.CloseDocument(docLc3.Uri);
+ VerifyLockCount(rdt, docLc3.Uri, 1);
+ }
+
+ [TestMethod, Priority(0)]
+ public async Task OpenCloseAnalysis() {
+ var uri = TestData.GetDefaultModuleUri();
+ await CreateServicesAsync(PythonVersions.LatestAvailable3X, uri.AbsolutePath);
+ var rdt = Services.GetService();
+
+ rdt.OpenDocument(uri, "from LockCount1 import *");
+ var doc = rdt.GetDocument(uri);
+ await doc.GetAstAsync(CancellationToken.None);
+ await Services.GetService().WaitForCompleteAnalysisAsync();
+
+ var docLc1 = rdt.First(d => d.Name.Contains("LockCount1"));
+ var docLc2 = rdt.First(d => d.Name.Contains("LockCount2"));
+ var docLc3 = rdt.First(d => d.Name.Contains("LockCount3"));
+
+ var ds = GetDiagnosticsService();
+ PublishDiagnostics();
+ ds.Diagnostics.Count.Should().Be(4);
+ ds.Diagnostics[uri].Should().BeEmpty();
+ ds.Diagnostics[docLc1.Uri].Should().BeEmpty();
+ ds.Diagnostics[docLc2.Uri].Should().BeEmpty();
+ ds.Diagnostics[docLc3.Uri].Should().BeEmpty();
+
+ rdt.OpenDocument(docLc1.Uri, null);
+ PublishDiagnostics();
+ ds.Diagnostics[uri].Should().BeEmpty();
+ ds.Diagnostics[docLc1.Uri].Count.Should().Be(2);
+ ds.Diagnostics[docLc2.Uri].Should().BeEmpty();
+ ds.Diagnostics[docLc3.Uri].Should().BeEmpty();
+
+ rdt.CloseDocument(docLc1.Uri);
+ PublishDiagnostics();
+ ds.Diagnostics[uri].Should().BeEmpty();
+ ds.Diagnostics[docLc1.Uri].Should().BeEmpty();
+ ds.Diagnostics[docLc2.Uri].Should().BeEmpty();
+ ds.Diagnostics[docLc3.Uri].Should().BeEmpty();
+
+ rdt.OpenDocument(docLc1.Uri, null);
+ var analysis = await docLc1.GetAnalysisAsync(-1);
+ analysis.Should().HaveVariable("y").OfType(BuiltinTypeId.Int);
+
+ PublishDiagnostics();
+ ds.Diagnostics[uri].Should().BeEmpty();
+ ds.Diagnostics[docLc1.Uri].Count.Should().Be(2);
+ ds.Diagnostics[docLc2.Uri].Should().BeEmpty();
+ ds.Diagnostics[docLc3.Uri].Should().BeEmpty();
+ }
+
+
+ private void VerifyLockCount(IRunningDocumentTable rdt, Uri uri, int expected) {
+ rdt.LockDocument(uri).Should().Be(expected + 1);
+ rdt.UnlockDocument(uri);
+ }
}
}
diff --git a/src/UnitTests/TestData/AstAnalysis/LockCount1.py b/src/UnitTests/TestData/AstAnalysis/LockCount1.py
new file mode 100644
index 000000000..da267e6d0
--- /dev/null
+++ b/src/UnitTests/TestData/AstAnalysis/LockCount1.py
@@ -0,0 +1,4 @@
+import LockCount2
+import doestnotexist
+y = 1
+x.
diff --git a/src/UnitTests/TestData/AstAnalysis/LockCount2.py b/src/UnitTests/TestData/AstAnalysis/LockCount2.py
new file mode 100644
index 000000000..062d0ac1f
--- /dev/null
+++ b/src/UnitTests/TestData/AstAnalysis/LockCount2.py
@@ -0,0 +1 @@
+import LockCount3
diff --git a/src/UnitTests/TestData/AstAnalysis/LockCount3.py b/src/UnitTests/TestData/AstAnalysis/LockCount3.py
new file mode 100644
index 000000000..9f5f1cf8e
--- /dev/null
+++ b/src/UnitTests/TestData/AstAnalysis/LockCount3.py
@@ -0,0 +1,2 @@
+X = 1
+