diff --git a/src/Analysis/Engine/Impl/Analyzer/DDG.cs b/src/Analysis/Engine/Impl/Analyzer/DDG.cs index 96d10ab6b..1174295f1 100644 --- a/src/Analysis/Engine/Impl/Analyzer/DDG.cs +++ b/src/Analysis/Engine/Impl/Analyzer/DDG.cs @@ -73,7 +73,6 @@ public void Analyze(Deque queue, CancellationToken cancel, Action< SetCurrentUnit(_unit); AnalyzedEntries.Add(_unit.ProjectEntry); _unit.Analyze(this, cancel); - _unit.ProjectEntry.SetCompleteAnalysis(); } if (reportQueueSize != null) { diff --git a/src/Analysis/Engine/Impl/Infrastructure/Extensions/TaskCompletionSourceExtensions.cs b/src/Analysis/Engine/Impl/Infrastructure/Extensions/TaskCompletionSourceExtensions.cs index 409ddcc0a..84058abda 100644 --- a/src/Analysis/Engine/Impl/Infrastructure/Extensions/TaskCompletionSourceExtensions.cs +++ b/src/Analysis/Engine/Impl/Infrastructure/Extensions/TaskCompletionSourceExtensions.cs @@ -19,12 +19,6 @@ namespace Microsoft.PythonTools.Analysis.Infrastructure { internal static class TaskCompletionSourceExtensions { - public static void TrySetResultOnThreadPool(this TaskCompletionSource taskCompletionSource, T result) { - if (!taskCompletionSource.Task.IsCompleted) { - ThreadPool.QueueUserWorkItem(new TrySetResultStateAction(taskCompletionSource, result).Invoke); - } - } - public static CancellationTokenRegistration RegisterForCancellation(this TaskCompletionSource taskCompletionSource, CancellationToken cancellationToken) => taskCompletionSource.RegisterForCancellation(-1, cancellationToken); diff --git a/src/Analysis/Engine/Impl/Infrastructure/PathUtils.cs b/src/Analysis/Engine/Impl/Infrastructure/PathUtils.cs index 14b345a83..7bc6a7399 100644 --- a/src/Analysis/Engine/Impl/Infrastructure/PathUtils.cs +++ b/src/Analysis/Engine/Impl/Infrastructure/PathUtils.cs @@ -362,6 +362,8 @@ public static FileStream OpenWithRetry(string file, FileMode mode, FileAccess ac return null; } catch (DirectoryNotFoundException) when (!create) { return null; + } catch (UnauthorizedAccessException) { + Thread.Sleep(10); } catch (IOException) { if (create) { var dir = Path.GetDirectoryName(file); diff --git a/src/Analysis/Engine/Impl/Interpreter/Ast/AstScrapedPythonModule.cs b/src/Analysis/Engine/Impl/Interpreter/Ast/AstScrapedPythonModule.cs index 2b7009e71..d1248c5dd 100644 --- a/src/Analysis/Engine/Impl/Interpreter/Ast/AstScrapedPythonModule.cs +++ b/src/Analysis/Engine/Impl/Interpreter/Ast/AstScrapedPythonModule.cs @@ -39,6 +39,7 @@ class AstScrapedPythonModule : IPythonModule public AstScrapedPythonModule(string name, string filePath) { Name = name ?? throw new ArgumentNullException(nameof(name)); + ParseErrors = Enumerable.Empty(); _filePath = filePath; _members = new Dictionary(); _scraped = false; diff --git a/src/Analysis/Engine/Impl/ProjectEntry.cs b/src/Analysis/Engine/Impl/ProjectEntry.cs index f61e03818..056e1c59d 100644 --- a/src/Analysis/Engine/Impl/ProjectEntry.cs +++ b/src/Analysis/Engine/Impl/ProjectEntry.cs @@ -152,19 +152,22 @@ public IPythonParse GetCurrentParse() { } }, cancellationToken); - return Task.WhenAny(timeoutTask, _analysisTcs.Task).Unwrap(); + return Task.WhenAny(timeoutTask, task).Unwrap(); } internal void SetCompleteAnalysis() { lock (this) { - _analysisTcs.TrySetResultOnThreadPool(Analysis); + _analysisTcs.TrySetResult(Analysis); } } internal void ResetCompleteAnalysis() { + TaskCompletionSource analysisTcs; lock (this) { - _analysisTcs = new TaskCompletionSource(); + analysisTcs = _analysisTcs; + _analysisTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } + analysisTcs?.TrySetCanceled(); } public void SetCurrentParse(PythonAst tree, IAnalysisCookie cookie, bool notify = true) { diff --git a/src/Analysis/Engine/Impl/PythonAnalyzer.cs b/src/Analysis/Engine/Impl/PythonAnalyzer.cs index bc3d77612..a54363cbb 100644 --- a/src/Analysis/Engine/Impl/PythonAnalyzer.cs +++ b/src/Analysis/Engine/Impl/PythonAnalyzer.cs @@ -953,6 +953,7 @@ public void AnalyzeQueuedEntries(CancellationToken cancel) { var ddg = new DDG(); ddg.Analyze(Queue, cancel, _reportQueueSize, _reportQueueInterval); foreach (var entry in ddg.AnalyzedEntries) { + entry.SetCompleteAnalysis(); entry.RaiseOnNewAnalysis(); } } diff --git a/src/Analysis/Engine/Test/AnalysisTest.cs b/src/Analysis/Engine/Test/AnalysisTest.cs index dde49b6c6..2aff1c02e 100644 --- a/src/Analysis/Engine/Test/AnalysisTest.cs +++ b/src/Analysis/Engine/Test/AnalysisTest.cs @@ -1489,21 +1489,14 @@ print abc } } - [TestMethod, Priority(0)] - public async Task LambdaInComprehension() { + [DataRow(PythonLanguageVersion.V27)] + [DataRow(PythonLanguageVersion.V31)] + [DataRow(PythonLanguageVersion.V33)] + [DataTestMethod, Priority(0)] + public async Task LambdaInComprehension(PythonLanguageVersion version) { var text = "x = [(lambda a:[a**i for i in range(a+1)])(j) for j in range(5)]"; - using (var server = await CreateServerAsync(PythonVersions.Required_Python27X)) { - var analysis = await server.OpenDefaultDocumentAndGetAnalysisAsync(text); - analysis.Should().HaveVariable("x").OfType(BuiltinTypeId.List); - } - - using (var server = await CreateServerAsync(PythonVersions.Required_Python31X)) { - var analysis = await server.OpenDefaultDocumentAndGetAnalysisAsync(text); - analysis.Should().HaveVariable("x").OfType(BuiltinTypeId.List); - } - - using (var server = await CreateServerAsync(PythonVersions.Required_Python33X)) { + using (var server = await CreateServerAsync(PythonVersions.GetRequiredCPythonConfiguration(version))) { var analysis = await server.OpenDefaultDocumentAndGetAnalysisAsync(text); analysis.Should().HaveVariable("x").OfType(BuiltinTypeId.List); } diff --git a/src/Analysis/Engine/Test/PythonVersions.cs b/src/Analysis/Engine/Test/PythonVersions.cs index 01afc0421..b6acba440 100644 --- a/src/Analysis/Engine/Test/PythonVersions.cs +++ b/src/Analysis/Engine/Test/PythonVersions.cs @@ -141,6 +141,9 @@ public static class PythonVersions { Python27, Python27_x64).First(); + public static InterpreterConfiguration GetRequiredCPythonConfiguration(PythonLanguageVersion version) + => GetCPythonVersion(version, InterpreterArchitecture.x86) ?? GetCPythonVersion(version, InterpreterArchitecture.x64) ?? NotInstalled(version.ToString()); + private static IEnumerable GetVersions(params InterpreterConfiguration[] configurations) => configurations.Where(v => v != null); private static InterpreterConfiguration GetCPythonVersion(PythonLanguageVersion version, InterpreterArchitecture arch) { diff --git a/src/LanguageServer/Impl/LanguageServer.cs b/src/LanguageServer/Impl/LanguageServer.cs index 87ad1e88b..e56a55428 100644 --- a/src/LanguageServer/Impl/LanguageServer.cs +++ b/src/LanguageServer/Impl/LanguageServer.cs @@ -562,12 +562,12 @@ private struct QueueItem { public bool IsAwaitable { get; } public QueueItem(bool isAwaitable, CancellationToken cancellationToken) { - _tcs = new TaskCompletionSource(); + _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); IsAwaitable = isAwaitable; _tcs.RegisterForCancellation(cancellationToken).UnregisterOnCompletion(_tcs.Task); } - public void SetResult(IDisposable disposable) => _tcs.TrySetResultOnThreadPool(disposable); + public void SetResult(IDisposable disposable) => _tcs.TrySetResult(disposable); } private class PrioritizerDisposable : IDisposable { diff --git a/src/UnitTests/Core/Impl/TestData.cs b/src/UnitTests/Core/Impl/TestData.cs index 9e54bd4f9..982469acc 100644 --- a/src/UnitTests/Core/Impl/TestData.cs +++ b/src/UnitTests/Core/Impl/TestData.cs @@ -47,7 +47,7 @@ private static string CalculateTestDataRoot() { if (Directory.Exists(path)) { foreach (var landmark in new[] { "TestData", - @"Python\Tests\TestData" + @"src\UnitTests\TestData" }) { var candidate = PathUtils.GetAbsoluteDirectoryPath(path, landmark); if (Directory.Exists(candidate)) { diff --git a/src/UnitTests/TestData/AstAnalysis/Classes.py b/src/UnitTests/TestData/AstAnalysis/Classes.py new file mode 100644 index 000000000..3c6abdf26 --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/Classes.py @@ -0,0 +1,27 @@ +class C1: + """C1""" + pass + +class C2(object): pass +class C3(C2): pass +class C4(C1, C2, C3): pass + +C5 = C1 + +if True: + class D: pass + +if False: + class E: pass + +class F1: + class F2: pass + class F3: + if True: + class F4: pass + else: + class F5: pass + F6 = C1 + +def f(): + class X: pass diff --git a/src/UnitTests/TestData/AstAnalysis/Functions.py b/src/UnitTests/TestData/AstAnalysis/Functions.py new file mode 100644 index 000000000..57d463ae0 --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/Functions.py @@ -0,0 +1,21 @@ +def f(): + '''f''' + pass + def f1(): pass + +f2 = f + +if True: + def g(): pass +else: + def h(): pass + +class C: + def i(self): pass + + def j(self): + def j2(self): + pass + + class C2: + def k(self): pass diff --git a/src/UnitTests/TestData/AstAnalysis/Imports.py b/src/UnitTests/TestData/AstAnalysis/Imports.py new file mode 100644 index 000000000..cc8865e99 --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/Imports.py @@ -0,0 +1,4 @@ +from __future__ import absolute_imports + +from sys import version_info +import a_made_up_module diff --git a/src/UnitTests/TestData/AstAnalysis/InstanceMethod.py b/src/UnitTests/TestData/AstAnalysis/InstanceMethod.py new file mode 100644 index 000000000..9173b18de --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/InstanceMethod.py @@ -0,0 +1,6 @@ +class C: + def f(self): pass + +f1 = C.f +c = C() +f2 = c.f diff --git a/src/UnitTests/TestData/AstAnalysis/MultiValues.py b/src/UnitTests/TestData/AstAnalysis/MultiValues.py new file mode 100644 index 000000000..885ebe988 --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/MultiValues.py @@ -0,0 +1,15 @@ +from Values import x, y, z +l = [] +t = () +s = {1} + +XY = x +XY = y +XYZ = z +XYZ = XY + +D = l +D = t +D = s +D = s # ensure multiple assignments do not clear it +from Values import D diff --git a/src/UnitTests/TestData/AstAnalysis/Package-stubs/Module.pyi b/src/UnitTests/TestData/AstAnalysis/Package-stubs/Module.pyi new file mode 100644 index 000000000..1d86b4493 --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/Package-stubs/Module.pyi @@ -0,0 +1,4 @@ + +class Class: + def typed_method(self, a : int) -> float: ... + diff --git a/src/UnitTests/TestData/AstAnalysis/Package/Module.py b/src/UnitTests/TestData/AstAnalysis/Package/Module.py new file mode 100644 index 000000000..c81331bdf --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/Package/Module.py @@ -0,0 +1,4 @@ + +class Class: + def untyped_method(self, a): pass + def inferred_method(self, a = 1): return 3.14 diff --git a/src/UnitTests/TestData/AstAnalysis/Package/__init__.py b/src/UnitTests/TestData/AstAnalysis/Package/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/UnitTests/TestData/AstAnalysis/ReturnAnnotations.pyi b/src/UnitTests/TestData/AstAnalysis/ReturnAnnotations.pyi new file mode 100644 index 000000000..2ccf633db --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/ReturnAnnotations.pyi @@ -0,0 +1,2 @@ +def f(p : int = ...) -> int: ... +def g() -> str: ... diff --git a/src/UnitTests/TestData/AstAnalysis/ReturnValues.py b/src/UnitTests/TestData/AstAnalysis/ReturnValues.py new file mode 100644 index 000000000..86e2e55f0 --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/ReturnValues.py @@ -0,0 +1,15 @@ +def r_a(a, b): + return a + +def r_b(a, b): + return b + +def r_str(): + return '' + +def r_object(): + return object() + +class A: + def r_A(self): + return type(self)() diff --git a/src/UnitTests/TestData/AstAnalysis/Stubs/Package/Module.pyi b/src/UnitTests/TestData/AstAnalysis/Stubs/Package/Module.pyi new file mode 100644 index 000000000..b0d1d2216 --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/Stubs/Package/Module.pyi @@ -0,0 +1,4 @@ + +class Class: + def typed_method_2(self, a : int) -> float: ... + diff --git a/src/UnitTests/TestData/AstAnalysis/TopLevelCompletions.py b/src/UnitTests/TestData/AstAnalysis/TopLevelCompletions.py new file mode 100644 index 000000000..4a3b6c389 --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/TopLevelCompletions.py @@ -0,0 +1,7 @@ +x = 1 +y = 'a' +z = b'b' + +def f(a): + + pass diff --git a/src/UnitTests/TestData/AstAnalysis/Values.py b/src/UnitTests/TestData/AstAnalysis/Values.py new file mode 100644 index 000000000..1be3fcffe --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/Values.py @@ -0,0 +1,17 @@ +x = 1 +y = 'a' +z = b'b' +pi = 3.1415 +l = [] +t = () +d = {} +s = {1} + +X = x +Y = y +Z = z +PI = pi +L = l +T = t +D = d +S = s