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

Multiple analysis fixes #1297

Merged
merged 2 commits into from
Jul 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 4 additions & 38 deletions src/Analysis/Ast/Impl/Analyzer/ActivityTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,21 @@

namespace Microsoft.Python.Analysis.Analyzer {
internal static class ActivityTracker {
private static readonly Dictionary<string, AnalysisState> _modules = new Dictionary<string, AnalysisState>();
private static readonly HashSet<string> _modules = new HashSet<string>();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AnalysisState has never been used

private static readonly object _lock = new object();
private static bool _tracking;
private static Stopwatch _sw;

private struct AnalysisState {
public int Count;
public bool IsComplete;
}

public static void OnEnqueueModule(string path) {
if (string.IsNullOrEmpty(path)) {
return;
}

lock (_lock) {
if (!_modules.TryGetValue(path, out var st)) {
_modules[path] = default;
} else {
st.IsComplete = false;
}
}
}

public static void OnModuleAnalysisComplete(string path) {
lock (_lock) {
if (_modules.TryGetValue(path, out var st)) {
st.Count++;
st.IsComplete = true;
}
_modules.Add(path);
}
}

public static bool IsAnalysisComplete {
get {
lock (_lock) {
return _modules.All(m => m.Value.IsComplete);
}
}
}


public static void StartTracking() {
lock (_lock) {
if (!_tracking) {
Expand All @@ -71,22 +44,15 @@ public static void StartTracking() {
}
}

public static void EndTracking() {
public static (int modulesCount, double totalMilliseconds) EndTracking() {
lock (_lock) {
if (_tracking) {
_sw?.Stop();
_tracking = false;
}
}
}

public static int ModuleCount {
get {
lock (_lock) {
return _modules.Count;
}
return (_modules.Count, _sw?.Elapsed.TotalMilliseconds ?? 0);
}
}
public static double MillisecondsElapsed => _sw?.Elapsed.TotalMilliseconds ?? 0;
}
}
29 changes: 23 additions & 6 deletions src/Analysis/Ast/Impl/Analyzer/AnalysisModuleKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,56 @@

using System;
using System.Diagnostics;
using Microsoft.Python.Analysis.Documents;
using Microsoft.Python.Analysis.Modules;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Core;

namespace Microsoft.Python.Analysis.Analyzer {
[DebuggerDisplay("{Name} : {FilePath}")]
internal struct AnalysisModuleKey : IEquatable<AnalysisModuleKey> {
internal readonly struct AnalysisModuleKey : IEquatable<AnalysisModuleKey> {
private enum KeyType { Default, Typeshed, LibraryAsDocument }

private readonly KeyType _type;
public string Name { get; }
public string FilePath { get; }
public bool IsTypeshed { get; }
public bool IsTypeshed => _type == KeyType.Typeshed;
public bool IsLibraryAsDocument => _type == KeyType.LibraryAsDocument;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a change for the #1294. Opened library module may be part of the loop, so with LibraryAnalysis it will trigger reparsing of all the modules in the loop. As a workaroung, we create new instance of the PythonAnalyzerEntry that will contain DocumentAnalysis.


public AnalysisModuleKey(IPythonModule module) {
Name = module.Name;
FilePath = module.ModuleType == ModuleType.CompiledBuiltin ? null : module.FilePath;
IsTypeshed = module is StubPythonModule stub && stub.IsTypeshed;
_type = module is StubPythonModule stub && stub.IsTypeshed
? KeyType.Typeshed
: module.ModuleType == ModuleType.Library && module is IDocument document && document.IsOpen
? KeyType.LibraryAsDocument
: KeyType.Default;
}

public AnalysisModuleKey(string name, string filePath, bool isTypeshed) {
Name = name;
FilePath = filePath;
IsTypeshed = isTypeshed;
_type = isTypeshed ? KeyType.Typeshed : KeyType.Default;
}

private AnalysisModuleKey(string name, string filePath, KeyType type) {
Name = name;
FilePath = filePath;
_type = type;
}

public AnalysisModuleKey GetLibraryAsDocumentKey() => new AnalysisModuleKey(Name, FilePath, KeyType.LibraryAsDocument);

public bool Equals(AnalysisModuleKey other)
=> Name.EqualsOrdinal(other.Name) && FilePath.PathEquals(other.FilePath) && IsTypeshed == other.IsTypeshed;
=> Name.EqualsOrdinal(other.Name) && FilePath.PathEquals(other.FilePath) && _type == other._type;

public override bool Equals(object obj) => obj is AnalysisModuleKey other && Equals(other);

public override int GetHashCode() {
unchecked {
var hashCode = (Name != null ? Name.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (FilePath != null ? FilePath.GetPathHashCode() : 0);
hashCode = (hashCode * 397) ^ IsTypeshed.GetHashCode();
hashCode = (hashCode * 397) ^ _type.GetHashCode();
return hashCode;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/Analysis/Ast/Impl/Analyzer/Definitions/IAnalyzable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal interface IAnalyzable {
/// <summary>
/// Notifies document that its analysis is now complete.
/// </summary>
void NotifyAnalysisComplete(int version, ModuleWalker walker, bool isFinalPass);
/// <param name="analysis">Document analysis</param>
void NotifyAnalysisComplete(IDocumentAnalysis analysis);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@
using Microsoft.Python.Analysis.Diagnostics;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Core.Collections;
using Microsoft.Python.Parsing.Ast;

namespace Microsoft.Python.Analysis.Analyzer {
public interface IPythonAnalyzer {
Task WaitForCompleteAnalysisAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Schedules module for analysis. Module will be scheduled if version of AST is greater than the one used to get previous analysis
/// </summary>
void EnqueueDocumentForAnalysis(IPythonModule module, int version);
void EnqueueDocumentForAnalysis(IPythonModule module, PythonAst ast, int version);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were several cases when ast was out of sync, so we need to pass to PythonAnalyzer explicitly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this indicate some other race condition, if the module's Ast is changing during an analysis? When would this occur? When the user modifies their code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any change to the file, including opening/closing it. I'm not sure that having separate Parse/Analyze queue is still valuable for us, cause we don't have analysis on demand anymore - we use a graph, but that would be a separate change that I don't want to make until database support is in master.


/// <summary>
/// Schedules module for analysis for its existing AST, but with new dependencies.
Expand Down
2 changes: 1 addition & 1 deletion src/Analysis/Ast/Impl/Analyzer/EmptyAnalysis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public EmptyAnalysis(IServiceContainer services, IDocument document) {
Document = document ?? throw new ArgumentNullException(nameof(document));
GlobalScope = new EmptyGlobalScope(document);
Ast = AstUtilities.MakeEmptyAst(document.Uri);
ExpressionEvaluator = new ExpressionEval(services, document);
ExpressionEvaluator = new ExpressionEval(services, document, Ast);
}

public IDocument Document { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public IMember GetValueFromClassCtor(IPythonClassType cls, CallExpression expr)
var init = cls.GetMember<IPythonFunctionType>(@"__init__");
if (init != null) {
using (OpenScope(cls.DeclaringModule, cls.ClassDefinition, out _)) {
var a = new ArgumentSet(init, 0, new PythonInstance(cls), expr, Module, this);
var a = new ArgumentSet(init, 0, new PythonInstance(cls), expr, this);
if (a.Errors.Count > 0) {
// AddDiagnostics(Module.Uri, a.Errors);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void DeclareVariable(string name, IMember value, VariableSource source)
=> DeclareVariable(name, value, source, default(Location));

public void DeclareVariable(string name, IMember value, VariableSource source, IPythonModule module)
=> DeclareVariable(name, value, source, new Location(module, default));
=> DeclareVariable(name, value, source, new Location(module));

public void DeclareVariable(string name, IMember value, VariableSource source, Node location, bool overwrite = false)
=> DeclareVariable(name, value, source, GetLocationOfName(location), overwrite);
Expand Down
13 changes: 5 additions & 8 deletions src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,12 @@ internal sealed partial class ExpressionEval : IExpressionEvaluator {
private readonly object _lock = new object();
private readonly List<DiagnosticsEntry> _diagnostics = new List<DiagnosticsEntry>();

public ExpressionEval(IServiceContainer services, IPythonModule module)
: this(services, module, new GlobalScope(module)) { }

public ExpressionEval(IServiceContainer services, IPythonModule module, GlobalScope gs) {
public ExpressionEval(IServiceContainer services, IPythonModule module, PythonAst ast) {
Services = services ?? throw new ArgumentNullException(nameof(services));
Module = module ?? throw new ArgumentNullException(nameof(module));
Ast = module.GetAst();
Ast = ast ?? throw new ArgumentNullException(nameof(ast));

GlobalScope = gs;
GlobalScope = new GlobalScope(module, ast);
CurrentScope = GlobalScope;
DefaultLocation = new Location(module);
//Log = services.GetService<ILogger>();
Expand All @@ -60,7 +57,7 @@ public ExpressionEval(IServiceContainer services, IPythonModule module, GlobalSc
public IPythonType UnknownType => Interpreter.UnknownType;
public Location DefaultLocation { get; }

public LocationInfo GetLocationInfo(Node node) => node?.GetLocation(Module) ?? LocationInfo.Empty;
public LocationInfo GetLocationInfo(Node node) => node?.GetLocation(this) ?? LocationInfo.Empty;

public Location GetLocationOfName(Node node) {
if (node == null || (Module.ModuleType != ModuleType.User && Module.ModuleType != ModuleType.Library)) {
Expand Down Expand Up @@ -103,7 +100,7 @@ public Location GetLocationOfName(Node node) {
public IServiceContainer Services { get; }
IScope IExpressionEvaluator.CurrentScope => CurrentScope;
IGlobalScope IExpressionEvaluator.GlobalScope => GlobalScope;
public LocationInfo GetLocation(Node node) => node?.GetLocation(Module) ?? LocationInfo.Empty;
public LocationInfo GetLocation(Node node) => node?.GetLocation(this) ?? LocationInfo.Empty;
public IEnumerable<DiagnosticsEntry> Diagnostics => _diagnostics;

public void ReportDiagnostics(Uri documentUri, DiagnosticsEntry entry) {
Expand Down
4 changes: 2 additions & 2 deletions src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ private void SpecializeFuture(FromImportStatement node) {

var printNameExpression = node.Names.FirstOrDefault(n => n?.Name == "print_function");
if (printNameExpression != null) {
var fn = new PythonFunctionType("print", new Location(Module, default), null, string.Empty);
var o = new PythonFunctionOverload(fn.Name, new Location(Module, default));
var fn = new PythonFunctionType("print", new Location(Module), null, string.Empty);
var o = new PythonFunctionOverload(fn.Name, new Location(Module));
var parameters = new List<ParameterInfo> {
new ParameterInfo("*values", Interpreter.GetBuiltinType(BuiltinTypeId.Object), ParameterKind.List, null),
new ParameterInfo("sep", Interpreter.GetBuiltinType(BuiltinTypeId.Str), ParameterKind.KeywordOnly, null),
Expand Down
11 changes: 7 additions & 4 deletions src/Analysis/Ast/Impl/Analyzer/Handlers/ImportHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,22 @@ private void HandleImport(ModuleName moduleImportExpression, NameExpression asNa
// import_module('fob.oar.baz')
var importNames = ImmutableArray<string>.Empty;
var variableModule = default(PythonVariableModule);
var importedNamesCount = 0;
foreach (var nameExpression in moduleImportExpression.Names) {
importNames = importNames.Add(nameExpression.Name);
var imports = ModuleResolution.CurrentPathResolver.GetImportsFromAbsoluteName(Module.FilePath, importNames, forceAbsolute);
if (!HandleImportSearchResult(imports, variableModule, asNameExpression, moduleImportExpression, out variableModule)) {
return;
break;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handles a case when import package.subpackage.module fails on subpackage part, but package has __init__.py and successfully imported.

}

importedNamesCount++;
}

// "import fob.oar.baz as baz" is handled as baz = import_module('fob.oar.baz')
// "import fob.oar.baz" is handled as fob = import_module('fob')
if (!string.IsNullOrEmpty(asNameExpression?.Name)) {
if (!string.IsNullOrEmpty(asNameExpression?.Name) && importedNamesCount == moduleImportExpression.Names.Count) {
Eval.DeclareVariable(asNameExpression.Name, variableModule, VariableSource.Import, asNameExpression);
} else if (importNames.Count > 0 && !string.IsNullOrEmpty(importNames[0]) && _variableModules.TryGetValue(importNames[0], out variableModule)) {
} else if (importedNamesCount > 0 && !string.IsNullOrEmpty(importNames[0]) && _variableModules.TryGetValue(importNames[0], out variableModule)) {
var firstName = moduleImportExpression.Names[0];
Eval.DeclareVariable(importNames[0], variableModule, VariableSource.Import, firstName);
}
Expand All @@ -87,7 +90,7 @@ private bool HandleImportSearchResult(in IImportSearchResult imports, in PythonV
case RelativeImportBeyondTopLevel importBeyondTopLevel:
var message = Resources.ErrorRelativeImportBeyondTopLevel.FormatInvariant(importBeyondTopLevel.RelativeImportName);
Eval.ReportDiagnostics(Eval.Module.Uri,
new DiagnosticsEntry(message, location.GetLocation(Eval.Module).Span, ErrorCodes.UnresolvedImport, Severity.Warning, DiagnosticSource.Analysis));
new DiagnosticsEntry(message, location.GetLocation(Eval).Span, ErrorCodes.UnresolvedImport, Severity.Warning, DiagnosticSource.Analysis));
variableModule = default;
return false;
case ImportNotFound importNotFound:
Expand Down
11 changes: 2 additions & 9 deletions src/Analysis/Ast/Impl/Analyzer/LibraryAnalysis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,15 @@ namespace Microsoft.Python.Analysis.Analyzer {
/// Analysis of a library code.
/// </summary>
internal sealed class LibraryAnalysis : IDocumentAnalysis {
public LibraryAnalysis(IDocument document, int version, IServiceContainer services, GlobalScope globalScope, IReadOnlyList<string> starImportMemberNames) {
public LibraryAnalysis(IDocument document, int version, IGlobalScope globalScope, IExpressionEvaluator eval, IReadOnlyList<string> starImportMemberNames) {
Check.ArgumentNotNull(nameof(document), document);
Check.ArgumentNotNull(nameof(globalScope), globalScope);

Document = document;
Version = version;
GlobalScope = globalScope;

var ast = Document.GetAst();
ast.Reduce(x => x is ImportStatement || x is FromImportStatement);
var c = (IAstNodeContainer)Document;
c.ClearContent();
c.ClearAst();
c.AddAstNode(document, ast);

ExpressionEvaluator = new ExpressionEval(services, document, globalScope);
ExpressionEvaluator = eval;
StarImportMemberNames = starImportMemberNames;
}

Expand Down
4 changes: 2 additions & 2 deletions src/Analysis/Ast/Impl/Analyzer/ModuleWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ internal class ModuleWalker : AnalysisWalker {
private int _allReferencesCount;
private bool _allIsUsable = true;

public ModuleWalker(IServiceContainer services, IPythonModule module)
: base(new ExpressionEval(services, module)) {
public ModuleWalker(IServiceContainer services, IPythonModule module, PythonAst ast)
: base(new ExpressionEval(services, module, ast)) {
_stubAnalysis = Module.Stub is IDocument doc ? doc.GetAnyAnalysis() : null;
}

Expand Down
Loading