diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Hints.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Hints.cs index b12e08072..557ad5a2e 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Hints.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Hints.cs @@ -26,8 +26,15 @@ namespace Microsoft.Python.Analysis.Analyzer.Evaluation { /// and types in a chain of scopes during analysis. /// internal sealed partial class ExpressionEval { + private const string _pepHintKey = "PEP Hint"; + public IPythonType GetTypeFromPepHint(Node node) { + if (Ast.TryGetAttribute(node, _pepHintKey, out var typeStringObject) && typeStringObject is string typeString) { + return GetTypeFromString(typeString); + } + var location = GetLocationInfo(node); + var content = (Module as IDocument)?.Content; if (string.IsNullOrEmpty(content) || !location.EndLine.HasValue) { return null; @@ -79,7 +86,9 @@ public IPythonType GetTypeFromPepHint(Node node) { } // Type alone is not a valid syntax, so we need to simulate the annotation. - var typeString = content.Substring(hintStart, i - hintStart).Trim(); + typeString = content.Substring(hintStart, i - hintStart).Trim(); + Ast.SetAttribute(node, _pepHintKey, typeString); + return GetTypeFromString(typeString); } diff --git a/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs b/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs index 6221d6af7..00657ad83 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs @@ -79,6 +79,11 @@ public override void Evaluate() { if (ctor || annotationType.IsUnknown() || Module.ModuleType == ModuleType.User) { // Return type from the annotation is sufficient for libraries and stubs, no need to walk the body. FunctionDefinition.Body?.Walk(this); + // For libraries remove declared local function variables to free up some memory. + var optionsProvider = Eval.Services.GetService(); + if (Module.ModuleType != ModuleType.User && optionsProvider?.Options.KeepLibraryLocalVariables != true) { + ((VariableCollection)Eval.CurrentScope.Variables).Clear(); + } } } Result = _function; diff --git a/src/Analysis/Ast/Impl/Definitions/AnalysisOptions.cs b/src/Analysis/Ast/Impl/Definitions/AnalysisOptions.cs index d34c88998..36c4eb180 100644 --- a/src/Analysis/Ast/Impl/Definitions/AnalysisOptions.cs +++ b/src/Analysis/Ast/Impl/Definitions/AnalysisOptions.cs @@ -16,5 +16,18 @@ namespace Microsoft.Python.Analysis { public class AnalysisOptions { public bool LintingEnabled { get; set; } + + /// + /// Keep in memory information on local variables declared in + /// functions in libraries. Provides ability to navigate to + /// symbols used in function bodies in packages and libraries. + /// + public bool KeepLibraryLocalVariables { get; set; } + + /// + /// Keep in memory AST of library source code. May somewhat + /// improve performance when library code has to be re-analyzed. + /// + public bool KeepLibraryAst { get; set; } } } diff --git a/src/Analysis/Ast/Impl/Modules/PythonModule.cs b/src/Analysis/Ast/Impl/Modules/PythonModule.cs index 18677e4b3..58ec9379d 100644 --- a/src/Analysis/Ast/Impl/Modules/PythonModule.cs +++ b/src/Analysis/Ast/Impl/Modules/PythonModule.cs @@ -438,6 +438,10 @@ public void NotifyAnalysisComplete(IDocumentAnalysis analysis) { // as declare additional variables, etc. OnAnalysisComplete(); ContentState = State.Analyzed; + + if (ModuleType != ModuleType.User) { + _buffer.Reset(_buffer.Version, string.Empty); + } } // Do not report issues with libraries or stubs diff --git a/src/Analysis/Ast/Impl/Values/VariableCollection.cs b/src/Analysis/Ast/Impl/Values/VariableCollection.cs index 893358d73..817456eca 100644 --- a/src/Analysis/Ast/Impl/Values/VariableCollection.cs +++ b/src/Analysis/Ast/Impl/Values/VariableCollection.cs @@ -122,5 +122,11 @@ internal void RemoveVariable(string name) { _variables.Remove(name); } } + + internal void Clear() { + lock (_syncObj) { + _variables.Clear(); + } + } } } diff --git a/src/LanguageServer/Impl/Definitions/ServerSettings.cs b/src/LanguageServer/Impl/Definitions/ServerSettings.cs index 442538191..2f1c38d22 100644 --- a/src/LanguageServer/Impl/Definitions/ServerSettings.cs +++ b/src/LanguageServer/Impl/Definitions/ServerSettings.cs @@ -29,6 +29,22 @@ public class PythonAnalysisOptions { public string[] warnings { get; private set; } = Array.Empty(); public string[] information { get; private set; } = Array.Empty(); public string[] disabled { get; private set; } = Array.Empty(); + + public class AnalysisMemoryOptions { + /// + /// Keep in memory information on local variables declared in + /// functions in libraries. Provides ability to navigate to + /// symbols used in function bodies in packages and libraries. + /// + public bool keepLibraryLocalVariables; + + /// + /// Keep in memory AST of library source code. May somewhat + /// improve performance when library code has to be re-analyzed. + /// + public bool keepLibraryAst; + } + public AnalysisMemoryOptions memory; } public readonly PythonAnalysisOptions analysis = new PythonAnalysisOptions(); diff --git a/src/LanguageServer/Impl/Implementation/Server.cs b/src/LanguageServer/Impl/Implementation/Server.cs index a21c1c1ea..a69d25b87 100644 --- a/src/LanguageServer/Impl/Implementation/Server.cs +++ b/src/LanguageServer/Impl/Implementation/Server.cs @@ -212,6 +212,11 @@ private bool HandleConfigurationChanges(ServerSettings newSettings) { return true; } + if(newSettings.analysis?.memory?.keepLibraryAst != oldSettings.analysis?.memory?.keepLibraryAst || + newSettings.analysis?.memory?.keepLibraryLocalVariables != oldSettings.analysis?.memory?.keepLibraryLocalVariables) { + return true; + } + return false; } diff --git a/src/LanguageServer/Impl/LanguageServer.Configuration.cs b/src/LanguageServer/Impl/LanguageServer.Configuration.cs index 9f66b4f7b..180a8fb21 100644 --- a/src/LanguageServer/Impl/LanguageServer.Configuration.cs +++ b/src/LanguageServer/Impl/LanguageServer.Configuration.cs @@ -73,6 +73,11 @@ private void HandleDiagnosticsChanges(JToken pythonSection, LanguageServerSettin var linting = pythonSection["linting"]; HandleLintingOnOff(_services, GetSetting(linting, "enabled", true)); + + var memory = analysis["memory"]; + var optionsProvider = _services.GetService(); + optionsProvider.Options.KeepLibraryLocalVariables = GetSetting(memory, "keepLibraryLocalVariables", false); + optionsProvider.Options.KeepLibraryAst = GetSetting(memory, "keepLibraryAst", false); } internal static void HandleLintingOnOff(IServiceContainer services, bool linterEnabled) { diff --git a/src/LanguageServer/Test/GoToDefinitionTests.cs b/src/LanguageServer/Test/GoToDefinitionTests.cs index f42f9a696..0e23cc904 100644 --- a/src/LanguageServer/Test/GoToDefinitionTests.cs +++ b/src/LanguageServer/Test/GoToDefinitionTests.cs @@ -332,6 +332,9 @@ def foo(self): var mainPath = TestData.GetTestSpecificUri("main.py"); var doc = rdt.OpenDocument(mainPath, code); + var analyzer = Services.GetService(); + await analyzer.WaitForCompleteAnalysisAsync(); + var analysis = await doc.GetAnalysisAsync(Timeout.Infinite); var ds = new DefinitionSource(Services); diff --git a/src/Parsing/Impl/Ast/PythonAst.cs b/src/Parsing/Impl/Ast/PythonAst.cs index 8ce4a97de..17cc32571 100644 --- a/src/Parsing/Impl/Ast/PythonAst.cs +++ b/src/Parsing/Impl/Ast/PythonAst.cs @@ -96,7 +96,7 @@ public override async Task WalkAsync(PythonWalkerAsync walker, CancellationToken public PythonLanguageVersion LanguageVersion { get; } - internal bool TryGetAttribute(Node node, object key, out object value) { + public bool TryGetAttribute(Node node, object key, out object value) { if (_attributes.TryGetValue(node, out var nodeAttrs)) { return nodeAttrs.TryGetValue(key, out value); } @@ -104,7 +104,7 @@ internal bool TryGetAttribute(Node node, object key, out object value) { return false; } - internal void SetAttribute(Node node, object key, object value) { + public void SetAttribute(Node node, object key, object value) { if (!_attributes.TryGetValue(node, out var nodeAttrs)) { nodeAttrs = _attributes[node] = new Dictionary(); }