diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..a89fc45343 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,56 @@ +root = true + +[*] +indent_style = space +insert_final_newline = true + +[*.cs] +indent_size = 4 +trim_trailing_whitespace = true + +dotnet_style_qualification_for_event = true:suggestion +dotnet_style_qualification_for_field = true:suggestion +dotnet_style_qualification_for_method = true:suggestion +dotnet_style_qualification_for_property = true:suggestion + +dotnet_diagnostic.IDE0002.severity = warning # Name can be simplified + +# StyleCop: Special Rules +dotnet_diagnostic.SA0001.severity = none # XML comment analysis disabled + +# StyleCop: Readability Rules +dotnet_diagnostic.SA1118.severity = none # Parameter should not span multiple lines +dotnet_diagnostic.SA1122.severity = none # Use string.Empty for empty strings +dotnet_diagnostic.SA1127.severity = none # Generic type constraints should be on their own line +dotnet_diagnostic.SA1128.severity = none # Put constructor initializers on their own line +dotnet_diagnostic.SA1135.severity = none # Using directives should be qualified + +# StyleCop: Ordering Rules +dotnet_diagnostic.SA1201.severity = none # Elements should appear in the correct order +dotnet_diagnostic.SA1202.severity = none # Elements should be ordered by access +dotnet_diagnostic.SA1203.severity = none # Constants should appear before fields +dotnet_diagnostic.SA1204.severity = none # Static members should appear before non-static members +dotnet_diagnostic.SA1214.severity = none # Readonly fields should appear before non-readonly fields + +# StyleCop: Maintainability Rules +dotnet_diagnostic.SA1401.severity = none # Fields should be private +dotnet_diagnostic.SA1402.severity = none # File may only contain a single type +dotnet_diagnostic.SA1405.severity = none # Debug.Assert should provide message text +dotnet_diagnostic.SA1413.severity = none # Use trailing comma in multi-line initializers +dotnet_diagnostic.SA1414.severity = none # Tuple types in signatures should have element names + +# StyleCop: Layout Rules +dotnet_diagnostic.SA1512.severity = none # Single-line comments should not be followed by blank line +dotnet_diagnostic.SA1513.severity = none # Closing brace should be followed by blank line +dotnet_diagnostic.SA1515.severity = none # Single-line comment should be preceded by blank line + +# StyleCop: Documentation Rules +dotnet_diagnostic.SA1611.severity = none # Element parameters should be documented +dotnet_diagnostic.SA1612.severity = none # Element parameter documentation should match element parameters +dotnet_diagnostic.SA1615.severity = none # Element return value should be documented +dotnet_diagnostic.SA1618.severity = none # Generic type parameters should be documented +dotnet_diagnostic.SA1623.severity = none # Property summary documentation should match accessors +dotnet_diagnostic.SA1629.severity = none # Documentation text should end with a period +dotnet_diagnostic.SA1633.severity = none # File should have header +dotnet_diagnostic.SA1642.severity = none # Constructor summary documentation should begin with standard text +dotnet_diagnostic.SA1649.severity = none # File name should match first type name diff --git a/.vscode/extensions.json b/.vscode/extensions.json index aa40a22552..1426b3366b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,7 @@ { "recommendations": [ - "ms-dotnettools.csharp", + "editorconfig.editorconfig", "ionide.ionide-fsharp", + "ms-dotnettools.csharp" ] -} \ No newline at end of file +} diff --git a/bootstrap.ps1 b/bootstrap.ps1 index ce3f8a4a8f..824748cb51 100644 --- a/bootstrap.ps1 +++ b/bootstrap.ps1 @@ -9,7 +9,7 @@ param( $NuGetVersion = $Env:NUGET_VERSION, [string] - $VsixVersion = $Env:VSIX_VERSION + $VsVsixVersion = $Env:VSVSIX_VERSION ); if ("$AssemblyVersion".Trim().Length -eq 0) { @@ -25,10 +25,10 @@ Write-Host "Assembly version: $AssemblyVersion"; $pieces = "$AssemblyVersion".split("."); $MajorVersion = "$($pieces[0])"; $MinorVersion = "$($pieces[1])"; +$patch = "$($pieces[2])" +$rev = "$($pieces[3])".PadLeft(4, "0"); if ("$SemverVersion".Trim().Length -eq 0) { - $patch = "$($pieces[2])" - $rev = "$($pieces[3])".PadLeft(4, "0"); $SemverVersion = "$MajorVersion.$MinorVersion.$patch$rev"; } @@ -36,8 +36,8 @@ if ("$NuGetVersion".Trim().Length -eq 0) { $NuGetVersion = "$AssemblyVersion-alpha"; } -if ("$VsixVersion".Trim().Length -eq 0) { - $VsixVersion = "$AssemblyVersion"; +if ("$VsVsixVersion".Trim().Length -eq 0) { + $VsVsixVersion = "$MajorVersion.$MinorVersion.$patch.$rev"; } $Telemetry = "$($Env:ASSEMBLY_CONSTANTS)".Contains("TELEMETRY").ToString().ToLower(); @@ -55,7 +55,7 @@ Get-ChildItem -Recurse *.v.template ` Replace("#MINOR_VERSION#", $MinorVersion). Replace("#ASSEMBLY_VERSION#", $AssemblyVersion). Replace("#NUGET_VERSION#", $NuGetVersion). - Replace("#VSIX_VERSION#", $VsixVersion). + Replace("#VSVSIX_VERSION#", $VsVsixVersion). Replace("#SEMVER_VERSION#", $SemverVersion). Replace("#ENABLE_TELEMETRY#", $Telemetry) } ` @@ -65,7 +65,8 @@ Get-ChildItem -Recurse *.v.template ` If ($Env:ASSEMBLY_VERSION -eq $null) { $Env:ASSEMBLY_VERSION ="$AssemblyVersion" } If ($Env:NUGET_VERSION -eq $null) { $Env:NUGET_VERSION ="$NuGetVersion" } If ($Env:SEMVER_VERSION -eq $null) { $Env:SEMVER_VERSION ="$SemverVersion" } -If ($Env:VSIX_VERSION -eq $null) { $Env:VSIX_VERSION ="$VsixVersion" } +If ($Env:VSVSIX_VERSION -eq $null) { $Env:VSVSIX_VERSION ="$VsVsixVersion" } +Write-Host "##vso[task.setvariable variable=VsVsix.Version]$VsVsixVersion" Push-Location (Join-Path $PSScriptRoot 'src/QsCompiler/Compiler') .\FindNuspecReferences.ps1; diff --git a/build/assets/qdk-nuget-icon.png b/build/assets/qdk-nuget-icon.png new file mode 100644 index 0000000000..ca63d20e32 Binary files /dev/null and b/build/assets/qdk-nuget-icon.png differ diff --git a/build/build.ps1 b/build/build.ps1 index e7ba452f1b..167428063d 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -26,7 +26,8 @@ function Build-One { -c $Env:BUILD_CONFIGURATION ` -v $Env:BUILD_VERBOSITY ` @args ` - /property:Version=$Env:ASSEMBLY_VERSION + /property:Version=$Env:ASSEMBLY_VERSION ` + /property:InformationalVersion=$Env:SEMVER_VERSION if ($LastExitCode -ne 0) { Write-Host "##vso[task.logissue type=error;]Failed to build $project." @@ -83,7 +84,8 @@ function Build-VS() { msbuild VisualStudioExtension.sln ` /property:Configuration=$Env:BUILD_CONFIGURATION ` @args ` - /property:AssemblyVersion=$Env:ASSEMBLY_VERSION + /property:AssemblyVersion=$Env:ASSEMBLY_VERSION ` + /property:InformationalVersion=$Env:SEMVER_VERSION if ($LastExitCode -ne 0) { throw @@ -125,4 +127,3 @@ if (-not $all_ok) { } else { exit 0 } - diff --git a/build/ci.yml b/build/ci.yml index cdb234b1f1..d03d8b0819 100644 --- a/build/ci.yml +++ b/build/ci.yml @@ -4,7 +4,7 @@ trigger: variables: Build.Major: 0 - Build.Minor: 11 + Build.Minor: 12 Assembly.Version: $(Build.BuildNumber) Assembly.Constants: '' Drops.Dir: $(Build.ArtifactStagingDirectory)/drops diff --git a/build/init.yml b/build/init.yml index e85e77a7d4..f7961da226 100644 --- a/build/init.yml +++ b/build/init.yml @@ -11,15 +11,15 @@ steps: # Pre-reqs ## - task: NuGetToolInstaller@0 - displayName: 'Use NuGet 5.2.0' + displayName: 'Use NuGet 5.6.0' inputs: - versionSpec: '5.2.0' + versionSpec: '5.6.0' - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 3.1.100' + displayName: 'Use .NET Core SDK 3.1.300' inputs: packageType: sdk - version: '3.1.100' + version: '3.1.300' - task: Npm@1 displayName: 'Install vsce' diff --git a/build/pack.ps1 b/build/pack.ps1 index 71428fa711..c414aab6a3 100644 --- a/build/pack.ps1 +++ b/build/pack.ps1 @@ -122,7 +122,7 @@ function Pack-SelfContained() { } else { $args = @(); } - $ArchivePath = Join-Path $ArchiveDir "$BaseName-$DotNetRuntimeID-$Env:ASSEMBLY_VERSION.zip"; + $ArchivePath = Join-Path $ArchiveDir "$BaseName-$DotNetRuntimeID-$Env:SEMVER_VERSION.zip"; dotnet publish (Join-Path $PSScriptRoot $Project) ` -c $Env:BUILD_CONFIGURATION ` -v $Env:BUILD_VERBOSITY ` @@ -130,7 +130,8 @@ function Pack-SelfContained() { --runtime $DotNetRuntimeID ` --output $TargetDir ` @args ` - /property:Version=$Env:ASSEMBLY_VERSION + /property:Version=$Env:ASSEMBLY_VERSION ` + /property:InformationalVersion=$Env:SEMVER_VERSION Write-Host "##[info]Writing self-contained deployment to $ArchivePath..." Compress-Archive ` -Force ` @@ -186,7 +187,8 @@ function Pack-VS() { msbuild QsharpVSIX.csproj ` /t:CreateVsixContainer ` /property:Configuration=$Env:BUILD_CONFIGURATION ` - /property:AssemblyVersion=$Env:ASSEMBLY_VERSION + /property:AssemblyVersion=$Env:ASSEMBLY_VERSION ` + /property:InformationalVersion=$Env:SEMVER_VERSION if ($LastExitCode -ne 0) { throw @@ -235,4 +237,3 @@ if (-not $all_ok) { } else { exit 0 } - diff --git a/build/steps.yml b/build/steps.yml index 8f48ca2401..0623033f7f 100644 --- a/build/steps.yml +++ b/build/steps.yml @@ -26,7 +26,7 @@ steps: condition: and(succeeded(), ne(variables['Enable.VSIX'], 'false')) inputs: solution: '$(System.DefaultWorkingDirectory)/VisualStudioExtension.sln' - msbuildArgs: > + msbuildArgs: >- /p:DefineConstants=$(Assembly.Constants) /p:AssemblyVersion=$(Assembly.Version) configuration: $(Build.Configuration) diff --git a/build/test.ps1 b/build/test.ps1 index e44b1e0d22..5b9f9bcdba 100644 --- a/build/test.ps1 +++ b/build/test.ps1 @@ -29,7 +29,8 @@ function Test-One { -v $Env:BUILD_VERBOSITY ` --logger trx ` @args ` - /property:Version=$Env:ASSEMBLY_VERSION + /property:Version=$Env:ASSEMBLY_VERSION ` + /property:InformationalVersion=$Env:SEMVER_VERSION if ($LastExitCode -ne 0) { Write-Host "##vso[task.logissue type=error;]Failed to test $project." @@ -44,4 +45,3 @@ if (-not $all_ok) { throw "Running tests failed. Check the logs." } - diff --git a/omnisharp.json b/omnisharp.json new file mode 100644 index 0000000000..ec1a2fe03e --- /dev/null +++ b/omnisharp.json @@ -0,0 +1,8 @@ +{ + "FormattingOptions": { + "enableEditorConfigSupport": true + }, + "RoslynExtensionsOptions": { + "enableAnalyzersSupport": true + } +} diff --git a/src/Common/DelaySign.cs b/src/Common/DelaySign.cs index d838fcce8e..118226cde4 100644 --- a/src/Common/DelaySign.cs +++ b/src/Common/DelaySign.cs @@ -12,7 +12,7 @@ internal static class SigningConstants { #if SIGNED - public const string PUBLIC_KEY = ", PublicKey=" + + public const string PublicKey = ", PublicKey=" + "002400000c800000140100000602000000240000525341310008000001000100613399aff18ef1" + "a2c2514a273a42d9042b72321f1757102df9ebada69923e2738406c21e5b801552ab8d200a65a2" + "35e001ac9adc25f2d811eb09496a4c6a59d4619589c69f5baf0c4179a47311d92555cd006acc8b" + @@ -22,6 +22,6 @@ internal static class SigningConstants "02d5efcdeae953658d3452561b5f36c542efdbdd9f888538d374cef106acf7d93a4445c3c73cd9" + "11f0571aaf3d54da12b11ddec375b3"; #else - public const string PUBLIC_KEY = ""; + public const string PublicKey = ""; #endif } diff --git a/src/Common/stylecop.json b/src/Common/stylecop.json new file mode 100644 index 0000000000..403d4cd116 --- /dev/null +++ b/src/Common/stylecop.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "orderingRules": { + "usingDirectivesPlacement": "preserve" + }, + "documentationRules": { + "documentInternalElements": false + } + } +} diff --git a/src/ProjectTemplates/Microsoft.Quantum.ProjectTemplates.nuspec b/src/ProjectTemplates/Microsoft.Quantum.ProjectTemplates.nuspec index bfbb26f75f..cd1519a770 100644 --- a/src/ProjectTemplates/Microsoft.Quantum.ProjectTemplates.nuspec +++ b/src/ProjectTemplates/Microsoft.Quantum.ProjectTemplates.nuspec @@ -1,5 +1,5 @@ - + Microsoft.Quantum.ProjectTemplates $version$ @@ -9,7 +9,7 @@ MIT https://docs.microsoft.com/en-us/quantum - https://secure.gravatar.com/avatar/bd1f02955b2853ba0a3b1cdc2434e8ec.png + images\qdk-nuget-icon.png false .NET Core templates pack for Q#, part of the Microsoft Quantum Development Kit. @@ -25,7 +25,8 @@ - + + \ No newline at end of file diff --git a/src/QsCompiler/CommandLineTool/CommandLineTool.csproj b/src/QsCompiler/CommandLineTool/CommandLineTool.csproj index dc959da91a..d3556ce0fb 100644 --- a/src/QsCompiler/CommandLineTool/CommandLineTool.csproj +++ b/src/QsCompiler/CommandLineTool/CommandLineTool.csproj @@ -15,7 +15,7 @@ See: https://docs.microsoft.com/en-us/quantum/relnotes/ MIT https://github.com/Microsoft/qsharp-compiler - https://secure.gravatar.com/avatar/bd1f02955b2853ba0a3b1cdc2434e8ec.png + qdk-nuget-icon.png Quantum Q# Qsharp true qsc @@ -26,11 +26,8 @@ - - - - - + + @@ -42,8 +39,13 @@ + Always + + + + diff --git a/src/QsCompiler/CommandLineTool/Commands/Build.cs b/src/QsCompiler/CommandLineTool/Commands/Build.cs index 1f4919d4fe..f59a244130 100644 --- a/src/QsCompiler/CommandLineTool/Commands/Build.cs +++ b/src/QsCompiler/CommandLineTool/Commands/Build.cs @@ -11,7 +11,6 @@ using Microsoft.Quantum.QsCompiler.Diagnostics; using static Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants; - namespace Microsoft.Quantum.QsCompiler.CommandLineCompiler { public static class BuildCompilation @@ -24,47 +23,70 @@ public static IEnumerable UsageExamples { get { - yield return new Example("***\nCompiling a Q# source file", + yield return new Example( + "***\nCompiling a Q# source file", new BuildOptions { Input = new string[] { "file.qs" } }); - yield return new Example("***\nCompiling a Q# source file using additional compilation steps defined in a .NET Core dll", + yield return new Example( + "***\nCompiling a Q# source file using additional compilation steps defined in a .NET Core dll", new BuildOptions { Input = new string[] { "file.qs" }, Plugins = new string[] { "myCustomStep.dll" } }); - yield return new Example("***\nCompiling several Q# source files and referenced compiled libraries", - new BuildOptions { Input = new string[] { "file1.qs", "file2.qs" }, References = new string[] { "library1.dll", "library2.dll" }}); - yield return new Example("***\nSetting the output folder for the compilation output", + yield return new Example( + "***\nCompiling several Q# source files and referenced compiled libraries", + new BuildOptions { Input = new string[] { "file1.qs", "file2.qs" }, References = new string[] { "library1.dll", "library2.dll" } }); + yield return new Example( + "***\nSetting the output folder for the compilation output", new BuildOptions { Input = new string[] { "file.qs" }, References = new string[] { "library.dll" }, OutputFolder = Path.Combine("obj", "qsharp") }); } } - [Option("response-files", Required = true, SetName = RESPONSE_FILES, - HelpText = "Response file(s) providing command arguments. Required only if no other arguments are specified. Non-default values for options specified via command line take precedence.")] + [Option( + "response-files", + Required = true, + SetName = RESPONSE_FILES, + HelpText = "Response file(s) providing command arguments. Required only if no other arguments are specified. Non-default values for options specified via command line take precedence.")] public IEnumerable ResponseFiles { get; set; } - [Option('o', "output", Required = false, SetName = CODE_MODE, - HelpText = "Destination folder where the output of the compilation will be generated.")] + [Option( + 'o', + "output", + Required = false, + SetName = CODE_MODE, + HelpText = "Destination folder where the output of the compilation will be generated.")] public string OutputFolder { get; set; } - [Option("doc", Required = false, SetName = CODE_MODE, - HelpText = "Destination folder where documentation will be generated.")] + [Option( + "doc", + Required = false, + SetName = CODE_MODE, + HelpText = "Destination folder where documentation will be generated.")] public string DocFolder { get; set; } - [Option("proj", Required = false, SetName = CODE_MODE, - HelpText = "Name of the project (needs to be usable as file name).")] + [Option( + "proj", + Required = false, + SetName = CODE_MODE, + HelpText = "Name of the project (needs to be usable as file name).")] public string ProjectName { get; set; } - [Option("emit-dll", Required = false, Default = false, SetName = CODE_MODE, - HelpText = "Specifies whether the compiler should emit a .NET Core dll containing the compiled Q# code.")] + [Option( + "emit-dll", + Required = false, + Default = false, + SetName = CODE_MODE, + HelpText = "Specifies whether the compiler should emit a .NET Core dll containing the compiled Q# code.")] public bool EmitDll { get; set; } - [Option("perf", Required = false, SetName = CODE_MODE, - HelpText = "Destination folder where the output of the performance assessment will be generated.")] + [Option( + "perf", + Required = false, + SetName = CODE_MODE, + HelpText = "Destination folder where the output of the performance assessment will be generated.")] public string PerfFolder { get; set; } - /// - /// Reads the content of all specified response files and processes it using FromResponseFiles. + /// Reads the content of all specified response files and processes it using FromResponseFiles. /// Updates the settings accordingly, prioritizing already specified non-default values over the values from response-files. /// Returns true and a new BuildOptions object as out parameter with all the settings from response files incorporated. - /// Returns false if the content of the specified response-files could not be processed. + /// Returns false if the content of the specified response-files could not be processed. /// internal static bool IncorporateResponseFiles(BuildOptions options, out BuildOptions incorporated, ILogger logger = null) { @@ -74,7 +96,10 @@ internal static bool IncorporateResponseFiles(BuildOptions options, out BuildOpt try { var fromResponseFiles = FromResponseFiles(options.ResponseFiles); - if (fromResponseFiles == null) return false; + if (fromResponseFiles == null) + { + return false; + } fromResponseFiles.UpdateSetIndependentSettings(options); options = fromResponseFiles; } @@ -89,9 +114,8 @@ internal static bool IncorporateResponseFiles(BuildOptions options, out BuildOpt } } - /// - /// Given a string representing the command line arguments, splits them into a suitable string array. + /// Given a string representing the command line arguments, splits them into a suitable string array. /// private static IEnumerable SplitCommandLineArguments(string commandLine) { @@ -101,38 +125,48 @@ private static IEnumerable SplitCommandLineArguments(string commandLine) { var precededByBackslash = index > 0 && parmChars[index - 1] == '\\'; var ignoreIfQuote = inQuote && precededByBackslash; - if (parmChars[index] == '"' && !ignoreIfQuote) inQuote = !inQuote; - if (inQuote && parmChars[index] == '\n') parmChars[index] = ' '; - if (!inQuote && !precededByBackslash && Char.IsWhiteSpace(parmChars[index])) parmChars[index] = '\n'; + if (parmChars[index] == '"' && !ignoreIfQuote) + { + inQuote = !inQuote; + } + if (inQuote && parmChars[index] == '\n') + { + parmChars[index] = ' '; + } + if (!inQuote && !precededByBackslash && char.IsWhiteSpace(parmChars[index])) + { + parmChars[index] = '\n'; + } } - return (new string(parmChars)) + return new string(parmChars) .Split('\n', StringSplitOptions.RemoveEmptyEntries) - .Select(arg => arg.Trim('"')); + .Select(arg => arg.Trim('"')); } /// - /// Reads the content off all given response files and tries to parse their concatenated content as command line arguments. - /// Logs a suitable exceptions and returns null if the parsing fails. - /// Throws an ArgumentNullException if the given sequence of responseFiles is null. + /// Reads the content off all given response files and tries to parse their concatenated content as command line arguments. + /// Logs a suitable exceptions and returns null if the parsing fails. + /// Throws an ArgumentNullException if the given sequence of responseFiles is null. /// private static BuildOptions FromResponseFiles(IEnumerable responseFiles) { - if (responseFiles == null) throw new ArgumentNullException(nameof(responseFiles)); - var commandLine = String.Join(" ", responseFiles.Select(File.ReadAllText)); + if (responseFiles == null) + { + throw new ArgumentNullException(nameof(responseFiles)); + } + var commandLine = string.Join(" ", responseFiles.Select(File.ReadAllText)); var args = SplitCommandLineArguments(commandLine); var parsed = Parser.Default.ParseArguments(args); return parsed.MapResult( (BuildOptions opts) => opts, - (errs => - { + errs => + { HelpText.AutoBuild(parsed); return null; - }) - ); + }); } - - // publicly accessible routines + // publicly accessible routines /// /// Builds the compilation for the Q# code or Q# snippet and referenced assemblies defined by the given options. @@ -141,8 +175,14 @@ private static BuildOptions FromResponseFiles(IEnumerable responseFiles) /// public static int Run(BuildOptions options, ConsoleLogger logger) { - if (options == null) throw new ArgumentNullException(nameof(options)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } if (!BuildOptions.IncorporateResponseFiles(options, out options)) { logger.Log(ErrorCode.InvalidCommandLineArgsInResponseFiles, Array.Empty()); @@ -172,7 +212,7 @@ public static int Run(BuildOptions options, ConsoleLogger logger) RewriteSteps = options.Plugins?.Select(step => (step, (string)null)) ?? ImmutableArray<(string, string)>.Empty, EnableAdditionalChecks = false, // todo: enable debug mode? ExposeReferencesViaTestNames = options.ExposeReferencesViaTestNames - }; + }; if (options.PerfFolder != null) { @@ -188,7 +228,7 @@ public static int Run(BuildOptions options, ConsoleLogger logger) } catch (Exception ex) { - logger.Log(ErrorCode.PublishingPerfResultsFailed, new string[]{ options.PerfFolder }); + logger.Log(ErrorCode.PublishingPerfResultsFailed, new string[] { options.PerfFolder }); logger.Log(ex); } } diff --git a/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs b/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs index 77498e1e44..ec757407b2 100644 --- a/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs +++ b/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs @@ -11,14 +11,13 @@ using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.Diagnostics; using Microsoft.Quantum.QsCompiler.SyntaxTree; -using Microsoft.Quantum.QsCompiler.Transformations.QsCodeOutput; using Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations; +using Microsoft.Quantum.QsCompiler.Transformations.QsCodeOutput; using Microsoft.VisualStudio.LanguageServer.Protocol; using Newtonsoft.Json; using static Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants; using Compilation = Microsoft.Quantum.QsCompiler.CompilationBuilder.CompilationUnitManager.Compilation; - namespace Microsoft.Quantum.QsCompiler.CommandLineCompiler { public static class DiagnoseCompilation @@ -31,104 +30,136 @@ public static IEnumerable UsageExamples { get { - yield return new Example("***\nCompiling a Q# snippet and getting the syntax tree", + yield return new Example( + "***\nCompiling a Q# snippet and getting the syntax tree", new DiagnoseOptions { CodeSnippet = "let a = 0;", PrintSyntaxTree = true }); - yield return new Example("***\nCompiling a Q# snippet and getting the tokenization", + yield return new Example( + "***\nCompiling a Q# snippet and getting the tokenization", new DiagnoseOptions { CodeSnippet = "let a = 0;", PrintTokenization = true }); - yield return new Example("***\nCompiling a file containing Q# code and getting the syntax tree", + yield return new Example( + "***\nCompiling a file containing Q# code and getting the syntax tree", new DiagnoseOptions { Input = new string[] { Path.Combine("path", "to", "file.qs") }, PrintSyntaxTree = true }); - yield return new Example("***\nCompiling a file containing Q# code and getting the Q# representation of the syntax tree", + yield return new Example( + "***\nCompiling a file containing Q# code and getting the Q# representation of the syntax tree", new DiagnoseOptions { Input = new string[] { Path.Combine("path", "to", "file.qs") }, PrintCompiledCode = true }); } } - [Option("text", Required = false, Default = false, - HelpText = "Specifies whether to print the text representation of the code in memory.")] + [Option( + "text", + Required = false, + Default = false, + HelpText = "Specifies whether to print the text representation of the code in memory.")] public bool PrintTextRepresentation { get; set; } - [Option("tokenization", Required = false, Default = false, - HelpText = "Specifies whether to print the tokenization of the code.")] + [Option( + "tokenization", + Required = false, + Default = false, + HelpText = "Specifies whether to print the tokenization of the code.")] public bool PrintTokenization { get; set; } - [Option("tree", Required = false, Default = false, - HelpText = "Specifies whether to print the serialization of the built syntax tree.")] + [Option( + "tree", + Required = false, + Default = false, + HelpText = "Specifies whether to print the serialization of the built syntax tree.")] public bool PrintSyntaxTree { get; set; } - [Option("code", Required = false, Default = false, - HelpText = "Specifies whether to print the Q# code generated based on the built syntax tree.")] + [Option( + "code", + Required = false, + Default = false, + HelpText = "Specifies whether to print the Q# code generated based on the built syntax tree.")] public bool PrintCompiledCode { get; set; } } /// - /// Logs the content of each file in the given compilation as Information using the given logger. + /// Logs the content of each file in the given compilation as Information using the given logger. /// If the id of a file is consistent with the one assigned to a code snippet, /// strips the lines of code that correspond to the wrapping defined by WrapSnippet. /// Throws an ArgumentNullException if the given compilation is null. /// private static void PrintFileContentInMemory(Compilation compilation, ILogger logger) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } foreach (var file in compilation.SourceFiles) { IEnumerable inMemory = compilation.FileContent[file]; var stripWrapping = Options.IsCodeSnippet(file); - QsCompilerError.Verify(!stripWrapping || inMemory.Count() >= 4, + QsCompilerError.Verify( + !stripWrapping || inMemory.Count() >= 4, "expecting at least four lines of code for the compilation of a code snippet"); - if (stripWrapping) inMemory = inMemory.Skip(2).Take(inMemory.Count() - 4); + if (stripWrapping) + { + inMemory = inMemory.Skip(2).Take(inMemory.Count() - 4); + } logger.Log( InformationCode.FileContentInMemory, Enumerable.Empty(), - stripWrapping ? null : file.Value, - messageParam: $"{Environment.NewLine}{String.Concat(inMemory)}" - ); + stripWrapping ? null : file.Value, + messageParam: $"{Environment.NewLine}{string.Concat(inMemory)}"); } } /// - /// Logs the tokenization of each file in the given compilation as Information using the given logger. + /// Logs the tokenization of each file in the given compilation as Information using the given logger. /// If the id of a file is consistent with the one assigned to a code snippet, /// strips the tokens that correspond to the wrapping defined by WrapSnippet. /// Throws an ArgumentNullException if the given compilation is null. /// private static void PrintContentTokenization(Compilation compilation, ILogger logger) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } foreach (var file in compilation.SourceFiles) { var tokenization = compilation.Tokenization[file].Select(tokens => tokens.Select(token => token.Kind)); var stripWrapping = Options.IsCodeSnippet(file); - QsCompilerError.Verify(!stripWrapping || tokenization.Count() >= 4, + QsCompilerError.Verify( + !stripWrapping || tokenization.Count() >= 4, "expecting at least four lines of code for the compilation of a code snippet"); - if (stripWrapping) tokenization = tokenization.Skip(2).Take(tokenization.Count() - 4).ToImmutableArray(); + if (stripWrapping) + { + tokenization = tokenization.Skip(2).Take(tokenization.Count() - 4).ToImmutableArray(); + } var serialization = tokenization .Select(line => line.Select(item => JsonConvert.SerializeObject(item, Newtonsoft.Json.Formatting.Indented))) - .Zip(Enumerable.Range(1, tokenization.Count()), - (ts, i) => ts.Any() ? $"\n[ln {i}]: \n{String.Join("\n", ts)} \n" : ""); + .Zip( + Enumerable.Range(1, tokenization.Count()), + (ts, i) => ts.Any() ? $"\n[ln {i}]: \n{string.Join("\n", ts)} \n" : ""); serialization = new string[] { "" }.Concat(serialization); logger.Log( InformationCode.BuiltTokenization, Enumerable.Empty(), - stripWrapping ? null : file.Value, - messageParam: serialization.ToArray() - ); + stripWrapping ? null : file.Value, + messageParam: serialization.ToArray()); } } /// - /// Logs the part of the given evaluated syntax tree that corresponds to each file - /// in the given compilation as Information using the given logger. + /// Logs the part of the given evaluated syntax tree that corresponds to each file + /// in the given compilation as Information using the given logger. /// If the given evaluated tree is null, queries the tree contained in the given compilation instead. /// If the id of a file is consistent with the one assigned to a code snippet, /// strips the lines of code that correspond to the wrapping defined by WrapSnippet. - /// Throws an ArgumentException if this is not possible because the given syntax tree is inconsistent with that wrapping. + /// Throws an ArgumentException if this is not possible because the given syntax tree is inconsistent with that wrapping. /// Throws an ArgumentNullException if the given compilation is null. /// private static void PrintSyntaxTree(IEnumerable evaluatedTree, Compilation compilation, ILogger logger) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } evaluatedTree ??= compilation.SyntaxTree.Values; foreach (var file in compilation.SourceFiles) @@ -139,26 +170,35 @@ private static void PrintSyntaxTree(IEnumerable evaluatedTree, Comp void PrintTree(string serialization) => logger.Log( InformationCode.BuiltSyntaxTree, Enumerable.Empty(), - stripWrapping ? null : file.Value, + stripWrapping ? null : file.Value, messageParam: new string[] { "", serialization }); - if (!stripWrapping) PrintTree(JsonConvert.SerializeObject(subtree, Newtonsoft.Json.Formatting.Indented)); - else PrintTree(JsonConvert.SerializeObject(StripSnippetWrapping(subtree), Newtonsoft.Json.Formatting.Indented)); + if (!stripWrapping) + { + PrintTree(JsonConvert.SerializeObject(subtree, Newtonsoft.Json.Formatting.Indented)); + } + else + { + PrintTree(JsonConvert.SerializeObject(StripSnippetWrapping(subtree), Newtonsoft.Json.Formatting.Indented)); + } } } /// - /// Logs the generated Q# code for the part of the given evaluated syntax tree that corresponds to each file - /// in the given compilation as Information using the given logger. + /// Logs the generated Q# code for the part of the given evaluated syntax tree that corresponds to each file + /// in the given compilation as Information using the given logger. /// If the given evaluated tree is null, queries the tree contained in the given compilation instead. /// If the id of a file is consistent with the one assigned to a code snippet, /// strips the lines of code that correspond to the wrapping defined by WrapSnippet. - /// Throws an ArgumentException if this is not possible because the given syntax tree is inconsistent with that wrapping. + /// Throws an ArgumentException if this is not possible because the given syntax tree is inconsistent with that wrapping. /// Throws an ArgumentNullException if the given compilation is null. /// private static void PrintGeneratedQs(IEnumerable evaluatedTree, Compilation compilation, ILogger logger) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } evaluatedTree ??= compilation.SyntaxTree.Values; foreach (var file in compilation.SourceFiles) @@ -169,34 +209,44 @@ private static void PrintGeneratedQs(IEnumerable evaluatedTree, Com var code = new string[] { "" }.Concat(StripSnippetWrapping(subtree).Select(SyntaxTreeToQsharp.Default.ToCode)); logger.Log(InformationCode.FormattedQsCode, Enumerable.Empty(), messageParam: code.ToArray()); } - else + else { var imports = evaluatedTree.ToImmutableDictionary(ns => ns.Name, ns => compilation.OpenDirectives(file, ns.Name).ToImmutableArray()); SyntaxTreeToQsharp.Apply(out List, string>> generated, evaluatedTree, (file, imports)); var code = new string[] { "" }.Concat(generated.Single().Values.Select(nsCode => $"{nsCode}{Environment.NewLine}")); logger.Log(InformationCode.FormattedQsCode, Enumerable.Empty(), file.Value, messageParam: code.ToArray()); - }; + } } } - // publicly accessible methods /// - /// Strips the namespace and callable declaration that is consistent with the wrapping defined by WrapSnippet. + /// Strips the namespace and callable declaration that is consistent with the wrapping defined by WrapSnippet. /// Throws an ArgumentException if this is not possible because the given syntax tree is inconsistent with that wrapping. - /// Throws an ArgumentNullException if the given syntaxTree is null. + /// Throws an ArgumentNullException if the given syntaxTree is null. /// public static IEnumerable StripSnippetWrapping(IEnumerable syntaxTree) { - if (syntaxTree == null) throw new ArgumentNullException(nameof(syntaxTree)); + if (syntaxTree == null) + { + throw new ArgumentNullException(nameof(syntaxTree)); + } var incorrectWrapperException = new ArgumentException("syntax tree does not reflect the expected wrapper"); - if (syntaxTree.Count() != 1 || syntaxTree.Single().Elements.Count() != 1) throw incorrectWrapperException; + if (syntaxTree.Count() != 1 || syntaxTree.Single().Elements.Count() != 1) + { + throw incorrectWrapperException; + } if (syntaxTree.Single().Elements.Single() is QsNamespaceElement.QsCallable callable && callable.Item.Specializations.Count() == 1 && callable.Item.Specializations.Single().Implementation is SpecializationImplementation.Provided impl) + { return impl.Item2.Statements; - else throw incorrectWrapperException; + } + else + { + throw incorrectWrapperException; + } } /// @@ -207,8 +257,14 @@ public static IEnumerable StripSnippetWrapping(IEnumerable public static int Run(DiagnoseOptions options, ConsoleLogger logger) { - if (options == null) throw new ArgumentNullException(nameof(options)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } if (!options.ParseAssemblyProperties(out var assemblyConstants)) { @@ -228,15 +284,33 @@ public static int Run(DiagnoseOptions options, ConsoleLogger logger) RewriteSteps = options.Plugins?.Select(step => (step, (string)null)) ?? ImmutableArray<(string, string)>.Empty, EnableAdditionalChecks = true, ExposeReferencesViaTestNames = options.ExposeReferencesViaTestNames - }; + }; var loaded = new CompilationLoader(options.LoadSourcesOrSnippet(logger), options.References, loadOptions, logger); - if (loaded.VerifiedCompilation == null) return ReturnCode.Status(loaded); + if (loaded.VerifiedCompilation == null) + { + return ReturnCode.Status(loaded); + } - if (logger.Verbosity < DiagnosticSeverity.Information) logger.Verbosity = DiagnosticSeverity.Information; - if (options.PrintTextRepresentation) PrintFileContentInMemory(loaded.VerifiedCompilation, logger); - if (options.PrintTokenization) PrintContentTokenization(loaded.VerifiedCompilation, logger); - if (options.PrintSyntaxTree) PrintSyntaxTree(loaded.CompilationOutput?.Namespaces, loaded.VerifiedCompilation, logger); - if (options.PrintCompiledCode) PrintGeneratedQs(loaded.CompilationOutput?.Namespaces, loaded.VerifiedCompilation, logger); + if (logger.Verbosity < DiagnosticSeverity.Information) + { + logger.Verbosity = DiagnosticSeverity.Information; + } + if (options.PrintTextRepresentation) + { + PrintFileContentInMemory(loaded.VerifiedCompilation, logger); + } + if (options.PrintTokenization) + { + PrintContentTokenization(loaded.VerifiedCompilation, logger); + } + if (options.PrintSyntaxTree) + { + PrintSyntaxTree(loaded.CompilationOutput?.Namespaces, loaded.VerifiedCompilation, logger); + } + if (options.PrintCompiledCode) + { + PrintGeneratedQs(loaded.CompilationOutput?.Namespaces, loaded.VerifiedCompilation, logger); + } return ReturnCode.Status(loaded); } } diff --git a/src/QsCompiler/CommandLineTool/Commands/Format.cs b/src/QsCompiler/CommandLineTool/Commands/Format.cs index 59494a4097..f4ac6ca23b 100644 --- a/src/QsCompiler/CommandLineTool/Commands/Format.cs +++ b/src/QsCompiler/CommandLineTool/Commands/Format.cs @@ -11,15 +11,12 @@ using CommandLine.Text; using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.Diagnostics; -using Microsoft.Quantum.QsCompiler.SyntaxTree; -using Microsoft.Quantum.QsCompiler.Transformations.QsCodeOutput; using Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations; +using Microsoft.Quantum.QsCompiler.Transformations.QsCodeOutput; using Microsoft.VisualStudio.LanguageServer.Protocol; - using Compilation = Microsoft.Quantum.QsCompiler.CompilationBuilder.CompilationUnitManager.Compilation; using SourceFileLoader = System.Func, System.Collections.Immutable.ImmutableDictionary>; - namespace Microsoft.Quantum.QsCompiler.CommandLineCompiler { public static class FormatCompilation @@ -31,16 +28,22 @@ public class FormatOptions : Options public static IEnumerable UsageExamples { get - { - yield return new Example("***\nFormat Q# source files in place", + { + yield return new Example( + "***\nFormat Q# source files in place", new FormatOptions { Input = new string[] { "file1.qs", "file2.qs" }, OutputFolder = " " }); - yield return new Example("***\nFormat Q# source files that depend on referenced libraries and setting the output folder where the formatted files will be generated", + yield return new Example( + "***\nFormat Q# source files that depend on referenced libraries and setting the output folder where the formatted files will be generated", new FormatOptions { Input = new string[] { "file.qs" }, References = new string[] { "library1.dll", "library2.dll" }, OutputFolder = Path.Combine("src", "FormattedFiles") }); } } - [Option('o', "output", Required = true, SetName = CODE_MODE, - HelpText = "Destination folder where the formatted files will be generated.")] + [Option( + 'o', + "output", + Required = true, + SetName = CODE_MODE, + HelpText = "Destination folder where the formatted files will be generated.")] public string OutputFolder { get; set; } } @@ -51,7 +54,7 @@ public static IEnumerable UsageExamples new Regex(@"\[(?:[^\[\]]|(?\[)|(?<-ctr>\]))*(?(ctr)(?!))\]"); /// - /// Replaces all semicolons that occur within array brackets in the given string with commas. + /// Replaces all semicolons that occur within array brackets in the given string with commas. /// public static string UpdateArrayLiterals(string fileContent) { @@ -59,17 +62,19 @@ public static string UpdateArrayLiterals(string fileContent) return WithinArrayBrackets.Replace(fileContent, ReplaceSemicolons); } - /// - /// Generates formatted Q# code based on the part of the syntax tree that corresponds to each file in the given compilation. + /// Generates formatted Q# code based on the part of the syntax tree that corresponds to each file in the given compilation. /// If the id of a file is consistent with the one assigned to a code snippet, - /// strips the lines of code that correspond to the wrapping defined by WrapSnippet. - /// Throws an ArgumentException if this is not possible because the given syntax tree is inconsistent with that wrapping. + /// strips the lines of code that correspond to the wrapping defined by WrapSnippet. + /// Throws an ArgumentException if this is not possible because the given syntax tree is inconsistent with that wrapping. /// Throws an ArgumentNullException if the given compilation is null. /// private static IEnumerable GenerateQsCode(Compilation compilation, NonNullable file, ILogger logger) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } if (Options.IsCodeSnippet(file)) { var subtree = compilation.SyntaxTree.Values.Select(ns => FilterBySourceFile.Apply(ns, file)).Where(ns => ns.Elements.Any()); @@ -77,43 +82,53 @@ private static IEnumerable GenerateQsCode(Compilation compilation, NonNu } else { - var imports = compilation.SyntaxTree.Values + var imports = compilation.SyntaxTree.Values .ToImmutableDictionary(ns => ns.Name, ns => compilation.OpenDirectives(file, ns.Name).ToImmutableArray()); var success = SyntaxTreeToQsharp.Apply(out List, string>> generated, compilation.SyntaxTree.Values, (file, imports)); - if (!success) logger?.Log(WarningCode.UnresolvedItemsInGeneratedQs, Enumerable.Empty(), file.Value); + if (!success) + { + logger?.Log(WarningCode.UnresolvedItemsInGeneratedQs, Enumerable.Empty(), file.Value); + } return generated.Single().Select(entry => { var nsComments = compilation.NamespaceComments(file, entry.Key); string FormatComments(IEnumerable comments) => - String.Join(Environment.NewLine, - comments.Select(line => line.Trim()).Select(line => String.IsNullOrWhiteSpace(line) ? "" : $"// {line}") - ).Trim(); - var leadingComments = entry.Value.StartsWith("///") + string.Join( + Environment.NewLine, + comments.Select(line => line.Trim()).Select(line => string.IsNullOrWhiteSpace(line) ? "" : $"// {line}")) + .Trim(); + var leadingComments = entry.Value.StartsWith("///") ? $"{FormatComments(nsComments.OpeningComments)}{Environment.NewLine}" : FormatComments(nsComments.OpeningComments); var trailingComments = FormatComments(nsComments.ClosingComments); - var code = new string[] { leadingComments, entry.Value, trailingComments }.Where(s => !String.IsNullOrWhiteSpace(s)); - return String.Join(Environment.NewLine, code); + var code = new string[] { leadingComments, entry.Value, trailingComments }.Where(s => !string.IsNullOrWhiteSpace(s)); + return string.Join(Environment.NewLine, code); }); - }; + } } /// - /// Generates formatted Q# code for the file with the given uri based on the syntax tree in the given compilation. + /// Generates formatted Q# code for the file with the given uri based on the syntax tree in the given compilation. /// If the id of the file is consistent with the one assigned to a code snippet, - /// logs the generated code using the given logger. - /// Creates a file containing the generated code in the given output folder otherwise. - /// Returns true if the generation succeeded, and false if an exception was thrown. - /// Throws an ArgumentNullException if the given compilation, the file uri or its absolute path are null. + /// logs the generated code using the given logger. + /// Creates a file containing the generated code in the given output folder otherwise. + /// Returns true if the generation succeeded, and false if an exception was thrown. + /// Throws an ArgumentNullException if the given compilation, the file uri or its absolute path are null. /// private static bool GenerateFormattedQsFile(Compilation compilation, NonNullable fileName, string outputFolder, ILogger logger) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } var code = Enumerable.Empty(); - try { code = code.Concat(GenerateQsCode(compilation, fileName, logger).Where(c => !String.IsNullOrWhiteSpace(c))); } + try + { + code = code.Concat(GenerateQsCode(compilation, fileName, logger).Where(c => !string.IsNullOrWhiteSpace(c))); + } catch (Exception ex) { logger?.Log(ErrorCode.QsGenerationFailed, Enumerable.Empty(), fileName.Value); @@ -128,7 +143,7 @@ private static bool GenerateFormattedQsFile(Compilation compilation, NonNullable } else { - var content = String.Join(Environment.NewLine, code.Select(block => $"{block}{Environment.NewLine}{Environment.NewLine}")); + var content = string.Join(Environment.NewLine, code.Select(block => $"{block}{Environment.NewLine}{Environment.NewLine}")); CompilationLoader.GeneratedFile(fileName, outputFolder ?? "FormattedFiles", ".qs", content); } return true; @@ -136,33 +151,46 @@ private static bool GenerateFormattedQsFile(Compilation compilation, NonNullable /// /// Builds the compilation for the Q# code or Q# snippet and referenced assemblies defined by the given options. - /// Generates formatted Q# code for each source file in the compilation. + /// Generates formatted Q# code for each source file in the compilation. /// Returns a suitable error code if some of the source files or references could not be found or loaded, or if the Q# generation failed. - /// Compilation errors are not reflected in the return code, but are logged using the given logger. + /// Compilation errors are not reflected in the return code, but are logged using the given logger. /// Throws an ArgumentNullException if any of the given arguments is null. /// public static int Run(FormatOptions options, ConsoleLogger logger) { - if (options == null) throw new ArgumentNullException(nameof(options)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } - ImmutableDictionary LoadSources (SourceFileLoader loadFromDisk) => + ImmutableDictionary LoadSources(SourceFileLoader loadFromDisk) => options.LoadSourcesOrSnippet(logger)(loadFromDisk) .ToImmutableDictionary(entry => entry.Key, entry => UpdateArrayLiterals(entry.Value)); // manually replace array literals - var loaded = new CompilationLoader(LoadSources, options.References, logger: logger); // no rewrite steps, no generation - if (ReturnCode.Status(loaded) == ReturnCode.UNRESOLVED_FILES) return ReturnCode.UNRESOLVED_FILES; // ignore compilation errors + var loaded = new CompilationLoader(LoadSources, options.References, logger: logger); // no rewrite steps, no generation + if (ReturnCode.Status(loaded) == ReturnCode.UNRESOLVED_FILES) + { + return ReturnCode.UNRESOLVED_FILES; // ignore compilation errors + } - // TODO: a lot of the formatting logic defined here and also in the routines above + // TODO: a lot of the formatting logic defined here and also in the routines above // is supposed to move into the compilation manager in order to be available for the language server to provide formatting var success = true; foreach (var file in loaded.VerifiedCompilation.SourceFiles) { var verbosity = logger.Verbosity; if (Options.IsCodeSnippet(file) && logger.Verbosity < DiagnosticSeverity.Information) - { logger.Verbosity = DiagnosticSeverity.Information; } + { + logger.Verbosity = DiagnosticSeverity.Information; + } if (!GenerateFormattedQsFile(loaded.VerifiedCompilation, file, options.OutputFolder, logger)) - { success = false; } + { + success = false; + } logger.Verbosity = verbosity; } return success ? ReturnCode.SUCCESS : ReturnCode.CODE_GENERATION_ERRORS; diff --git a/src/QsCompiler/CommandLineTool/CompilationTracker.cs b/src/QsCompiler/CommandLineTool/CompilationTracker.cs index f6a463a4f6..fe0824bb53 100644 --- a/src/QsCompiler/CommandLineTool/CompilationTracker.cs +++ b/src/QsCompiler/CommandLineTool/CompilationTracker.cs @@ -8,15 +8,13 @@ using System.Threading; using Newtonsoft.Json; - namespace Microsoft.Quantum.QsCompiler.CommandLineCompiler { /// - /// Provides an event tracker of the compilation process for the purpose of assessing performance. + /// Provides an event tracker of the compilation process for the purpose of assessing performance. /// public static class CompilationTracker { - // Private classes and types. /// @@ -28,33 +26,38 @@ private class CompilationTask /// Represents the name of the parent compilation task. /// public readonly string ParentName; + /// /// Represents the name of the compilation task. /// public readonly string Name; + /// /// Contains the UTC datetime when the task started. /// public readonly DateTime UtcStart; + /// /// Contains the UTC datetime when the task ended. /// public DateTime? UtcEnd; + /// /// Contains the duration of the task in milliseconds. /// public long? DurationInMs; + /// /// Stopwatch used to measure the duration of the task. /// - private readonly Stopwatch Watch; + private readonly Stopwatch watch; /// /// Generates a key that uniquely identifies a task in the compilation process based on the task's name and its parent's name. /// internal static string GenerateKey(string parentName, string name) { - return String.Format("{0}.{1}", parentName ?? "ROOT", name); + return string.Format("{0}.{1}", parentName ?? "ROOT", name); } /// @@ -62,12 +65,12 @@ internal static string GenerateKey(string parentName, string name) /// public CompilationTask(string parentName, string name) { - ParentName = parentName; - Name = name; - UtcStart = DateTime.UtcNow; - UtcEnd = null; - DurationInMs = null; - Watch = Stopwatch.StartNew(); + this.ParentName = parentName; + this.Name = name; + this.UtcStart = DateTime.UtcNow; + this.UtcEnd = null; + this.DurationInMs = null; + this.watch = Stopwatch.StartNew(); } /// @@ -75,9 +78,9 @@ public CompilationTask(string parentName, string name) /// public void End() { - UtcEnd = DateTime.UtcNow; - Watch.Stop(); - DurationInMs = Watch.ElapsedMilliseconds; + this.UtcEnd = DateTime.UtcNow; + this.watch.Stop(); + this.DurationInMs = this.watch.ElapsedMilliseconds; } /// @@ -85,7 +88,7 @@ public void End() /// public bool IsInProgress() { - return Watch.IsRunning; + return this.watch.IsRunning; } } @@ -99,8 +102,8 @@ private class CompilationTaskNode public CompilationTaskNode(CompilationTask task) { - Task = task; - Children = new Dictionary(); + this.Task = task; + this.Children = new Dictionary(); } } @@ -126,9 +129,9 @@ private class Warning public Warning(WarningType type, string key) { - UtcDateTime = DateTime.UtcNow; - Type = type; - Key = key; + this.UtcDateTime = DateTime.UtcNow; + this.Type = type; + this.Key = key; } } @@ -170,6 +173,7 @@ public Warning(WarningType type, string key) /// Note that thread-safe access to this member is done through the global lock. /// private static readonly IDictionary CompilationTasks = new Dictionary(); + /// /// Contains the warnings generated while handling the compiler tasks events. /// Note that thread-safe access to this member is done through the global lock. @@ -265,7 +269,8 @@ private static void CompilationEventEndHandler(CompilationLoader.CompilationTask /// public static void OnCompilationTaskEvent(object sender, CompilationLoader.CompilationTaskEventArgs args) { - lock (GlobalLock) { + lock (GlobalLock) + { if (CompilationEventTypeHandlers.TryGetValue(args.Type, out var hanlder)) { hanlder(args); @@ -297,7 +302,8 @@ public static void PublishResults(string outputFolder) serializer.Serialize(file, compilationProcessesForest); } - if (Warnings.Count > 0) { + if (Warnings.Count > 0) + { using (var file = File.CreateText(Path.Combine(outputPath, CompilationPerfWarningsFileName))) { var serializer = new JsonSerializer diff --git a/src/QsCompiler/CommandLineTool/LoadContext.cs b/src/QsCompiler/CommandLineTool/LoadContext.cs index db5ec2a15e..cbef94d616 100644 --- a/src/QsCompiler/CommandLineTool/LoadContext.cs +++ b/src/QsCompiler/CommandLineTool/LoadContext.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -// This unfortunately needs to live in this project +// This unfortunately needs to live in this project // since the functionality is not available in netstandard2.1. using System; @@ -12,55 +12,56 @@ using System.Reflection; using System.Runtime.Loader; - namespace Microsoft.Quantum.QsCompiler { /// - /// Context with some basic handling for loading dependencies. + /// Context with some basic handling for loading dependencies. /// Each assembly loaded via LoadAssembly is loaded into its own context. /// For more details, see https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/assemblyloadcontext.md /// public class LoadContext : AssemblyLoadContext { public readonly string PathToParentAssembly; - private readonly AssemblyDependencyResolver _Resolver; - private readonly HashSet _FallbackPaths; + private readonly AssemblyDependencyResolver resolver; + private readonly HashSet fallbackPaths; /// /// Adds the given path(s) to the list of paths the loader will search for a suitable .dll when an assembly could not be loaded. /// public void AddToPath(params string[] paths) => - this._FallbackPaths.UnionWith(paths); + this.fallbackPaths.UnionWith(paths); /// /// Removes the given path(s) from the list of paths the loader will search for a suitable .dll when an assembly could not be loaded. /// public void RemoveFromPath(params string[] paths) => - this._FallbackPaths.RemoveWhere(paths.Contains); + this.fallbackPaths.RemoveWhere(paths.Contains); private LoadContext(string parentAssembly) { this.PathToParentAssembly = parentAssembly ?? throw new ArgumentNullException(nameof(parentAssembly)); - this._Resolver = new AssemblyDependencyResolver(this.PathToParentAssembly); - this._FallbackPaths = new HashSet(); + this.resolver = new AssemblyDependencyResolver(this.PathToParentAssembly); + this.fallbackPaths = new HashSet(); this.Resolving += this.OnResolving; } + /// protected override Assembly Load(AssemblyName name) { - string path = _Resolver.ResolveAssemblyToPath(name); - return path == null ? null : LoadFromAssemblyPath(path); + string path = this.resolver.ResolveAssemblyToPath(name); + return path == null ? null : this.LoadFromAssemblyPath(path); } + /// protected override IntPtr LoadUnmanagedDll(string name) { - string path = _Resolver.ResolveUnmanagedDllToPath(name); - return path == null ? IntPtr.Zero : LoadUnmanagedDllFromPath(path); + string path = this.resolver.ResolveUnmanagedDllToPath(name); + return path == null ? IntPtr.Zero : this.LoadUnmanagedDllFromPath(path); } /// /// Search all fallback paths for a suitable .dll ignoring all exceptions. - /// Returns the full path to the dll if a suitable assembly could was found. + /// Returns the full path to the dll if a suitable assembly could was found. /// private string ResolveFromFallbackPaths(AssemblyName name) { @@ -69,12 +70,21 @@ bool MatchByName(string file) => .Equals(name.Name, StringComparison.InvariantCultureIgnoreCase); var found = new List(); - foreach (var dir in this._FallbackPaths) + foreach (var dir in this.fallbackPaths) + { + try + { + found.AddRange(Directory.GetFiles(dir, "*.dll", SearchOption.AllDirectories).Where(MatchByName)); + } + catch + { + continue; + } + } + if (found.Count <= 1 || name.Version == null) { - try { found.AddRange(Directory.GetFiles(dir, "*.dll", SearchOption.AllDirectories).Where(MatchByName)); } - catch { continue; } + return found.FirstOrDefault(); } - if (found.Count <= 1 || name.Version == null) return found.FirstOrDefault(); var tempContext = new LoadContext(this.PathToParentAssembly); var versions = new List<(string, Version)>(); @@ -87,56 +97,76 @@ bool MatchByName(string file) => versions.Add((file, asmVersion)); if (name.Version.Equals(asmVersion)) { - if (tempContext.IsCollectible) tempContext.Unload(); + if (tempContext.IsCollectible) + { + tempContext.Unload(); + } return file; } } - catch { continue; } + catch + { + continue; + } + } + if (tempContext.IsCollectible) + { + tempContext.Unload(); } - if (tempContext.IsCollectible) tempContext.Unload(); var matchesMajor = versions.Where(asm => name.Version.Major == asm.Item2?.Major); var matchesMinor = matchesMajor.Where(asm => name.Version.Minor == asm.Item2?.Minor); var matchesMajRev = matchesMinor.Where(asm => name.Version.MajorRevision == asm.Item2?.MajorRevision); - return matchesMajRev.Concat(matchesMinor).Concat(matchesMajor).Select(asm => asm.Item1).FirstOrDefault(); + return matchesMajRev.Concat(matchesMinor).Concat(matchesMajor).Select(asm => asm.Item1).FirstOrDefault(); } /// - /// Last effort to find a suitable dll for an assembly that could otherwise not be loaded. + /// Last effort to find a suitable dll for an assembly that could otherwise not be loaded. /// Search for a suitable .dll in the specified fallback locations. - /// Does not load any dependencies. + /// Does not load any dependencies. /// private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) { var path = this.ResolveFromFallbackPaths(name); - return path == null ? null : LoadFromAssemblyPath(path); + return path == null ? null : this.LoadFromAssemblyPath(path); } private static readonly ConcurrentBag Loaded = new ConcurrentBag(); /// - /// Unloads all created contexts that can be unloaded. + /// Unloads all created contexts that can be unloaded. /// public static void UnloadAll() { - while (LoadContext.Loaded.TryTake(out var context)) - { if (context.IsCollectible) context.Unload(); } + while (Loaded.TryTake(out var context)) + { + if (context.IsCollectible) + { + context.Unload(); + } + } } /// - /// Loads an assembly at the given location into a new context. - /// Adds the specified fallback locations, if any, - /// to the list of paths where the context will try to look for assemblies that could otherwise not be loaded. - /// Throws a FileNotFoundException if no file with the given path exists. + /// Loads an assembly at the given location into a new context. + /// Adds the specified fallback locations, if any, + /// to the list of paths where the context will try to look for assemblies that could otherwise not be loaded. + /// Throws a FileNotFoundException if no file with the given path exists. /// public static Assembly LoadAssembly(string path, string[] fallbackPaths = null) { - if (!File.Exists(path)) throw new FileNotFoundException("Failed to create contex for \"path\". No such file exists."); + if (!File.Exists(path)) + { + throw new FileNotFoundException("Failed to create contex for \"path\". No such file exists."); + } var context = new LoadContext(path); - if (fallbackPaths != null) context.AddToPath(fallbackPaths); - LoadContext.Loaded.Add(context); + if (fallbackPaths != null) + { + context.AddToPath(fallbackPaths); + } + Loaded.Add(context); var assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(path)); return context.LoadFromAssemblyName(assemblyName); } } -} \ No newline at end of file +} diff --git a/src/QsCompiler/CommandLineTool/Logging.cs b/src/QsCompiler/CommandLineTool/Logging.cs index 970b82111d..d88337e084 100644 --- a/src/QsCompiler/CommandLineTool/Logging.cs +++ b/src/QsCompiler/CommandLineTool/Logging.cs @@ -5,45 +5,49 @@ using System.Collections.Generic; using Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.Quantum.QsCompiler.Diagnostics +namespace Microsoft.Quantum.QsCompiler.Diagnostics { /// - /// Provides a logger that logs all diagnostics and information generated by the Q# command line compiler to the console. + /// Provides a logger that logs all diagnostics and information generated by the Q# command line compiler to the console. /// public class ConsoleLogger : LogTracker { - private readonly Func ApplyFormatting; + private readonly Func applyFormatting; protected internal virtual string Format(Diagnostic msg) => - this.ApplyFormatting(msg); + this.applyFormatting(msg); + /// protected sealed override void Print(Diagnostic msg) => PrintToConsole(msg.Severity, this.Format(msg)); /// /// Construct a logger that uses the given function (if any) to format all diagnostics prior to logging them to the console. - /// If no function is specified, the format for logged diagnostics is optimized for readability. - /// All diagnostics that are as severe as or more severe than the specified verbosity (if any) are logged. - /// If the verbosity is left unspecified, all errors and warnings are logged. - /// Warnings that are listed under noWarn (if any) are suppressed. + /// If no function is specified, the format for logged diagnostics is optimized for readability. + /// All diagnostics that are as severe as or more severe than the specified verbosity (if any) are logged. + /// If the verbosity is left unspecified, all errors and warnings are logged. + /// Warnings that are listed under noWarn (if any) are suppressed. /// If a line number offset is specified, then the ranges for all logged diagnostics are shifted by the spedified offset. /// public ConsoleLogger( Func format = null, DiagnosticSeverity verbosity = DiagnosticSeverity.Warning, - IEnumerable noWarn = null, int lineNrOffset = 0) + IEnumerable noWarn = null, + int lineNrOffset = 0) : base(verbosity, noWarn, lineNrOffset) => - this.ApplyFormatting = format ?? Formatting.HumanReadableFormat; + this.applyFormatting = format ?? Formatting.HumanReadableFormat; /// - /// Prints the given message to the Console. + /// Prints the given message to the Console. /// Errors and Warnings are printed to the error stream. - /// Throws an ArgumentNullException if the given message is null. + /// Throws an ArgumentNullException if the given message is null. /// private static void PrintToConsole(DiagnosticSeverity severity, string message) { - if (message == null) throw new ArgumentNullException(nameof(message)); + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } var (stream, color) = severity == DiagnosticSeverity.Error ? (Console.Error, ConsoleColor.Red) : severity == DiagnosticSeverity.Warning ? (Console.Error, ConsoleColor.Yellow) : @@ -56,19 +60,22 @@ private static void PrintToConsole(DiagnosticSeverity severity, string message) var output = message; stream.WriteLine(output); } - finally { Console.ForegroundColor = consoleColor; } + finally + { + Console.ForegroundColor = consoleColor; + } } /// /// Prints a summary containing the currently counted number of errors, warnings and exceptions. - /// Indicates a compilation failure if the given status does not correspond to the ReturnCode indicating a success. + /// Indicates a compilation failure if the given status does not correspond to the ReturnCode indicating a success. /// public virtual void ReportSummary(int status = CommandLineCompiler.ReturnCode.SUCCESS) { string ItemString(int nr, string name) => $"{nr} {name}{(nr == 1 ? "" : "s")}"; var errors = ItemString(this.NrErrorsLogged, "error"); var warnings = ItemString(this.NrWarningsLogged, "warning"); - var exceptions = this.NrExceptionsLogged > 0 + var exceptions = this.NrExceptionsLogged > 0 ? $"\n{ItemString(this.NrExceptionsLogged, "logged exception")}" : ""; @@ -80,8 +87,14 @@ public virtual void ReportSummary(int status = CommandLineCompiler.ReturnCode.SU } var color = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; - try { Console.WriteLine($"Q# compilation failed: {errors}, {warnings} {exceptions}\n"); } - finally { Console.ForegroundColor = color; } + try + { + Console.WriteLine($"Q# compilation failed: {errors}, {warnings} {exceptions}\n"); + } + finally + { + Console.ForegroundColor = color; + } } } } diff --git a/src/QsCompiler/CommandLineTool/Options.cs b/src/QsCompiler/CommandLineTool/Options.cs index ab9433ee3d..d40aa607a1 100644 --- a/src/QsCompiler/CommandLineTool/Options.cs +++ b/src/QsCompiler/CommandLineTool/Options.cs @@ -11,14 +11,13 @@ using Microsoft.Quantum.QsCompiler.CompilationBuilder; using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.Diagnostics; -using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.Quantum.QsCompiler.ReservedKeywords; - +using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.Quantum.QsCompiler.CommandLineCompiler { /// - /// Default values for command line options if nothing is specified. + /// Default values for command line options if nothing is specified. /// internal static class DefaultOptions { @@ -27,39 +26,62 @@ internal static class DefaultOptions public const int TrimLevel = 1; } - public class CompilationOptions : Options { - [Option("trim", Required = false, Default = DefaultOptions.TrimLevel, SetName = CODE_MODE, - HelpText = "[Experimental feature] Integer indicating how much to simplify the syntax tree by eliminating selective abstractions.")] + [Option( + "trim", + Required = false, + Default = DefaultOptions.TrimLevel, + SetName = CODE_MODE, + HelpText = "[Experimental feature] Integer indicating how much to simplify the syntax tree by eliminating selective abstractions.")] public int TrimLevel { get; set; } - [Option("load", Required = false, SetName = CODE_MODE, - HelpText = "Path to the .NET Core dll(s) defining additional transformations to include in the compilation process.")] + [Option( + "load", + Required = false, + SetName = CODE_MODE, + HelpText = "Path to the .NET Core dll(s) defining additional transformations to include in the compilation process.")] public IEnumerable Plugins { get; set; } - [Option("target-specific-decompositions", Required = false, SetName = CODE_MODE, - HelpText = "[Experimental feature] Path to the .NET Core dll(s) containing target specific implementations.")] + [Option( + "target-specific-decompositions", + Required = false, + SetName = CODE_MODE, + HelpText = "[Experimental feature] Path to the .NET Core dll(s) containing target specific implementations.")] public IEnumerable TargetSpecificDecompositions { get; set; } - [Option("load-test-names", Required = false, Default = false, SetName = CODE_MODE, - HelpText = "Specifies whether public types and callables declared in referenced assemblies are exposed via their test name defined by the corresponding attribute.")] + [Option( + "load-test-names", + Required = false, + Default = false, + SetName = CODE_MODE, + HelpText = "Specifies whether public types and callables declared in referenced assemblies are exposed via their test name defined by the corresponding attribute.")] public bool ExposeReferencesViaTestNames { get; set; } - [Option("assembly-properties", Required = false, SetName = CODE_MODE, - HelpText = "Additional properties to populate the AssemblyConstants dictionary with. Each item is expected to be of the form \"key:value\".")] + [Option( + "assembly-properties", + Required = false, + SetName = CODE_MODE, + HelpText = "Additional properties to populate the AssemblyConstants dictionary with. Each item is expected to be of the form \"key:value\".")] public IEnumerable AdditionalAssemblyProperties { get; set; } - [Option("runtime", Required = false, SetName = CODE_MODE, - HelpText = "Specifies the classical capabilites of the runtime. Determines what QIR profile to compile to.")] + [Option( + "runtime", + Required = false, + SetName = CODE_MODE, + HelpText = "Specifies the classical capabilites of the runtime. Determines what QIR profile to compile to.")] public AssemblyConstants.RuntimeCapabilities RuntimeCapabilites { get; set; } - [Option("build-exe", Required = false, Default = false, SetName = CODE_MODE, - HelpText = "Specifies whether to build a Q# command line application.")] + [Option( + "build-exe", + Required = false, + Default = false, + SetName = CODE_MODE, + HelpText = "Specifies whether to build a Q# command line application.")] public bool MakeExecutable { get; set; } /// - /// Returns a dictionary with the specified assembly properties as out parameter. + /// Returns a dictionary with the specified assembly properties as out parameter. /// Returns a boolean indicating whether all specified properties were successfully added. /// internal bool ParseAssemblyProperties(out Dictionary parsed) @@ -76,7 +98,6 @@ internal bool ParseAssemblyProperties(out Dictionary parsed) } } - public class Options { public enum LogFormat @@ -90,43 +111,73 @@ public enum LogFormat protected const string SNIPPET_MODE = "snippetMode"; protected const string RESPONSE_FILES = "responseFiles"; - [Option('v', "verbosity", Required = false, Default = DefaultOptions.Verbosity, - HelpText = "Specifies the verbosity of the logged output. Valid values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic].")] + [Option( + 'v', + "verbosity", + Required = false, + Default = DefaultOptions.Verbosity, + HelpText = "Specifies the verbosity of the logged output. Valid values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic].")] public string Verbosity { get; set; } - [Option("format", Required = false, Default = DefaultOptions.OutputFormat, - HelpText = "Specifies the output format of the command line compiler.")] + [Option( + "format", + Required = false, + Default = DefaultOptions.OutputFormat, + HelpText = "Specifies the output format of the command line compiler.")] public LogFormat OutputFormat { get; set; } - [Option('i', "input", Required = true, SetName = CODE_MODE, - HelpText = "Q# code or name of the Q# file to compile.")] + [Option( + 'i', + "input", + Required = true, + SetName = CODE_MODE, + HelpText = "Q# code or name of the Q# file to compile.")] public IEnumerable Input { get; set; } - [Option('s', "snippet", Required = true, SetName = SNIPPET_MODE, - HelpText = "Q# snippet to compile - i.e. Q# code occuring within an operation or function declaration.")] + [Option( + 's', + "snippet", + Required = true, + SetName = SNIPPET_MODE, + HelpText = "Q# snippet to compile - i.e. Q# code occuring within an operation or function declaration.")] public string CodeSnippet { get; set; } - [Option('f', "within-function", Required = false, Default = false, SetName = SNIPPET_MODE, - HelpText = "Specifies whether a given Q# snipped occurs within a function")] + [Option( + 'f', + "within-function", + Required = false, + Default = false, + SetName = SNIPPET_MODE, + HelpText = "Specifies whether a given Q# snipped occurs within a function")] public bool WithinFunction { get; set; } - [Option('r', "references", Required = false, Default = new string[0], - HelpText = "Referenced binaries to include in the compilation.")] + [Option( + 'r', + "references", + Required = false, + Default = new string[0], + HelpText = "Referenced binaries to include in the compilation.")] public IEnumerable References { get; set; } - [Option('n', "no-warn", Required = false, Default = new int[0], - HelpText = "Warnings with the given code(s) will be ignored.")] + [Option( + 'n', + "no-warn", + Required = false, + Default = new int[0], + HelpText = "Warnings with the given code(s) will be ignored.")] public IEnumerable NoWarn { get; set; } - [Option("package-load-fallback-folders", Required = false, SetName = CODE_MODE, - HelpText = "Specifies the directories the compiler will search when a compiler dependency could not be found.")] + [Option( + "package-load-fallback-folders", + Required = false, + SetName = CODE_MODE, + HelpText = "Specifies the directories the compiler will search when a compiler dependency could not be found.")] public IEnumerable PackageLoadFallbackFolders { get; set; } - /// /// Updates the settings that can be used independent on the other arguments according to the setting in the given options. - /// Already specified non-default values are prioritized over the values in the given options, - /// unless overwriteNonDefaultValues is set to true. Sequences are merged. + /// Already specified non-default values are prioritized over the values in the given options, + /// unless overwriteNonDefaultValues is set to true. Sequences are merged. /// internal void UpdateSetIndependentSettings(Options updates, bool overwriteNonDefaultValues = false) { @@ -136,24 +187,23 @@ internal void UpdateSetIndependentSettings(Options updates, bool overwriteNonDef this.References = (this.References ?? Array.Empty()).Concat(updates.References ?? Array.Empty()); } - - // routines related to logging + // routines related to logging /// - /// If a logger is given, logs the options as CommandLineArguments Information before returning the printed string. + /// If a logger is given, logs the options as CommandLineArguments Information before returning the printed string. /// public string[] Print(ILogger logger = null) { - string value(PropertyInfo p) + string Value(PropertyInfo p) { var v = p.GetValue(this); - return v is String[] a - ? String.Join(';', a) + return v is string[] a + ? string.Join(';', a) : v?.ToString() ?? "(null)"; } var props = this.GetType().GetProperties().Where(p => Attribute.IsDefined(p, typeof(OptionAttribute))); - var msg = props.Select(p => $"{p.Name}: {value(p)}").ToArray(); + var msg = props.Select(p => $"{p.Name}: {Value(p)}").ToArray(); logger?.Log(InformationCode.CommandLineArguments, Enumerable.Empty(), messageParam: Formatting.Indent(msg).ToArray()); return msg; } @@ -161,7 +211,7 @@ string value(PropertyInfo p) /// /// Given a LogFormat, returns a suitable routing for formatting diagnostics. /// - internal static Func LoggingFormat(LogFormat format) => + internal static Func LoggingFormat(LogFormat format) => format switch { LogFormat.MsBuild => Formatting.MsBuildFormat, @@ -170,7 +220,7 @@ internal static Func LoggingFormat(LogFormat format) => }; /// - /// Creates a suitable logger for the given command line options, + /// Creates a suitable logger for the given command line options, /// logging the given arguments if the verbosity is high enough. /// public ConsoleLogger GetLogger(DiagnosticSeverity defaultVerbosity = DiagnosticSeverity.Warning) @@ -184,7 +234,7 @@ public ConsoleLogger GetLogger(DiagnosticSeverity defaultVerbosity = DiagnosticS "quiet".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase) || "q".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase) ? DiagnosticSeverity.Error : - defaultVerbosity; + defaultVerbosity; var logger = new ConsoleLogger( LoggingFormat(this.OutputFormat), verbosity, @@ -194,13 +244,13 @@ public ConsoleLogger GetLogger(DiagnosticSeverity defaultVerbosity = DiagnosticS return logger; } - // routines related to processing snippets /// /// text document identifier used to identify the code snippet in diagnostic mode /// private static readonly Uri SNIPPET_FILE_URI = new Uri(Path.GetFullPath("__CODE_SNIPPET__.qs")); + private static NonNullable SNIPPET_FILE_ID { get @@ -216,6 +266,7 @@ private static NonNullable SNIPPET_FILE_ID /// name of the namespace within which code snippets are compiled /// private const string SNIPPET_NAMESPACE = "_CODE_SNIPPET_NS_"; + /// /// name of the callable within which code snippets are compiled /// @@ -232,29 +283,38 @@ public static string AsSnippet(string content, bool inFunction = false) => $"}}"; /// - /// Helper function that returns true if the given file id is consistent with the one for a code snippet. + /// Helper function that returns true if the given file id is consistent with the one for a code snippet. /// public static bool IsCodeSnippet(NonNullable fileId) => fileId.Value == SNIPPET_FILE_ID.Value; - /// - /// Returns a function that given a routine for loading files from disk, - /// return an enumerable with all text document identifiers and the corresponding file content - /// for the source code or Q# snippet specified by the given options. - /// If both the Input and the CodeSnippet property are set, or none of these properties is set in the given options, + /// Returns a function that given a routine for loading files from disk, + /// return an enumerable with all text document identifiers and the corresponding file content + /// for the source code or Q# snippet specified by the given options. + /// If both the Input and the CodeSnippet property are set, or none of these properties is set in the given options, /// logs a suitable error and returns and empty dictionary. /// - internal CompilationLoader.SourceLoader LoadSourcesOrSnippet (ILogger logger) => loadFromDisk => + internal CompilationLoader.SourceLoader LoadSourcesOrSnippet(ILogger logger) => loadFromDisk => { bool inputIsEmptyOrNull = this.Input == null || !this.Input.Any(); if (this.CodeSnippet == null && !inputIsEmptyOrNull) - { return loadFromDisk(this.Input); } + { + return loadFromDisk(this.Input); + } else if (this.CodeSnippet != null && inputIsEmptyOrNull) - { return new Dictionary { { SNIPPET_FILE_URI, AsSnippet(this.CodeSnippet, this.WithinFunction) } }.ToImmutableDictionary(); } + { + return new Dictionary { { SNIPPET_FILE_URI, AsSnippet(this.CodeSnippet, this.WithinFunction) } }.ToImmutableDictionary(); + } - if (inputIsEmptyOrNull) logger?.Log(ErrorCode.MissingInputFileOrSnippet, Enumerable.Empty()); - else logger?.Log(ErrorCode.SnippetAndInputArguments, Enumerable.Empty()); + if (inputIsEmptyOrNull) + { + logger?.Log(ErrorCode.MissingInputFileOrSnippet, Enumerable.Empty()); + } + else + { + logger?.Log(ErrorCode.SnippetAndInputArguments, Enumerable.Empty()); + } return ImmutableDictionary.Empty; }; } diff --git a/src/QsCompiler/CommandLineTool/Program.cs b/src/QsCompiler/CommandLineTool/Program.cs index d1cdcd1dd4..3fdb6da21f 100644 --- a/src/QsCompiler/CommandLineTool/Program.cs +++ b/src/QsCompiler/CommandLineTool/Program.cs @@ -7,7 +7,6 @@ using Microsoft.Quantum.QsCompiler.Diagnostics; using Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CommandLineCompiler { public static class ReturnCode @@ -16,65 +15,75 @@ public static class ReturnCode /// Return code indicating that the invoked command to the Q# command line compiler succeeded. /// public const int SUCCESS = 0; + /// /// Return code indicating that the command to the Q# command line compiler was invoked with invalid arguments. /// public const int INVALID_ARGUMENTS = -1; + /// /// Return code indicating that some of the files given to the Q# command line compiler could not be loaded. /// public const int UNRESOLVED_FILES = -2; + /// /// Return code indicating that the compilation using the Q# command line compiler failed due to in build errors. /// public const int COMPILATION_ERRORS = -3; + /// /// Return code indicating that generating the necessary functor support for the built compilation failed. /// public const int FUNCTOR_GENERATION_ERRORS = -4; + /// /// Return code indicating that pre-evaluating the built compilation if possible failed. /// public const int PREEVALUATION_ERRORS = -5; + /// /// Return code indicating that generating a binary file with the content of the built compilation failed. /// public const int BINARY_GENERATION_ERRORS = -6; + /// /// Return code indicating that generating a dll containing the compiled binary failed. /// public const int DLL_GENERATION_ERRORS = -7; + /// /// Return code indicating that generating formatted Q# code based on the built compilation failed. /// public const int CODE_GENERATION_ERRORS = -8; + /// /// Return code indicating that generating documentation for the built compilation failed. /// public const int DOC_GENERATION_ERRORS = -9; + /// /// Return code indicating that invoking the specified compiler plugin(s) failed. /// public const int PLUGIN_EXECUTION_ERRORS = -10; + /// /// Return code indicating that an unexpected exception was thrown when executing the invoked command to the Q# command line compiler. /// public const int UNEXPECTED_ERROR = -1000; public static int Status(CompilationLoader loaded) => - loaded.SourceFileLoading == CompilationLoader.Status.Failed ? ReturnCode.UNRESOLVED_FILES : - loaded.ReferenceLoading == CompilationLoader.Status.Failed ? ReturnCode.UNRESOLVED_FILES : - loaded.Validation == CompilationLoader.Status.Failed ? ReturnCode.COMPILATION_ERRORS : - loaded.FunctorSupport == CompilationLoader.Status.Failed ? ReturnCode.FUNCTOR_GENERATION_ERRORS : - loaded.PreEvaluation == CompilationLoader.Status.Failed ? ReturnCode.PREEVALUATION_ERRORS : - loaded.Documentation == CompilationLoader.Status.Failed ? ReturnCode.DOC_GENERATION_ERRORS : - loaded.BinaryFormat == CompilationLoader.Status.Failed ? ReturnCode.BINARY_GENERATION_ERRORS : - loaded.DllGeneration == CompilationLoader.Status.Failed ? ReturnCode.DLL_GENERATION_ERRORS : - loaded.AllLoadedRewriteSteps == CompilationLoader.Status.Failed ? ReturnCode.PLUGIN_EXECUTION_ERRORS : - loaded.Success ? ReturnCode.SUCCESS : ReturnCode.UNEXPECTED_ERROR; + loaded.SourceFileLoading == CompilationLoader.Status.Failed ? UNRESOLVED_FILES : + loaded.ReferenceLoading == CompilationLoader.Status.Failed ? UNRESOLVED_FILES : + loaded.Validation == CompilationLoader.Status.Failed ? COMPILATION_ERRORS : + loaded.FunctorSupport == CompilationLoader.Status.Failed ? FUNCTOR_GENERATION_ERRORS : + loaded.PreEvaluation == CompilationLoader.Status.Failed ? PREEVALUATION_ERRORS : + loaded.Documentation == CompilationLoader.Status.Failed ? DOC_GENERATION_ERRORS : + loaded.BinaryFormat == CompilationLoader.Status.Failed ? BINARY_GENERATION_ERRORS : + loaded.DllGeneration == CompilationLoader.Status.Failed ? DLL_GENERATION_ERRORS : + loaded.AllLoadedRewriteSteps == CompilationLoader.Status.Failed ? PLUGIN_EXECUTION_ERRORS : + loaded.Success ? SUCCESS : UNEXPECTED_ERROR; } - public static class Program { private static int Run(Func compile, T options) where T : Options @@ -82,7 +91,7 @@ private static int Run(Func compile, T options) where var logger = options.GetLogger(); try { - CompilationLoader.LoadAssembly = path => + CompilationLoader.LoadAssembly = path => LoadContext.LoadAssembly(path, options.PackageLoadFallbackFolders?.ToArray()); var result = compile(options, logger); @@ -102,10 +111,9 @@ public static int Main(string[] args) => Parser.Default .ParseArguments(args) .MapResult( - (BuildCompilation.BuildOptions opts) => Program.Run((c, o) => BuildCompilation.Run(c,o), opts), - (DiagnoseCompilation.DiagnoseOptions opts) => Program.Run((c, o) => DiagnoseCompilation.Run(c, o), opts), - (FormatCompilation.FormatOptions opts) => Program.Run((c, o) => FormatCompilation.Run(c, o), opts), - (errs => ReturnCode.INVALID_ARGUMENTS) - ); + (BuildCompilation.BuildOptions opts) => Run((c, o) => BuildCompilation.Run(c, o), opts), + (DiagnoseCompilation.DiagnoseOptions opts) => Run((c, o) => DiagnoseCompilation.Run(c, o), opts), + (FormatCompilation.FormatOptions opts) => Run((c, o) => FormatCompilation.Run(c, o), opts), + errs => ReturnCode.INVALID_ARGUMENTS); } } diff --git a/src/QsCompiler/CompilationManager/AssemblyLoader.cs b/src/QsCompiler/CompilationManager/AssemblyLoader.cs index 7c4937112d..afc336aa58 100644 --- a/src/QsCompiler/CompilationManager/AssemblyLoader.cs +++ b/src/QsCompiler/CompilationManager/AssemblyLoader.cs @@ -15,35 +15,39 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using Newtonsoft.Json.Bson; - namespace Microsoft.Quantum.QsCompiler { /// - /// This class relies on the ECMA-335 standard to extract information contained in compiled binaries. + /// This class relies on the ECMA-335 standard to extract information contained in compiled binaries. /// The standard can be found here: https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf, - /// and the section on custom attributes starts on page 267. + /// and the section on custom attributes starts on page 267. /// public static class AssemblyLoader { /// - /// Loads the Q# data structures in a referenced assembly given the Uri to that assembly, + /// Loads the Q# data structures in a referenced assembly given the Uri to that assembly, /// and returns the loaded content as out parameter. - /// Returns false if some of the content could not be loaded successfully, - /// possibly because the referenced assembly has been compiled with an older compiler version. - /// If onDeserializationException is specified, invokes the given action on any exception thrown during deserialization. - /// Throws an ArgumentNullException if the given uri is null. - /// Throws a FileNotFoundException if no file with the given name exists. + /// Returns false if some of the content could not be loaded successfully, + /// possibly because the referenced assembly has been compiled with an older compiler version. + /// If onDeserializationException is specified, invokes the given action on any exception thrown during deserialization. + /// Throws an ArgumentNullException if the given uri is null. + /// Throws a FileNotFoundException if no file with the given name exists. /// Throws the corresponding exceptions if the information cannot be extracted. /// public static bool LoadReferencedAssembly(Uri asm, out References.Headers headers, bool ignoreDllResources = false, Action onDeserializationException = null) { - if (asm == null) throw new ArgumentNullException(nameof(asm)); + if (asm == null) + { + throw new ArgumentNullException(nameof(asm)); + } if (!CompilationUnitManager.TryGetFileId(asm, out var id) || !File.Exists(asm.LocalPath)) - { throw new FileNotFoundException($"The uri '{asm}' given to the assembly loader is invalid or the file does not exist."); } + { + throw new FileNotFoundException($"The uri '{asm}' given to the assembly loader is invalid or the file does not exist."); + } using var stream = File.OpenRead(asm.LocalPath); using var assemblyFile = new PEReader(stream); - if (ignoreDllResources || !FromResource(assemblyFile, out var compilation, onDeserializationException)) + if (ignoreDllResources || !FromResource(assemblyFile, out var compilation, onDeserializationException)) { var attributes = LoadHeaderAttributes(assemblyFile); headers = new References.Headers(id, attributes); @@ -54,19 +58,25 @@ public static bool LoadReferencedAssembly(Uri asm, out References.Headers header } /// - /// Loads the Q# data structures in a referenced assembly given the Uri to that assembly, + /// Loads the Q# data structures in a referenced assembly given the Uri to that assembly, /// and returns the loaded content as out parameter. - /// Returns false if some of the content could not be loaded successfully, - /// possibly because the referenced assembly has been compiled with an older compiler version. - /// Catches any exception throw upon loading the compilation, and invokes onException with it if such an action has been specified. - /// Sets the out parameter to null if an exception occurred during loading. - /// Throws an ArgumentNullException if the given uri is null. - /// Throws a FileNotFoundException if no file with the given name exists. + /// Returns false if some of the content could not be loaded successfully, + /// possibly because the referenced assembly has been compiled with an older compiler version. + /// Catches any exception throw upon loading the compilation, and invokes onException with it if such an action has been specified. + /// Sets the out parameter to null if an exception occurred during loading. + /// Throws an ArgumentNullException if the given uri is null. + /// Throws a FileNotFoundException if no file with the given name exists. /// public static bool LoadReferencedAssembly(string asmPath, out QsCompilation compilation, Action onException = null) { - if (asmPath == null) throw new ArgumentNullException(nameof(asmPath)); - if (!File.Exists(asmPath)) throw new FileNotFoundException($"The file '{asmPath}' does not exist."); + if (asmPath == null) + { + throw new ArgumentNullException(nameof(asmPath)); + } + if (!File.Exists(asmPath)) + { + throw new FileNotFoundException($"The file '{asmPath}' does not exist."); + } using var stream = File.OpenRead(asmPath); using var assemblyFile = new PEReader(stream); @@ -82,87 +92,92 @@ public static bool LoadReferencedAssembly(string asmPath, out QsCompilation comp } } - // tools for loading the compiled syntax tree from the dll resource (later setup for shipping Q# libraries) /// /// Given a stream containing the binary representation of compiled Q# code, returns the corresponding Q# compilation. - /// Returns true if the compilation could be deserialized without throwing an exception, and false otherwise. - /// If onDeserializationException is specified, invokes the given action on any exception thrown during deserialization. + /// Returns true if the compilation could be deserialized without throwing an exception, and false otherwise. + /// If onDeserializationException is specified, invokes the given action on any exception thrown during deserialization. /// Throws an ArgumentNullException if the given stream is null, but ignores exceptions thrown during deserialization. /// public static bool LoadSyntaxTree(Stream stream, out QsCompilation compilation, Action onDeserializationException = null) { - if (stream == null) throw new ArgumentNullException(nameof(stream)); + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } using var reader = new BsonDataReader(stream); (compilation, reader.ReadRootValueAsArray) = (null, false); - try - { + try + { compilation = Json.Serializer.Deserialize(reader); return compilation != null && !compilation.Namespaces.IsDefault && !compilation.EntryPoints.IsDefault; } catch (Exception ex) { onDeserializationException?.Invoke(ex); - return false; + return false; } } /// - /// Creates a dictionary of all manifest resources in the given reader. - /// Returns null if the given reader is null. + /// Creates a dictionary of all manifest resources in the given reader. + /// Returns null if the given reader is null. /// private static ImmutableDictionary Resources(this MetadataReader reader) => reader?.ManifestResources .Select(reader.GetManifestResource) .ToImmutableDictionary( resource => reader.GetString(resource.Name), - resource => resource - ); + resource => resource); /// /// Given a reader for the byte stream of a dotnet dll, loads any Q# compilation included as a resource. - /// Returns true as well as the loaded compilation if the given dll includes a suitable resource, and returns false otherwise. - /// If onDeserializationException is specified, invokes the given action on any exception thrown during deserialization. + /// Returns true as well as the loaded compilation if the given dll includes a suitable resource, and returns false otherwise. + /// If onDeserializationException is specified, invokes the given action on any exception thrown during deserialization. /// Throws an ArgumentNullException if any of the given readers is null. /// May throw an exception if the given binary file has been compiled with a different compiler version. /// private static bool FromResource(PEReader assemblyFile, out QsCompilation compilation, Action onDeserializationException = null) { - if (assemblyFile == null) throw new ArgumentNullException(nameof(assemblyFile)); + if (assemblyFile == null) + { + throw new ArgumentNullException(nameof(assemblyFile)); + } var metadataReader = assemblyFile.GetMetadataReader(); compilation = null; - // The offset of resources is relative to the resources directory. - // It is possible that there is no offset given because a valid dll allows for extenal resources. - // In all Q# dlls there will be a resource with the specific name chosen by the compiler. + // The offset of resources is relative to the resources directory. + // It is possible that there is no offset given because a valid dll allows for extenal resources. + // In all Q# dlls there will be a resource with the specific name chosen by the compiler. var resourceDir = assemblyFile.PEHeaders.CorHeader.ResourcesDirectory; if (!assemblyFile.PEHeaders.TryGetDirectoryOffset(resourceDir, out var directoryOffset) || !metadataReader.Resources().TryGetValue(DotnetCoreDll.ResourceName, out var resource) || !resource.Implementation.IsNil) - { return false; } + { + return false; + } // This is going to be very slow, as it loads the entire assembly into a managed array, byte by byte. - // Due to the finite size of the managed array, that imposes a memory limitation of around 4GB. + // Due to the finite size of the managed array, that imposes a memory limitation of around 4GB. // The other alternative would be to have an unsafe block, or to contribute a fix to PEMemoryBlock to expose a ReadOnlySpan. var image = assemblyFile.GetEntireImage(); // uses int to denote the length and access parameters var absResourceOffset = (int)resource.Offset + directoryOffset; // the first four bytes of the resource denote how long the resource is, and are followed by the actual resource data - var resourceLength = BitConverter.ToInt32(image.GetContent(absResourceOffset, sizeof(Int32)).ToArray(), 0); - var resourceData = image.GetContent(absResourceOffset + sizeof(Int32), resourceLength).ToArray(); + var resourceLength = BitConverter.ToInt32(image.GetContent(absResourceOffset, sizeof(int)).ToArray(), 0); + var resourceData = image.GetContent(absResourceOffset + sizeof(int), resourceLength).ToArray(); return LoadSyntaxTree(new MemoryStream(resourceData), out compilation, onDeserializationException); } - // tools for loading headers based on attributes in compiled C# code (early setup for shipping Q# libraries) /// - /// There are two possible handle kinds in use for the constructor of a custom attribute, - /// one pointing to the MethodDef table and one to the MemberRef table, see p.216 in the ECMA standard linked above and + /// There are two possible handle kinds in use for the constructor of a custom attribute, + /// one pointing to the MethodDef table and one to the MemberRef table, see p.216 in the ECMA standard linked above and /// https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeSystem/CustomAttribute.cs#L42 - /// This routine extracts the namespace and type name of the given attribute and returns the corresponding string handles. - /// Returns null if the constructor handle is not a MethodDefinition or a MemberDefinition. + /// This routine extracts the namespace and type name of the given attribute and returns the corresponding string handles. + /// Returns null if the constructor handle is not a MethodDefinition or a MemberDefinition. /// private static (StringHandle, StringHandle)? GetAttributeType(MetadataReader metadataReader, CustomAttribute attribute) { @@ -178,11 +193,14 @@ private static (StringHandle, StringHandle)? GetAttributeType(MetadataReader met var type = metadataReader.GetTypeReference((TypeReferenceHandle)ctor.Parent); return (type.Namespace, type.Name); } - else return null; + else + { + return null; + } } - // TODO: this needs to be made more robust. - // We currently rely on the fact that all attributes defined by the Q# compiler + // TODO: this needs to be made more robust. + // We currently rely on the fact that all attributes defined by the Q# compiler // have a single constructor taking a single string argument. private static (string, string)? GetAttribute(MetadataReader metadataReader, CustomAttribute attribute) { @@ -194,27 +212,33 @@ private static (string, string)? GetAttribute(MetadataReader metadataReader, Cus if (attrNS.StartsWith("Microsoft.Quantum", StringComparison.InvariantCulture)) { var attrReader = metadataReader.GetBlobReader(attribute.Value); - var _ = attrReader.ReadUInt16(); // All custom attributes start with 0x0001, so read that now and discard it. + _ = attrReader.ReadUInt16(); // All custom attributes start with 0x0001, so read that now and discard it. try { var serialization = attrReader.ReadSerializedString(); // FIXME: this needs to be made more robust return (metadataReader.GetString(name), serialization); } - catch { return null; } + catch + { + return null; + } } return null; } /// /// Given a reader for the byte stream of a dotnet dll, read its custom attributes and - /// returns a tuple containing the name of the attribute and the constructor argument - /// for all attributes defined in a Microsoft.Quantum* namespace. - /// Throws an ArgumentNullException if the given stream is null. + /// returns a tuple containing the name of the attribute and the constructor argument + /// for all attributes defined in a Microsoft.Quantum* namespace. + /// Throws an ArgumentNullException if the given stream is null. /// Throws the corresponding exceptions if the information cannot be extracted. /// private static IEnumerable<(string, string)> LoadHeaderAttributes(PEReader assemblyFile) { - if (assemblyFile == null) throw new ArgumentNullException(nameof(assemblyFile)); + if (assemblyFile == null) + { + throw new ArgumentNullException(nameof(assemblyFile)); + } var metadataReader = assemblyFile.GetMetadataReader(); return metadataReader.GetAssemblyDefinition().GetCustomAttributes() .Select(metadataReader.GetCustomAttribute) diff --git a/src/QsCompiler/CompilationManager/CompilationManager.csproj b/src/QsCompiler/CompilationManager/CompilationManager.csproj index e90c727c47..97429f6e70 100644 --- a/src/QsCompiler/CompilationManager/CompilationManager.csproj +++ b/src/QsCompiler/CompilationManager/CompilationManager.csproj @@ -11,6 +11,7 @@ + @@ -25,4 +26,8 @@ + + + + diff --git a/src/QsCompiler/CompilationManager/CompilationUnit.cs b/src/QsCompiler/CompilationManager/CompilationUnit.cs index ada37b377f..b72dd17c84 100644 --- a/src/QsCompiler/CompilationManager/CompilationUnit.cs +++ b/src/QsCompiler/CompilationManager/CompilationUnit.cs @@ -18,7 +18,6 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using static Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { /// @@ -32,42 +31,50 @@ public class Headers public readonly ImmutableArray<(SpecializationDeclarationHeader, SpecializationImplementation)> Specializations; public readonly ImmutableArray Types; - internal Headers(string source, - IEnumerable callables = null, - IEnumerable<(SpecializationDeclarationHeader, SpecializationImplementation)> specs = null, + internal Headers( + string source, + IEnumerable callables = null, + IEnumerable<(SpecializationDeclarationHeader, SpecializationImplementation)> specs = null, IEnumerable types = null) { NonNullable SourceOr(NonNullable origSource) => NonNullable.New(source ?? origSource.Value); this.Types = types?.Select(t => t.FromSource(SourceOr(t.SourceFile))).ToImmutableArray() ?? ImmutableArray.Empty; this.Callables = callables?.Select(c => c.FromSource(SourceOr(c.SourceFile))).ToImmutableArray() ?? ImmutableArray.Empty; - this.Specializations = specs?.Select(s => (s.Item1.FromSource(SourceOr(s.Item1.SourceFile)), s.Item2 ?? SpecializationImplementation.External)).ToImmutableArray() + this.Specializations = specs?.Select(s => (s.Item1.FromSource(SourceOr(s.Item1.SourceFile)), s.Item2 ?? SpecializationImplementation.External)).ToImmutableArray() ?? ImmutableArray<(SpecializationDeclarationHeader, SpecializationImplementation)>.Empty; } /// - /// Initializes a set of reference headers based on the given syntax tree loaded from the specified source. - /// The source is expected to be the path to the dll from which the syntax has been loaded. - /// Returns an empty set of headers if the given syntax tree is null. + /// Initializes a set of reference headers based on the given syntax tree loaded from the specified source. + /// The source is expected to be the path to the dll from which the syntax has been loaded. + /// Returns an empty set of headers if the given syntax tree is null. /// - public Headers(NonNullable source, IEnumerable syntaxTree) : this ( - source.Value, - syntaxTree?.Callables().Where(c => c.SourceFile.Value.EndsWith(".qs")).Select(CallableDeclarationHeader.New), - syntaxTree?.Specializations().Where(c => c.SourceFile.Value.EndsWith(".qs")).Select(s => (SpecializationDeclarationHeader.New(s), s.Implementation)), - syntaxTree?.Types().Where(c => c.SourceFile.Value.EndsWith(".qs")).Select(TypeDeclarationHeader.New)) - { } - - internal Headers(NonNullable source, IEnumerable<(string, string)> attributes) : this( - source.Value, - References.CallableHeaders(attributes), - References.SpecializationHeaders(attributes).Select(h => (h, (SpecializationImplementation)null)), - References.TypeHeaders(attributes)) - { } + public Headers(NonNullable source, IEnumerable syntaxTree) + : this( + source.Value, + syntaxTree?.Callables().Where(c => c.SourceFile.Value.EndsWith(".qs")).Select(CallableDeclarationHeader.New), + syntaxTree?.Specializations().Where(c => c.SourceFile.Value.EndsWith(".qs")).Select(s => (SpecializationDeclarationHeader.New(s), s.Implementation)), + syntaxTree?.Types().Where(c => c.SourceFile.Value.EndsWith(".qs")).Select(TypeDeclarationHeader.New)) + { + } + + internal Headers(NonNullable source, IEnumerable<(string, string)> attributes) + : this( + source.Value, + CallableHeaders(attributes), + SpecializationHeaders(attributes).Select(h => (h, (SpecializationImplementation)null)), + TypeHeaders(attributes)) + { + } } private static Func<(string, string), string> IsDeclaration(string declarationType) => (attribute) => { var (typeName, serialization) = attribute; - if (!typeName.Equals(declarationType, StringComparison.InvariantCultureIgnoreCase)) return null; + if (!typeName.Equals(declarationType, StringComparison.InvariantCultureIgnoreCase)) + { + return null; + } return serialization; }; @@ -87,7 +94,7 @@ private static IEnumerable TypeHeaders(IEnumerable<(strin /// Renames all declarations in the headers for which an alternative name is specified /// that may be used when loading a type or callable for testing purposes. /// Leaves declarations for which no such name is defined unchanged. - /// Does not check whether there are any conflicts when using alternative names. + /// Does not check whether there are any conflicts when using alternative names. /// private static Headers LoadTestNames(string source, Headers headers) { @@ -102,30 +109,27 @@ static QsDeclarationAttribute Renamed(QsQualifiedName originalName, Tuple.New(GeneratedAttributes.Namespace), - NonNullable.New(GeneratedAttributes.LoadedViaTestNameInsteadOf), - QsNullable>.Null - ); + NonNullable.New(GeneratedAttributes.LoadedViaTestNameInsteadOf), + QsNullable>.Null); var attArg = SyntaxGenerator.StringLiteral( NonNullable.New(originalName.ToString()), - ImmutableArray.Empty - ); + ImmutableArray.Empty); return new QsDeclarationAttribute( QsNullable.NewValue(attName), attArg, declLocation, - QsComments.Empty - ); + QsComments.Empty); } var rename = new RenameReferences(renaming); var types = headers.Types - .Select(type => + .Select(type => renaming.TryGetValue(type.QualifiedName, out var newName) && !type.QualifiedName.Equals(newName) && type.Location.IsValue // TODO: we should instead fully support auto-generated attributes ? type.AddAttribute(Renamed(type.QualifiedName, type.Location.Item.Offset)) : type) .Select(rename.OnTypeDeclarationHeader); var callables = headers.Callables - .Select(callable => + .Select(callable => renaming.TryGetValue(callable.QualifiedName, out var newName) && !callable.QualifiedName.Equals(newName) && callable.Location.IsValue // TODO: we should instead fully support auto-generated attributes ? callable.AddAttribute(Renamed(callable.QualifiedName, callable.Location.Item.Offset)) : callable) @@ -137,58 +141,64 @@ static QsDeclarationAttribute Renamed(QsQualifiedName originalName, Tuple - /// Checks whether the given sequence of elements contains multiple items with the same qualified name. - /// Returns a sequence of two strings, with the first one containing the name of the duplication, - /// and the second one listing all sources in which it occurs. - /// Returns null if the given sequence of elements is null. + /// Checks whether the given sequence of elements contains multiple items with the same qualified name. + /// Returns a sequence of two strings, with the first one containing the name of the duplication, + /// and the second one listing all sources in which it occurs. + /// Returns null if the given sequence of elements is null. /// - private static IEnumerable<(string, string)> GenerateDiagnosticsForConflicts(IEnumerable<(QsQualifiedName name, NonNullable source, AccessModifier access)> elements) => - elements?.Where(e => Namespace.IsDeclarationAccessible(false, e.access)) - .GroupBy(e => e.name) + private static IEnumerable<(string, string)> GenerateDiagnosticsForConflicts(IEnumerable<(QsQualifiedName Name, NonNullable Source, AccessModifier Access)> elements) => + elements?.Where(e => Namespace.IsDeclarationAccessible(false, e.Access)) + .GroupBy(e => e.Name) .Where(g => g.Count() != 1) - .Select(g => (g.Key, String.Join(", ", g.Select(e => e.source.Value)))) + .Select(g => (g.Key, string.Join(", ", g.Select(e => e.Source.Value)))) .Select(c => ($"{c.Key.Namespace.Value}.{c.Key.Name.Value}", c.Item2)); /// /// Dictionary that maps the id of a referenced assembly (given by its location on disk) to the headers defined in that assembly. /// - public readonly ImmutableDictionary, Headers> Declarations; + public readonly ImmutableDictionary, Headers> Declarations; - public static References Empty = + public static References Empty = new References(ImmutableDictionary, Headers>.Empty); /// - /// Combines the current references with the given references, and verifies that there are no conflicts. - /// Calls the given Action onError with suitable diagnostics if two or more references conflict, - /// i.e. if two or more references contain a declaration with the same fully qualified name. - /// Throws an ArgumentNullException if the given dictionary of references is null. - /// Throws an ArgumentException if the given set shares references with the current one. + /// Combines the current references with the given references, and verifies that there are no conflicts. + /// Calls the given Action onError with suitable diagnostics if two or more references conflict, + /// i.e. if two or more references contain a declaration with the same fully qualified name. + /// Throws an ArgumentNullException if the given dictionary of references is null. + /// Throws an ArgumentException if the given set shares references with the current one. /// internal References CombineWith(References other, Action onError = null) { - if (other == null) throw new ArgumentNullException(nameof(other)); - if (this.Declarations.Keys.Intersect(other.Declarations.Keys).Any()) throw new ArgumentException("common references exist"); - return new References (this.Declarations.AddRange(other.Declarations), onError: onError); + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + if (this.Declarations.Keys.Intersect(other.Declarations.Keys).Any()) + { + throw new ArgumentException("common references exist"); + } + return new References(this.Declarations.AddRange(other.Declarations), onError: onError); } /// - /// Returns a new collection with the given reference and all its entries removed. - /// Verifies that there are no conflicts for the new set of references. - /// Calls the given Action onError with suitable diagnostics if two or more references conflict, - /// i.e. if two or more references contain a declaration with the same fully qualified name. - /// Throws an ArgumentNullException if the given diagnostics are null. + /// Returns a new collection with the given reference and all its entries removed. + /// Verifies that there are no conflicts for the new set of references. + /// Calls the given Action onError with suitable diagnostics if two or more references conflict, + /// i.e. if two or more references contain a declaration with the same fully qualified name. + /// Throws an ArgumentNullException if the given diagnostics are null. /// internal References Remove(NonNullable source, Action onError = null) => new References(this.Declarations.Remove(source), onError: onError); /// /// Given a dictionary that maps the ids of dll files to the corresponding headers, - /// initializes a new set of references based on the given headers and verifies that there are no conflicts. - /// Calls the given Action onError with suitable diagnostics if two or more references conflict, - /// i.e. if two or more references contain a declaration with the same fully qualified name. - /// If loadTestNames is set to true, then public types and callables declared in referenced assemblies - /// are exposed via their test name defined by the corresponding attribute. - /// Throws an ArgumentNullException if the given dictionary of references is null. + /// initializes a new set of references based on the given headers and verifies that there are no conflicts. + /// Calls the given Action onError with suitable diagnostics if two or more references conflict, + /// i.e. if two or more references contain a declaration with the same fully qualified name. + /// If loadTestNames is set to true, then public types and callables declared in referenced assemblies + /// are exposed via their test name defined by the corresponding attribute. + /// Throws an ArgumentNullException if the given dictionary of references is null. /// public References(ImmutableDictionary, Headers> refs, bool loadTestNames = false, Action onError = null) { @@ -197,12 +207,14 @@ public References(ImmutableDictionary, Headers> refs, bool l { this.Declarations = this.Declarations .ToImmutableDictionary( - reference => reference.Key, - reference => LoadTestNames(reference.Key.Value, reference.Value) - ); + reference => reference.Key, + reference => LoadTestNames(reference.Key.Value, reference.Value)); + } + + if (onError == null) + { + return; } - - if (onError == null) return; var conflicting = new List<(string, string)>(); var callables = this.Declarations.Values.SelectMany(r => r.Callables).Select(c => (c.QualifiedName, c.SourceFile, c.Modifiers.Access)); var types = this.Declarations.Values.SelectMany(r => r.Types).Select(t => (t.QualifiedName, t.SourceFile, t.Modifiers.Access)); @@ -211,30 +223,35 @@ public References(ImmutableDictionary, Headers> refs, bool l foreach (var (name, conflicts) in conflicting.Distinct()) { - onError?.Invoke(ErrorCode.ConflictInReferences, new[] { name, conflicts}); + onError?.Invoke(ErrorCode.ConflictInReferences, new[] { name, conflicts }); } } /// /// Combines the syntax trees loaded from different source assemblies and combines them into a single syntax tree. - /// The first item in the given arguments is expected to contain the id of the source from which the syntax tree was loaded, - /// and the second item is expected to contain the loaded syntax tree. - /// The source file of a declaration in the combined tree will be set to the specified source from which it was loaded, - /// and internal declaration as well as their usages will be renamed to avoid conflicts. + /// The first item in the given arguments is expected to contain the id of the source from which the syntax tree was loaded, + /// and the second item is expected to contain the loaded syntax tree. + /// The source file of a declaration in the combined tree will be set to the specified source from which it was loaded, + /// and internal declaration as well as their usages will be renamed to avoid conflicts. /// - /// Returns true and the combined syntax tree as out parameter + /// Returns true and the combined syntax tree as out parameter /// if the given syntax trees do not contain any conflicting declarations and were successfully combined. /// Returns false and an empty array of namespaces as out parameter otherwise. /// The number of additional assemblies included in the compilation besides the loaded assemblies. /// Invoked on the error messages generated when the given syntax trees contain conflicting declarations. - /// A parameter array of tuples containing the syntax trees to combine + /// A parameter array of tuples containing the syntax trees to combine /// as well as the sources from which they were loaded. - public static bool CombineSyntaxTrees(out ImmutableArray combined, - int additionalAssemblies = 0, Action onError = null, + public static bool CombineSyntaxTrees( + out ImmutableArray combined, + int additionalAssemblies = 0, + Action onError = null, params (NonNullable, ImmutableArray)[] loaded) { combined = ImmutableArray.Empty; - if (loaded == null) return false; + if (loaded == null) + { + return false; + } var (callables, types) = CompilationUnit.RenameInternalDeclarations( loaded.SelectMany(loaded => loaded.Item2.Callables().Select(c => @@ -254,36 +271,42 @@ public static bool CombineSyntaxTrees(out ImmutableArray combined, onError?.Invoke(ErrorCode.ConflictInReferences, new[] { name, conflicts }); } - if (conflicting.Any()) return false; + if (conflicting.Any()) + { + return false; + } combined = CompilationUnit.NewSyntaxTree(callables, types); return true; } } - /// - /// Class representing a compilation; - /// apart from storing and providing the means to update the compilation itself, + /// Class representing a compilation; + /// apart from storing and providing the means to update the compilation itself, /// it stores referenced content and provides the infrastructure to track global symbols. - /// IMPORTANT: The responsiblity to update the compilation to match changes to the GlobalSymbols lays within the the managing entity. + /// IMPORTANT: The responsiblity to update the compilation to match changes to the GlobalSymbols lays within the the managing entity. /// public class CompilationUnit : IReaderWriterLock, IDisposable { internal References Externals { get; private set; } + internal NamespaceManager GlobalSymbols { get; private set; } - private readonly Dictionary CompiledCallables; - private readonly Dictionary CompiledTypes; + private readonly Dictionary compiledCallables; + private readonly Dictionary compiledTypes; - private readonly ReaderWriterLockSlim SyncRoot; - private readonly HashSet DependentLocks; + private readonly ReaderWriterLockSlim syncRoot; + private readonly HashSet dependentLocks; internal readonly RuntimeCapabilities RuntimeCapabilities; internal readonly bool IsExecutable; - internal readonly NonNullable ExecutionTarget; + internal readonly NonNullable ProcessorArchitecture; + /// public void Dispose() - { this.SyncRoot.Dispose(); } + { + this.syncRoot.Dispose(); + } /// /// Returns a new CompilationUnit to store and update a compilation referencing the given content (if any), @@ -293,29 +316,31 @@ public void Dispose() internal CompilationUnit( RuntimeCapabilities capabilities, bool isExecutable, - NonNullable executionTarget, + NonNullable processorArchitecture, References externals = null, IEnumerable dependentLocks = null) { - this.SyncRoot = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - this.DependentLocks = dependentLocks == null + this.syncRoot = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + this.dependentLocks = dependentLocks == null ? new HashSet() : new HashSet(dependentLocks); - if (dependentLocks?.Contains(null) ?? false) throw new ArgumentNullException(nameof(dependentLocks), "one or more of the given locks is null"); + if (dependentLocks?.Contains(null) ?? false) + { + throw new ArgumentNullException(nameof(dependentLocks), "one or more of the given locks is null"); + } this.RuntimeCapabilities = capabilities; this.IsExecutable = isExecutable; - this.ExecutionTarget = executionTarget; + this.ProcessorArchitecture = processorArchitecture; - this.CompiledCallables = new Dictionary(); - this.CompiledTypes = new Dictionary(); + this.compiledCallables = new Dictionary(); + this.compiledTypes = new Dictionary(); this.UpdateReferences(externals ?? References.Empty); } - /// - /// Replaces the GlobalSymbols to match the newly specified references. - /// Throws an ArgumentNullException if the given references are null. + /// Replaces the GlobalSymbols to match the newly specified references. + /// Throws an ArgumentNullException if the given references are null. /// internal void UpdateReferences(References externals) { @@ -323,32 +348,45 @@ internal void UpdateReferences(References externals) try { this.Externals = externals ?? throw new ArgumentNullException(nameof(externals)); - this.GlobalSymbols = new NamespaceManager(this, - this.Externals.Declarations.Values.SelectMany(h => h.Callables), - this.Externals.Declarations.Values.SelectMany(h => h.Specializations.Select(t => new Tuple(t.Item1, t.Item2))), + this.GlobalSymbols = new NamespaceManager( + this, + this.Externals.Declarations.Values.SelectMany(h => h.Callables), + this.Externals.Declarations.Values.SelectMany(h => h.Specializations.Select(t => new Tuple(t.Item1, t.Item2))), this.Externals.Declarations.Values.SelectMany(h => h.Types), - this.RuntimeCapabilities, this.IsExecutable); + this.RuntimeCapabilities, + this.IsExecutable); + } + finally + { + this.ExitWriteLock(); } - finally { this.ExitWriteLock(); } } /// - /// Registers the given lock as a dependent lock - - /// i.e. whenever both this compilation unit and a dependent lock are required, + /// Registers the given lock as a dependent lock - + /// i.e. whenever both this compilation unit and a dependent lock are required, /// ensures that the compilation unit has to be the outer lock. /// Throws an ArgumentNullException if the given lock is null. /// internal void RegisterDependentLock(ReaderWriterLockSlim depLock) { #if DEBUG - if (depLock == null) throw new ArgumentNullException(nameof(depLock)); - this.SyncRoot.EnterWriteLock(); + if (depLock == null) + { + throw new ArgumentNullException(nameof(depLock)); + } + this.syncRoot.EnterWriteLock(); try { - lock (this.DependentLocks) - { this.DependentLocks.Add(depLock); } + lock (this.dependentLocks) + { + this.dependentLocks.Add(depLock); + } + } + finally + { + this.syncRoot.ExitWriteLock(); } - finally { this.SyncRoot.ExitWriteLock(); } #endif } @@ -360,71 +398,88 @@ internal void RegisterDependentLock(ReaderWriterLockSlim depLock) internal void UnregisterDependentLock(ReaderWriterLockSlim depLock) { #if DEBUG - if (depLock == null) throw new ArgumentNullException(nameof(depLock)); - this.SyncRoot.EnterWriteLock(); + if (depLock == null) + { + throw new ArgumentNullException(nameof(depLock)); + } + this.syncRoot.EnterWriteLock(); try { - lock (this.DependentLocks) - { this.DependentLocks.Remove(depLock); } + lock (this.dependentLocks) + { + this.dependentLocks.Remove(depLock); + } + } + finally + { + this.syncRoot.ExitWriteLock(); } - finally { this.SyncRoot.ExitWriteLock(); } #endif } - // routines replacing the direct access to the sync root /// - /// Enters a read-lock, provided none of the dependent locks is set, or a compilation lock is aready held. + /// Enters a read-lock, provided none of the dependent locks is set, or a compilation lock is aready held. /// Throws an InvalidOperationException if any of the dependent locks is set, but the SyncRoot is not at least read-lock-held. /// public void EnterReadLock() { #if DEBUG - lock (this.DependentLocks) + lock (this.dependentLocks) { - if (this.DependentLocks.Any(l => l.IsAtLeastReadLockHeld()) && !this.SyncRoot.IsAtLeastReadLockHeld()) - { throw new InvalidOperationException("cannot enter read lock when a dependent lock is active"); } + if (this.dependentLocks.Any(l => l.IsAtLeastReadLockHeld()) && !this.syncRoot.IsAtLeastReadLockHeld()) + { + throw new InvalidOperationException("cannot enter read lock when a dependent lock is active"); + } } #endif - this.SyncRoot.EnterReadLock(); + this.syncRoot.EnterReadLock(); } - public void ExitReadLock() => this.SyncRoot.ExitReadLock(); + + /// + public void ExitReadLock() => this.syncRoot.ExitReadLock(); /// - /// Enters an upgradeable read-lock, provided none of the dependent locks is set, or a suitable compilation lock is aready held. + /// Enters an upgradeable read-lock, provided none of the dependent locks is set, or a suitable compilation lock is aready held. /// Throws an InvalidOperationException if any of the dependent locks is set, but the SyncRoot is not at least read-lock-held. /// public void EnterUpgradeableReadLock() { #if DEBUG - lock (this.DependentLocks) + lock (this.dependentLocks) { - if (this.DependentLocks.Any(l => l.IsAtLeastReadLockHeld()) && !this.SyncRoot.IsAtLeastReadLockHeld()) - { throw new InvalidOperationException("cannot enter upgradeable read lock when a dependent lock is active"); } + if (this.dependentLocks.Any(l => l.IsAtLeastReadLockHeld()) && !this.syncRoot.IsAtLeastReadLockHeld()) + { + throw new InvalidOperationException("cannot enter upgradeable read lock when a dependent lock is active"); + } } #endif - this.SyncRoot.EnterUpgradeableReadLock(); + this.syncRoot.EnterUpgradeableReadLock(); } - public void ExitUpgradeableReadLock() => this.SyncRoot.ExitUpgradeableReadLock(); + + public void ExitUpgradeableReadLock() => this.syncRoot.ExitUpgradeableReadLock(); /// - /// Enters a write-lock, provided none of the dependent locks is set, or a suitable compilation lock is aready held. + /// Enters a write-lock, provided none of the dependent locks is set, or a suitable compilation lock is aready held. /// Throws an InvalidOperationException if any of the dependent locks is set, but the SyncRoot is not at least read-lock-held. /// public void EnterWriteLock() { #if DEBUG - lock (this.DependentLocks) + lock (this.dependentLocks) { - if (this.DependentLocks.Any(l => l.IsAtLeastReadLockHeld()) && !this.SyncRoot.IsAtLeastReadLockHeld()) - { throw new InvalidOperationException("cannot enter write lock when a dependent lock is active"); } + if (this.dependentLocks.Any(l => l.IsAtLeastReadLockHeld()) && !this.syncRoot.IsAtLeastReadLockHeld()) + { + throw new InvalidOperationException("cannot enter write lock when a dependent lock is active"); + } } #endif - this.SyncRoot.EnterWriteLock(); + this.syncRoot.EnterWriteLock(); } - public void ExitWriteLock() => this.SyncRoot.ExitWriteLock(); + /// + public void ExitWriteLock() => this.syncRoot.ExitWriteLock(); // methods related to accessing and managing information about the compilation @@ -434,9 +489,15 @@ public void EnterWriteLock() /// internal IReadOnlyDictionary GetCallables() { - this.SyncRoot.EnterReadLock(); - try { return new ReadOnlyDictionary(this.CompiledCallables); } - finally { this.SyncRoot.ExitReadLock(); } + this.syncRoot.EnterReadLock(); + try + { + return new ReadOnlyDictionary(this.compiledCallables); + } + finally + { + this.syncRoot.ExitReadLock(); + } } /// @@ -445,47 +506,60 @@ internal IReadOnlyDictionary GetCallables() /// internal IReadOnlyDictionary GetTypes() { - this.SyncRoot.EnterReadLock(); - try { return new ReadOnlyDictionary(this.CompiledTypes); } - finally { this.SyncRoot.ExitReadLock(); } + this.syncRoot.EnterReadLock(); + try + { + return new ReadOnlyDictionary(this.compiledTypes); + } + finally + { + this.syncRoot.ExitReadLock(); + } } /// /// If the given updates are not null, replaces the contained types in the compilation, or adds them if they do not yet exist. - /// Proceeds to remove any types that are not currently listed in GlobalSymbols from the compilation, and updates all position information. + /// Proceeds to remove any types that are not currently listed in GlobalSymbols from the compilation, and updates all position information. /// Throws an ArgumentNullException if any of the given types to update is null. /// internal void UpdateTypes(IEnumerable updates) { - this.SyncRoot.EnterWriteLock(); + this.syncRoot.EnterWriteLock(); try { if (updates != null) { foreach (var t in updates) - { this.CompiledTypes[t.FullName] = t ?? throw new ArgumentNullException(nameof(updates), "the given compiled type is null"); } + { + this.compiledTypes[t.FullName] = t ?? throw new ArgumentNullException(nameof(updates), "the given compiled type is null"); + } } // remove all types that are no listed in GlobalSymbols var currentlyDefined = this.GlobalSymbols.DefinedTypes().ToImmutableDictionary(decl => decl.QualifiedName); - var keys = this.CompiledTypes.Keys.ToImmutableArray(); + var keys = this.compiledTypes.Keys.ToImmutableArray(); foreach (var typeName in keys) { if (!currentlyDefined.ContainsKey(typeName)) - { this.CompiledTypes.Remove(typeName); } + { + this.compiledTypes.Remove(typeName); + } } // update the position information for the remaining types - // NOTE: type constructors are *callables* and hence there may be a temporary discrepancy + // NOTE: type constructors are *callables* and hence there may be a temporary discrepancy // between the declared types and the corresponding constructors that needs to be resolved before building foreach (var declaration in currentlyDefined) { var (fullName, header) = (declaration.Key, declaration.Value); - var compilationExists = this.CompiledTypes.TryGetValue(fullName, out QsCustomType compiled); - if (!compilationExists) continue; // may happen if a file has been modified during global type checking + var compilationExists = this.compiledTypes.TryGetValue(fullName, out QsCustomType compiled); + if (!compilationExists) + { + continue; // may happen if a file has been modified during global type checking + } var type = new QsCustomType( compiled.FullName, @@ -496,59 +570,72 @@ internal void UpdateTypes(IEnumerable updates) compiled.Type, compiled.TypeItems, compiled.Documentation, - compiled.Comments - ); - this.CompiledTypes[fullName] = type; + compiled.Comments); + this.compiledTypes[fullName] = type; } } - finally { this.SyncRoot.ExitWriteLock(); } - + finally + { + this.syncRoot.ExitWriteLock(); + } } /// /// If the given updates are not null, replaces the contained callables in the compilation, or adds them if they do not yet exist. - /// Proceeds to remove any callables and specializations that are not currently listed in GlobalSymbols from the compilation, - /// and updates the position information for all callables and specializations. + /// Proceeds to remove any callables and specializations that are not currently listed in GlobalSymbols from the compilation, + /// and updates the position information for all callables and specializations. /// Throws an ArgumentNullException if any of the given callables to update is null. /// internal void UpdateCallables(IEnumerable updates) { - this.SyncRoot.EnterWriteLock(); + this.syncRoot.EnterWriteLock(); try { foreach (var c in updates ?? Array.Empty()) { if (c?.Specializations == null || c.Specializations.Contains(null)) - { throw new ArgumentNullException(nameof(updates), "the given compiled callable or specialization is null"); } - this.CompiledCallables[c.FullName] = c; + { + throw new ArgumentNullException(nameof(updates), "the given compiled callable or specialization is null"); + } + this.compiledCallables[c.FullName] = c; } // remove all types and callables that are not listed in GlobalSymbols var currentlyDefined = this.GlobalSymbols.DefinedCallables().ToImmutableDictionary(decl => decl.QualifiedName); - var keys = this.CompiledCallables.Keys.ToImmutableArray(); + var keys = this.compiledCallables.Keys.ToImmutableArray(); foreach (var callableName in keys) { if (!currentlyDefined.ContainsKey(callableName)) - { this.CompiledCallables.Remove(callableName); } // todo: needs adaption if we want to support external specializations + { + this.compiledCallables.Remove(callableName); + } // todo: needs adaption if we want to support external specializations } // update the position information for the remaining types and callables, keeping only the specializations that are listed in GlobalSymbols // NOTE: It indeed needs to be possible that a compilation is still missing for an entry that is listed in GlobalSymbols. - // Rather than a discrepancy, this is the case when a file has been modified during global type checking - - // in that case the global type checking does not yield a valid compilation for everything, - // but still needs to be able to update the compilations that are valid. The same goes for specializations. + // Rather than a discrepancy, this is the case when a file has been modified during global type checking - + // in that case the global type checking does not yield a valid compilation for everything, + // but still needs to be able to update the compilations that are valid. The same goes for specializations. foreach (var declaration in currentlyDefined) { var (fullName, header) = (declaration.Key, declaration.Value); - if (header.Kind.IsTypeConstructor) + if (header.Kind.IsTypeConstructor) { - var defaultSpec = new QsSpecialization(QsSpecializationKind.QsBody, header.QualifiedName, header.Attributes, - header.SourceFile, header.Location, QsNullable>.Null, header.Signature, SpecializationImplementation.Intrinsic, - ImmutableArray.Empty, QsComments.Empty); - this.CompiledCallables[fullName] = new QsCallable( + var defaultSpec = new QsSpecialization( + QsSpecializationKind.QsBody, + header.QualifiedName, + header.Attributes, + header.SourceFile, + header.Location, + QsNullable>.Null, + header.Signature, + SpecializationImplementation.Intrinsic, + ImmutableArray.Empty, + QsComments.Empty); + this.compiledCallables[fullName] = new QsCallable( header.Kind, header.QualifiedName, header.Attributes, @@ -559,29 +646,41 @@ internal void UpdateCallables(IEnumerable updates) header.ArgumentTuple, ImmutableArray.Create(defaultSpec), header.Documentation, - QsComments.Empty - ); + QsComments.Empty); continue; } - var compilationExists = this.CompiledCallables.TryGetValue(fullName, out QsCallable compiled); - if (!compilationExists) continue; // may happen if a file has been modified during global type checking + var compilationExists = this.compiledCallables.TryGetValue(fullName, out QsCallable compiled); + if (!compilationExists) + { + continue; // may happen if a file has been modified during global type checking + } // TODO: this needs adaption if we want to support type specializations var specializations = this.GlobalSymbols.DefinedSpecializations(header.QualifiedName).Select(defined => { var specHeader = defined.Item2; - var compiledSpecs = compiled.Specializations.Where(spec => + var compiledSpecs = compiled.Specializations.Where(spec => spec.Kind == specHeader.Kind && - spec.TypeArguments.Equals(specHeader.TypeArguments) - ); + spec.TypeArguments.Equals(specHeader.TypeArguments)); QsCompilerError.Verify(compiledSpecs.Count() <= 1, "more than one specialization of the same kind exists"); - if (!compiledSpecs.Any()) return null; // may happen if a file has been modified during global type checking + if (!compiledSpecs.Any()) + { + return null; // may happen if a file has been modified during global type checking + } var compiledSpec = compiledSpecs.Single(); - return new QsSpecialization(compiledSpec.Kind, compiledSpec.Parent, compiledSpec.Attributes, - compiledSpec.SourceFile, specHeader.Location, compiledSpec.TypeArguments, compiledSpec.Signature, compiledSpec.Implementation, - compiledSpec.Documentation, compiledSpec.Comments); + return new QsSpecialization( + compiledSpec.Kind, + compiledSpec.Parent, + compiledSpec.Attributes, + compiledSpec.SourceFile, + specHeader.Location, + compiledSpec.TypeArguments, + compiledSpec.Signature, + compiledSpec.Implementation, + compiledSpec.Documentation, + compiledSpec.Comments); }) .Where(spec => spec != null).ToImmutableArray(); @@ -591,34 +690,40 @@ internal void UpdateCallables(IEnumerable updates) compiled.Attributes, compiled.Modifiers, compiled.SourceFile, - header.Location, + header.Location, compiled.Signature, compiled.ArgumentTuple, specializations, compiled.Documentation, - compiled.Comments - ); - this.CompiledCallables[fullName] = callable; + compiled.Comments); + this.compiledCallables[fullName] = callable; } } - finally { this.SyncRoot.ExitWriteLock(); } + finally + { + this.syncRoot.ExitWriteLock(); + } } /// - /// Constructs a suitable callable for a given callable declaration header - - /// i.e. given all information about a callable except the implementation of its specializations, - /// constructs a QsCallable with the implementation of each specialization set to External. + /// Constructs a suitable callable for a given callable declaration header - + /// i.e. given all information about a callable except the implementation of its specializations, + /// constructs a QsCallable with the implementation of each specialization set to External. /// Throws an ArgumentNullException if the given header is null. /// private QsCallable GetImportedCallable(CallableDeclarationHeader header) { // TODO: this needs to be adapted if we want to support external specializations - if (header == null) throw new ArgumentNullException(nameof(header)); + if (header == null) + { + throw new ArgumentNullException(nameof(header)); + } if (Namespace.IsDeclarationAccessible(false, header.Modifiers.Access)) { var definedSpecs = this.GlobalSymbols.DefinedSpecializations(header.QualifiedName); - QsCompilerError.Verify(definedSpecs.Length == 0, - "external specializations are currently not supported"); + QsCompilerError.Verify( + definedSpecs.Length == 0, + "external specializations are currently not supported"); } var specializations = @@ -636,9 +741,16 @@ private QsCallable GetImportedCallable(CallableDeclarationHeader header) ? SyntaxGenerator.BuildControlled(header.Signature) : header.Signature; return new QsSpecialization( - specHeader.Kind, header.QualifiedName, specHeader.Attributes, specHeader.SourceFile, - specHeader.Location, specHeader.TypeArguments, specSignature, implementation, - specHeader.Documentation, QsComments.Empty); + specHeader.Kind, + header.QualifiedName, + specHeader.Attributes, + specHeader.SourceFile, + specHeader.Location, + specHeader.TypeArguments, + specSignature, + implementation, + specHeader.Documentation, + QsComments.Empty); }) .ToImmutableArray(); return new QsCallable( @@ -652,17 +764,19 @@ private QsCallable GetImportedCallable(CallableDeclarationHeader header) header.ArgumentTuple, specializations, header.Documentation, - QsComments.Empty - ); + QsComments.Empty); } /// - /// Constructs a suitable type for a given type declaration header. + /// Constructs a suitable type for a given type declaration header. /// Throws an ArgumentNullException if the given header is null. /// private QsCustomType GetImportedType(TypeDeclarationHeader header) { - if (header == null) throw new ArgumentNullException(nameof(header)); + if (header == null) + { + throw new ArgumentNullException(nameof(header)); + } return new QsCustomType( header.QualifiedName, header.Attributes, @@ -672,22 +786,28 @@ private QsCustomType GetImportedType(TypeDeclarationHeader header) header.Type, header.TypeItems, header.Documentation, - QsComments.Empty - ); + QsComments.Empty); } /// - /// Builds a syntax tree containing the given callables and types, - /// and attaches the documentation specified by the given dictionary - if any - to each namespace. - /// All elements within a namespace will be sorted in alphabetical order. - /// Throws an ArgumentNullException if the given callables or types are null. + /// Builds a syntax tree containing the given callables and types, + /// and attaches the documentation specified by the given dictionary - if any - to each namespace. + /// All elements within a namespace will be sorted in alphabetical order. + /// Throws an ArgumentNullException if the given callables or types are null. /// public static ImmutableArray NewSyntaxTree( - IEnumerable callables, IEnumerable types, + IEnumerable callables, + IEnumerable types, IReadOnlyDictionary, ILookup, ImmutableArray>> documentation = null) { - if (callables == null) throw new ArgumentNullException(nameof(callables)); - if (types == null) throw new ArgumentNullException(nameof(types)); + if (callables == null) + { + throw new ArgumentNullException(nameof(callables)); + } + if (types == null) + { + throw new ArgumentNullException(nameof(types)); + } var emptyLookup = Array.Empty>().ToLookup(ns => ns, _ => ImmutableArray.Empty); static string QualifiedName(QsQualifiedName fullName) => $"{fullName.Namespace.Value}.{fullName.Name.Value}"; @@ -698,7 +818,9 @@ static string ElementName(QsNamespaceElement e) => .Concat(types.Select(t => (t.FullName.Namespace, QsNamespaceElement.NewQsCustomType(t)))); return namespaceElements .ToLookup(element => element.Item1, element => element.Item2) - .Select(elements => new QsNamespace(elements.Key, elements.OrderBy(ElementName).ToImmutableArray(), + .Select(elements => new QsNamespace( + elements.Key, + elements.OrderBy(ElementName).ToImmutableArray(), documentation != null && documentation.TryGetValue(elements.Key, out var doc) ? doc : emptyLookup)) .ToImmutableArray(); } @@ -706,21 +828,24 @@ static string ElementName(QsNamespaceElement e) => /// /// Returns the built Q# compilation reflecting the current internal state. /// Note that functor generation directives are *not* evaluated in the the returned compilation, - /// and the returned compilation may contain invalid parts. - /// Throws an InvalidOperationException if a callable definition is listed in GlobalSymbols for which no compilation exists. + /// and the returned compilation may contain invalid parts. + /// Throws an InvalidOperationException if a callable definition is listed in GlobalSymbols for which no compilation exists. /// public QsCompilation Build() { - this.SyncRoot.EnterReadLock(); + this.syncRoot.EnterReadLock(); try { // verify that a compilation indeed exists for each type, callable and specialization currently defined in global symbols foreach (var declaration in this.GlobalSymbols.DefinedTypes()) { - var compilationExists = this.CompiledTypes.TryGetValue(declaration.QualifiedName, out QsCustomType compiled); - if (!compilationExists) throw new InvalidOperationException($"missing compilation for type " + + var compilationExists = this.compiledTypes.TryGetValue(declaration.QualifiedName, out QsCustomType compiled); + if (!compilationExists) + { + throw new InvalidOperationException($"missing compilation for type " + $"{declaration.QualifiedName.Namespace.Value}.{declaration.QualifiedName.Name.Value} defined in '{declaration.SourceFile.Value}'"); + } } var entryPoints = ImmutableArray.CreateBuilder(); @@ -731,74 +856,111 @@ public QsCompilation Build() entryPoints.Add(declaration.QualifiedName); } - var compilationExists = this.CompiledCallables.TryGetValue(declaration.QualifiedName, out QsCallable compiled); - if (!compilationExists) throw new InvalidOperationException($"missing compilation for callable " + + var compilationExists = this.compiledCallables.TryGetValue(declaration.QualifiedName, out QsCallable compiled); + if (!compilationExists) + { + throw new InvalidOperationException($"missing compilation for callable " + $"{declaration.QualifiedName.Namespace.Value}.{declaration.QualifiedName.Name.Value} defined in '{declaration.SourceFile.Value}'"); + } - foreach (var (_,specHeader) in this.GlobalSymbols.DefinedSpecializations(declaration.QualifiedName)) + foreach (var (_, specHeader) in this.GlobalSymbols.DefinedSpecializations(declaration.QualifiedName)) { var compiledSpecs = compiled.Specializations.Where(spec => spec.Kind == specHeader.Kind); QsCompilerError.Verify(compiledSpecs.Count() <= 1, "more than one specialization of the same kind exists"); // currently not supported - if (!compiledSpecs.Any()) throw new InvalidOperationException($"missing compilation for specialization " + + if (!compiledSpecs.Any()) + { + throw new InvalidOperationException($"missing compilation for specialization " + $"{specHeader.Kind} of {specHeader.Parent.Namespace.Value}.{specHeader.Parent.Name.Value} in '{specHeader.SourceFile.Value}'"); + } } } // build the syntax tree - var callables = this.CompiledCallables.Values.Concat(this.GlobalSymbols.ImportedCallables().Select(this.GetImportedCallable)); - var types = this.CompiledTypes.Values.Concat(this.GlobalSymbols.ImportedTypes().Select(this.GetImportedType)); + var callables = this.compiledCallables.Values.Concat(this.GlobalSymbols.ImportedCallables().Select(this.GetImportedCallable)); + var types = this.compiledTypes.Values.Concat(this.GlobalSymbols.ImportedTypes().Select(this.GetImportedType)); // Rename imported internal declarations by tagging them with their source file to avoid potentially // having duplicate names in the syntax tree. - var (taggedCallables, taggedTypes) = RenameInternalDeclarations(callables, types, predicate: source => Externals.Declarations.ContainsKey(source)); + var (taggedCallables, taggedTypes) = RenameInternalDeclarations(callables, types, predicate: source => this.Externals.Declarations.ContainsKey(source)); var tree = NewSyntaxTree(taggedCallables, taggedTypes, this.GlobalSymbols.Documentation()); return new QsCompilation(tree, entryPoints.ToImmutable()); } - finally { this.SyncRoot.ExitReadLock(); } + finally + { + this.syncRoot.ExitReadLock(); + } } /// - /// Returns a look-up that contains the names of all namespaces and the corresponding short hand, if any, + /// Returns a look-up that contains the names of all namespaces and the corresponding short hand, if any, /// imported within a certain source file for the given namespace. - /// Throws an ArgumentException if no namespace with the given name exists. + /// Throws an ArgumentException if no namespace with the given name exists. /// public ILookup, (NonNullable, string)> GetOpenDirectives(NonNullable nsName) { - this.SyncRoot.EnterReadLock(); - try { return this.GlobalSymbols.OpenDirectives(nsName); } - finally { this.SyncRoot.ExitReadLock(); } + this.syncRoot.EnterReadLock(); + try + { + return this.GlobalSymbols.OpenDirectives(nsName); + } + finally + { + this.syncRoot.ExitReadLock(); + } } /// - /// Determines the closest preceding specialization for the given position in the given file. - /// Returns the name of the parent callable, its position in the file as well as the position of the relevant specialization as out parameters. - /// Returns null without setting any of the out parameters if the given file or position is null, or if the parent callable could not be determined. + /// Determines the closest preceding specialization for the given position in the given file. + /// Returns the name of the parent callable, its position in the file as well as the position of the relevant specialization as out parameters. + /// Returns null without setting any of the out parameters if the given file or position is null, or if the parent callable could not be determined. /// Sets the correct namespace name and callable position but returns no implementation if the given position is within a callable declaration. /// - internal QsScope TryGetSpecializationAt(FileContentManager file, Position pos, - out QsQualifiedName callableName, out Position callablePos, out Position specializationPos) + internal QsScope TryGetSpecializationAt( + FileContentManager file, + Position pos, + out QsQualifiedName callableName, + out Position callablePos, + out Position specializationPos) { (callableName, callablePos, specializationPos) = (null, null, null); - if (file == null || pos == null || !Utils.IsValidPosition(pos, file)) return null; + if (file == null || pos == null || !Utils.IsValidPosition(pos, file)) + { + return null; + } var nsName = file.TryGetNamespaceAt(pos); - if (nsName == null) return null; + if (nsName == null) + { + return null; + } var root = file.TryGetClosestSpecialization(pos); - if (root == null) return null; + if (root == null) + { + return null; + } var ((cName, cPos), (specKind, sPos)) = root.Value; - (callablePos, specializationPos) = (cPos, sPos); + (callablePos, specializationPos) = (cPos, sPos); callableName = new QsQualifiedName(NonNullable.New(nsName), cName); QsSpecialization GetSpecialization(QsQualifiedName fullName, QsSpecializationKind kind) { - if (kind == null || fullName == null) return null; - if (!this.GetCallables().TryGetValue(fullName, out var qsCallable)) return null; + if (kind == null || fullName == null) + { + return null; + } + if (!this.GetCallables().TryGetValue(fullName, out var qsCallable)) + { + return null; + } var compiled = qsCallable.Specializations.Where(spec => spec.Kind.Equals(kind)); QsCompilerError.Verify(compiled.Count() <= 1, "currently expecting at most one specialization per kind"); return compiled.SingleOrDefault(); } QsSpecialization relevantSpecialization = GetSpecialization(callableName, specKind); - if (relevantSpecialization == null || sPos == null || !relevantSpecialization.Implementation.IsProvided) return null; + if (relevantSpecialization == null || sPos == null || !relevantSpecialization.Implementation.IsProvided) + { + return null; + } QsCompilerError.Verify(sPos?.IsSmallerThanOrEqualTo(pos) ?? true, "computed closes preceding specialization does not precede the position in question"); QsCompilerError.Verify(sPos != null || relevantSpecialization == null, "the position offset should not be null unless the relevant specialization is"); @@ -806,23 +968,29 @@ QsSpecialization GetSpecialization(QsQualifiedName fullName, QsSpecializationKin } /// - /// Given all locally defined symbols within a particular specialization of a callable, + /// Given all locally defined symbols within a particular specialization of a callable, /// returns a new set of LocalDeclarations with the position information updated to the absolute values, - /// assuming the given positions for the parent callable and the specialization the symbols are defined in are correct. - /// If no LocalDeclarations are given or the given declarations are null, - /// returns all (valid) symbols defined as part of the declaration of the parent callable with their position information set to the absolute value. + /// assuming the given positions for the parent callable and the specialization the symbols are defined in are correct. + /// If no LocalDeclarations are given or the given declarations are null, + /// returns all (valid) symbols defined as part of the declaration of the parent callable with their position information set to the absolute value. /// Returns an empty set of declarations if the name of the parent callable is null or no callable with the name is currently compiled. /// internal LocalDeclarations PositionedDeclarations(QsQualifiedName parentCallable, Position callablePos, Position specPos, LocalDeclarations declarations = null) { LocalDeclarations TryGetLocalDeclarations() { - if (!this.GetCallables().TryGetValue(parentCallable, out var qsCallable)) return LocalDeclarations.Empty; + if (!this.GetCallables().TryGetValue(parentCallable, out var qsCallable)) + { + return LocalDeclarations.Empty; + } var definedVars = SyntaxGenerator.ExtractItems(qsCallable.ArgumentTuple).ValidDeclarations(); return new LocalDeclarations(definedVars); } - if (parentCallable == null) return LocalDeclarations.Empty; + if (parentCallable == null) + { + return LocalDeclarations.Empty; + } declarations ??= TryGetLocalDeclarations(); Tuple AbsolutePosition(QsNullable> relOffset) => @@ -834,44 +1002,52 @@ Tuple AbsolutePosition(QsNullable> relOffset) => /// /// Returns all locally declared symbols at the given (absolute) position in the given file - /// and sets the out parameter to the name of the parent callable at that position, - /// assuming that the position corresponds to a piece of code within the given file. - /// If includeDeclaredAtPosition is set to true, then this includes the symbols declared within the statement at the specified position, - /// even if those symbols are *not* visible after the statement ends (e.g. for-loops or qubit allocations). + /// and sets the out parameter to the name of the parent callable at that position, + /// assuming that the position corresponds to a piece of code within the given file. + /// If includeDeclaredAtPosition is set to true, then this includes the symbols declared within the statement at the specified position, + /// even if those symbols are *not* visible after the statement ends (e.g. for-loops or qubit allocations). /// Note that if the given position does not correspond to a piece of code but rather to whitespace possibly after a scope ending, - /// the returned declarations or the set parent name are not necessarily accurate - they are for any actual piece of code, though. - /// If the given file or position is null, or if the locally declared symbols could not be determined, returns an empty LocalDeclarations object. + /// the returned declarations or the set parent name are not necessarily accurate - they are for any actual piece of code, though. + /// If the given file or position is null, or if the locally declared symbols could not be determined, returns an empty LocalDeclarations object. /// Sets the parent name to null, if no parent could be determind. /// internal LocalDeclarations TryGetLocalDeclarations(FileContentManager file, Position pos, out QsQualifiedName parentCallable, bool includeDeclaredAtPosition = false) { var implementation = this.TryGetSpecializationAt(file, pos, out parentCallable, out var callablePos, out var specPos); var declarations = implementation?.LocalDeclarationsAt(pos.Subtract(specPos), includeDeclaredAtPosition); - return this.PositionedDeclarations(parentCallable, callablePos, specPos, declarations); + return this.PositionedDeclarations(parentCallable, callablePos, specPos, declarations); } /// - /// Tags the names of internal callables and types that are from a source that satisfies - /// the given predicate with a unique identifier based on the path to their source, - /// so that they do not conflict with public callables and types. + /// Tags the names of internal callables and types that are from a source that satisfies + /// the given predicate with a unique identifier based on the path to their source, + /// so that they do not conflict with public callables and types. /// If no predicate is specified or the given predicate is null, tags all types and callables. /// Renames all usages to the tagged names. /// /// The callables to rename and update if they are internal. /// The types to rename and update if they are internal. - /// The number of additional assemblies included in the compilation + /// The number of additional assemblies included in the compilation /// besides the ones listed as sources in the given types and callables. - /// If specified, only types and callables from a source for which + /// If specified, only types and callables from a source for which /// this function returns true are renamed. /// The renamed and updated callables and types. /// Thrown when the given callables or types are null. - internal static (IEnumerable, IEnumerable) - RenameInternalDeclarations(IEnumerable callables, IEnumerable types, - int additionalAssemblies = 0, Func, bool> predicate = null) + internal static (IEnumerable, IEnumerable) RenameInternalDeclarations( + IEnumerable callables, + IEnumerable types, + int additionalAssemblies = 0, + Func, bool> predicate = null) { - if (callables == null) throw new ArgumentNullException(nameof(callables)); - if (types == null) throw new ArgumentNullException(nameof(types)); - predicate ??= (_ => true); + if (callables == null) + { + throw new ArgumentNullException(nameof(callables)); + } + if (types == null) + { + throw new ArgumentNullException(nameof(types)); + } + predicate ??= _ => true; // Assign a unique ID to each reference. @@ -883,18 +1059,19 @@ internal static (IEnumerable, IEnumerable) .Where(source => predicate(NonNullable.New(source))) // this setup will mean that internal declarations won't get replaced with target specific implementations .Select((source, idx) => (source, idx)) - // we need an id here that is uniquely associated with a source name + // we need an id here that is uniquely associated with a source name // to ensure that internal names are unique even when this is not called on the entire compilation .ToImmutableDictionary(entry => entry.source, entry => entry.idx + additionalAssemblies); ImmutableDictionary GetMappingForSourceGroup( - IGrouping group) => + IGrouping group) => group .Where(item => - !Namespace.IsDeclarationAccessible(false, item.access) && - predicate(NonNullable.New(item.source))) - .ToImmutableDictionary(item => item.name, - item => decorator.Decorate(item.name, ids[item.source])); + !Namespace.IsDeclarationAccessible(false, item.Access) && + predicate(NonNullable.New(item.Source))) + .ToImmutableDictionary( + item => item.Name, + item => decorator.Decorate(item.Name, ids[item.Source])); // rename all internal declarations and their usages diff --git a/src/QsCompiler/CompilationManager/CompilationUnitManager.cs b/src/QsCompiler/CompilationManager/CompilationUnitManager.cs index 2cda5999be..e74d156d2b 100644 --- a/src/QsCompiler/CompilationManager/CompilationUnitManager.cs +++ b/src/QsCompiler/CompilationManager/CompilationUnitManager.cs @@ -16,7 +16,6 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { /// @@ -26,19 +25,22 @@ namespace Microsoft.Quantum.QsCompiler.CompilationBuilder public class CompilationUnitManager : IDisposable { internal readonly bool EnableVerification; + /// - /// the keys are the file identifiers of the source files obtained by GetFileId for the file uri and the values are the content of each file + /// the keys are the file identifiers of the source files obtained by GetFileId for the file uri and the values are the content of each file /// - private readonly ConcurrentDictionary, FileContentManager> FileContentManagers; + private readonly ConcurrentDictionary, FileContentManager> fileContentManagers; + /// /// contains the CompilationUnit to which all files managed in this class instance belong to /// - private readonly CompilationUnit CompilationUnit; + private readonly CompilationUnit compilationUnit; /// /// used to log exceptions raised during processing /// public readonly Action LogException; + /// /// called whenever diagnostics within a file have changed and are ready for publishing /// @@ -48,11 +50,13 @@ public class CompilationUnitManager : IDisposable /// WaitForTypeCheck is null if a global type checking has been queued but is not yet running. /// If WaitForTypeCheck is not null then a global type checking may be running and can be cancelled via WaitForTypeCheck. /// - private CancellationTokenSource WaitForTypeCheck; + private CancellationTokenSource waitForTypeCheck; + /// /// used to track which files have changed during global type checking /// - private readonly ManagedHashSet> ChangedFiles; + private readonly ManagedHashSet> changedFiles; + /// /// used to synchronously execute all write access /// @@ -60,7 +64,7 @@ public class CompilationUnitManager : IDisposable /// /// Initializes a CompilationUnitManager instance for a project with the given properties. - /// If an for publishing diagnostics is given and is not null, + /// If an for publishing diagnostics is given and is not null, /// that action is called whenever diagnostics within a file have changed and are ready for publishing. /// public CompilationUnitManager( @@ -69,59 +73,59 @@ public CompilationUnitManager( bool syntaxCheckOnly = false, AssemblyConstants.RuntimeCapabilities capabilities = AssemblyConstants.RuntimeCapabilities.Unknown, bool isExecutable = false, - NonNullable executionTarget = default) + NonNullable processorArchitecture = default) { this.EnableVerification = !syntaxCheckOnly; - this.CompilationUnit = new CompilationUnit(capabilities, isExecutable, executionTarget); - this.FileContentManagers = new ConcurrentDictionary, FileContentManager>(); - this.ChangedFiles = new ManagedHashSet>(new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion)); + this.compilationUnit = new CompilationUnit(capabilities, isExecutable, processorArchitecture); + this.fileContentManagers = new ConcurrentDictionary, FileContentManager>(); + this.changedFiles = new ManagedHashSet>(new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion)); this.PublishDiagnostics = publishDiagnostics ?? (_ => { }); this.LogException = exceptionLogger ?? Console.Error.WriteLine; this.Processing = new ProcessingQueue(this.LogException); - this.WaitForTypeCheck = new CancellationTokenSource(); + this.waitForTypeCheck = new CancellationTokenSource(); } - /// /// Cancels any asynchronously running, ongoing global type checking and sets WaitForTypeCheck to null, /// indicating that a global type checking is queued and not yet started. - /// After all currently queued tasks have finished, - /// locks all processing, flushes the unprocessed changes in each source file, - /// synchronously runs a global type checking (unless verifications are disabled), - /// and then executes the given function, returning its result. - /// Returns null if the given function to execute is null, but does everything else. + /// After all currently queued tasks have finished, + /// locks all processing, flushes the unprocessed changes in each source file, + /// synchronously runs a global type checking (unless verifications are disabled), + /// and then executes the given function, returning its result. + /// Returns null if the given function to execute is null, but does everything else. /// public T FlushAndExecute(Func execute = null) where T : class { // To enforce an up-to-date content for executing the given function, // we want to do a (synchronous!) global type checking on flushing, - // and hence cancel any ongoing type checking and set the WaitForTypeCheck handle to null. - // However, we *also* need to make sure that any global type checking that is queued prior to the flushing task - // (indicated by the WaitForTypeChecking handle currently being null) does not extend past it - - // i.e. during the flushing task we again need to cancel any ongoing type checking. - this.WaitForTypeCheck?.Cancel(); - this.WaitForTypeCheck = null; - var succeeded = this.Processing.QueueForExecution(() => - { - this.WaitForTypeCheck?.Cancel(); // needed in the case where WaitForTypeCheck above was null - foreach (var file in this.FileContentManagers.Values) + // and hence cancel any ongoing type checking and set the WaitForTypeCheck handle to null. + // However, we *also* need to make sure that any global type checking that is queued prior to the flushing task + // (indicated by the WaitForTypeChecking handle currently being null) does not extend past it - + // i.e. during the flushing task we again need to cancel any ongoing type checking. + this.waitForTypeCheck?.Cancel(); + this.waitForTypeCheck = null; + var succeeded = this.Processing.QueueForExecution( + () => { - file.Flush(); - this.PublishDiagnostics(file.Diagnostics()); - } - var task = this.EnableVerification ? this.SpawnGlobalTypeCheckingAsync(runSynchronously: true) : Task.CompletedTask; - QsCompilerError.Verify(task.IsCompleted, "global type checking hasn't completed"); - return execute?.Invoke(); - } - , out T result); + this.waitForTypeCheck?.Cancel(); // needed in the case where WaitForTypeCheck above was null + foreach (var file in this.fileContentManagers.Values) + { + file.Flush(); + this.PublishDiagnostics(file.Diagnostics()); + } + var task = this.EnableVerification ? this.SpawnGlobalTypeCheckingAsync(runSynchronously: true) : Task.CompletedTask; + QsCompilerError.Verify(task.IsCompleted, "global type checking hasn't completed"); + return execute?.Invoke(); + }, + out T result); return succeeded ? result : null; } /// - /// Cancels any ongoing type checking, waits for all queued tasks to finish, + /// Cancels any ongoing type checking, waits for all queued tasks to finish, /// and then disposes disposable content initialized within this CompilationUnitManager (in particular disposes the Compilation). /// Any FileContentManager managed by this compilation is *not* disposed, - /// to allow potentially sharing file content managers between different compilation units. + /// to allow potentially sharing file content managers between different compilation units. /// public void Dispose() { @@ -129,23 +133,22 @@ public void Dispose() // - or (global type checking) tasks spawned by those - trying to access disposed stuff this.FlushAndExecute(() => { - this.WaitForTypeCheck?.Dispose(); - this.CompilationUnit.Dispose(); - foreach (var file in this.FileContentManagers.Values) + this.waitForTypeCheck?.Dispose(); + this.compilationUnit.Dispose(); + foreach (var file in this.fileContentManagers.Values) { // do *not* dispose of the FileContentManagers! this.UnsubscribeFromFileManagerEvents(file); this.PublishDiagnostics(new PublishDiagnosticParams { Uri = file.Uri, Diagnostics = Array.Empty() }); - } + } return null; }); } - // routines related to tracking the source files /// - /// Converts a URI into the file ID used during compilation if the URI is an absolute file URI. + /// Converts a URI into the file ID used during compilation if the URI is an absolute file URI. /// /// Thrown if the URI is null. /// Thrown if the URI is not an absolute URI or not a file URI. @@ -157,7 +160,7 @@ uri is null : throw new ArgumentException("The URI is not an absolute file URI.", nameof(uri)); /// - /// Converts a URI into the file ID used during compilation if the URI is an absolute file URI. + /// Converts a URI into the file ID used during compilation if the URI is an absolute file URI. /// /// True if converting the URI to a file ID succeeded. [Obsolete("Use GetFileId instead after ensuring that the URI is an absolute file URI.")] @@ -177,67 +180,104 @@ public static bool TryGetFileId(Uri uri, out NonNullable fileId) /// /// Given a file id assigned by the compilation unit manager, returns the URI based on which the ID was constructed as out parameter. - /// Returns false if the given file id is not compatible with an id generated by the compilation unit manager. + /// Returns false if the given file id is not compatible with an id generated by the compilation unit manager. /// public static bool TryGetUri(NonNullable fileId, out Uri uri) => Uri.TryCreate(Uri.UnescapeDataString(fileId.Value), UriKind.Absolute, out uri); /// - /// Subscribes to diagnostics updated events, - /// to events indicating when queued changes in the file content manager have not yet been processed, - /// and to events indicating that the compilation unit wide semantic verification needs to be updated. + /// Subscribes to diagnostics updated events, + /// to events indicating when queued changes in the file content manager have not yet been processed, + /// and to events indicating that the compilation unit wide semantic verification needs to be updated. /// Throws an ArgumentNullException if the given file is null. /// private void SubscribeToFileManagerEvents(FileContentManager file) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } file.TimerTriggeredUpdateEvent += this.TriggerFileUpdateAsync; - if (this.EnableVerification) file.GlobalTypeCheckingEvent += this.QueueGlobalTypeCheckingAsync; + if (this.EnableVerification) + { + file.GlobalTypeCheckingEvent += this.QueueGlobalTypeCheckingAsync; + } } /// /// Unsubscribes from all events that SubscribeToFileManagerEvents subscribes to for the given file. - /// Throws an ArgumentNullException if the given file is null. + /// Throws an ArgumentNullException if the given file is null. /// private void UnsubscribeFromFileManagerEvents(FileContentManager file) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } file.TimerTriggeredUpdateEvent -= this.TriggerFileUpdateAsync; - if (this.EnableVerification) file.GlobalTypeCheckingEvent -= this.QueueGlobalTypeCheckingAsync; + if (this.EnableVerification) + { + file.GlobalTypeCheckingEvent -= this.QueueGlobalTypeCheckingAsync; + } } - /// /// Initializes a FileContentManager for the given document with the given content. - /// If an Action for publishing is given, publishes the diagnostics generated upon processing the given content. - /// Throws an ArgumentNullException if the given file content is null. - /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. + /// If an Action for publishing is given, publishes the diagnostics generated upon processing the given content. + /// Throws an ArgumentNullException if the given file content is null. + /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. /// - public static FileContentManager InitializeFileManager(Uri uri, string fileContent, - Action publishDiagnostics = null, Action onException = null) + public static FileContentManager InitializeFileManager( + Uri uri, + string fileContent, + Action publishDiagnostics = null, + Action onException = null) { - if (!TryGetFileId(uri, out NonNullable docKey)) throw new ArgumentException("invalid TextDocumentIdentifier"); - if (fileContent == null) throw new ArgumentNullException(nameof(fileContent)); + if (!TryGetFileId(uri, out NonNullable docKey)) + { + throw new ArgumentException("invalid TextDocumentIdentifier"); + } + if (fileContent == null) + { + throw new ArgumentNullException(nameof(fileContent)); + } var file = new FileContentManager(uri, docKey); - try { file.ReplaceFileContent(fileContent); } - catch (Exception ex) { onException?.Invoke(ex); } + try + { + file.ReplaceFileContent(fileContent); + } + catch (Exception ex) + { + onException?.Invoke(ex); + } publishDiagnostics?.Invoke(file.Diagnostics()); return file; } /// - /// Initializes a FileContentManager for each entry in the given dictionary of source files and their content. - /// If an Action for publishing is given, publishes the diagnostics generated upon content processing. - /// Throws an ArgumentNullException if the given dictionary of files and their content is null. - /// Throws an ArgumentException if any of the given uris is null or not an absolute file uri, or if any of the content is null. + /// Initializes a FileContentManager for each entry in the given dictionary of source files and their content. + /// If an Action for publishing is given, publishes the diagnostics generated upon content processing. + /// Throws an ArgumentNullException if the given dictionary of files and their content is null. + /// Throws an ArgumentException if any of the given uris is null or not an absolute file uri, or if any of the content is null. /// - public static ImmutableHashSet InitializeFileManagers(IDictionary files, - Action publishDiagnostics = null, Action onException = null) + public static ImmutableHashSet InitializeFileManagers( + IDictionary files, + Action publishDiagnostics = null, + Action onException = null) { - if (files == null) throw new ArgumentNullException(nameof(files)); - if (files.Any(item => item.Value == null)) throw new ArgumentException("file content cannot be null"); - if (files.Any(item => !TryGetFileId(item.Key, out NonNullable docKey))) throw new ArgumentException("invalid TextDocumentIdentifier"); + if (files == null) + { + throw new ArgumentNullException(nameof(files)); + } + if (files.Any(item => item.Value == null)) + { + throw new ArgumentException("file content cannot be null"); + } + if (files.Any(item => !TryGetFileId(item.Key, out NonNullable docKey))) + { + throw new ArgumentException("invalid TextDocumentIdentifier"); + } return files.AsParallel() .WithDegreeOfParallelism(Environment.ProcessorCount > 1 ? Environment.ProcessorCount - 1 : Environment.ProcessorCount) @@ -246,26 +286,34 @@ public static ImmutableHashSet InitializeFileManagers(IDicti .ToImmutableHashSet(); } - /// /// Adds the given source file to this compilation unit, adapting the diagnostics for all remaining files as needed. /// If a file with that Uri is already listed as source file, /// replaces the current FileContentManager for that file with the given one. - /// If the content to update is specified and not null, replaces the tracked content in the file manager with the given one. + /// If the content to update is specified and not null, replaces the tracked content in the file manager with the given one. /// Throws an ArgumentNullException if any of the compulsory arguments is null or the set uri is. - /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. + /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. /// public Task AddOrUpdateSourceFileAsync(FileContentManager file, string updatedContent = null) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } return this.Processing.QueueForExecutionAsync(() => { - this.CompilationUnit.RegisterDependentLock(file.SyncRoot); + this.compilationUnit.RegisterDependentLock(file.SyncRoot); this.SubscribeToFileManagerEvents(file); - this.FileContentManagers.AddOrUpdate(file.FileName, file, (k, v) => file); - if (updatedContent != null) file.ReplaceFileContent(updatedContent); - this.ChangedFiles.Add(file.FileName); - if (this.EnableVerification && this.WaitForTypeCheck != null) file.Verify(this.CompilationUnit); + this.fileContentManagers.AddOrUpdate(file.FileName, file, (k, v) => file); + if (updatedContent != null) + { + file.ReplaceFileContent(updatedContent); + } + this.changedFiles.Add(file.FileName); + if (this.EnableVerification && this.waitForTypeCheck != null) + { + file.Verify(this.compilationUnit); + } this.PublishDiagnostics(file.Diagnostics()); }); } @@ -273,25 +321,31 @@ public Task AddOrUpdateSourceFileAsync(FileContentManager file, string updatedCo /// /// Adds the given source files to this compilation unit, adapting the diagnostics for all remaining files as needed. /// If a file with the same Uri is already listed as source file, - /// replaces the current FileContentManager for that file with a new one and initialized its content to the given one. - /// Spawns a compilation unit wide type checking unless suppressVerification is set to true, even if no files have been added. + /// replaces the current FileContentManager for that file with a new one and initialized its content to the given one. + /// Spawns a compilation unit wide type checking unless suppressVerification is set to true, even if no files have been added. /// Throws an ArgumentNullException if the given source files are null. - /// Throws an ArgumentException if an uri is not a valid absolute file uri, or if the content for a file is null. + /// Throws an ArgumentException if an uri is not a valid absolute file uri, or if the content for a file is null. /// public Task AddOrUpdateSourceFilesAsync(ImmutableHashSet files, bool suppressVerification = false) { - if (files == null || files.Contains(null)) throw new ArgumentNullException(nameof(files)); + if (files == null || files.Contains(null)) + { + throw new ArgumentNullException(nameof(files)); + } return this.Processing.QueueForExecutionAsync(() => { foreach (var file in files) { - this.CompilationUnit.RegisterDependentLock(file.SyncRoot); + this.compilationUnit.RegisterDependentLock(file.SyncRoot); this.SubscribeToFileManagerEvents(file); - this.FileContentManagers.AddOrUpdate(file.FileName, file, (k, v) => file); - this.ChangedFiles.Add(file.FileName); + this.fileContentManagers.AddOrUpdate(file.FileName, file, (k, v) => file); + this.changedFiles.Add(file.FileName); this.PublishDiagnostics(file.Diagnostics()); } - if (this.EnableVerification && !suppressVerification) this.QueueGlobalTypeCheckingAsync(); + if (this.EnableVerification && !suppressVerification) + { + this.QueueGlobalTypeCheckingAsync(); + } }); } @@ -299,319 +353,435 @@ public Task AddOrUpdateSourceFilesAsync(ImmutableHashSet fil /// Modifies the compilation and all diagnostics to reflect the given change. /// Throws an ArgumentNullException if any of the arguments is null. /// Throws a InvalidOperationException if file for which a change is given is not listed as source file. - /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. + /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. /// public Task SourceFileDidChangeAsync(DidChangeTextDocumentParams param) { - if (!TryGetFileId(param?.TextDocument?.Uri, out NonNullable docKey)) throw new ArgumentException("invalid TextDocumentIdentifier"); - if (param.ContentChanges == null) throw new ArgumentNullException(nameof(param.ContentChanges)); + if (!TryGetFileId(param?.TextDocument?.Uri, out NonNullable docKey)) + { + throw new ArgumentException("invalid TextDocumentIdentifier"); + } + if (param.ContentChanges == null) + { + throw new ArgumentNullException(nameof(param.ContentChanges)); + } return this.Processing.QueueForExecutionAsync(() => { - var isSource = this.FileContentManagers.TryGetValue(docKey, out FileContentManager file); - if (!isSource) throw new InvalidOperationException ($"changed file {docKey.Value} is not a source file of this compilation unit"); + var isSource = this.fileContentManagers.TryGetValue(docKey, out FileContentManager file); + if (!isSource) + { + throw new InvalidOperationException($"changed file {docKey.Value} is not a source file of this compilation unit"); + } - this.ChangedFiles.Add(docKey); + this.changedFiles.Add(docKey); var publish = false; - foreach (var change in param.ContentChanges) file.PushChange(change, out publish); // only the last one here is relevant + foreach (var change in param.ContentChanges) + { + file.PushChange(change, out publish); // only the last one here is relevant + } if (publish) { - if (this.EnableVerification && this.WaitForTypeCheck != null) - { file.AddTimerTriggeredUpdateEvent(); } + if (this.EnableVerification && this.waitForTypeCheck != null) + { + file.AddTimerTriggeredUpdateEvent(); + } this.PublishDiagnostics(file.Diagnostics()); } - else file.AddTimerTriggeredUpdateEvent(); + else + { + file.AddTimerTriggeredUpdateEvent(); + } }); } /// - /// Called in order to process any queued changes in the file with the given URI and + /// Called in order to process any queued changes in the file with the given URI and /// - if verifications are enabled - trigger a semantic check. - /// Does not do anything if no file with the given URI is listed as source file of this compilation. - /// Throws an ArgumentException if the URI of the given text document identifier is null or not an absolute file URI. + /// Does not do anything if no file with the given URI is listed as source file of this compilation. + /// Throws an ArgumentException if the URI of the given text document identifier is null or not an absolute file URI. /// private Task TriggerFileUpdateAsync(Uri uri) { - if (!TryGetFileId(uri, out NonNullable docKey)) throw new ArgumentException("invalid TextDocumentIdentifier"); + if (!TryGetFileId(uri, out NonNullable docKey)) + { + throw new ArgumentException("invalid TextDocumentIdentifier"); + } return this.Processing.QueueForExecutionAsync(() => { - // Note that we cannot fail if the file is no longer part of the compilation, + // Note that we cannot fail if the file is no longer part of the compilation, // because it is possible that between the time when a file has been removed and when that removal is executed // a file update has been triggered e.g. by timer... - var isSource = this.FileContentManagers.TryGetValue(docKey, out FileContentManager file); - if (!isSource) return; + var isSource = this.fileContentManagers.TryGetValue(docKey, out FileContentManager file); + if (!isSource) + { + return; + } // we need to mark a file as edited whenever some processing is spawned by the file, since - // otherwise if a global type checking runs while changes are still queued in the file, - // then the automatically spawned processing of those changes will be (partially) overwritten + // otherwise if a global type checking runs while changes are still queued in the file, + // then the automatically spawned processing of those changes will be (partially) overwritten // when the global type checking results are pushed back in - this.ChangedFiles.Add(docKey); + this.changedFiles.Add(docKey); file.Flush(); - if (this.EnableVerification && this.WaitForTypeCheck != null) file.Verify(this.CompilationUnit); + if (this.EnableVerification && this.waitForTypeCheck != null) + { + file.Verify(this.compilationUnit); + } this.PublishDiagnostics(file.Diagnostics()); }); } /// - /// Removes the specified file from the list of source files for this compilation unit, + /// Removes the specified file from the list of source files for this compilation unit, /// publishes empty Diagnostics for that file unless publishEmptyDiagnostics is set to false, /// and adapts all remaining diagnostics as needed. - /// Does nothing if no file with the given Uri is listed as source file. - /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. + /// Does nothing if no file with the given Uri is listed as source file. + /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. /// public Task TryRemoveSourceFileAsync(Uri uri, bool publishEmptyDiagnostics = true) { - if (!TryGetFileId(uri, out NonNullable docKey)) throw new ArgumentException("invalid TextDocumentIdentifier"); + if (!TryGetFileId(uri, out NonNullable docKey)) + { + throw new ArgumentException("invalid TextDocumentIdentifier"); + } return this.Processing.QueueForExecutionAsync(() => { - if (!this.FileContentManagers.TryRemove(docKey, out FileContentManager file)) return; - this.ChangedFiles.Add(docKey); - this.CompilationUnit.UnregisterDependentLock(file.SyncRoot); // do *not* dispose of the FileContentManager! + if (!this.fileContentManagers.TryRemove(docKey, out FileContentManager file)) + { + return; + } + this.changedFiles.Add(docKey); + this.compilationUnit.UnregisterDependentLock(file.SyncRoot); // do *not* dispose of the FileContentManager! this.UnsubscribeFromFileManagerEvents(file); // ... but unsubscribe from all file events - this.CompilationUnit.GlobalSymbols.RemoveSource(docKey); - if (publishEmptyDiagnostics) this.PublishDiagnostics(new PublishDiagnosticParams { Uri = uri, Diagnostics = Array.Empty() }); - if (this.EnableVerification) this.QueueGlobalTypeCheckingAsync(); // we need to trigger a global type checking for the remaining files... + this.compilationUnit.GlobalSymbols.RemoveSource(docKey); + if (publishEmptyDiagnostics) + { + this.PublishDiagnostics(new PublishDiagnosticParams { Uri = uri, Diagnostics = Array.Empty() }); + } + if (this.EnableVerification) + { + this.QueueGlobalTypeCheckingAsync(); // we need to trigger a global type checking for the remaining files... + } }); } /// - /// Removes the specified files from the list of source files for this compilation unit, - /// publishes empty Diagnostics for the removed files unless publishEmptyDiagnostics is set to false, + /// Removes the specified files from the list of source files for this compilation unit, + /// publishes empty Diagnostics for the removed files unless publishEmptyDiagnostics is set to false, /// and adapts all remaining diagnostics as needed. - /// Does nothing if a file with the given Uri is not listed as source file. - /// Spawns a compilation unit wide type checking unless suppressVerification is set to true, even if no files have been removed. + /// Does nothing if a file with the given Uri is not listed as source file. + /// Spawns a compilation unit wide type checking unless suppressVerification is set to true, even if no files have been removed. /// Throws an ArgumentNullException if the given sequence of files are null. - /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. + /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. /// public Task TryRemoveSourceFilesAsync(IEnumerable files, bool suppressVerification = false, bool publishEmptyDiagnostics = true) { - if (files == null) throw new ArgumentNullException(nameof(files)); - if (files.Any(uri => !TryGetFileId(uri, out var _))) throw new ArgumentException("invalid TextDocumentIdentifier"); + if (files == null) + { + throw new ArgumentNullException(nameof(files)); + } + if (files.Any(uri => !TryGetFileId(uri, out var _))) + { + throw new ArgumentException("invalid TextDocumentIdentifier"); + } return this.Processing.QueueForExecutionAsync(() => { foreach (var uri in files) { TryGetFileId(uri, out NonNullable docKey); - if (!this.FileContentManagers.TryRemove(docKey, out FileContentManager file)) return; - this.ChangedFiles.Add(docKey); - this.CompilationUnit.UnregisterDependentLock(file.SyncRoot); // do *not* dispose of the FileContentManager! + if (!this.fileContentManagers.TryRemove(docKey, out FileContentManager file)) + { + return; + } + this.changedFiles.Add(docKey); + this.compilationUnit.UnregisterDependentLock(file.SyncRoot); // do *not* dispose of the FileContentManager! this.UnsubscribeFromFileManagerEvents(file); // ... but unsubscribe from all file events - this.CompilationUnit.GlobalSymbols.RemoveSource(docKey); - if (publishEmptyDiagnostics) this.PublishDiagnostics(new PublishDiagnosticParams { Uri = uri, Diagnostics = Array.Empty() }); - if (this.EnableVerification) this.QueueGlobalTypeCheckingAsync(); // we need to trigger a global type checking for the remaining files... + this.compilationUnit.GlobalSymbols.RemoveSource(docKey); + if (publishEmptyDiagnostics) + { + this.PublishDiagnostics(new PublishDiagnosticParams { Uri = uri, Diagnostics = Array.Empty() }); + } + if (this.EnableVerification) + { + this.QueueGlobalTypeCheckingAsync(); // we need to trigger a global type checking for the remaining files... + } + } + if (this.EnableVerification && !suppressVerification) + { + this.QueueGlobalTypeCheckingAsync(); } - if (this.EnableVerification && !suppressVerification) this.QueueGlobalTypeCheckingAsync(); }); } /// /// Replaces the content from all referenced assemblies with the given references. - /// Updates all diagnostics accordingly, unless suppressVerification has been set to true. - /// Throws an ArgumentNullException if the given references are null. + /// Updates all diagnostics accordingly, unless suppressVerification has been set to true. + /// Throws an ArgumentNullException if the given references are null. /// internal Task UpdateReferencesAsync(References references, bool suppressVerification = false) { - if (references == null) throw new ArgumentNullException(nameof(references)); + if (references == null) + { + throw new ArgumentNullException(nameof(references)); + } return this.Processing.QueueForExecutionAsync(() => { - this.CompilationUnit.UpdateReferences(references); - if (this.EnableVerification && !suppressVerification) this.QueueGlobalTypeCheckingAsync(); + this.compilationUnit.UpdateReferences(references); + if (this.EnableVerification && !suppressVerification) + { + this.QueueGlobalTypeCheckingAsync(); + } }); } /// - /// Replaces the content from all referenced assemblies with the given references, and updates all diagnostics accordingly. - /// Throws an ArgumentNullException if the given references are null. + /// Replaces the content from all referenced assemblies with the given references, and updates all diagnostics accordingly. + /// Throws an ArgumentNullException if the given references are null. /// public Task UpdateReferencesAsync(References references) => - UpdateReferencesAsync(references, false); - + this.UpdateReferencesAsync(references, false); // routines related to global type checking (all calls to these need to be suppressed if verifications are disabled) /// - /// If a global type checking is already queued, but hasn't started executing yet, does nothing and returns a completed task. + /// If a global type checking is already queued, but hasn't started executing yet, does nothing and returns a completed task. /// If a global type checking is in progress, cancels that process via WaitForTypeChecking, - /// then queues SpawnGlobalTypeCheckingAsync for exectution into the task queue, and sets WaitForTypeChecking to null, - /// indicating that a type global type checking is queued and has not started executing yet. + /// then queues SpawnGlobalTypeCheckingAsync for exectution into the task queue, and sets WaitForTypeChecking to null, + /// indicating that a type global type checking is queued and has not started executing yet. /// private Task QueueGlobalTypeCheckingAsync() { - if (this.WaitForTypeCheck == null) return Task.CompletedTask; // type check is already queued - this.WaitForTypeCheck.Cancel(); // cancel any ongoing type check... - this.WaitForTypeCheck = null; // ... and queue a new type check + if (this.waitForTypeCheck == null) + { + return Task.CompletedTask; // type check is already queued + } + this.waitForTypeCheck.Cancel(); // cancel any ongoing type check... + this.waitForTypeCheck = null; // ... and queue a new type check return this.Processing.QueueForExecutionAsync(() => - QsCompilerError.RaiseOnFailure(() => - this.SpawnGlobalTypeCheckingAsync(), "error while spawning global type checking")); + QsCompilerError.RaiseOnFailure( + () => this.SpawnGlobalTypeCheckingAsync(), "error while spawning global type checking")); } /// - /// Updates all global symbols for all files in the compilation using a separate CompilationUnit instance. - /// Copies the symbol information over to the Compilation property, - /// updates the HeaderDiagnostics and clears the SemanticDiagnostics in all files, and publishes the updated diagnostics. - /// Sets WaitForTypeCheck to a new CancellationTokenSource, - /// and then spawns a task calling RunGlobalTypeChecking on the computed content using the separate CompilationUnit, - /// with a cancellation token from the new cancellation source. - /// If runSynchronously is set to true, then the task is run synchronously ignoring any cancellations and the completed task is returned. + /// Updates all global symbols for all files in the compilation using a separate CompilationUnit instance. + /// Copies the symbol information over to the Compilation property, + /// updates the HeaderDiagnostics and clears the SemanticDiagnostics in all files, and publishes the updated diagnostics. + /// Sets WaitForTypeCheck to a new CancellationTokenSource, + /// and then spawns a task calling RunGlobalTypeChecking on the computed content using the separate CompilationUnit, + /// with a cancellation token from the new cancellation source. + /// If runSynchronously is set to true, then the task is run synchronously ignoring any cancellations and the completed task is returned. /// private Task SpawnGlobalTypeCheckingAsync(bool runSynchronously = false) { - this.WaitForTypeCheck = new CancellationTokenSource(); // set a handle for cancelling this type check - var cancellationToken = runSynchronously ? new CancellationToken() : this.WaitForTypeCheck.Token; + this.waitForTypeCheck = new CancellationTokenSource(); // set a handle for cancelling this type check + var cancellationToken = runSynchronously ? CancellationToken.None : this.waitForTypeCheck.Token; // work with a separate compilation unit instance such that processing of all further edits can go on in parallel - var sourceFiles = this.FileContentManagers.Values.OrderBy(m => m.FileName); - this.ChangedFiles.RemoveAll(f => sourceFiles.Any(m => m.FileName.Value == f.Value)); + var sourceFiles = this.fileContentManagers.Values.OrderBy(m => m.FileName); + this.changedFiles.RemoveAll(f => sourceFiles.Any(m => m.FileName.Value == f.Value)); var compilation = new CompilationUnit( - this.CompilationUnit.RuntimeCapabilities, - this.CompilationUnit.IsExecutable, - this.CompilationUnit.ExecutionTarget, - this.CompilationUnit.Externals, + this.compilationUnit.RuntimeCapabilities, + this.compilationUnit.IsExecutable, + this.compilationUnit.ProcessorArchitecture, + this.compilationUnit.Externals, sourceFiles.Select(file => file.SyncRoot)); var content = compilation.UpdateGlobalSymbolsFor(sourceFiles); - foreach (var file in sourceFiles) this.PublishDiagnostics(file.Diagnostics()); + foreach (var file in sourceFiles) + { + this.PublishDiagnostics(file.Diagnostics()); + } // move the content of symbols over to the Compilation - this.CompilationUnit.EnterWriteLock(); + this.compilationUnit.EnterWriteLock(); try { - this.CompilationUnit.GlobalSymbols.Clear(); - compilation.GlobalSymbols.CopyTo(this.CompilationUnit.GlobalSymbols); - this.CompilationUnit.GlobalSymbols.ResolveAll(BuiltIn.NamespacesToAutoOpen); + this.compilationUnit.GlobalSymbols.Clear(); + compilation.GlobalSymbols.CopyTo(this.compilationUnit.GlobalSymbols); + this.compilationUnit.GlobalSymbols.ResolveAll(BuiltIn.NamespacesToAutoOpen); + } + finally + { + this.compilationUnit.ExitWriteLock(); } - finally { this.CompilationUnit.ExitWriteLock(); } // after the relevant information is extracted, the actual type checking can run in parallel - var task = new Task(() => - { - try - { // Do *not* remove the RaiseOnFailure! - // -> keep it here, such that all internal exceptions are being piped through one central routine (in QsCompilerError) - QsCompilerError.RaiseOnFailure(() => - this.RunGlobalTypeChecking(compilation, content, cancellationToken), - "error while running global type checking"); - } - catch (Exception ex) { this.LogException(ex); } - }, cancellationToken); + var task = new Task( + () => + { + try + { + // Do *not* remove the RaiseOnFailure! + // -> keep it here, such that all internal exceptions are being piped through one central routine (in QsCompilerError) + QsCompilerError.RaiseOnFailure( + () => this.RunGlobalTypeChecking(compilation, content, cancellationToken), + "error while running global type checking"); + } + catch (Exception ex) + { + this.LogException(ex); + } + }, cancellationToken); - if (runSynchronously) task.RunSynchronously(); - else if (!cancellationToken.IsCancellationRequested) task.Start(); + if (runSynchronously) + { + task.RunSynchronously(); + } + else if (!cancellationToken.IsCancellationRequested) + { + task.Start(); + } return task; } /// - /// Runs a type checking on the given content using the given CompilationUnit instance, with the given cancellation token. - /// Once the type checking is done, locks all processing, and updates the Compilation property with the built content + /// Runs a type checking on the given content using the given CompilationUnit instance, with the given cancellation token. + /// Once the type checking is done, locks all processing, and updates the Compilation property with the built content /// for all files that have not been modified while the type checking was running. Updates and publishes the semantic diagnostics for those files. - /// For each file that has been modified while the type checking was running, + /// For each file that has been modified while the type checking was running, /// queues a task calling TypeCheckFile on that file with the given cancellationToken. - /// Throws an ArgumentNullException if any of the given arguments is null. + /// Throws an ArgumentNullException if any of the given arguments is null. /// private void RunGlobalTypeChecking(CompilationUnit compilation, ImmutableDictionary content, CancellationToken cancellationToken) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); - if (content == null) throw new ArgumentNullException(nameof(content)); - if (cancellationToken == null) throw new ArgumentNullException(nameof(cancellationToken)); + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + if (cancellationToken == null) + { + throw new ArgumentNullException(nameof(cancellationToken)); + } - var diagnostics = QsCompilerError.RaiseOnFailure(() => - TypeChecking.RunTypeChecking(compilation, content, cancellationToken), + var diagnostics = QsCompilerError.RaiseOnFailure( + () => TypeChecking.RunTypeChecking(compilation, content, cancellationToken), "error while running type checking in background"); this.Processing.QueueForExecution(() => // -> could be fast-tracked to be executed immediately, but needs exclusive access (write thread) { - foreach (var file in this.FileContentManagers.Values) file.SyncRoot.EnterUpgradeableReadLock(); - this.ChangedFiles.SyncRoot.EnterReadLock(); // need to lock such that the content of the list remains the same during the execution of the block below + foreach (var file in this.fileContentManagers.Values) + { + file.SyncRoot.EnterUpgradeableReadLock(); + } + this.changedFiles.SyncRoot.EnterReadLock(); // need to lock such that the content of the list remains the same during the execution of the block below try { - var changedFiles = this.ChangedFiles.ToImmutableHashSet(); - if (cancellationToken.IsCancellationRequested) return; + var changedFiles = this.changedFiles.ToImmutableHashSet(); + if (cancellationToken.IsCancellationRequested) + { + return; + } var validCallables = compilation.GetCallables().Values.Where(c => !changedFiles.Contains(c.SourceFile)); var validTypes = compilation.GetTypes().Values.Where(t => !changedFiles.Contains(t.SourceFile)); - this.CompilationUnit.UpdateCallables(validCallables); - this.CompilationUnit.UpdateTypes(validTypes); + this.compilationUnit.UpdateCallables(validCallables); + this.compilationUnit.UpdateTypes(validTypes); - if (cancellationToken.IsCancellationRequested) return; + if (cancellationToken.IsCancellationRequested) + { + return; + } var allDiagnostics = diagnostics.ToLookup(msg => NonNullable.New(msg.Source)); - foreach (var file in this.FileContentManagers.Values) + foreach (var file in this.fileContentManagers.Values) { - if (changedFiles.Contains(file.FileName)) continue; + if (changedFiles.Contains(file.FileName)) + { + continue; + } file.ReplaceSemanticDiagnostics(allDiagnostics[file.FileName]); this.PublishDiagnostics(file.Diagnostics()); } - foreach (var docKey in changedFiles) + foreach (var docKey in changedFiles) { this.Processing.QueueForExecutionAsync(() => - QsCompilerError.RaiseOnFailure(() => this.TypeCheckFile(docKey, cancellationToken), - "error while re-doing the type checking for an source file that had been modified during a global type check update")); + QsCompilerError.RaiseOnFailure( + () => this.TypeCheckFile(docKey, cancellationToken), + "error while re-doing the type checking for an source file that had been modified during a global type check update")); } } finally { - this.ChangedFiles.SyncRoot.ExitReadLock(); - foreach (var file in this.FileContentManagers.Values) file.SyncRoot.ExitUpgradeableReadLock(); + this.changedFiles.SyncRoot.ExitReadLock(); + foreach (var file in this.fileContentManagers.Values) + { + file.SyncRoot.ExitUpgradeableReadLock(); + } } }); } /// - /// If the given cancellation token is not cancellation requested, and the file with the document id is listed as source file, + /// If the given cancellation token is not cancellation requested, and the file with the document id is listed as source file, /// updates and resolves all global symbols for that file, and runs a type checking on the obtained content using the Compilation property. - /// Replaces the header diagnostics and the semantic diagnostics in the file with the obtained diagnostics, and publishes them. + /// Replaces the header diagnostics and the semantic diagnostics in the file with the obtained diagnostics, and publishes them. /// Throws an ArgumentNullException if the given cancellation token is null. /// private void TypeCheckFile(NonNullable fileId, CancellationToken cancellationToken) { - if (cancellationToken == null) throw new ArgumentNullException(nameof(cancellationToken)); - var isSource = this.FileContentManagers.TryGetValue(fileId, out FileContentManager file); - if (!isSource || cancellationToken.IsCancellationRequested) return; + if (cancellationToken == null) + { + throw new ArgumentNullException(nameof(cancellationToken)); + } + var isSource = this.fileContentManagers.TryGetValue(fileId, out FileContentManager file); + if (!isSource || cancellationToken.IsCancellationRequested) + { + return; + } - this.CompilationUnit.EnterUpgradeableReadLock(); + this.compilationUnit.EnterUpgradeableReadLock(); file.SyncRoot.EnterUpgradeableReadLock(); try - { // NOTE: invalidating the right diagnostics is kind of error prone, + { + // NOTE: invalidating the right diagnostics is kind of error prone, // so instead of calling file.UpdateTypeChecking the type checking for the entire file is simply recomputed. var diagnostics = new List(); - var contentToCompile = file.UpdateGlobalSymbols(this.CompilationUnit, diagnostics); - file.ImportGlobalSymbols(this.CompilationUnit, diagnostics); - TypeChecking.ResolveGlobalSymbols(this.CompilationUnit.GlobalSymbols, diagnostics, file.FileName.Value); + var contentToCompile = file.UpdateGlobalSymbols(this.compilationUnit, diagnostics); + file.ImportGlobalSymbols(this.compilationUnit, diagnostics); + TypeChecking.ResolveGlobalSymbols(this.compilationUnit.GlobalSymbols, diagnostics, file.FileName.Value); file.ReplaceHeaderDiagnostics(diagnostics); - diagnostics = TypeChecking.RunTypeChecking(this.CompilationUnit, file.GetDeclarationTrees(contentToCompile), new CancellationToken()); + diagnostics = TypeChecking.RunTypeChecking(this.compilationUnit, file.GetDeclarationTrees(contentToCompile), CancellationToken.None); file.ReplaceSemanticDiagnostics(diagnostics); this.PublishDiagnostics(file.Diagnostics()); } - finally { + finally + { file.SyncRoot.ExitUpgradeableReadLock(); - this.CompilationUnit.ExitUpgradeableReadLock(); + this.compilationUnit.ExitUpgradeableReadLock(); } } - // editor commands that need to be blocking /// - /// Returns the workspace edit that describes the changes to be done if the symbol at the given position - if any - is renamed to the given name. + /// Returns the workspace edit that describes the changes to be done if the symbol at the given position - if any - is renamed to the given name. /// Returns null if no symbol exists at the specified position, /// or if some parameters are unspecified (null), /// or if the specified position is not a valid position within the file. /// public WorkspaceEdit Rename(RenameParams param) { - if (!TryGetFileId(param?.TextDocument?.Uri, out NonNullable docKey)) return null; - var success = this.Processing.QueueForExecution(() => // FIXME: the correct thing to do here would be to call FlushAndExecute...! - this.FileContentManagers.TryGetValue(docKey, out FileContentManager file) - ? file.Rename(this.CompilationUnit, param.Position, param.NewName) - : null - , out WorkspaceEdit edit); + if (!TryGetFileId(param?.TextDocument?.Uri, out NonNullable docKey)) + { + return null; + } + // FIXME: the correct thing to do here would be to call FlushAndExecute...! + var success = this.Processing.QueueForExecution( + () => this.fileContentManagers.TryGetValue(docKey, out FileContentManager file) + ? file.Rename(this.compilationUnit, param.Position, param.NewName) + : null, + out WorkspaceEdit edit); return success ? edit : null; } - // routines related to providing information for non-blocking editor commands // -> these commands need to be responsive and therefore won't wait for any processing to finish // -> if the query cannot be processed immediately, they simply return null @@ -621,13 +791,18 @@ public WorkspaceEdit Rename(RenameParams param) /// /// NOTE: In debug mode, exceptions are always logged even if this parameter is true. /// - internal T FileQuery(TextDocumentIdentifier textDocument, - Func Query, bool suppressExceptionLogging = false) + internal T FileQuery( + TextDocumentIdentifier textDocument, + Func query, + bool suppressExceptionLogging = false) where T : class { T TryQueryFile(FileContentManager f) { - try { return Query(f, this.CompilationUnit); } + try + { + return query(f, this.compilationUnit); + } catch (Exception ex) { #if DEBUG @@ -638,40 +813,42 @@ T TryQueryFile(FileContentManager f) return null; } } - if (!TryGetFileId(textDocument?.Uri, out NonNullable docKey)) return null; - var isSource = this.FileContentManagers.TryGetValue(docKey, out FileContentManager file); + if (!TryGetFileId(textDocument?.Uri, out NonNullable docKey)) + { + return null; + } + var isSource = this.fileContentManagers.TryGetValue(docKey, out FileContentManager file); return isSource ? TryQueryFile(file) : null; } - // routines giving read access to the compilation state (e.g. for the command line compiler, or testing/debugging) // -> these routines will wait for any processing to finish before executing the query /// /// If a TextDocumentIdentifier is given, returns an array with a single item containing all current diagnostics for the given file. /// Returns null if the given file is not listed as source file. - /// If no TextDocumentIdentifier is given, then the diagnostics for all source files are returned. - /// Note: this method waits for all currently running or queued tasks to finish + /// If no TextDocumentIdentifier is given, then the diagnostics for all source files are returned. + /// Note: this method waits for all currently running or queued tasks to finish /// before accumulating the diagnostics by calling FlushAndExecute. /// public PublishDiagnosticParams[] GetDiagnostics(TextDocumentIdentifier textDocument = null) => this.FlushAndExecute(() => textDocument != null ? this.FileQuery(textDocument, (file, _) => new PublishDiagnosticParams[] { file.Diagnostics() }) - : this.FileContentManagers.Values.Select(file => file.Diagnostics()).ToArray()); + : this.fileContentManagers.Values.Select(file => file.Diagnostics()).ToArray()); /// /// Returns a sequence of all source files that are currently contained in this compilation unit. - /// Note: this method waits for all currently running or queued tasks to finish + /// Note: this method waits for all currently running or queued tasks to finish /// before constructing the requested information by calling FlushAndExecute. /// public IEnumerable GetSourceFiles() => - this.FlushAndExecute(() => this.FileContentManagers.Keys.Select(id => new Uri(id.Value)).ToImmutableArray().AsEnumerable()); + this.FlushAndExecute(() => this.fileContentManagers.Keys.Select(id => new Uri(id.Value)).ToImmutableArray().AsEnumerable()); /// /// Returns the current file content (text representation) in memory. /// Returns null if the given file is not listed as source file. - /// Note: this method waits for all currently running or queued tasks to finish + /// Note: this method waits for all currently running or queued tasks to finish /// before getting the file content by calling FlushAndExecute. /// public string[] FileContentInMemory(TextDocumentIdentifier textDocument) => @@ -680,9 +857,9 @@ public string[] FileContentInMemory(TextDocumentIdentifier textDocument) => /// /// Returns the current tokenization of the file content in memory. - /// Each returned array item contains the array of tokens on the line with the corresponding index. + /// Each returned array item contains the array of tokens on the line with the corresponding index. /// Returns null if the given file is not listed as source file. - /// Note: this method waits for all currently running or queued tasks to finish + /// Note: this method waits for all currently running or queued tasks to finish /// before getting the file content by calling FlushAndExecute. /// public QsFragmentKind[][] GetTokenization(TextDocumentIdentifier textDocument) => @@ -691,198 +868,218 @@ public QsFragmentKind[][] GetTokenization(TextDocumentIdentifier textDocument) = /// /// Returns the syntax tree for the current state of the compilation. - /// Note: this method waits for all currently running or queued tasks to finish + /// Note: this method waits for all currently running or queued tasks to finish /// before constructing the syntax tree by calling FlushAndExecute. /// public IEnumerable GetSyntaxTree() => - this.FlushAndExecute(() => this.CompilationUnit.Build().Namespaces.AsEnumerable()); + this.FlushAndExecute(() => this.compilationUnit.Build().Namespaces.AsEnumerable()); /// - /// Returns a Compilation object containing all information about the current state of the compilation. - /// Note: this method waits for all currently running or queued tasks to finish + /// Returns a Compilation object containing all information about the current state of the compilation. + /// Note: this method waits for all currently running or queued tasks to finish /// before constructing the Compilation object by calling FlushAndExecute. /// - public Compilation Build() => - this.FlushAndExecute(() => new Compilation(this)); + public Compilation Build() => this.FlushAndExecute(() => + { + try + { + return new Compilation(this); + } + catch (Exception ex) + { + this.LogException(ex); + return null; + } + }); /// - /// Class used to accumulate all information about the state of a compilation unit in immutable form. + /// Class used to accumulate all information about the state of a compilation unit in immutable form. /// public class Compilation { /// - /// Contains the file IDs assigned by the Q# compiler for all source files included in the compilation. + /// Contains the file IDs assigned by the Q# compiler for all source files included in the compilation. /// public readonly ImmutableHashSet> SourceFiles; + /// - /// Contains the IDs assigned by the Q# compiler for all assemblies referenced in the compilation. + /// Contains the IDs assigned by the Q# compiler for all assemblies referenced in the compilation. /// public readonly ImmutableHashSet> References; /// - /// Contains a dictionary that maps the ID of a file included in the compilation - /// to the text representation of its content. + /// Contains a dictionary that maps the ID of a file included in the compilation + /// to the text representation of its content. /// public readonly ImmutableDictionary, ImmutableArray> FileContent; + /// - /// Contains a dictionary that maps the ID of a file included in the compilation - /// to the tokenization built based on its content. + /// Contains a dictionary that maps the ID of a file included in the compilation + /// to the tokenization built based on its content. /// public readonly ImmutableDictionary, ImmutableArray>> Tokenization; + /// /// Contains a dictionary that maps the name of a namespace to the compiled Q# namespace. /// public readonly ImmutableDictionary, QsNamespace> SyntaxTree; + /// /// Contains the built Q# compilation. /// - public readonly QsCompilation BuiltCompilation; + public readonly QsCompilation BuiltCompilation; /// - /// Contains a dictionary that maps the name of each namespace defined in the compilation to a look-up - /// containing the names and corresponding short form (if any) of all opened namespaces for that (part of the) namespace in a particular source file. + /// Contains a dictionary that maps the name of each namespace defined in the compilation to a look-up + /// containing the names and corresponding short form (if any) of all opened namespaces for that (part of the) namespace in a particular source file. /// - private readonly ImmutableDictionary, ILookup, (NonNullable, string)>> OpenDirectivesForEachFile; + private readonly ImmutableDictionary, ILookup, (NonNullable, string)>> openDirectivesForEachFile; + /// - /// Contains a dictionary that given the ID of a file included in the compilation + /// Contains a dictionary that given the ID of a file included in the compilation /// returns all tokenized code fragments containing namespace declarations in that file. /// - private readonly ImmutableDictionary, ImmutableArray> NamespaceDeclarations; + private readonly ImmutableDictionary, ImmutableArray> namespaceDeclarations; + /// - /// Contains a dictionary that given the fully qualified name of a compiled callable returns its syntax tree. + /// Contains a dictionary that given the fully qualified name of a compiled callable returns its syntax tree. /// public readonly ImmutableDictionary Callables; + /// - /// Contains a dictionary that given the fully qualified name of a compiled type returns its syntax tree. + /// Contains a dictionary that given the fully qualified name of a compiled type returns its syntax tree. /// public readonly ImmutableDictionary Types; /// - /// Contains a dictionary that maps the ID of a file included in the compilation - /// to all scope-related diagnostics generated during compilation. + /// Contains a dictionary that maps the ID of a file included in the compilation + /// to all scope-related diagnostics generated during compilation. /// public readonly ImmutableDictionary, ImmutableArray> ScopeDiagnostics; + /// - /// Contains a dictionary that maps the ID of a file included in the compilation - /// to all syntax-related diagnostics generated during compilation. + /// Contains a dictionary that maps the ID of a file included in the compilation + /// to all syntax-related diagnostics generated during compilation. /// public readonly ImmutableDictionary, ImmutableArray> SyntaxDiagnostics; + /// - /// Contains a dictionary that maps the ID of a file included in the compilation - /// to all context-related diagnostics generated during compilation. + /// Contains a dictionary that maps the ID of a file included in the compilation + /// to all context-related diagnostics generated during compilation. /// public readonly ImmutableDictionary, ImmutableArray> ContextDiagnostics; + /// - /// Contains a dictionary that maps the ID of a file included in the compilation - /// to all diagnostics generated during compilation related to header information for declarations. + /// Contains a dictionary that maps the ID of a file included in the compilation + /// to all diagnostics generated during compilation related to header information for declarations. /// public readonly ImmutableDictionary, ImmutableArray> HeaderDiagnostics; + /// - /// Contains a dictionary that maps the ID of a file included in the compilation - /// to all semantic diagnostics generated during compilation for the specified implementations. + /// Contains a dictionary that maps the ID of a file included in the compilation + /// to all semantic diagnostics generated during compilation for the specified implementations. /// public readonly ImmutableDictionary, ImmutableArray> SemanticDiagnostics; /// - /// Maps a file ID assigned by the Q# compiler to all diagnostics generated during compilation. - /// Returns an empty sequence if no file with the given ID has been included in the compilation. + /// Maps a file ID assigned by the Q# compiler to all diagnostics generated during compilation. + /// Returns an empty sequence if no file with the given ID has been included in the compilation. /// public IEnumerable Diagnostics(NonNullable file) => - this.SourceFiles.Contains(file) ? - ScopeDiagnostics[file] - .Concat(SyntaxDiagnostics[file]) - .Concat(ContextDiagnostics[file]) - .Concat(HeaderDiagnostics[file]) - .Concat(SemanticDiagnostics[file]) : + this.SourceFiles.Contains(file) ? + this.ScopeDiagnostics[file] + .Concat(this.SyntaxDiagnostics[file]) + .Concat(this.ContextDiagnostics[file]) + .Concat(this.HeaderDiagnostics[file]) + .Concat(this.SemanticDiagnostics[file]) : Enumerable.Empty(); /// /// Returns all diagnostics generated during compilation. /// public IEnumerable Diagnostics() => - ScopeDiagnostics.Values - .Concat(SyntaxDiagnostics.Values) - .Concat(ContextDiagnostics.Values) - .Concat(HeaderDiagnostics.Values) - .Concat(SemanticDiagnostics.Values) + this.ScopeDiagnostics.Values + .Concat(this.SyntaxDiagnostics.Values) + .Concat(this.ContextDiagnostics.Values) + .Concat(this.HeaderDiagnostics.Values) + .Concat(this.SemanticDiagnostics.Values) .SelectMany(d => d); /// - /// If a source file with the given name exists in the compilation, and if there is exactly one (partial) namespace with the given name, - /// returns the (non-documenting) comments associated with that namespace declaration. - /// Returns a set of empty comments otherwise. + /// If a source file with the given name exists in the compilation, and if there is exactly one (partial) namespace with the given name, + /// returns the (non-documenting) comments associated with that namespace declaration. + /// Returns a set of empty comments otherwise. /// public QsComments NamespaceComments(NonNullable sourceFile, NonNullable nsName) { - if (!this.NamespaceDeclarations.TryGetValue(sourceFile, out ImmutableArray namespaces)) return QsComments.Empty; + if (!this.namespaceDeclarations.TryGetValue(sourceFile, out ImmutableArray namespaces)) + { + return QsComments.Empty; + } var declarations = namespaces.Where(token => token.Kind?.DeclaredNamespaceName(null) == nsName.Value); return declarations.Count() == 1 ? declarations.Single().Comments : QsComments.Empty; } /// - /// Given ID of a source file and the name of a namespace, - /// returns the names and corresponding short form (if any) of all opened namespaces for the (part of the) namespace in that file. - /// Returns an empy sequence if no source file with the given ID and/or namespace with the given name exists in the compilation. + /// Given ID of a source file and the name of a namespace, + /// returns the names and corresponding short form (if any) of all opened namespaces for the (part of the) namespace in that file. + /// Returns an empy sequence if no source file with the given ID and/or namespace with the given name exists in the compilation. /// public IEnumerable<(NonNullable, string)> OpenDirectives(NonNullable sourceFile, NonNullable nsName) => - this.OpenDirectivesForEachFile - .TryGetValue(nsName, out ILookup, (NonNullable, string)> lookUp) + this.openDirectivesForEachFile + .TryGetValue(nsName, out ILookup, (NonNullable, string)> lookUp) ? lookUp[sourceFile] : Enumerable.Empty<(NonNullable, string)>(); + /// /// Returns all the names of all callable and types defined in the namespace with the given name. - /// The returned names are unique and do not contain duplications e.g. for types and the corresponding constructor. - /// Returns an empty sequence if no namespace with the given name exists in the compilation. + /// The returned names are unique and do not contain duplications e.g. for types and the corresponding constructor. + /// Returns an empty sequence if no namespace with the given name exists in the compilation. /// public IEnumerable> SymbolsDefinedInNamespace(NonNullable nsName) => - this.SyntaxTree.TryGetValue(nsName, out QsNamespace ns) + this.SyntaxTree.TryGetValue(nsName, out QsNamespace ns) ? ns.Elements.Select(element => (element is QsNamespaceElement.QsCallable c) ? c.Item.FullName.Name.Value : null) - .Where(name => name != null).Select(name => NonNullable.New(name)) + .Where(name => name != null).Select(name => NonNullable.New(name)) : Enumerable.Empty>(); - internal Compilation(CompilationUnitManager manager) { - try - { - this.BuiltCompilation = manager.CompilationUnit.Build(); - this.SourceFiles = manager.FileContentManagers.Keys.ToImmutableHashSet(); - this.References = manager.CompilationUnit.Externals.Declarations.Keys.ToImmutableHashSet(); - - this.FileContent = this.SourceFiles - .Select(file => (file, manager.FileContentManagers[file].GetLines().Select(line => line.Text).ToImmutableArray())) - .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); - this.Tokenization = this.SourceFiles - .Select(file => (file, manager.FileContentManagers[file].GetTokenizedLines().Select(line => line.Select(frag => frag.Copy()).ToImmutableArray()).ToImmutableArray())) - .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); - this.SyntaxTree = this.BuiltCompilation.Namespaces.ToImmutableDictionary(ns => ns.Name); - - this.OpenDirectivesForEachFile = this.SyntaxTree.Keys.ToImmutableDictionary( - nsName => nsName, - nsName => manager.CompilationUnit.GetOpenDirectives(nsName)); - this.NamespaceDeclarations = this.SourceFiles - .Select(file => (file, manager.FileContentManagers[file].NamespaceDeclarationTokens().Select(t => t.GetFragmentWithClosingComments()).ToImmutableArray())) - .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); - this.Callables = this.SyntaxTree.Values.GlobalCallableResolutions(); - this.Types = this.SyntaxTree.Values.GlobalTypeResolutions(); - - this.ScopeDiagnostics = this.SourceFiles - .Select(file => (file, manager.FileContentManagers[file].CurrentScopeDiagnostics())) - .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); - this.SyntaxDiagnostics = this.SourceFiles - .Select(file => (file, manager.FileContentManagers[file].CurrentSyntaxDiagnostics())) - .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); - this.ContextDiagnostics = this.SourceFiles - .Select(file => (file, manager.FileContentManagers[file].CurrentContextDiagnostics())) - .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); - this.HeaderDiagnostics = this.SourceFiles - .Select(file => (file, manager.FileContentManagers[file].CurrentHeaderDiagnostics())) - .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); - this.SemanticDiagnostics = this.SourceFiles - .Select(file => (file, manager.FileContentManagers[file].CurrentSemanticDiagnostics())) - .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); - } - catch (Exception ex) { manager.LogException(ex); } + this.BuiltCompilation = manager.compilationUnit.Build(); + this.SourceFiles = manager.fileContentManagers.Keys.ToImmutableHashSet(); + this.References = manager.compilationUnit.Externals.Declarations.Keys.ToImmutableHashSet(); + + this.FileContent = this.SourceFiles + .Select(file => (file, manager.fileContentManagers[file].GetLines().Select(line => line.Text).ToImmutableArray())) + .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); + this.Tokenization = this.SourceFiles + .Select(file => (file, manager.fileContentManagers[file].GetTokenizedLines().Select(line => line.Select(frag => frag.Copy()).ToImmutableArray()).ToImmutableArray())) + .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); + this.SyntaxTree = this.BuiltCompilation.Namespaces.ToImmutableDictionary(ns => ns.Name); + + this.openDirectivesForEachFile = this.SyntaxTree.Keys.ToImmutableDictionary( + nsName => nsName, + nsName => manager.compilationUnit.GetOpenDirectives(nsName)); + this.namespaceDeclarations = this.SourceFiles + .Select(file => (file, manager.fileContentManagers[file].NamespaceDeclarationTokens().Select(t => t.GetFragmentWithClosingComments()).ToImmutableArray())) + .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); + this.Callables = this.SyntaxTree.Values.GlobalCallableResolutions(); + this.Types = this.SyntaxTree.Values.GlobalTypeResolutions(); + + this.ScopeDiagnostics = this.SourceFiles + .Select(file => (file, manager.fileContentManagers[file].CurrentScopeDiagnostics())) + .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); + this.SyntaxDiagnostics = this.SourceFiles + .Select(file => (file, manager.fileContentManagers[file].CurrentSyntaxDiagnostics())) + .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); + this.ContextDiagnostics = this.SourceFiles + .Select(file => (file, manager.fileContentManagers[file].CurrentContextDiagnostics())) + .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); + this.HeaderDiagnostics = this.SourceFiles + .Select(file => (file, manager.fileContentManagers[file].CurrentHeaderDiagnostics())) + .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); + this.SemanticDiagnostics = this.SourceFiles + .Select(file => (file, manager.fileContentManagers[file].CurrentSemanticDiagnostics())) + .ToImmutableDictionary(tuple => tuple.Item1, tuple => tuple.Item2); } } } diff --git a/src/QsCompiler/CompilationManager/ContextBuilder.cs b/src/QsCompiler/CompilationManager/ContextBuilder.cs index f0d8f79d86..be1d051224 100644 --- a/src/QsCompiler/CompilationManager/ContextBuilder.cs +++ b/src/QsCompiler/CompilationManager/ContextBuilder.cs @@ -13,7 +13,6 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { internal static class ContextBuilder @@ -27,7 +26,10 @@ internal static class ContextBuilder /// internal static void VerifyTokenOrdering(IEnumerable tokens) { - if (tokens == null || tokens.Any(x => x == null)) throw new ArgumentNullException(nameof(tokens)); + if (tokens == null || tokens.Any(x => x == null)) + { + throw new ArgumentNullException(nameof(tokens)); + } Position previousEnding = null; foreach (var token in tokens) @@ -36,7 +38,7 @@ internal static void VerifyTokenOrdering(IEnumerable tokens) if (!(previousEnding?.IsSmallerThanOrEqualTo(range.Start) ?? true)) { throw new ArgumentException($"the given tokens to update are not ordered according to their range - \n" + - $"Ranges were: {String.Join("\n", tokens.Select(t => t.GetRange().DiagnosticString()))}"); + $"Ranges were: {string.Join("\n", tokens.Select(t => t.GetRange().DiagnosticString()))}"); } previousEnding = range.End; } @@ -48,11 +50,16 @@ internal static void VerifyTokenOrdering(IEnumerable tokens) /// internal static CodeFragment.TokenIndex FirstToken(this FileContentManager file) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } var lineNr = 0; - while (file.GetTokenizedLine(lineNr).Length == 0 && ++lineNr < file.NrLines()) ; - return lineNr == file.NrLines() - ? null + while (file.GetTokenizedLine(lineNr).Length == 0 && ++lineNr < file.NrLines()) + { + } + return lineNr == file.NrLines() + ? null : new CodeFragment.TokenIndex(file, lineNr, 0); } @@ -62,9 +69,14 @@ internal static CodeFragment.TokenIndex FirstToken(this FileContentManager file) /// internal static CodeFragment.TokenIndex LastToken(this FileContentManager file) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } var lastNonEmpty = file.NrLines(); - while (lastNonEmpty-- > 0 && file.GetTokenizedLine(lastNonEmpty).Length == 0) ; + while (lastNonEmpty-- > 0 && file.GetTokenizedLine(lastNonEmpty).Length == 0) + { + } return lastNonEmpty < 0 ? null : new CodeFragment.TokenIndex(file, lastNonEmpty, file.GetTokenizedLine(lastNonEmpty).Length - 1); @@ -77,8 +89,14 @@ internal static CodeFragment.TokenIndex LastToken(this FileContentManager file) /// internal static bool IsWithinRange(this CodeFragment token, LSP.Range range) { - if (token == null) throw new ArgumentNullException(nameof(token)); - if (!Utils.IsValidRange(range)) throw new ArgumentException("invalid range"); + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + if (!Utils.IsValidRange(range)) + { + throw new ArgumentException("invalid range"); + } var tokenRange = token.GetRange(); return tokenRange.Start.IsWithinRange(range) && tokenRange.End.IsWithinRange(range, includeEnd: true); } @@ -107,30 +125,42 @@ internal static Func TokensAfter(Position pos) => internal static Func NotOverlappingWith(LSP.Range relRange) => token => token.IsWithinRange(new LSP.Range { Start = new Position(0, 0), End = relRange.Start }) || - (ContextBuilder.TokensAfter(relRange.End))(token); + TokensAfter(relRange.End)(token); /// /// Returns the CodeFragment at the given position if such a fragment exists and null otherwise. /// If the given position is equal to the end of the fragment, that fragment is returned if includeEnd is set to true. - /// If a fragment is determined for the given position, returns the corresponding token index as out parameter. - /// Note that token indices are no longer valid as soon as the file is modified (possibly e.g. by queued background processing). - /// Any query or attempt operation on the returned token index may result in an exception once it lost its validity. + /// If a fragment is determined for the given position, returns the corresponding token index as out parameter. + /// Note that token indices are no longer valid as soon as the file is modified (possibly e.g. by queued background processing). + /// Any query or attempt operation on the returned token index may result in an exception once it lost its validity. /// Returns null if the given file or the specified position is null, /// or if the specified position is not within the current Content range. /// - public static CodeFragment TryGetFragmentAt(this FileContentManager file, Position pos, - out CodeFragment.TokenIndex tIndex, bool includeEnd = false) + public static CodeFragment TryGetFragmentAt( + this FileContentManager file, + Position pos, + out CodeFragment.TokenIndex tIndex, + bool includeEnd = false) { tIndex = null; - if (file == null || pos == null || !Utils.IsValidPosition(pos, file)) return null; + if (file == null || pos == null || !Utils.IsValidPosition(pos, file)) + { + return null; + } var start = pos.Line; var previous = file.GetTokenizedLine(start).Where(token => token.GetRange().Start.Character <= pos.Character).ToImmutableArray(); - while (!previous.Any() && --start >= 0) previous = file.GetTokenizedLine(start); - if (!previous.Any()) return null; + while (!previous.Any() && --start >= 0) + { + previous = file.GetTokenizedLine(start); + } + if (!previous.Any()) + { + return null; + } var lastPreceding = previous.Last().WithUpdatedLineNumber(start); var overlaps = includeEnd - ? pos.IsSmallerThanOrEqualTo(lastPreceding.GetRange().End) + ? pos.IsSmallerThanOrEqualTo(lastPreceding.GetRange().End) : pos.IsSmallerThan(lastPreceding.GetRange().End); tIndex = overlaps ? new CodeFragment.TokenIndex(file, start, previous.Length - 1) : null; return overlaps ? lastPreceding : null; @@ -138,60 +168,82 @@ public static CodeFragment TryGetFragmentAt(this FileContentManager file, Positi /// /// Returns the name of the namespace to which the given position belongs. - /// Returns null if the given file or position is null, or if no such namespace can be found + /// Returns null if the given file or position is null, or if no such namespace can be found /// (e.g. because the namespace name is invalid). /// public static string TryGetNamespaceAt(this FileContentManager file, Position pos) { - if (file == null || pos == null || !Utils.IsValidPosition(pos, file)) return null; + if (file == null || pos == null || !Utils.IsValidPosition(pos, file)) + { + return null; + } var namespaces = file.GetNamespaceDeclarations(); var preceding = namespaces.TakeWhile(tuple => tuple.Item2.Start.IsSmallerThan(pos)); return preceding.Any() ? preceding.Last().Item1.Value : null; } /// - /// Returns the position and the kind of the closest specialization preceding the given position, + /// Returns the position and the kind of the closest specialization preceding the given position, /// and the name of the callable it belongs to as well as its position as Nullable. /// Returns null if the given file or position is null, or if no preceding callable can be found (e.g. because the callable name is invalid). - /// If a callable name but no specializations (preceding or otherwise) within that callable can be found, - /// assumes that the correct specialization is an auto-inserted default body, - /// and returns QsBody as well as the start position of the callable declaration along with the callable name and its position. + /// If a callable name but no specializations (preceding or otherwise) within that callable can be found, + /// assumes that the correct specialization is an auto-inserted default body, + /// and returns QsBody as well as the start position of the callable declaration along with the callable name and its position. /// If a callable name as well as existing specializations can be found, but no specialization precedes the given position, - /// returns null for the specialization kind as well as for its position. + /// returns null for the specialization kind as well as for its position. /// public static ((NonNullable, Position), (QsSpecializationKind, Position))? TryGetClosestSpecialization(this FileContentManager file, Position pos) { QsSpecializationKind GetSpecializationKind(CodeFragment fragment) { var specDecl = fragment.Kind.DeclaredSpecialization(); - if (specDecl.IsNull) return null; + if (specDecl.IsNull) + { + return null; + } var ((kind, gen), typeArgs) = specDecl.Item; // note: if we want to support type specializations we need to compute the signature of the spec to find the right one return kind; } - if (file == null || pos == null || !Utils.IsValidPosition(pos, file)) return null; + if (file == null || pos == null || !Utils.IsValidPosition(pos, file)) + { + return null; + } file.SyncRoot.EnterReadLock(); - try { + try + { var declarations = file.CallableDeclarationTokens(); var precedingDecl = declarations.TakeWhile(tIndex => tIndex.GetFragment().GetRange().Start.IsSmallerThan(pos)); - if (!precedingDecl.Any()) return null; + if (!precedingDecl.Any()) + { + return null; + } var closestCallable = precedingDecl.Last(); var callablePosition = closestCallable.GetFragment().GetRange().Start; var callableName = closestCallable.GetFragment().Kind.DeclaredCallableName(null); - if (callableName == null) return null; + if (callableName == null) + { + return null; + } var specializations = FileHeader.FilterCallableSpecializations(closestCallable.GetChildren(deep: false).Select(tIndex => tIndex.GetFragment())); var precedingSpec = specializations.TakeWhile(fragment => fragment.GetRange().Start.IsSmallerThan(pos)); var lastPreceding = precedingSpec.Any() ? precedingSpec.Last() : null; - if (specializations.Any() && lastPreceding == null) // the given position is within a callable declaration - { return ((NonNullable.New(callableName), callablePosition), (null, null)); } + if (specializations.Any() && lastPreceding == null) + { + // the given position is within a callable declaration + return ((NonNullable.New(callableName), callablePosition), (null, null)); + } return lastPreceding == null ? ((NonNullable.New(callableName), callablePosition), (QsSpecializationKind.QsBody, callablePosition)) : ((NonNullable.New(callableName), callablePosition), (GetSpecializationKind(lastPreceding), lastPreceding.GetRange().Start)); } - finally { file.SyncRoot.ExitReadLock(); } + finally + { + file.SyncRoot.ExitReadLock(); + } } /// @@ -203,12 +255,20 @@ QsSpecializationKind GetSpecializationKind(CodeFragment fragment) /// internal static bool ContainsTokensOverlappingWith(this FileContentManager file, LSP.Range range) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (!Utils.IsValidRange(range, file)) throw new ArgumentOutOfRangeException(nameof(range)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (!Utils.IsValidRange(range, file)) + { + throw new ArgumentOutOfRangeException(nameof(range)); + } var (start, end) = (range.Start.Line, range.End.Line); if (start != end && file.GetTokenizedLines(start + 1, end - start - 1).SelectMany(x => x).Any()) - { return true; }; + { + return true; + } var inRange = file.GetTokenizedLine(start).Where(TokensAfter(new Position(0, range.Start.Character))); // checking tokens overlapping with range.Start below inRange = start == end @@ -217,8 +277,8 @@ internal static bool ContainsTokensOverlappingWith(this FileContentManager file, if (inRange.Any()) { QsCompilerError.Raise($"{range.DiagnosticString()} overlaps for start = {start}, end = {end}, \n\n" + - $"{String.Join("\n", file.GetTokenizedLine(start).Select(x => $"{x.GetRange().DiagnosticString()}"))},\n\n " + - $"{String.Join("\n", file.GetTokenizedLine(end).Select(x => $"{x.GetRange().DiagnosticString()}"))},"); + $"{string.Join("\n", file.GetTokenizedLine(start).Select(x => $"{x.GetRange().DiagnosticString()}"))},\n\n " + + $"{string.Join("\n", file.GetTokenizedLine(end).Select(x => $"{x.GetRange().DiagnosticString()}"))},"); return true; } @@ -234,8 +294,14 @@ internal static bool ContainsTokensOverlappingWith(this FileContentManager file, /// internal static List MergeTokens(IEnumerable current, IEnumerable updated) { - if (current == null || current.Any(x => x == null)) throw new ArgumentNullException(nameof(current)); - if (updated == null || updated.Any(x => x == null)) throw new ArgumentNullException(nameof(updated)); + if (current == null || current.Any(x => x == null)) + { + throw new ArgumentNullException(nameof(current)); + } + if (updated == null || updated.Any(x => x == null)) + { + throw new ArgumentNullException(nameof(updated)); + } var merged = new List(0); void NextBatch(ref IEnumerable batch, IEnumerable next) @@ -260,19 +326,22 @@ void NextBatch(ref IEnumerable batch, IEnumerable ne } var mergedTokens = merged.ToList(); - QsCompilerError.RaiseOnFailure(() => VerifyTokenOrdering(mergedTokens), "merged tokens are not ordered"); + QsCompilerError.RaiseOnFailure(() => VerifyTokenOrdering(mergedTokens), "merged tokens are not ordered"); return mergedTokens; } /// /// Comparing for equality by value, - /// returns the index of the first element in the given list of CodeFragments that matches the given token, - /// or -1 if no such element exists. + /// returns the index of the first element in the given list of CodeFragments that matches the given token, + /// or -1 if no such element exists. /// Throws an ArgumentNullException if the given list is null. /// internal static int FindByValue(this IReadOnlyList list, CodeFragment token) { - if (list == null) throw new ArgumentNullException(nameof(list)); + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } if (token == null) { var nrNonNull = list.TakeWhile(x => x != null).Count(); @@ -281,7 +350,9 @@ internal static int FindByValue(this IReadOnlyList list, CodeFragm var index = -1; var tokenRange = token.GetRange(); - while (++index < list.Count && list[index].GetRange().Start.IsSmallerThan(tokenRange.Start)); + while (++index < list.Count && list[index].GetRange().Start.IsSmallerThan(tokenRange.Start)) + { + } return index < list.Count && list[index].Equals(token) ? index : -1; } @@ -292,14 +363,20 @@ internal static int FindByValue(this IReadOnlyList list, CodeFragm /// internal static CodeFragment.TokenIndex GetNonEmptyParent(this CodeFragment.TokenIndex tIndex) { - if (tIndex == null) throw new ArgumentNullException(nameof(tIndex)); + if (tIndex == null) + { + throw new ArgumentNullException(nameof(tIndex)); + } var tokenIndex = new CodeFragment.TokenIndex(tIndex); var indentation = tokenIndex.GetFragment().Indentation; - while (--tokenIndex != null) + while (--tokenIndex != null) { var fragment = tokenIndex.GetFragment(); - if (fragment.Kind != null && fragment.Indentation < indentation) break; // ignore empty fragments + if (fragment.Kind != null && fragment.Indentation < indentation) + { + break; // ignore empty fragments + } } return tokenIndex != null && tokenIndex.GetFragment().Indentation == indentation - 1 ? tokenIndex : null; } @@ -310,27 +387,40 @@ internal static CodeFragment.TokenIndex GetNonEmptyParent(this CodeFragment.Toke /// internal static IEnumerable GetNonEmptyParents(this CodeFragment.TokenIndex tIndex) { - if (tIndex == null) throw new ArgumentNullException(nameof(tIndex)); + if (tIndex == null) + { + throw new ArgumentNullException(nameof(tIndex)); + } for (var current = tIndex.GetNonEmptyParent(); current != null; current = current.GetNonEmptyParent()) - { yield return current; } + { + yield return current; + } } /// /// Returnes an IEnumerable with the indices of all children of the given token. - /// If deep is set to true (default value), then the returned children are all following tokens - /// with a higher indentation level than the token corresponding to tIndex + /// If deep is set to true (default value), then the returned children are all following tokens + /// with a higher indentation level than the token corresponding to tIndex /// up to the point where we are at the same indentation level again. - /// If deep is set to false, then of those only the tokens with an indentation level that is precisely + /// If deep is set to false, then of those only the tokens with an indentation level that is precisely /// one larger than the one of the parent token are returned. /// Throws an ArgumentNullException if tIndex is null. /// internal static IEnumerable GetChildren(this CodeFragment.TokenIndex tIndex, bool deep = true) { - if (tIndex == null) throw new ArgumentNullException(nameof(tIndex)); + if (tIndex == null) + { + throw new ArgumentNullException(nameof(tIndex)); + } var tokenIndex = new CodeFragment.TokenIndex(tIndex); var indentation = tokenIndex.GetFragment().Indentation; while (++tokenIndex != null && tokenIndex.GetFragment().Indentation > indentation) - { if (deep || tokenIndex.GetFragment().Indentation == indentation + 1) yield return tokenIndex; } + { + if (deep || tokenIndex.GetFragment().Indentation == indentation + 1) + { + yield return tokenIndex; + } + } } /// @@ -340,14 +430,20 @@ internal static CodeFragment.TokenIndex GetNonEmptyParent(this CodeFragment.Toke /// internal static CodeFragment.TokenIndex PreviousOnScope(this CodeFragment.TokenIndex tIndex, bool includeEmpty = false) { - if (tIndex == null) throw new ArgumentNullException(nameof(tIndex)); + if (tIndex == null) + { + throw new ArgumentNullException(nameof(tIndex)); + } var tokenIndex = new CodeFragment.TokenIndex(tIndex); var indentation = tokenIndex.GetFragment().Indentation; while (--tokenIndex != null) { var fragment = tokenIndex.GetFragment(); - if (fragment.Indentation <= indentation && (fragment.Kind != null || includeEmpty)) break; + if (fragment.Indentation <= indentation && (fragment.Kind != null || includeEmpty)) + { + break; + } } return tokenIndex != null && tokenIndex.GetFragment().Indentation == indentation ? tokenIndex : null; } @@ -359,28 +455,36 @@ internal static CodeFragment.TokenIndex PreviousOnScope(this CodeFragment.TokenI /// internal static CodeFragment.TokenIndex NextOnScope(this CodeFragment.TokenIndex tIndex, bool includeEmpty = false) { - if (tIndex == null) throw new ArgumentNullException(nameof(tIndex)); + if (tIndex == null) + { + throw new ArgumentNullException(nameof(tIndex)); + } - var tokenIndex = new CodeFragment.TokenIndex(tIndex); + var tokenIndex = new CodeFragment.TokenIndex(tIndex); var indentation = tokenIndex.GetFragment().Indentation; while (++tokenIndex != null) { var fragment = tokenIndex.GetFragment(); - if (fragment.Indentation <= indentation && (fragment.Kind != null || includeEmpty)) break; + if (fragment.Indentation <= indentation && (fragment.Kind != null || includeEmpty)) + { + break; + } } return tokenIndex != null && tokenIndex.GetFragment().Indentation == indentation ? tokenIndex : null; } - // routines related to "reconstructing" the syntax tree from the saved tokens to do context checks /// - /// Returns the context object for the given token index, ignoring empty fragments. + /// Returns the context object for the given token index, ignoring empty fragments. /// Throws an ArgumentNullException if the given token index is null. /// private static Context.SyntaxTokenContext GetContext(this CodeFragment.TokenIndex tokenIndex) { - if (tokenIndex == null) throw new ArgumentNullException(nameof(tokenIndex)); + if (tokenIndex == null) + { + throw new ArgumentNullException(nameof(tokenIndex)); + } QsNullable Nullable(CodeFragment token, bool precedesSelf) => token?.Kind == null ? QsNullable.Null @@ -394,21 +498,27 @@ QsNullable Nullable(CodeFragment token, bool precedesSelf) => var self = Nullable(fragment, false); // making sure that errors for fragments excluded from compilation still get logged var previous = Nullable(tokenIndex.PreviousOnScope()?.GetFragment(), true); // excludes empty tokens var next = Nullable(tokenIndex.NextOnScope()?.GetFragment(), false); // excludes empty tokens - var parents = tokenIndex.GetNonEmptyParents().Select(tIndex => Nullable(tIndex.GetFragment(), true)).ToArray(); + var parents = tokenIndex.GetNonEmptyParents().Select(tIndex => Nullable(tIndex.GetFragment(), true)).ToArray(); return new Context.SyntaxTokenContext(headerRange, self, previous, next, parents); } /// - /// Given an SortedSet of changed lines, verifies the context for each token on one of these lines, - /// and adds the computed diagnostics to the ones returned as out parameter. - /// Marks the token indices which are to be excluded from compilation due to context errors. + /// Given an SortedSet of changed lines, verifies the context for each token on one of these lines, + /// and adds the computed diagnostics to the ones returned as out parameter. + /// Marks the token indices which are to be excluded from compilation due to context errors. /// Returns the line numbers for which the context diagnostics have been recomputed. - /// Throws an ArgumentNullException if any of the arguments is null. + /// Throws an ArgumentNullException if any of the arguments is null. /// private static HashSet VerifyContext(this FileContentManager file, SortedSet changedLines, out List diagnostics) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (changedLines == null) throw new ArgumentNullException(nameof(changedLines)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (changedLines == null) + { + throw new ArgumentNullException(nameof(changedLines)); + } IEnumerable TokenIndices(int lineNr) => Enumerable.Range(0, file.GetTokenizedLine(lineNr).Count()).Select(index => new CodeFragment.TokenIndex(file, lineNr, index)); @@ -416,7 +526,7 @@ private static HashSet VerifyContext(this FileContentManager file, SortedSe var tokensToVerify = changedLines.SelectMany(TokenIndices); var verifiedLines = new HashSet(); - List Verify (CodeFragment.TokenIndex tokenIndex) + List Verify(CodeFragment.TokenIndex tokenIndex) { var messages = new List(); var fragment = tokenIndex.GetFragment(); @@ -425,13 +535,21 @@ List Verify (CodeFragment.TokenIndex tokenIndex) var fragmentStart = fragment.GetRange().Start; var (include, verifications) = Context.VerifySyntaxTokenContext(context); foreach (var msg in verifications) - { messages.Add(Diagnostics.Generate(file.FileName.Value, msg, fragmentStart)); } + { + messages.Add(Diagnostics.Generate(file.FileName.Value, msg, fragmentStart)); + } - if (include) tokenIndex.MarkAsIncluded(); - else tokenIndex.MarkAsExcluded(); + if (include) + { + tokenIndex.MarkAsIncluded(); + } + else + { + tokenIndex.MarkAsExcluded(); + } verifiedLines.Add(tokenIndex.Line); - // if a token is newly included in or excluded from the compilation, + // if a token is newly included in or excluded from the compilation, // then this may impact context information for all following tokens var changedStatus = include ^ fragment.IncludeInCompilation; var next = tokenIndex.NextOnScope(); @@ -439,7 +557,10 @@ List Verify (CodeFragment.TokenIndex tokenIndex) { // NOTE: since we invalidate context diagnostics on a per-line basis, we need to verify the whole line! var tokens = TokenIndices(next.Line); - foreach (var token in tokens) messages.AddRange(Verify(token)); + foreach (var token in tokens) + { + messages.AddRange(Verify(token)); + } } return messages; } @@ -448,7 +569,6 @@ List Verify (CodeFragment.TokenIndex tokenIndex) return verifiedLines; } - // external routines for context verification /// @@ -458,18 +578,24 @@ List Verify (CodeFragment.TokenIndex tokenIndex) /// internal static IEnumerable<(LSP.Range, QsQualifiedName)> CallablesWithContentModifications(this FileContentManager file, IEnumerable changedLines) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (changedLines == null) throw new ArgumentNullException(nameof(changedLines)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (changedLines == null) + { + throw new ArgumentNullException(nameof(changedLines)); + } var lastInFile = file.LastToken()?.GetFragment()?.GetRange()?.End ?? file.End(); - var callables = file.GetCallableDeclarations().Select(tuple => // these are sorted according to their line number + var callables = file.GetCallableDeclarations().Select(tuple => // these are sorted according to their line number { - var ns = file.TryGetNamespaceAt(tuple.Item2.Start); + var ns = file.TryGetNamespaceAt(tuple.Item2.Start); QsCompilerError.Verify(ns != null, "namespace for callable declaration should not be null"); // invalid namespace names default to an unknown namespace name, but remain included in the compilation return (tuple.Item2.Start, new QsQualifiedName(NonNullable.New(ns), tuple.Item1)); }).ToList(); - // NOTE: The range of modifications that has to trigger an update of the syntax tree for a callable + // NOTE: The range of modifications that has to trigger an update of the syntax tree for a callable // does need to go up to and include modifications to the line containing the next callable! // Otherwise inserting a callable declaration in the middle of an existing callable does not trigger the right behavior! (LSP.Range, QsQualifiedName) TypeCheckingRange((Position, QsQualifiedName) lastPreceding, IEnumerable<(Position, QsQualifiedName)> next) @@ -481,42 +607,56 @@ List Verify (CodeFragment.TokenIndex tokenIndex) foreach (var lineNr in changedLines) { - bool precedes((Position, QsQualifiedName) tuple) => tuple.Item1.Line < lineNr; - var preceding = callables.TakeWhile(precedes); - var following = callables.SkipWhile(precedes); + bool Precedes((Position, QsQualifiedName) tuple) => tuple.Item1.Line < lineNr; + var preceding = callables.TakeWhile(Precedes); + var following = callables.SkipWhile(Precedes); - if (preceding.Any()) - { yield return TypeCheckingRange(preceding.Last(), following); } + if (preceding.Any()) + { + yield return TypeCheckingRange(preceding.Last(), following); + } if (following.Any() && following.First().Item1.Line == lineNr) - { yield return TypeCheckingRange(following.First(), following.Skip(1)); } + { + yield return TypeCheckingRange(following.First(), following.Skip(1)); + } } } /// /// Dequeues all lines whose tokens has changed and verifies the positions of these tokens. - /// Does nothing if no lines have been modified. + /// Does nothing if no lines have been modified. /// Recomputes and pushes the context diagnostics for the processed tokens otherwise. - /// Throws an ArgumentNullException if file is null. + /// Throws an ArgumentNullException if file is null. /// internal static void UpdateContext(this FileContentManager file) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } file.SyncRoot.EnterUpgradeableReadLock(); try { var changedLines = file.DequeueTokenChanges(); - if (!changedLines.Any()) return; - QsCompilerError.RaiseOnFailure(() => + if (!changedLines.Any()) { - var verifiedLines = file.VerifyContext(changedLines, out List diagnostics); - file.UpdateContextDiagnostics(verifiedLines, diagnostics); - }, "updating the ContextDiagnostics failed"); + return; + } + QsCompilerError.RaiseOnFailure( + () => + { + var verifiedLines = file.VerifyContext(changedLines, out List diagnostics); + file.UpdateContextDiagnostics(verifiedLines, diagnostics); + }, "updating the ContextDiagnostics failed"); var edited = file.CallablesWithContentModifications(changedLines); file.MarkCallableAsContentEdited(edited); } - finally { file.SyncRoot.ExitUpgradeableReadLock(); } + finally + { + file.SyncRoot.ExitUpgradeableReadLock(); + } } } } diff --git a/src/QsCompiler/CompilationManager/DataStructures.cs b/src/QsCompiler/CompilationManager/DataStructures.cs index 868f958532..0f1a11f5c7 100644 --- a/src/QsCompiler/CompilationManager/DataStructures.cs +++ b/src/QsCompiler/CompilationManager/DataStructures.cs @@ -14,25 +14,26 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures { /// /// Contains all information managed by the ScopeTracking. /// All properties are readonly, and any modification leads to the creation of a new instance. /// - internal class CodeLine + internal class CodeLine { internal readonly string Text; internal readonly string LineEnding; // contains the line break for this line (included in text) + /// /// contains the text content of the line, without any end of line or doc comment and *without* the line break /// public readonly string WithoutEnding; + /// - /// Contains the end of line comment without any leading or trailing whitespace, and without the comment slashes. - /// Is null if no such comment exists. - /// Documenting comments (i.e. triple-slash comments) are *not* considered to be end of line comments. + /// Contains the end of line comment without any leading or trailing whitespace, and without the comment slashes. + /// Is null if no such comment exists. + /// Documenting comments (i.e. triple-slash comments) are *not* considered to be end of line comments. /// All comments which *either* follow non-whitespace content or do not start with triple-slash are considered end of line comments. /// public readonly string EndOfLineComment; @@ -49,11 +50,14 @@ public CodeLine(string text, IEnumerable delimiters, int eolComment, int in this.Text = text; this.LineEnding = Utils.EndOfLine.Match(text).Value; // empty string if the matching failed - var lineLength = text.Length - LineEnding.Length; - if (eolComment > lineLength) eolComment = lineLength; + var lineLength = text.Length - this.LineEnding.Length; + if (eolComment > lineLength) + { + eolComment = lineLength; + } this.WithoutEnding = text.Substring(0, eolComment); var commentStr = text.Substring(eolComment, lineLength - eolComment).Trim(); - var isDocComment = String.IsNullOrWhiteSpace(this.WithoutEnding) && (commentStr.Length - commentStr.TrimStart('/').Length == 3); + var isDocComment = string.IsNullOrWhiteSpace(this.WithoutEnding) && (commentStr.Length - commentStr.TrimStart('/').Length == 3); var hasComment = commentStr.StartsWith("//") && !isDocComment; this.EndOfLineComment = hasComment ? commentStr.Substring(2) : null; @@ -62,38 +66,51 @@ public CodeLine(string text, IEnumerable delimiters, int eolComment, int in this.ExcessBracketPositions = excessBrackets.ToImmutableArray(); if (eolComment < 0 || (eolComment != text.Length && eolComment > text.Length - this.LineEnding.Length)) + { throw new ArgumentOutOfRangeException(nameof(eolComment)); + } ScopeTracking.VerifyStringDelimiters(text, delimiters); ScopeTracking.VerifyExcessBracketPositions(this, excessBrackets); } - public CodeLine(string text, IEnumerable delimiters, int eofComment) - : this(text, delimiters, eofComment, 0, new List()) { } + public CodeLine(string text, IEnumerable delimiters, int eofComment) + : this(text, delimiters, eofComment, 0, new List()) + { + } - public static CodeLine Empty() => new CodeLine(String.Empty, Enumerable.Empty(), 0); + public static CodeLine Empty() => new CodeLine(string.Empty, Enumerable.Empty(), 0); internal CodeLine SetText(string newText) - { return new CodeLine(newText, StringDelimiters, WithoutEnding.Length, Indentation, ExcessBracketPositions); } + { + return new CodeLine(newText, this.StringDelimiters, this.WithoutEnding.Length, this.Indentation, this.ExcessBracketPositions); + } internal CodeLine SetStringDelimiters(IEnumerable newStringDelimiters) - { return new CodeLine(Text, newStringDelimiters, WithoutEnding.Length, Indentation, ExcessBracketPositions); } + { + return new CodeLine(this.Text, newStringDelimiters, this.WithoutEnding.Length, this.Indentation, this.ExcessBracketPositions); + } internal CodeLine SetCommentIndex(int newCommentIndex) - { return new CodeLine(Text, StringDelimiters, newCommentIndex, Indentation, ExcessBracketPositions); } + { + return new CodeLine(this.Text, this.StringDelimiters, newCommentIndex, this.Indentation, this.ExcessBracketPositions); + } internal CodeLine SetIndentation(int newIndentation) - { return new CodeLine(Text, StringDelimiters, WithoutEnding.Length, newIndentation, ExcessBracketPositions); } + { + return new CodeLine(this.Text, this.StringDelimiters, this.WithoutEnding.Length, newIndentation, this.ExcessBracketPositions); + } internal CodeLine SetExcessBrackets(IEnumerable newExcessBrackets) - { return new CodeLine(Text, StringDelimiters, WithoutEnding.Length, Indentation, newExcessBrackets); } + { + return new CodeLine(this.Text, this.StringDelimiters, this.WithoutEnding.Length, this.Indentation, newExcessBrackets); + } } - /// /// Contains all information managed by the LanguageProcessor. /// All properties except IncludeInCompilation are readonly, and any modification leads to the creation of a new instance. /// Access to IncludeInCompilation is limited to be via the TokenIndex subclass. - /// Note that GetRange will return a new instance of the CodeFragment Range upon each call, + /// Note that GetRange will return a new instance of the CodeFragment Range upon each call, /// and modifications to the returned instance won't be reflected in the CodeFragment. /// public class CodeFragment @@ -101,15 +118,16 @@ public class CodeFragment /// /// returns a copy of the CodeFragment Range /// - internal LSP.Range GetRange() => this.FragmentRange.Copy(); + internal LSP.Range GetRange() => this.fragmentRange.Copy(); - private readonly LSP.Range FragmentRange; + private readonly LSP.Range fragmentRange; internal readonly Tuple HeaderRange; internal readonly int Indentation; internal readonly string Text; internal readonly char FollowedBy; internal readonly QsComments Comments; public readonly QsFragmentKind Kind; + internal bool IncludeInCompilation { get; private set; } // used to exclude certain code fragments from the compilation if they are e.g. misplaced internal const char MissingDelimiter = '@'; // arbitrarily chosen @@ -125,28 +143,38 @@ private static Tuple GetHeaderRange(string text, /// private CodeFragment(int indent, LSP.Range r, string text, char next, QsComments comments, QsFragmentKind kind, bool include) { - if (!Utils.IsValidRange(r)) throw new ArgumentException("invalid range for code fragment"); - if (!DelimitingChars.Contains(next) && next != MissingDelimiter) throw new ArgumentException("a CodeFragment needs to be followed by a DelimitingChar"); + if (!Utils.IsValidRange(r)) + { + throw new ArgumentException("invalid range for code fragment"); + } + if (!DelimitingChars.Contains(next) && next != MissingDelimiter) + { + throw new ArgumentException("a CodeFragment needs to be followed by a DelimitingChar"); + } this.Indentation = indent < 0 ? throw new ArgumentException("indentation needs to be positive") : indent; this.Text = text?.TrimEnd() ?? throw new ArgumentNullException(nameof(text)); this.FollowedBy = next; this.Comments = comments ?? QsComments.Empty; this.Kind = kind; // nothing here should be modifiable - this.FragmentRange = r.Copy(); + this.fragmentRange = r.Copy(); this.HeaderRange = GetHeaderRange(this.Text, this.Kind); this.IncludeInCompilation = include; } - internal CodeFragment(int indent, LSP.Range r, string text, char next, QsFragmentKind kind = null) : - this(indent, r, text, next, null, kind, true) - { } + internal CodeFragment(int indent, LSP.Range r, string text, char next, QsFragmentKind kind = null) + : this(indent, r, text, next, null, kind, true) + { + } internal CodeFragment Copy() => new CodeFragment(this.Indentation, this.GetRange(), this.Text, this.FollowedBy, this.Comments, this.Kind, this.IncludeInCompilation); public bool Equals(CodeFragment other) { - if (other == null) return false; + if (other == null) + { + return false; + } return this.GetRange().Equals(other.GetRange()) && this.Indentation == other.Indentation && @@ -156,78 +184,86 @@ public bool Equals(CodeFragment other) (this.Kind == null ? other.Kind == null : this.Kind.Equals(other.Kind)); } - internal CodeFragment WithUpdatedLineNumber(int lineNrChange) => + internal CodeFragment WithUpdatedLineNumber(int lineNrChange) => this?.SetRange(this.GetRange().WithUpdatedLineNumber(lineNrChange)); internal CodeFragment SetRange(LSP.Range range) => - new CodeFragment(this.Indentation, range, this.Text, this.FollowedBy, this.Comments, this.Kind, this.IncludeInCompilation); + new CodeFragment(this.Indentation, range, this.Text, this.FollowedBy, this.Comments, this.Kind, this.IncludeInCompilation); internal CodeFragment SetIndentation(int indent) => - new CodeFragment(indent, this.FragmentRange, this.Text, this.FollowedBy, this.Comments, this.Kind, this.IncludeInCompilation); + new CodeFragment(indent, this.fragmentRange, this.Text, this.FollowedBy, this.Comments, this.Kind, this.IncludeInCompilation); internal CodeFragment SetCode(string code) => - new CodeFragment(this.Indentation, this.FragmentRange, code, this.FollowedBy, this.Comments, this.Kind, this.IncludeInCompilation); + new CodeFragment(this.Indentation, this.fragmentRange, code, this.FollowedBy, this.Comments, this.Kind, this.IncludeInCompilation); internal CodeFragment SetFollowedBy(char delim) => - new CodeFragment(this.Indentation, this.FragmentRange, this.Text, delim, this.Comments, this.Kind, this.IncludeInCompilation); + new CodeFragment(this.Indentation, this.fragmentRange, this.Text, delim, this.Comments, this.Kind, this.IncludeInCompilation); internal CodeFragment SetKind(QsFragmentKind kind) => - new CodeFragment(this.Indentation, this.FragmentRange, this.Text, this.FollowedBy, this.Comments, kind, this.IncludeInCompilation); + new CodeFragment(this.Indentation, this.fragmentRange, this.Text, this.FollowedBy, this.Comments, kind, this.IncludeInCompilation); internal CodeFragment ClearComments() => - new CodeFragment(this.Indentation, this.FragmentRange, this.Text, this.FollowedBy, null, this.Kind, this.IncludeInCompilation); + new CodeFragment(this.Indentation, this.fragmentRange, this.Text, this.FollowedBy, null, this.Kind, this.IncludeInCompilation); internal CodeFragment SetOpeningComments(IEnumerable commentsBefore) { var relevantComments = commentsBefore.SkipWhile(c => c == null).Reverse(); relevantComments = relevantComments.SkipWhile(c => c == null).Reverse(); - var comments = new QsComments(relevantComments.Select(c => c ?? String.Empty).ToImmutableArray(), this.Comments.ClosingComments); - return new CodeFragment(this.Indentation, this.FragmentRange, this.Text, this.FollowedBy, comments, this.Kind, this.IncludeInCompilation); + var comments = new QsComments(relevantComments.Select(c => c ?? string.Empty).ToImmutableArray(), this.Comments.ClosingComments); + return new CodeFragment(this.Indentation, this.fragmentRange, this.Text, this.FollowedBy, comments, this.Kind, this.IncludeInCompilation); } internal CodeFragment SetClosingComments(IEnumerable commentsAfter) { var relevantComments = commentsAfter.SkipWhile(c => c == null).Reverse(); relevantComments = relevantComments.SkipWhile(c => c == null).Reverse(); - var comments = new QsComments(this.Comments.OpeningComments, relevantComments.Select(c => c ?? String.Empty).ToImmutableArray()); - return new CodeFragment(this.Indentation, this.FragmentRange, this.Text, this.FollowedBy, comments, this.Kind, this.IncludeInCompilation); + var comments = new QsComments(this.Comments.OpeningComments, relevantComments.Select(c => c ?? string.Empty).ToImmutableArray()); + return new CodeFragment(this.Indentation, this.fragmentRange, this.Text, this.FollowedBy, comments, this.Kind, this.IncludeInCompilation); } - /// /// A class to conveniently walk the saved tokens. /// This class is a subclass of CodeFragment to limit access to IncludeInCompilation to be via TokenIndex. /// internal class TokenIndex // not disposable because File mustn't be disposed since several token indices may be using it { - private readonly FileContentManager File; + private readonly FileContentManager file; + public int Line { get; private set; } + public int Index { get; private set; } /// - /// Verifies the given line number and index *only* against the Tokens listed in file (and not against the content) + /// Verifies the given line number and index *only* against the Tokens listed in file (and not against the content) /// and initializes an instance of TokenIndex. /// Throws an ArgumentNullException if file is null. - /// Throws an ArgumentOutOfRangeException if line or index are negative, - /// or line is larger than or equal to the number of Tokens lists in file, + /// Throws an ArgumentOutOfRangeException if line or index are negative, + /// or line is larger than or equal to the number of Tokens lists in file, /// or index is larger than or equal to the number of Tokens on the given line. /// internal TokenIndex(FileContentManager file, int line, int index) { - this.File = file ?? throw new ArgumentNullException(nameof(file)); - if (line < 0 || line >= file.NrTokenizedLines()) throw new ArgumentOutOfRangeException(nameof(line)); - if (index < 0 || index >= file.GetTokenizedLine(line).Length) throw new ArgumentOutOfRangeException(nameof(index)); + this.file = file ?? throw new ArgumentNullException(nameof(file)); + if (line < 0 || line >= file.NrTokenizedLines()) + { + throw new ArgumentOutOfRangeException(nameof(line)); + } + if (index < 0 || index >= file.GetTokenizedLine(line).Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } this.Line = line; this.Index = index; } internal TokenIndex(TokenIndex tIndex) - : this(tIndex.File, tIndex.Line, tIndex.Index) { } + : this(tIndex.file, tIndex.Line, tIndex.Index) + { + } private bool IsWithinFile() => // used within class methods, since the file itself may change during the lifetime of the token ... - this.Line < this.File.NrTokenizedLines() && this.Index < this.File.GetTokenizedLine(this.Line).Length; - + this.Line < this.file.NrTokenizedLines() && this.Index < this.file.GetTokenizedLine(this.Line).Length; /// /// Marks the token returned by GetToken for the associated file as excluded from the compilation. @@ -235,8 +271,11 @@ private bool IsWithinFile() => // used within class methods, since the file itse /// internal void MarkAsExcluded() { - if (!this.IsWithinFile()) throw new InvalidOperationException("token index is no longer valid within its associated file"); - this.File.GetTokenizedLine(this.Line)[this.Index].IncludeInCompilation = false; + if (!this.IsWithinFile()) + { + throw new InvalidOperationException("token index is no longer valid within its associated file"); + } + this.file.GetTokenizedLine(this.Line)[this.Index].IncludeInCompilation = false; } /// @@ -245,23 +284,29 @@ internal void MarkAsExcluded() /// internal void MarkAsIncluded() { - if (!this.IsWithinFile()) throw new InvalidOperationException("token index is no longer valid within its associated file"); - this.File.GetTokenizedLine(this.Line)[this.Index].IncludeInCompilation = true; + if (!this.IsWithinFile()) + { + throw new InvalidOperationException("token index is no longer valid within its associated file"); + } + this.file.GetTokenizedLine(this.Line)[this.Index].IncludeInCompilation = true; } /// - /// Returns the corresponding fragment for the token at the saved TokenIndex - + /// Returns the corresponding fragment for the token at the saved TokenIndex - /// i.e. a copy of the token where its range denotes the absolute range within the file. /// Throws an InvalidOperationException if the token is no longer within the file associated with it. /// internal CodeFragment GetFragment() { - if (!this.IsWithinFile()) throw new InvalidOperationException("token index is no longer valid within its associated file"); - return this.File.GetTokenizedLine(this.Line)[this.Index].WithUpdatedLineNumber(this.Line); + if (!this.IsWithinFile()) + { + throw new InvalidOperationException("token index is no longer valid within its associated file"); + } + return this.file.GetTokenizedLine(this.Line)[this.Index].WithUpdatedLineNumber(this.Line); } /// - /// Returns the corresponding fragment for the token at the saved TokenIndex including any closing comments for that fragment - + /// Returns the corresponding fragment for the token at the saved TokenIndex including any closing comments for that fragment - /// i.e. a copy of the token where its range denotes the absolute range within the file. /// Throws an InvalidOperationException if the token is no longer within the file associated with it. /// @@ -275,7 +320,9 @@ internal CodeFragment GetFragmentWithClosingComments() { var lastChild = allChildren.Last().GetFragment(); if (lastChild.FollowedBy == '}' && !lastChild.IncludeInCompilation) - { fragment = fragment.SetClosingComments(lastChild.Comments.OpeningComments); } + { + fragment = fragment.SetClosingComments(lastChild.Comments.OpeningComments); + } } return fragment; } @@ -287,13 +334,24 @@ internal CodeFragment GetFragmentWithClosingComments() /// public static TokenIndex operator ++(TokenIndex tIndex) { - if (tIndex == null) throw new ArgumentNullException(nameof(tIndex)); - if (!tIndex.IsWithinFile()) throw new InvalidOperationException("token index is no longer valid within its associated file"); - var res = new TokenIndex(tIndex); // the overload for ++ must *not* mutate the argument - this is handled/done by the compiler - if (++res.Index < res.File.GetTokenizedLine(res.Line).Length) return res; + if (tIndex == null) + { + throw new ArgumentNullException(nameof(tIndex)); + } + if (!tIndex.IsWithinFile()) + { + throw new InvalidOperationException("token index is no longer valid within its associated file"); + } + var res = new TokenIndex(tIndex); // the overload for ++ must *not* mutate the argument - this is handled/done by the compiler + if (++res.Index < res.file.GetTokenizedLine(res.Line).Length) + { + return res; + } res.Index = 0; - while (++res.Line < res.File.NrTokenizedLines() && res.File.GetTokenizedLine(res.Line).Length == 0) ; - return res.Line == res.File.NrTokenizedLines() ? null : res; + while (++res.Line < res.file.NrTokenizedLines() && res.file.GetTokenizedLine(res.Line).Length == 0) + { + } + return res.Line == res.file.NrTokenizedLines() ? null : res; } /// @@ -303,18 +361,28 @@ internal CodeFragment GetFragmentWithClosingComments() /// public static TokenIndex operator --(TokenIndex tIndex) { - if (tIndex == null) throw new ArgumentNullException(nameof(tIndex)); - if (!tIndex.IsWithinFile()) throw new InvalidOperationException("token index is no longer valid within its associated file"); - var res = new TokenIndex(tIndex); // the overload for -- must *not* mutate the argument - this is handled/done by the compiler - if (res.Index-- > 0) return res; - while (res.Index < 0 && res.Line-- > 0) res.Index = res.File.GetTokenizedLine(res.Line).Length - 1; + if (tIndex == null) + { + throw new ArgumentNullException(nameof(tIndex)); + } + if (!tIndex.IsWithinFile()) + { + throw new InvalidOperationException("token index is no longer valid within its associated file"); + } + var res = new TokenIndex(tIndex); // the overload for -- must *not* mutate the argument - this is handled/done by the compiler + if (res.Index-- > 0) + { + return res; + } + while (res.Index < 0 && res.Line-- > 0) + { + res.Index = res.file.GetTokenizedLine(res.Line).Length - 1; + } return res.Line < 0 ? null : res; } } - } - /// /// struct used to do the (local) type checking and build the SyntaxTree /// @@ -326,8 +394,13 @@ internal struct TreeNode private readonly Position rootPosition; public readonly CodeFragment Fragment; public readonly IReadOnlyList Children; - public Position GetRootPosition() => rootPosition.Copy(); - public Position GetPositionRelativeToRoot() => relPosition.Copy(); + + /// + /// Returns the position of the root node that all child node positions are relative to. + /// + public Position GetRootPosition() => this.rootPosition.Copy(); + + public Position GetPositionRelativeToRoot() => this.relPosition.Copy(); /// /// Builds the TreeNode consisting of the given fragment and children. @@ -341,12 +414,19 @@ public TreeNode(CodeFragment fragment, IReadOnlyList children, Positio this.Children = children ?? throw new ArgumentNullException(nameof(children)); var fragStart = fragment.GetRange().Start; - if (!Utils.IsValidPosition(parentStart)) throw new ArgumentException(nameof(parentStart)); - if (fragStart.IsSmallerThan(parentStart)) throw new ArgumentException(nameof(parentStart), "parentStart needs to be smaller than or equal to the fragment start"); + if (!Utils.IsValidPosition(parentStart)) + { + throw new ArgumentException(nameof(parentStart)); + } + if (fragStart.IsSmallerThan(parentStart)) + { + throw new ArgumentException(nameof(parentStart), "parentStart needs to be smaller than or equal to the fragment start"); + } this.rootPosition = parentStart; this.relPosition = fragStart.Subtract(parentStart); } } + public readonly NonNullable Source; public readonly NonNullable Namespace; public readonly NonNullable Callable; @@ -361,14 +441,14 @@ public FragmentTree(NonNullable source, NonNullable ns, NonNulla } } - /// - /// struct used for convenience to manage header information in the symbol table + /// struct used for convenience to manage header information in the symbol table /// internal struct HeaderEntry { - private readonly Position Position; - internal Position GetPosition() => this.Position.Copy(); + private readonly Position position; + + internal Position GetPosition() => this.position.Copy(); internal readonly NonNullable SymbolName; internal readonly Tuple, Tuple> PositionedSymbol; @@ -378,13 +458,25 @@ internal struct HeaderEntry internal readonly ImmutableArray Documentation; internal readonly QsComments Comments; - private HeaderEntry(CodeFragment.TokenIndex tIndex, Position offset, - (NonNullable, Tuple) sym, T decl, ImmutableArray attributes, ImmutableArray doc, QsComments comments) + private HeaderEntry( + CodeFragment.TokenIndex tIndex, + Position offset, + (NonNullable, Tuple) sym, + T decl, + ImmutableArray attributes, + ImmutableArray doc, + QsComments comments) { - if (tIndex == null) throw new ArgumentNullException(nameof(tIndex)); - if (!Utils.IsValidPosition(offset)) throw new ArgumentException(nameof(offset)); + if (tIndex == null) + { + throw new ArgumentNullException(nameof(tIndex)); + } + if (!Utils.IsValidPosition(offset)) + { + throw new ArgumentException(nameof(offset)); + } - this.Position = offset.Copy(); + this.position = offset.Copy(); this.SymbolName = sym.Item1; this.PositionedSymbol = new Tuple, Tuple>(sym.Item1, sym.Item2); this.Declaration = decl; @@ -395,44 +487,56 @@ private HeaderEntry(CodeFragment.TokenIndex tIndex, Position offset, /// /// Tries to construct a HeaderItem from the given token index using the given GetDeclaration. - /// If the construction succeeds, returns the HeaderItem. - /// If the symbol of the extracted declaration is not an unqualified symbol, + /// If the construction succeeds, returns the HeaderItem. + /// If the symbol of the extracted declaration is not an unqualified symbol, /// verifies that it corresponds instead to an invalid symbol and returns null unless the keepInvalid parameter has been set to a string value. /// If the keepInvalid parameter has been set to a (non-null) string, uses that string as the SymbolName for the returned HeaderEntry instance. - /// Throws an ArgumentException if this verification fails as well. + /// Throws an ArgumentException if this verification fails as well. /// Throws an ArgumentException if the extracted declaration is Null. /// Throws an ArgumentNullException if the given token index is null. /// - static internal HeaderEntry? From(Func>> GetDeclaration, - CodeFragment.TokenIndex tIndex, ImmutableArray attributes, ImmutableArray doc, string keepInvalid = null) + internal static HeaderEntry? From( + Func>> getDeclaration, + CodeFragment.TokenIndex tIndex, + ImmutableArray attributes, + ImmutableArray doc, + string keepInvalid = null) { - if (GetDeclaration == null) throw new ArgumentNullException(nameof(GetDeclaration)); - if (tIndex == null) throw new ArgumentNullException(nameof(tIndex)); + if (getDeclaration == null) + { + throw new ArgumentNullException(nameof(getDeclaration)); + } + if (tIndex == null) + { + throw new ArgumentNullException(nameof(tIndex)); + } var fragment = tIndex.GetFragmentWithClosingComments(); var fragmentStart = fragment.GetRange().Start; - var extractedDecl = GetDeclaration(fragment); + var extractedDecl = getDeclaration(fragment); var (sym, decl) = extractedDecl.IsNull ? throw new ArgumentException("extracted declaration is Null") : extractedDecl.Item; var symName = sym.Symbol.AsDeclarationName(keepInvalid); - if (symName == null && !sym.Symbol.IsInvalidSymbol) throw new ArgumentException("extracted declaration does not have a suitable name"); + if (symName == null && !sym.Symbol.IsInvalidSymbol) + { + throw new ArgumentException("extracted declaration does not have a suitable name"); + } var symRange = sym.Range.IsNull ? new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero) : sym.Range.Item; - return symName == null - ? (HeaderEntry?)null + return symName == null + ? (HeaderEntry?)null : new HeaderEntry(tIndex, fragmentStart, (NonNullable.New(symName), symRange), decl, attributes, doc, fragment.Comments); } } - /// /// this class contains information about everything that impacts type checking on a global scope /// @@ -441,70 +545,76 @@ internal class FileHeader // *don't* dispose of the sync root! public readonly ReaderWriterLockSlim SyncRoot; // IMPORTANT: quite a couple of places rely on these being sorted! - private readonly ManagedSortedSet NamespaceDeclarations; - private readonly ManagedSortedSet OpenDirectives; - private readonly ManagedSortedSet TypeDeclarations; - private readonly ManagedSortedSet CallableDeclarations; + private readonly ManagedSortedSet namespaceDeclarations; + private readonly ManagedSortedSet openDirectives; + private readonly ManagedSortedSet typeDeclarations; + private readonly ManagedSortedSet callableDeclarations; public FileHeader(ReaderWriterLockSlim syncRoot) { this.SyncRoot = syncRoot ?? throw new ArgumentNullException(nameof(syncRoot)); - this.NamespaceDeclarations = new ManagedSortedSet(syncRoot); - this.OpenDirectives = new ManagedSortedSet(syncRoot); - this.TypeDeclarations = new ManagedSortedSet(syncRoot); - this.CallableDeclarations = new ManagedSortedSet(syncRoot); + this.namespaceDeclarations = new ManagedSortedSet(syncRoot); + this.openDirectives = new ManagedSortedSet(syncRoot); + this.typeDeclarations = new ManagedSortedSet(syncRoot); + this.callableDeclarations = new ManagedSortedSet(syncRoot); } /// - /// Invalidates (i.e. removes) all elements in the range [start, start + count), and + /// Invalidates (i.e. removes) all elements in the range [start, start + count), and /// updates all elements that are larger than or equal to start + count with the given lineNrChange. /// Throws an ArgumentOutOfRange exception if start or count are negative, or if lineNrChange is smaller than -count. /// public void InvalidateOrUpdate(int start, int count, int lineNrChange) { - this.NamespaceDeclarations.InvalidateOrUpdate(start, count, lineNrChange); - this.OpenDirectives.InvalidateOrUpdate(start, count, lineNrChange); - this.TypeDeclarations.InvalidateOrUpdate(start, count, lineNrChange); - this.CallableDeclarations.InvalidateOrUpdate(start, count, lineNrChange); + this.namespaceDeclarations.InvalidateOrUpdate(start, count, lineNrChange); + this.openDirectives.InvalidateOrUpdate(start, count, lineNrChange); + this.typeDeclarations.InvalidateOrUpdate(start, count, lineNrChange); + this.callableDeclarations.InvalidateOrUpdate(start, count, lineNrChange); } /// /// Returns an array with the *sorted* line numbers containing namespace declarations. /// public int[] GetNamespaceDeclarations() - { return this.NamespaceDeclarations.ToArray(); } + { + return this.namespaceDeclarations.ToArray(); + } /// - /// Returns an array with the *sorted* line numbers containing open directives. + /// Returns an array with the *sorted* line numbers containing open directives. /// public int[] GetOpenDirectives() - { return this.OpenDirectives.ToArray(); } + { + return this.openDirectives.ToArray(); + } /// - /// Returns an array with the *sorted* line numbers containing type declarations. + /// Returns an array with the *sorted* line numbers containing type declarations. /// public int[] GetTypeDeclarations() - { return this.TypeDeclarations.ToArray(); } + { + return this.typeDeclarations.ToArray(); + } /// - /// Returns an array with the *sorted* line numbers containing callable declarations. + /// Returns an array with the *sorted* line numbers containing callable declarations. /// public int[] GetCallableDeclarations() - { return this.CallableDeclarations.ToArray(); } - + { + return this.callableDeclarations.ToArray(); + } public void AddNamespaceDeclarations(IEnumerable declarations) => - this.NamespaceDeclarations.Add(declarations); + this.namespaceDeclarations.Add(declarations); - public void AddOpenDirectives(IEnumerable declarations) => - this.OpenDirectives.Add(declarations); + public void AddOpenDirectives(IEnumerable declarations) => + this.openDirectives.Add(declarations); - public void AddTypeDeclarations(IEnumerable declarations) => - this.TypeDeclarations.Add(declarations); - - public void AddCallableDeclarations(IEnumerable declarations) => - this.CallableDeclarations.Add(declarations); + public void AddTypeDeclarations(IEnumerable declarations) => + this.typeDeclarations.Add(declarations); + public void AddCallableDeclarations(IEnumerable declarations) => + this.callableDeclarations.Add(declarations); public static bool IsNamespaceDeclaration(CodeFragment fragment) => fragment?.Kind != null && fragment.IncludeInCompilation && fragment.Kind.IsNamespaceDeclaration; @@ -517,7 +627,7 @@ public static bool IsTypeDeclaration(CodeFragment fragment) => public static bool IsCallableDeclaration(CodeFragment fragment) => fragment?.Kind != null && fragment.IncludeInCompilation && ( - fragment.Kind.IsOperationDeclaration || + fragment.Kind.IsOperationDeclaration || fragment.Kind.IsFunctionDeclaration); public static bool IsCallableSpecialization(CodeFragment fragment) => @@ -535,13 +645,13 @@ public static bool IsHeaderItem(CodeFragment fragment) => IsCallableSpecialization(fragment); public static IEnumerable FilterNamespaceDeclarations(IEnumerable fragments) => - fragments?.Where(IsNamespaceDeclaration); + fragments?.Where(IsNamespaceDeclaration); public static IEnumerable FilterOpenDirectives(IEnumerable fragments) => - fragments?.Where(IsOpenDirective); + fragments?.Where(IsOpenDirective); - public static IEnumerable FilterTypeDeclarations(IEnumerable fragments) => - fragments?.Where(IsTypeDeclaration); + public static IEnumerable FilterTypeDeclarations(IEnumerable fragments) => + fragments?.Where(IsTypeDeclaration); public static IEnumerable FilterCallableDeclarations(IEnumerable fragments) => fragments?.Where(IsCallableDeclaration); @@ -550,34 +660,49 @@ public static IEnumerable FilterCallableSpecializations(IEnumerabl fragments?.Where(IsCallableSpecialization); } - /// /// threadsafe wrapper to SortedSet /// public class ManagedSortedSet // *don't* dispose of the sync root! { - private SortedSet Set = new SortedSet(); + private SortedSet set = new SortedSet(); public readonly ReaderWriterLockSlim SyncRoot; public ManagedSortedSet(ReaderWriterLockSlim syncRoot) - { this.SyncRoot = syncRoot; } + { + this.SyncRoot = syncRoot; + } public int[] ToArray() { this.SyncRoot.EnterReadLock(); - try { return this.Set.ToArray(); } - finally { this.SyncRoot.ExitReadLock(); } + try + { + return this.set.ToArray(); + } + finally + { + this.SyncRoot.ExitReadLock(); + } } public void Add(IEnumerable items) { this.SyncRoot.EnterWriteLock(); - try { this.Set.UnionWith(items); } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.set.UnionWith(items); + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public void Add(params int[] items) - { this.Add((IEnumerable)items); } + { + this.Add((IEnumerable)items); + } /// /// Clears all elements from the set and returned the removed elements. @@ -585,10 +710,13 @@ public void Add(params int[] items) public SortedSet Clear() { this.SyncRoot.EnterWriteLock(); - try { return this.Set; } + try + { + return this.set; + } finally { - this.Set = new SortedSet(); + this.set = new SortedSet(); this.SyncRoot.ExitWriteLock(); } } @@ -601,210 +729,351 @@ public SortedSet Clear() /// public int InvalidateOrUpdate(int start, int count, int lineNrChange) { - if (start < 0) throw new ArgumentOutOfRangeException(nameof(start)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (lineNrChange < -count) throw new ArgumentOutOfRangeException(nameof(lineNrChange)); + if (start < 0) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + if (lineNrChange < -count) + { + throw new ArgumentOutOfRangeException(nameof(lineNrChange)); + } this.SyncRoot.EnterWriteLock(); try { - var nrRemoved = this.Set.RemoveWhere(lineNr => start <= lineNr && lineNr < start + count); + var nrRemoved = this.set.RemoveWhere(lineNr => start <= lineNr && lineNr < start + count); if (lineNrChange != 0) { - var updatedLineNrs = this.Set.Where(lineNr => lineNr >= start + count).Select(lineNr => lineNr + lineNrChange).ToArray(); // calling ToArray to make sure updateLineNrs is not affected by RemoveWhere below - this.Set.RemoveWhere(lineNr => start <= lineNr); - this.Set.UnionWith(updatedLineNrs); + var updatedLineNrs = this.set.Where(lineNr => lineNr >= start + count).Select(lineNr => lineNr + lineNrChange).ToArray(); // calling ToArray to make sure updateLineNrs is not affected by RemoveWhere below + this.set.RemoveWhere(lineNr => start <= lineNr); + this.set.UnionWith(updatedLineNrs); } return nrRemoved; } - finally { this.SyncRoot.ExitWriteLock(); } + finally + { + this.SyncRoot.ExitWriteLock(); + } } } - /// /// threadsafe wrapper to HashSet /// public class ManagedHashSet // *don't* dispose of the sync root! { - private readonly HashSet Content = new HashSet(); + private readonly HashSet content = new HashSet(); public readonly ReaderWriterLockSlim SyncRoot; public ManagedHashSet(IEnumerable collection, ReaderWriterLockSlim syncRoot) - { Content = new HashSet(collection); SyncRoot = syncRoot; } + { + this.content = new HashSet(collection); + this.SyncRoot = syncRoot; + } public ManagedHashSet(ReaderWriterLockSlim syncRoot) - : this(Enumerable.Empty(), syncRoot) { } + : this(Enumerable.Empty(), syncRoot) + { + } public void Add(T item) { this.SyncRoot.EnterWriteLock(); - try { this.Content.Add(item); } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.content.Add(item); + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public void RemoveAll(Func condition) { this.SyncRoot.EnterWriteLock(); - try { this.Content.RemoveWhere(item => condition(item)); } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.content.RemoveWhere(item => condition(item)); + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public ImmutableHashSet ToImmutableHashSet() { this.SyncRoot.EnterReadLock(); - try { return this.Content.ToImmutableHashSet(); } - finally { this.SyncRoot.ExitReadLock(); } + try + { + return this.content.ToImmutableHashSet(); + } + finally + { + this.SyncRoot.ExitReadLock(); + } } } - /// /// threadsafe wrapper to List /// public class ManagedList // *don't* dispose of the sync root! { - private List Content = new List(); + private List content = new List(); public readonly ReaderWriterLockSlim SyncRoot; private ManagedList(List content, ReaderWriterLockSlim syncRoot) - { Content = content; SyncRoot = syncRoot; } + { + this.content = content; + this.SyncRoot = syncRoot; + } public ManagedList(IEnumerable collection, ReaderWriterLockSlim syncRoot) - : this(collection.ToList(), syncRoot) { } + : this(collection.ToList(), syncRoot) + { + } public ManagedList(ReaderWriterLockSlim syncRoot) - : this(new List(), syncRoot) { } - + : this(new List(), syncRoot) + { + } // members for content manipulation public void Add(T item) { this.SyncRoot.EnterWriteLock(); - try { this.Content.Add(item); } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.content.Add(item); + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public void AddRange(IEnumerable elements) { this.SyncRoot.EnterWriteLock(); - try { this.Content.AddRange(elements); } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.content.AddRange(elements); + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public void RemoveRange(int index, int count) { this.SyncRoot.EnterWriteLock(); - try { this.Content.RemoveRange(index, count); } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.content.RemoveRange(index, count); + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public void RemoveAll(Func condition) { this.SyncRoot.EnterWriteLock(); - try { this.Content.RemoveAll(item => condition(item)); } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.content.RemoveAll(item => condition(item)); + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public void InsertRange(int index, IEnumerable newContent) { this.SyncRoot.EnterWriteLock(); - try { this.Content.InsertRange(index, newContent); } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.content.InsertRange(index, newContent); + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public IEnumerable Get() { this.SyncRoot.EnterReadLock(); - try { return this.Content.GetRange(0, this.Content.Count); } // creates a shallow copy - finally { this.SyncRoot.ExitReadLock(); } + try + { + // creates a shallow copy + return this.content.GetRange(0, this.content.Count); + } + finally + { + this.SyncRoot.ExitReadLock(); + } } - public IEnumerable GetRange(int index, int count) + public IEnumerable GetRange(int index, int count) { this.SyncRoot.EnterReadLock(); - try { return this.Content.GetRange(index, count); } // creates a shallow copy - finally { this.SyncRoot.ExitReadLock(); } + try + { + // creates a shallow copy + return this.content.GetRange(index, count); + } + finally + { + this.SyncRoot.ExitReadLock(); + } } - public T GetItem(int index) + public T GetItem(int index) { this.SyncRoot.EnterReadLock(); - try { return this.Content[index]; } - finally { this.SyncRoot.ExitReadLock(); } + try + { + return this.content[index]; + } + finally + { + this.SyncRoot.ExitReadLock(); + } } public void SetItem(int index, T newItem) { this.SyncRoot.EnterWriteLock(); - try { this.Content[index] = newItem; } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.content[index] = newItem; + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public void Replace(int start, int count, IReadOnlyList replacements) { - if (replacements == null) throw new ArgumentNullException(nameof(replacements)); + if (replacements == null) + { + throw new ArgumentNullException(nameof(replacements)); + } this.SyncRoot.EnterWriteLock(); try { if (replacements.Count - count != 0) { - this.Content.RemoveRange(start, count); - this.Content.InsertRange(start, replacements); + this.content.RemoveRange(start, count); + this.content.InsertRange(start, replacements); } else { for (var offset = 0; offset < count; ++offset) - { this.Content[start + offset] = replacements[offset]; } + { + this.content[start + offset] = replacements[offset]; + } } } - finally{ this.SyncRoot.ExitWriteLock(); } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public void ReplaceAll(IEnumerable replacement) { - if (replacement == null) throw new ArgumentNullException(nameof(replacement)); + if (replacement == null) + { + throw new ArgumentNullException(nameof(replacement)); + } this.SyncRoot.EnterWriteLock(); - try { this.Content = replacement.ToList(); } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.content = replacement.ToList(); + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public void ReplaceAll(ManagedList replacement) { - if (replacement == null) throw new ArgumentNullException(nameof(replacement)); + if (replacement == null) + { + throw new ArgumentNullException(nameof(replacement)); + } this.SyncRoot.EnterWriteLock(); - try { this.Content = replacement.ToList(); } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.content = replacement.ToList(); + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public void Transform(int index, Func transformation) { - if (transformation == null) throw new ArgumentNullException(nameof(transformation)); + if (transformation == null) + { + throw new ArgumentNullException(nameof(transformation)); + } this.SyncRoot.EnterWriteLock(); - try { this.Content[index] = transformation(this.Content[index]); } - finally { this.SyncRoot.ExitWriteLock(); } + try + { + this.content[index] = transformation(this.content[index]); + } + finally + { + this.SyncRoot.ExitWriteLock(); + } } public void Transform(Func transformation) { - if (transformation == null) throw new ArgumentNullException(nameof(transformation)); + if (transformation == null) + { + throw new ArgumentNullException(nameof(transformation)); + } this.SyncRoot.EnterWriteLock(); try { - for (var index = 0; index < this.Content.Count(); ++index) - { this.Content[index] = transformation(this.Content[index]); } + for (var index = 0; index < this.content.Count(); ++index) + { + this.content[index] = transformation(this.content[index]); + } + } + finally + { + this.SyncRoot.ExitWriteLock(); } - finally { this.SyncRoot.ExitWriteLock(); } } public List Clear() { this.SyncRoot.EnterWriteLock(); - try { return this.Content; } + try + { + return this.content; + } finally { - this.Content = new List(); + this.content = new List(); this.SyncRoot.ExitWriteLock(); } } @@ -812,15 +1081,27 @@ public List Clear() public int Count() { this.SyncRoot.EnterReadLock(); - try { return this.Content.Count(); } - finally { this.SyncRoot.ExitReadLock(); } + try + { + return this.content.Count(); + } + finally + { + this.SyncRoot.ExitReadLock(); + } } public List ToList() { this.SyncRoot.EnterReadLock(); - try { return this.Content.ToList(); } - finally { this.SyncRoot.ExitReadLock(); } + try + { + return this.content.ToList(); + } + finally + { + this.SyncRoot.ExitReadLock(); + } } } } diff --git a/src/QsCompiler/CompilationManager/DiagnosticTools.cs b/src/QsCompiler/CompilationManager/DiagnosticTools.cs index 3144a3a7ec..37c8f04b26 100644 --- a/src/QsCompiler/CompilationManager/DiagnosticTools.cs +++ b/src/QsCompiler/CompilationManager/DiagnosticTools.cs @@ -10,7 +10,6 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { public static class DiagnosticTools @@ -19,8 +18,8 @@ public static class DiagnosticTools /// Returns the line and character of the given position as tuple without verifying them. /// Throws an ArgumentNullException if the given position is null. /// - public static Tuple AsTuple(Position position) => - position != null + public static Tuple AsTuple(Position position) => + position != null ? new Tuple(position.Line, position.Character) : throw new ArgumentNullException(nameof(position)); @@ -42,10 +41,19 @@ public static Position AsPosition(Tuple position) => /// internal static Position GetAbsolutePosition(Position offset, QsPositionInfo relativePosition) { - if (relativePosition.Line < 1 || relativePosition.Column < 1) throw new ArgumentOutOfRangeException(nameof(relativePosition)); + if (relativePosition.Line < 1 || relativePosition.Column < 1) + { + throw new ArgumentOutOfRangeException(nameof(relativePosition)); + } - if (offset == null) return new Position(relativePosition.Line - 1, relativePosition.Column - 1); // fparsec position info is one based - if (!Utils.IsValidPosition(offset)) throw new ArgumentException(nameof(offset)); + if (offset == null) + { + return new Position(relativePosition.Line - 1, relativePosition.Column - 1); // fparsec position info is one based + } + if (!Utils.IsValidPosition(offset)) + { + throw new ArgumentException(nameof(offset)); + } var absPos = offset.Copy(); absPos.Line += relativePosition.Line - 1; // fparsec position info is one based @@ -53,31 +61,51 @@ internal static Position GetAbsolutePosition(Position offset, QsPositionInfo rel return absPos; } + /// + /// Creates an absolute position by adding the zero-based offset to the relative position. + /// + /// The position. + /// A zero-based line and column offset. + /// Thrown if the position line or column is negative. + /// Thrown if offset line or column is negative. + /// The absolute position. + internal static Position GetAbsolutePosition(Position position, Tuple offset) + { + var (line, column) = offset; + return GetAbsolutePosition(position, new QsPositionInfo(line + 1, column + 1)); + } + /// /// Given the starting position, convertes the relative range returned by the Q# compiler w.r.t. that starting position into an absolute range as expected by VS. /// IMPORTANT: The position returned by the Q# Compiler is (assumed to be) one-based, whereas the given and returned absolute positions are (assumed to be) zero-based! /// If the starting position is null, then this routine simply converts the PositionInfo given by the Q# compiler to the Position object that VS expects. - /// Throws an ArgumentNullException if the given relative range is null. - /// Throws an ArgumentException if the Line or Column of the given offset are smaller than zero, - /// or if the end position (second item) of the given relative range is larger than the start position (first item). + /// Throws an ArgumentNullException if the given relative range is null. + /// Throws an ArgumentException if the Line or Column of the given offset are smaller than zero, + /// or if the end position (second item) of the given relative range is larger than the start position (first item). /// Throws an ArgumentOutOfRangeException if the Line or Column of the given relative position are smaller than one. /// internal static LSP.Range GetAbsoluteRange(Position offset, Tuple relativeRange) { bool LargerThan(QsPositionInfo lhs, QsPositionInfo rhs) => - lhs.Line > rhs.Line || (lhs.Line == rhs.Line && lhs.Column > rhs.Column); - if (relativeRange == null) throw new ArgumentNullException(nameof(relativeRange)); - if (LargerThan(relativeRange.Item1, relativeRange.Item2)) throw new ArgumentException("invalid range", nameof(relativeRange)); + lhs.Line > rhs.Line || (lhs.Line == rhs.Line && lhs.Column > rhs.Column); + if (relativeRange == null) + { + throw new ArgumentNullException(nameof(relativeRange)); + } + if (LargerThan(relativeRange.Item1, relativeRange.Item2)) + { + throw new ArgumentException("invalid range", nameof(relativeRange)); + } return new LSP.Range { Start = GetAbsolutePosition(offset, relativeRange.Item1), End = GetAbsolutePosition(offset, relativeRange.Item2) }; } /// /// Given the location information for a declared symbol, - /// as well as the position of the declaration within which the symbol is declared, + /// as well as the position of the declaration within which the symbol is declared, /// returns the zero-based line and character index indicating the position of the symbol in the file. /// Returns null if the given object is not compatible with the position information generated by this CompilationBuilder. /// - public static Tuple SymbolPosition(QsLocation rootLocation, QsNullable> symbolPosition, Tuple symbolRange) + public static Tuple SymbolPosition(QsLocation rootLocation, QsNullable> symbolPosition, Tuple symbolRange) { var offset = symbolPosition.IsNull // the position offset is set to null (only) for variables defined in the declaration ? DeclarationPosition(rootLocation) @@ -86,21 +114,23 @@ public static Tuple SymbolPosition(QsLocation rootLocation, QsNullable } /// - /// Given the position of the specialization declaration to whose implementation the statement belongs, - /// and the position of the statement within the implementation, + /// Given the position of the specialization declaration to whose implementation the statement belongs, + /// and the position of the statement within the implementation, /// returns the zero-based line and character index indicating the position of the statement in the file. /// Returns null if either one of the given objects is not compatible with the position information generated by this CompilationBuilder. /// public static Tuple StatementPosition(QsLocation rootLocation, QsLocation statementLocation) => StatementPosition(rootLocation.Offset, statementLocation.Offset); - internal static Tuple StatementPosition(Tuple rootOffset, Tuple statementPos) => + + internal static Tuple StatementPosition(Tuple rootOffset, Tuple statementPos) => DeclarationPosition(AsPosition(rootOffset).Add(AsPosition(statementPos))); /// - /// Given the location of a callable, type or specialization declaration, + /// Given the location of a callable, type or specialization declaration, /// returns the zero-based line and character index indicating the position of the declaration in the file. /// public static Tuple DeclarationPosition(QsLocation location) => DeclarationPosition(AsPosition(location.Offset)); + private static Tuple DeclarationPosition(Position position) => new Tuple(position.Line, position.Character); /// @@ -109,8 +139,8 @@ internal static Tuple StatementPosition(Tuple rootOffset, Tup /// public static Position Copy(this Position pos) { - return pos == null - ? null + return pos == null + ? null : new Position(pos.Line, pos.Character); } @@ -122,21 +152,27 @@ public static Position Copy(this Position pos) /// public static Position WithUpdatedLineNumber(this Position pos, int lineNrChange) { - if (!Utils.IsValidPosition(pos)) throw new ArgumentException($"invalid Position in {nameof(WithUpdatedLineNumber)}"); - if (pos.Line + lineNrChange < 0) throw new ArgumentOutOfRangeException(nameof(lineNrChange)); + if (!Utils.IsValidPosition(pos)) + { + throw new ArgumentException($"invalid Position in {nameof(WithUpdatedLineNumber)}"); + } + if (pos.Line + lineNrChange < 0) + { + throw new ArgumentOutOfRangeException(nameof(lineNrChange)); + } var updated = pos.Copy(); updated.Line += lineNrChange; return updated; } /// - /// For a given Range, returns a new Range with its starting and ending position a copy of the start and end of the given Range + /// For a given Range, returns a new Range with its starting and ending position a copy of the start and end of the given Range /// (i.e. does a deep copy) or null in case the given Range is null. /// public static LSP.Range Copy(this LSP.Range r) { - return r == null - ? null + return r == null + ? null : new LSP.Range { Start = r.Start.Copy(), End = r.End.Copy() }; } @@ -148,9 +184,18 @@ public static LSP.Range Copy(this LSP.Range r) /// public static LSP.Range WithUpdatedLineNumber(this LSP.Range range, int lineNrChange) { - if (lineNrChange == 0) return range ?? throw new ArgumentNullException(nameof(range)); - if (!Utils.IsValidRange(range)) throw new ArgumentException($"invalid Range in {nameof(WithUpdatedLineNumber)}"); // range can be empty - if (range.Start.Line + lineNrChange < 0) throw new ArgumentOutOfRangeException(nameof(lineNrChange)); + if (lineNrChange == 0) + { + return range ?? throw new ArgumentNullException(nameof(range)); + } + if (!Utils.IsValidRange(range)) + { + throw new ArgumentException($"invalid Range in {nameof(WithUpdatedLineNumber)}"); // range can be empty + } + if (range.Start.Line + lineNrChange < 0) + { + throw new ArgumentOutOfRangeException(nameof(lineNrChange)); + } var updated = range.Copy(); updated.Start.Line += lineNrChange; updated.End.Line += lineNrChange; @@ -163,7 +208,10 @@ public static LSP.Range WithUpdatedLineNumber(this LSP.Range range, int lineNrCh /// public static Diagnostic Copy(this Diagnostic message) { - if (message == null) return null; + if (message == null) + { + return null; + } return new Diagnostic() { Range = message.Range.Copy(), @@ -182,7 +230,10 @@ public static Diagnostic Copy(this Diagnostic message) /// public static Diagnostic WithUpdatedLineNumber(this Diagnostic diagnostic, int lineNrChange) { - if (lineNrChange == 0) return diagnostic ?? throw new ArgumentNullException(nameof(diagnostic)); + if (lineNrChange == 0) + { + return diagnostic ?? throw new ArgumentNullException(nameof(diagnostic)); + } var updatedRange = diagnostic.Range.WithUpdatedLineNumber(lineNrChange); // throws if the given diagnostic is null var updated = diagnostic.Copy(); updated.Range = updatedRange; @@ -227,12 +278,15 @@ public static bool IsInformation(this Diagnostic m) => /// /// Extracts all elements satisfying the given condition and which start at a line that is larger or equal to lowerBound. - /// Diagnostics without any range information are only extracted if no lower bound is specified or the specified lower bound is smaller than zero. + /// Diagnostics without any range information are only extracted if no lower bound is specified or the specified lower bound is smaller than zero. /// Throws an ArgumentNullException if the given condition is null. /// public static IEnumerable Filter(this IEnumerable orig, Func condition, int lowerBound = -1) { - if (condition == null) throw new ArgumentNullException(nameof(condition)); + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } return orig?.Where(m => condition(m) && lowerBound <= (m.Range?.Start?.Line ?? -1)); } @@ -242,7 +296,10 @@ public static IEnumerable Filter(this IEnumerable orig, /// public static IEnumerable Filter(this IEnumerable orig, Func condition, int lowerBound, int upperBound) { - if (condition == null) throw new ArgumentNullException(nameof(condition)); + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } return orig?.Where(m => condition(m) && lowerBound <= m.Range.Start.Line && m.Range.End.Line < upperBound); } @@ -250,62 +307,80 @@ public static IEnumerable Filter(this IEnumerable orig, /// Extracts all elements which start at a line that is larger or equal to lowerBound. /// public static IEnumerable Filter(this IEnumerable orig, int lowerBound) - { return orig?.Filter(m => true, lowerBound); } + { + return orig?.Filter(m => true, lowerBound); + } /// /// Extracts all elements which start at a line that is larger or equal to lowerBound and smaller than upperBound. /// public static IEnumerable Filter(this IEnumerable orig, int lowerBound, int upperBound) - { return orig?.Filter(m => true, lowerBound, upperBound); } - + { + return orig?.Filter(m => true, lowerBound, upperBound); + } /// /// Returns true if the start line of the given diagnostic is larger or equal to lowerBound. /// internal static bool SelectByStartLine(this Diagnostic m, int lowerBound) - { return m?.Range?.Start?.Line == null ? false : lowerBound <= m.Range.Start.Line; } + { + return m?.Range?.Start?.Line == null ? false : lowerBound <= m.Range.Start.Line; + } /// /// Returns true if the start line of the given diagnostic is larger or equal to lowerBound, and smaller than upperBound. /// internal static bool SelectByStartLine(this Diagnostic m, int lowerBound, int upperBound) - { return m?.Range?.Start?.Line == null ? false : lowerBound <= m.Range.Start.Line && m.Range.Start.Line < upperBound; } + { + return m?.Range?.Start?.Line == null ? false : lowerBound <= m.Range.Start.Line && m.Range.Start.Line < upperBound; + } /// /// Returns true if the end line of the given diagnostic is larger or equal to lowerBound. /// internal static bool SelectByEndLine(this Diagnostic m, int lowerBound) - { return m?.Range?.End?.Line == null ? false : lowerBound <= m.Range.End.Line; } + { + return m?.Range?.End?.Line == null ? false : lowerBound <= m.Range.End.Line; + } /// /// Returns true if the end line of the given diagnostic is larger or equal to lowerBound, and smaller than upperBound. /// internal static bool SelectByEndLine(this Diagnostic m, int lowerBound, int upperBound) - { return m?.Range?.End?.Line == null ? false : lowerBound <= m.Range.End.Line && m.Range.End.Line < upperBound; } - + { + return m?.Range?.End?.Line == null ? false : lowerBound <= m.Range.End.Line && m.Range.End.Line < upperBound; + } /// /// Returns true if the start position of the given diagnostic is larger or equal to lowerBound. /// internal static bool SelectByStart(this Diagnostic m, Position lowerBound) - { return m?.Range?.Start?.Line == null ? false : lowerBound.IsSmallerThanOrEqualTo(m.Range.Start); } + { + return m?.Range?.Start?.Line == null ? false : lowerBound.IsSmallerThanOrEqualTo(m.Range.Start); + } /// /// Returns true if the start position of the given diagnostic is larger or equal to lowerBound, and smaller than upperBound. /// internal static bool SelectByStart(this Diagnostic m, Position lowerBound, Position upperBound) - { return m?.Range?.Start?.Line == null ? false : lowerBound.IsSmallerThanOrEqualTo(m.Range.Start) && m.Range.Start.IsSmallerThan(upperBound); } + { + return m?.Range?.Start?.Line == null ? false : lowerBound.IsSmallerThanOrEqualTo(m.Range.Start) && m.Range.Start.IsSmallerThan(upperBound); + } /// /// Returns true if the end position of the given diagnostic is larger or equal to lowerBound. /// internal static bool SelectByEnd(this Diagnostic m, Position lowerBound) - { return m?.Range?.End?.Line == null ? false : lowerBound.IsSmallerThanOrEqualTo(m.Range.End); } + { + return m?.Range?.End?.Line == null ? false : lowerBound.IsSmallerThanOrEqualTo(m.Range.End); + } /// /// Returns true if the end position of the given diagnostic is larger or equal to lowerBound, and smaller than upperBound. /// internal static bool SelectByEnd(this Diagnostic m, Position lowerBound, Position upperBound) - { return m?.Range?.End?.Line == null ? false : lowerBound.IsSmallerThanOrEqualTo(m.Range.End) && m.Range.End.IsSmallerThan(upperBound); } + { + return m?.Range?.End?.Line == null ? false : lowerBound.IsSmallerThanOrEqualTo(m.Range.End) && m.Range.End.IsSmallerThan(upperBound); + } } } diff --git a/src/QsCompiler/CompilationManager/Diagnostics.cs b/src/QsCompiler/CompilationManager/Diagnostics.cs index 69d5906930..81fe03d58f 100644 --- a/src/QsCompiler/CompilationManager/Diagnostics.cs +++ b/src/QsCompiler/CompilationManager/Diagnostics.cs @@ -10,71 +10,109 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { public static class Diagnostics { internal static char ExpectedEnding(ErrorCode invalidFragmentEnding) { - if (invalidFragmentEnding == ErrorCode.ExpectingOpeningBracket) return '{'; - else if (invalidFragmentEnding == ErrorCode.ExpectingSemicolon) return ';'; - else if (invalidFragmentEnding == ErrorCode.UnexpectedFragmentDelimiter) return CodeFragment.MissingDelimiter; - else throw new NotImplementedException("unrecognized fragment ending"); + if (invalidFragmentEnding == ErrorCode.ExpectingOpeningBracket) + { + return '{'; + } + else if (invalidFragmentEnding == ErrorCode.ExpectingSemicolon) + { + return ';'; + } + else if (invalidFragmentEnding == ErrorCode.UnexpectedFragmentDelimiter) + { + return CodeFragment.MissingDelimiter; + } + else + { + throw new NotImplementedException("unrecognized fragment ending"); + } } private static DiagnosticSeverity Severity(QsCompilerDiagnostic msg) { - if (msg.Diagnostic.IsError) return DiagnosticSeverity.Error; - else if (msg.Diagnostic.IsWarning) return DiagnosticSeverity.Warning; - else if (msg.Diagnostic.IsInformation) return DiagnosticSeverity.Information; - else throw new NotImplementedException("Hints are currently not supported - they need to be added to Diagnostics.fs in the QsLanguageProcessor, and here."); + if (msg.Diagnostic.IsError) + { + return DiagnosticSeverity.Error; + } + else if (msg.Diagnostic.IsWarning) + { + return DiagnosticSeverity.Warning; + } + else if (msg.Diagnostic.IsInformation) + { + return DiagnosticSeverity.Information; + } + else + { + throw new NotImplementedException("Hints are currently not supported - they need to be added to Diagnostics.fs in the QsLanguageProcessor, and here."); + } } private static string Code(QsCompilerDiagnostic msg) { - if (msg.Diagnostic.IsError) return Errors.Error(msg.Code); - else if (msg.Diagnostic.IsWarning) return Warnings.Warning(msg.Code); - else if (msg.Diagnostic.IsInformation) return Informations.Information(msg.Code); - else throw new NotImplementedException("Hints are currently not supported - they need to be added to Diagnostics.fs in the QsLanguageProcessor, and here."); + if (msg.Diagnostic.IsError) + { + return Errors.Error(msg.Code); + } + else if (msg.Diagnostic.IsWarning) + { + return Warnings.Warning(msg.Code); + } + else if (msg.Diagnostic.IsInformation) + { + return Informations.Information(msg.Code); + } + else + { + throw new NotImplementedException("Hints are currently not supported - they need to be added to Diagnostics.fs in the QsLanguageProcessor, and here."); + } } /// /// Generates a suitable Diagnostic from the given CompilerDiagnostic returned by the Q# compiler. - /// The message range contained in the given CompilerDiagnostic is first converted to a Position object, + /// The message range contained in the given CompilerDiagnostic is first converted to a Position object, /// and then added to the given positionOffset if the latter is not null. /// Throws an ArgumentNullException if the Range of the given CompilerDiagnostic is null. - /// Throws an ArgumentOutOfRangeException if the contained range contains zero or negative entries, or if its Start is bigger than its End. + /// Throws an ArgumentOutOfRangeException if the contained range contains zero or negative entries, or if its Start is bigger than its End. /// - internal static Diagnostic Generate(string filename, QsCompilerDiagnostic msg, Position positionOffset = null) + internal static Diagnostic Generate(string filename, QsCompilerDiagnostic msg, Position positionOffset = null) { - if (msg.Range == null) throw new ArgumentNullException(nameof(msg.Range)); + if (msg.Range == null) + { + throw new ArgumentNullException(nameof(msg.Range)); + } return new Diagnostic { Severity = Severity(msg), Code = Code(msg), Source = filename, - Message = msg.Message, + Message = msg.Message, Range = DiagnosticTools.GetAbsoluteRange(positionOffset, msg.Range) }; } internal const string QsCodePrefix = "QS"; + public static bool TryGetCode(string str, out int code) { code = -1; str = str?.Trim(); - return !String.IsNullOrWhiteSpace(str) && - str.StartsWith(QsCodePrefix, StringComparison.InvariantCultureIgnoreCase) && + return !string.IsNullOrWhiteSpace(str) && + str.StartsWith(QsCodePrefix, StringComparison.InvariantCultureIgnoreCase) && int.TryParse(str.Substring(2), out code); } } - public static class Informations { public static string Code(this InformationCode code) => - Information((int)code); + Information((int)code); internal static string Information(int code) => $"{Diagnostics.QsCodePrefix}{code}"; @@ -83,7 +121,7 @@ internal static string Information(int code) => public static class Warnings { public static string Code(this WarningCode code) => - Warning((int)code); + Warning((int)code); internal static string Warning(int code) => $"{Diagnostics.QsCodePrefix}{code}"; @@ -94,7 +132,7 @@ public static Diagnostic LoadWarning(WarningCode code, IEnumerable args, new Diagnostic { Severity = DiagnosticSeverity.Warning, - Code = Warnings.Code(code), + Code = Code(code), Source = source, Message = DiagnosticItem.Message(code, args ?? Enumerable.Empty()), Range = null @@ -118,7 +156,7 @@ internal static Diagnostic EmptyStatementWarning(string filename, Position pos) public static class Errors { public static string Code(this ErrorCode code) => - Error((int)code); + Error((int)code); internal static string Error(int code) => $"{Diagnostics.QsCodePrefix}{code}"; @@ -129,7 +167,7 @@ public static Diagnostic LoadError(ErrorCode code, IEnumerable args, str new Diagnostic { Severity = DiagnosticSeverity.Error, - Code = Errors.Code(code), + Code = Code(code), Source = source, Message = DiagnosticItem.Message(code, args ?? Enumerable.Empty()), Range = null diff --git a/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs b/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs index ae477d4b7e..9c76c8284b 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs @@ -10,13 +10,11 @@ using Microsoft.Quantum.QsCompiler.Diagnostics; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; -using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.TextProcessing; using Microsoft.Quantum.QsCompiler.Transformations.QsCodeOutput; using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { internal static class SuggestedEdits @@ -27,8 +25,14 @@ internal static class SuggestedEdits /// private static WorkspaceEdit GetWorkspaceEdit(this FileContentManager file, params TextEdit[] edits) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (edits == null || edits.Any(edit => edit == null)) throw new ArgumentNullException(nameof(edits)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (edits == null || edits.Any(edit => edit == null)) + { + throw new ArgumentNullException(nameof(edits)); + } var versionedFileId = new VersionedTextDocumentIdentifier { Uri = file.Uri, Version = 1 }; // setting version to null here won't work in VS Code ... return new WorkspaceEdit @@ -47,8 +51,8 @@ private static WorkspaceEdit GetWorkspaceEdit(this FileContentManager file, para /// /// Returns the name of the identifier as an out parameter if an unqualified symbol exists at that location. /// - private static IEnumerable> NamespaceSuggestionsForIdAtPosition - (this FileContentManager file, Position pos, CompilationUnit compilation, out string idName) + private static IEnumerable> NamespaceSuggestionsForIdAtPosition( + this FileContentManager file, Position pos, CompilationUnit compilation, out string idName) { var variables = file?.TryGetQsSymbolInfo(pos, true, out CodeFragment _)?.UsedVariables; idName = variables != null && variables.Any() ? variables.Single().Symbol.AsDeclarationName(null) : null; @@ -66,8 +70,8 @@ private static IEnumerable> NamespaceSuggestionsForIdAtPosit /// /// Returns the name of the type as an out parameter if an unqualified symbol exists at that location. /// - private static IEnumerable> NamespaceSuggestionsForTypeAtPosition - (this FileContentManager file, Position pos, CompilationUnit compilation, out string typeName) + private static IEnumerable> NamespaceSuggestionsForTypeAtPosition( + this FileContentManager file, Position pos, CompilationUnit compilation, out string typeName) { var types = file?.TryGetQsSymbolInfo(pos, true, out CodeFragment _)?.UsedTypes; typeName = types != null && types.Any() && @@ -84,7 +88,10 @@ private static IEnumerable> NamespaceSuggestionsForTypeAtPos /// private static IEnumerable FragmentsOverlappingWithRange(this FileContentManager file, LSP.Range range) { - if (file == null || range?.Start == null || range.End == null) return Enumerable.Empty(); + if (file == null || range?.Start == null || range.End == null) + { + return Enumerable.Empty(); + } var (start, end) = (range.Start.Line, range.End.Line); var fragAtStart = file.TryGetFragmentAt(range.Start, out var _, includeEnd: true); @@ -95,7 +102,10 @@ private static IEnumerable FragmentsOverlappingWithRange(this File .Concat(file.GetTokenizedLine(end).Select(t => t.WithUpdatedLineNumber(end)).Where(ContextBuilder.TokensStartingBefore(range.End))); var fragments = ImmutableArray.CreateBuilder(); - if (fragAtStart != null) fragments.Add(fragAtStart); + if (fragAtStart != null) + { + fragments.Add(fragAtStart); + } fragments.AddRange(inRange); return fragments.ToImmutableArray(); } @@ -112,7 +122,10 @@ private static IEnumerable OpenDirectiveSuggestions(this FileContentMa .TakeWhile(t => t.Line <= lineNr).LastOrDefault() // going by line here is fine - inaccuracies if someone has multiple namespace and callable declarations on the same line seem acceptable... ?.GetChildren(deep: false); var firstInNs = nsElements?.FirstOrDefault()?.GetFragment(); - if (firstInNs?.Kind == null) return Enumerable.Empty(); + if (firstInNs?.Kind == null) + { + return Enumerable.Empty(); + } // determine what open directives already exist var insertOpenDirAt = firstInNs.GetRange().Start; @@ -129,10 +142,10 @@ private static IEnumerable OpenDirectiveSuggestions(this FileContentMa var openDirEditRange = new LSP.Range { Start = insertOpenDirAt, End = insertOpenDirAt }; var additionalLinesAfterOpenDir = firstInNs.Kind.OpenedNamespace().IsNull ? $"{Environment.NewLine}{Environment.NewLine}" : ""; var indentationAfterOpenDir = file.GetLine(insertOpenDirAt.Line).Text.Substring(0, insertOpenDirAt.Character); - var whitespaceAfterOpenDir = $"{Environment.NewLine}{additionalLinesAfterOpenDir}{(String.IsNullOrWhiteSpace(indentationAfterOpenDir) ? indentationAfterOpenDir : " ")}"; + var whitespaceAfterOpenDir = $"{Environment.NewLine}{additionalLinesAfterOpenDir}{(string.IsNullOrWhiteSpace(indentationAfterOpenDir) ? indentationAfterOpenDir : " ")}"; // construct a suitable edit - return namespaces.Distinct().Where(ns => !openDirs.Contains(ns.Value, null)).Select(suggestedNS => // filter all namespaces that are already open + return namespaces.Distinct().Where(ns => !openDirs.Contains(ns.Value, null)).Select(suggestedNS => // filter all namespaces that are already open { var directive = $"{Keywords.importDirectiveHeader.id} {suggestedNS.Value}"; return new TextEdit { Range = openDirEditRange, NewText = $"{directive};{whitespaceAfterOpenDir}" }; @@ -144,13 +157,19 @@ private static IEnumerable OpenDirectiveSuggestions(this FileContentMa /// given the file for which those diagnostics were generated and the corresponding compilation. /// Returns an empty enumerable if any of the given arguments is null. /// - internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForAmbiguousIdentifiers - (this FileContentManager file, CompilationUnit compilation, IEnumerable diagnostics) + internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForAmbiguousIdentifiers( + this FileContentManager file, CompilationUnit compilation, IEnumerable diagnostics) { - if (file == null || diagnostics == null) return Enumerable.Empty<(string, WorkspaceEdit)>(); + if (file == null || diagnostics == null) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } var ambiguousCallables = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.AmbiguousCallable)); var ambiguousTypes = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.AmbiguousType)); - if (!ambiguousCallables.Any() && !ambiguousTypes.Any()) return Enumerable.Empty<(string, WorkspaceEdit)>(); + if (!ambiguousCallables.Any() && !ambiguousTypes.Any()) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } (string, WorkspaceEdit) SuggestedNameQualification(NonNullable suggestedNS, string id, Position pos) { @@ -173,13 +192,19 @@ private static IEnumerable OpenDirectiveSuggestions(this FileContentMa /// The given line number is used to determine the containing namespace. /// Returns an empty enumerable if any of the given arguments is null. /// - internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForUnknownIdentifiers - (this FileContentManager file, CompilationUnit compilation, int lineNr, IEnumerable diagnostics) + internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForUnknownIdentifiers( + this FileContentManager file, CompilationUnit compilation, int lineNr, IEnumerable diagnostics) { - if (file == null || diagnostics == null) return Enumerable.Empty<(string, WorkspaceEdit)>(); + if (file == null || diagnostics == null) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } var unknownCallables = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UnknownIdentifier)); var unknownTypes = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UnknownType)); - if (!unknownCallables.Any() && !unknownTypes.Any()) return Enumerable.Empty<(string, WorkspaceEdit)>(); + if (!unknownCallables.Any() && !unknownTypes.Any()) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } var suggestionsForIds = unknownCallables.Select(d => d.Range.Start) .SelectMany(pos => file.NamespaceSuggestionsForIdAtPosition(pos, compilation, out var _)); @@ -194,10 +219,13 @@ private static IEnumerable OpenDirectiveSuggestions(this FileContentMa /// and given the file for which those diagnostics were generated. /// Returns an empty enumerable if any of the given arguments is null. /// - internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForDeprecatedSyntax - (this FileContentManager file, IEnumerable diagnostics) + internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForDeprecatedSyntax( + this FileContentManager file, IEnumerable diagnostics) { - if (file == null || diagnostics == null) return Enumerable.Empty<(string, WorkspaceEdit)>(); + if (file == null || diagnostics == null) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } var deprecatedUnitTypes = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedUnitType)); var deprecatedNOToperators = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedNOToperator)); var deprecatedANDoperators = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedANDoperator)); @@ -206,13 +234,19 @@ private static IEnumerable OpenDirectiveSuggestions(this FileContentMa (string, WorkspaceEdit) ReplaceWith(string text, LSP.Range range) { - static bool NeedsWs(Char ch) => Char.IsLetterOrDigit(ch) || ch == '_'; + static bool NeedsWs(char ch) => char.IsLetterOrDigit(ch) || ch == '_'; if (range?.Start != null && range.End != null) { var beforeEdit = file.GetLine(range.Start.Line).Text.Substring(0, range.Start.Character); var afterEdit = file.GetLine(range.End.Line).Text.Substring(range.End.Character); - if (beforeEdit.Any() && NeedsWs(beforeEdit.Last())) text = $" {text}"; - if (afterEdit.Any() && NeedsWs(afterEdit.First())) text = $"{text} "; + if (beforeEdit.Any() && NeedsWs(beforeEdit.Last())) + { + text = $" {text}"; + } + if (afterEdit.Any() && NeedsWs(afterEdit.First())) + { + text = $"{text} "; + } } var edit = new TextEdit { Range = range?.Copy(), NewText = text }; return ($"Replace with \"{text.Trim()}\".", file.GetWorkspaceEdit(edit)); @@ -247,9 +281,6 @@ static IEnumerable GetCharacteristics(QsTuple(); - //var symbolInfo = file.TryGetQsSymbolInfo(d.Range.Start, false, out var fragment); - //var characteristicsInFragment = (symbolInfo?.UsedTypes ?? Enumerable.Empty()) - // .SelectMany(t => t.ExtractCharacteristics()).Distinct(); var fragmentStart = fragment?.GetRange()?.Start; return characteristicsInFragment .Where(c => c.Range.IsValue && DiagnosticTools.GetAbsoluteRange(fragmentStart, c.Range.Item).Overlaps(d.Range)) @@ -268,17 +299,23 @@ static IEnumerable GetCharacteristics(QsTuple - internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForUpdateAndReassignStatements - (this FileContentManager file, IEnumerable diagnostics) + internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForUpdateAndReassignStatements( + this FileContentManager file, IEnumerable diagnostics) { - if (file == null || diagnostics == null) return Enumerable.Empty<(string, WorkspaceEdit)>(); + if (file == null || diagnostics == null) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } var updateOfArrayItemExprs = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UpdateOfArrayItemExpr)); (string, WorkspaceEdit) SuggestedCopyAndUpdateExpr(CodeFragment fragment) { var exprInfo = Parsing.ProcessUpdateOfArrayItemExpr.Invoke(fragment.Text); // Skip if the statement did not match a pattern for which we can give a code action - if (exprInfo == null || (exprInfo.Item1.Line == 1 && exprInfo.Item1.Column == 1)) return ("", null); + if (exprInfo == null || (exprInfo.Item1.Line == 1 && exprInfo.Item1.Column == 1)) + { + return ("", null); + } // Convert set [] = to set w/= <- var rhs = $"{exprInfo.Item3} {Keywords.qsCopyAndUpdateOp.cont} {exprInfo.Item4}"; @@ -300,8 +337,8 @@ static IEnumerable GetCharacteristics(QsTuple - internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForIndexRange - (this FileContentManager file, CompilationUnit compilation, LSP.Range range) + internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForIndexRange( + this FileContentManager file, CompilationUnit compilation, LSP.Range range) { if (file == null || compilation == null || range?.Start == null) { @@ -323,18 +360,27 @@ static IEnumerable GetCharacteristics(QsTuple(); } - /// Returns true the given expression is of the form "0 .. Length(args) - 1", - /// as well as the range of the entire expression and the argument tuple "(args)" as out parameters. + // Returns true the given expression is of the form "0 .. Length(args) - 1", + // as well as the range of the entire expression and the argument tuple "(args)" as out parameters. static bool IsIndexRange(QsExpression iterExpr, Position offset, out LSP.Range exprRange, out LSP.Range argRange) { - if (iterExpr.Expression is QsExpressionKind.RangeLiteral rangeExpression && iterExpr.Range.IsValue && // iterable expression is a valid range literal - rangeExpression.Item1.Expression is QsExpressionKind.IntLiteral intLiteralExpression && intLiteralExpression.Item == 0L && // .. starting at 0 .. - rangeExpression.Item2.Expression is QsExpressionKind.SUB SUBExpression && // .. and ending in subtracting .. - SUBExpression.Item2.Expression is QsExpressionKind.IntLiteral subIntLiteralExpression && subIntLiteralExpression.Item == 1L && // .. 1 from .. - SUBExpression.Item1.Expression is QsExpressionKind.CallLikeExpression callLikeExression && // .. a call .. - callLikeExression.Item1.Expression is QsExpressionKind.Identifier identifier && // .. to and identifier .. - identifier.Item1.Symbol is QsSymbolKind.Symbol symName && symName.Item.Value == BuiltIn.Length.FullName.Name.Value && // .. "Length" called with .. - callLikeExression.Item2.Expression is QsExpressionKind.ValueTuple valueTuple && callLikeExression.Item2.Range.IsValue) // .. a valid argument tuple + if ( + // iterable expression is a valid range literal + iterExpr.Expression is QsExpressionKind.RangeLiteral rangeExpression && iterExpr.Range.IsValue && + // .. starting at 0 .. + rangeExpression.Item1.Expression is QsExpressionKind.IntLiteral intLiteralExpression && intLiteralExpression.Item == 0L && + // .. and ending in subtracting .. + rangeExpression.Item2.Expression is QsExpressionKind.SUB sUBExpression && + // .. 1 from .. + sUBExpression.Item2.Expression is QsExpressionKind.IntLiteral subIntLiteralExpression && subIntLiteralExpression.Item == 1L && + // .. a call .. + sUBExpression.Item1.Expression is QsExpressionKind.CallLikeExpression callLikeExression && + // .. to and identifier .. + callLikeExression.Item1.Expression is QsExpressionKind.Identifier identifier && + // .. "Length" called with .. + identifier.Item1.Symbol is QsSymbolKind.Symbol symName && symName.Item.Value == BuiltIn.Length.FullName.Name.Value && + // .. a valid argument tuple + callLikeExression.Item2.Expression is QsExpressionKind.ValueTuple valueTuple && callLikeExression.Item2.Range.IsValue) { exprRange = DiagnosticTools.GetAbsoluteRange(offset, iterExpr.Range.Item); argRange = DiagnosticTools.GetAbsoluteRange(offset, callLikeExression.Item2.Range.Item); @@ -345,8 +391,8 @@ callLikeExression.Item1.Expression is QsExpressionKind IndexRangeEdits(CodeFragment fragment) { if (fragment.Kind is QsFragmentKind.ForLoopIntro forLoopIntro && // todo: in principle we could give these suggestions for any index range @@ -378,17 +424,23 @@ static IEnumerable IndexRangeEdits(CodeFragment fragment) /// and given the file for which those diagnostics were generated. /// Returns an empty enumerable if any of the given arguments is null. /// - internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForUnreachableCode - (this FileContentManager file, IEnumerable diagnostics) + internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForUnreachableCode( + this FileContentManager file, IEnumerable diagnostics) { - if (file == null || diagnostics == null) return Enumerable.Empty<(string, WorkspaceEdit)>(); + if (file == null || diagnostics == null) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } var unreachableCode = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.UnreachableCode)); WorkspaceEdit SuggestedRemoval(Position pos) { var fragment = file.TryGetFragmentAt(pos, out var currentFragToken); var lastFragToken = new CodeFragment.TokenIndex(currentFragToken); - if (fragment == null || --lastFragToken == null) return null; + if (fragment == null || --lastFragToken == null) + { + return null; + } // work off of the last reachable fragment, if there is one var lastBeforeErase = lastFragToken.GetFragment(); @@ -434,14 +486,20 @@ WorkspaceEdit SuggestedRemoval(Position pos) { var overlapping = file?.FragmentsOverlappingWithRange(range); var fragment = overlapping?.FirstOrDefault(); - if (fragment?.Kind == null || overlapping.Count() != 1) return Enumerable.Empty<(string, WorkspaceEdit)>(); // only suggest doc comment directly on the declaration + if (fragment?.Kind == null || overlapping.Count() != 1) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); // only suggest doc comment directly on the declaration + } var (nsDecl, callableDecl, typeDecl) = (fragment.Kind.DeclaredNamespace(), fragment.Kind.DeclaredCallable(), fragment.Kind.DeclaredType()); var declSymbol = nsDecl.IsValue ? nsDecl.Item.Item1.Symbol : callableDecl.IsValue ? callableDecl.Item.Item1.Symbol : typeDecl.IsValue ? typeDecl.Item.Item1.Symbol : null; var declStart = fragment.GetRange().Start; - if (declSymbol == null || file.DocumentingComments(declStart).Any()) return Enumerable.Empty<(string, WorkspaceEdit)>(); + if (declSymbol == null || file.DocumentingComments(declStart).Any()) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } // set declStart to the position of the first attribute attached to the declaration static bool EmptyOrFirstAttribute(IEnumerable line, out CodeFragment att) @@ -450,10 +508,12 @@ static bool EmptyOrFirstAttribute(IEnumerable line, out CodeFragme return att != null || (line != null && !line.Any()); } var preceding = file.GetTokenizedLine(declStart.Line).TakeWhile(ContextBuilder.TokensUpTo(new Position(0, declStart.Character))); - for (var lineNr = declStart.Line; EmptyOrFirstAttribute(preceding, out var precedingAttribute); ) + for (var lineNr = declStart.Line; EmptyOrFirstAttribute(preceding, out var precedingAttribute);) { if (precedingAttribute != null) - { declStart = precedingAttribute.GetRange().Start.WithUpdatedLineNumber(lineNr); } + { + declStart = precedingAttribute.GetRange().Start.WithUpdatedLineNumber(lineNr); + } preceding = lineNr-- > 0 ? file.GetTokenizedLine(lineNr) : (IEnumerable)null; } @@ -469,21 +529,20 @@ static bool EmptyOrFirstAttribute(IEnumerable line, out CodeFragme var hasOutput = callableDecl.IsValue && !callableDecl.Item.Item2.Item3.ReturnType.Type.IsUnitType; var args = argTuple == null ? ImmutableArray>.Empty : SyntaxGenerator.ExtractItems(argTuple); - docString = String.Concat( + docString = string.Concat( docString, // Document Input Parameters - args.Any() ? $"{docPrefix}# Input{endLine}" : String.Empty, - String.Concat(args.Select(x => $"{docPrefix}## {x.Item1.Symbol.AsDeclarationName(null)}{endLine}{docPrefix}{endLine}")), + args.Any() ? $"{docPrefix}# Input{endLine}" : string.Empty, + string.Concat(args.Select(x => $"{docPrefix}## {x.Item1.Symbol.AsDeclarationName(null)}{endLine}{docPrefix}{endLine}")), // Document Output - hasOutput ? $"{docPrefix}# Output{endLine}{docPrefix}{endLine}" : String.Empty, + hasOutput ? $"{docPrefix}# Output{endLine}{docPrefix}{endLine}" : string.Empty, // Document Type Parameters - typeParams.Any() ? $"{docPrefix}# Type Parameters{endLine}" : String.Empty, - String.Concat(typeParams.Select(x => $"{docPrefix}## '{x.Symbol.AsDeclarationName(null)}{endLine}{docPrefix}{endLine}")) - ); + typeParams.Any() ? $"{docPrefix}# Type Parameters{endLine}" : string.Empty, + string.Concat(typeParams.Select(x => $"{docPrefix}## '{x.Symbol.AsDeclarationName(null)}{endLine}{docPrefix}{endLine}"))); var whichDecl = $" for {declSymbol.AsDeclarationName(null)}"; var suggestedEdit = file.GetWorkspaceEdit(new TextEdit { Range = new LSP.Range { Start = declStart, End = declStart }, NewText = docString }); return new[] { ($"Add documentation{whichDecl}.", suggestedEdit) }; } } -} \ No newline at end of file +} diff --git a/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs b/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs index 4ec82b6449..65a96164e3 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs @@ -1,19 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; using Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures; using Microsoft.Quantum.QsCompiler.DataTypes; -using Microsoft.Quantum.QsCompiler.SymbolManagement; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.TextProcessing; using Microsoft.Quantum.QsCompiler.TextProcessing.CodeCompletion; using Microsoft.VisualStudio.LanguageServer.Protocol; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; using static Microsoft.Quantum.QsCompiler.SyntaxGenerator; using static Microsoft.Quantum.QsCompiler.TextProcessing.CodeCompletion.FragmentParsing; @@ -42,9 +41,9 @@ public class CompletionItemData /// public QsQualifiedName QualifiedName { - get => @namespace == null || name == null + get => this.@namespace == null || this.name == null ? null - : new QsQualifiedName(NonNullable.New(@namespace), NonNullable.New(name)); + : new QsQualifiedName(NonNullable.New(this.@namespace), NonNullable.New(this.name)); } /// @@ -56,10 +55,10 @@ public QsQualifiedName QualifiedName public CompletionItemData( TextDocumentIdentifier textDocument = null, QsQualifiedName qualifiedName = null, string sourceFile = null) { - TextDocument = textDocument; - @namespace = qualifiedName?.Namespace.Value; - name = qualifiedName?.Name.Value; - SourceFile = sourceFile; + this.TextDocument = textDocument; + this.@namespace = qualifiedName?.Namespace.Value; + this.name = qualifiedName?.Name.Value; + this.SourceFile = sourceFile; } } @@ -72,30 +71,40 @@ internal static class CodeCompletion /// Returns a list of suggested completion items for the given position. /// /// Returns null if any argument is null or the position is invalid. - /// Returns an empty completion list if the given position is within a comment. + /// Returns an empty completion list if the given position is within a comment. /// public static CompletionList Completions( this FileContentManager file, CompilationUnit compilation, Position position) { if (file == null || compilation == null || position == null || !Utils.IsValidPosition(position)) + { return null; + } if (file.GetLine(position.Line).WithoutEnding.Length < position.Character) + { return Enumerable.Empty().ToCompletionList(false); + } var (scope, previous) = GetCompletionEnvironment(file, position, out var fragment); if (scope == null) + { return GetFallbackCompletions(file, compilation, position).ToCompletionList(false); + } var result = GetCompletionKinds( scope, previous != null ? QsNullable.NewValue(previous) : QsNullable.Null, GetFragmentTextBeforePosition(file, fragment, position)); if (result is CompletionResult.Success success) + { return success.Item .SelectMany(kind => GetCompletionsForKind(file, compilation, position, kind)) .ToCompletionList(false); + } else + { return GetFallbackCompletions(file, compilation, position).ToCompletionList(false); + } } /// @@ -109,10 +118,14 @@ public static CompletionItem ResolveCompletion( this CompilationUnit compilation, CompletionItem item, CompletionItemData data, MarkupKind format) { if (compilation == null || item == null || data == null) + { return null; + } var documentation = TryGetDocumentation(compilation, data, item.Kind, format == MarkupKind.Markdown); if (documentation != null) + { item.Documentation = new MarkupContent { Kind = format, Value = documentation }; + } return item; } @@ -126,9 +139,13 @@ private static (CompletionScope, QsFragmentKind) GetCompletionEnvironment( FileContentManager file, Position position, out CodeFragment fragment) { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (!Utils.IsValidPosition(position)) + { throw new ArgumentException(nameof(position)); + } if (!Utils.IsValidPosition(position, file)) { // FileContentManager.IndentationAt will fail if the position is not within the file. @@ -153,23 +170,39 @@ private static (CompletionScope, QsFragmentKind) GetCompletionEnvironment( CompletionScope scope = null; if (!parents.Any()) + { scope = CompletionScope.TopLevel; + } else if (parents.Any() && parents.First().Kind.IsNamespaceDeclaration) + { scope = CompletionScope.NamespaceTopLevel; + } else if (parents.Where(parent => parent.Kind.IsFunctionDeclaration).Any()) + { scope = CompletionScope.Function; + } else if (parents.Any() && parents.First().Kind.IsOperationDeclaration) + { scope = CompletionScope.OperationTopLevel; + } else if (parents.Where(parent => parent.Kind.IsOperationDeclaration).Any()) + { scope = CompletionScope.Operation; + } QsFragmentKind previous = null; if (relativeIndentation == 0 && IsPositionAfterDelimiter(file, fragment, position)) + { previous = fragment.Kind; + } else if (relativeIndentation == 0) + { previous = token.PreviousOnScope()?.GetFragment().Kind; + } else if (relativeIndentation == 1) + { previous = token.GetNonEmptyParent()?.GetFragment().Kind; + } return (scope, previous); } @@ -187,21 +220,35 @@ private static IEnumerable GetCompletionsForKind( string namespacePrefix = "") { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (compilation == null) + { throw new ArgumentNullException(nameof(compilation)); + } if (!Utils.IsValidPosition(position)) + { throw new ArgumentException(nameof(position)); + } if (kind == null) + { throw new ArgumentNullException(nameof(kind)); + } if (namespacePrefix == null) + { throw new ArgumentNullException(nameof(namespacePrefix)); + } switch (kind) { case CompletionKind.Member member: - return GetCompletionsForKind(file, compilation, position, member.Item2, - ResolveNamespaceAlias(file, compilation, position, member.Item1)); + return GetCompletionsForKind( + file, + compilation, + position, + member.Item2, + ResolveNamespaceAlias(file, compilation, position, member.Item1)); case CompletionKind.Keyword keyword: return new[] { new CompletionItem { Label = keyword.Item, Kind = CompletionItemKind.Keyword } }; } @@ -245,11 +292,17 @@ private static IEnumerable GetFallbackCompletions( FileContentManager file, CompilationUnit compilation, Position position) { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (compilation == null) + { throw new ArgumentNullException(nameof(compilation)); + } if (position == null) + { throw new ArgumentNullException(nameof(position)); + } // If the character at the position is a dot but no valid namespace path precedes it (for example, in a // decimal number), then no completions are valid here. @@ -293,11 +346,17 @@ private static IEnumerable GetLocalCompletions( FileContentManager file, CompilationUnit compilation, Position position, bool mutableOnly = false) { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (compilation == null) + { throw new ArgumentNullException(nameof(compilation)); + } if (!Utils.IsValidPosition(position)) + { throw new ArgumentException(nameof(position)); + } return compilation .TryGetLocalDeclarations(file, position, out _, includeDeclaredAtPosition: false) @@ -324,14 +383,22 @@ private static IEnumerable GetCallableCompletions( IEnumerable openNamespaces) { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (compilation == null) + { throw new ArgumentNullException(nameof(compilation)); + } if (openNamespaces == null) + { throw new ArgumentNullException(nameof(openNamespaces)); + } if (!compilation.GlobalSymbols.ContainsResolutions) + { return Array.Empty(); + } return compilation.GlobalSymbols.AccessibleCallables() .Where(callable => @@ -363,14 +430,22 @@ private static IEnumerable GetTypeCompletions( IEnumerable openNamespaces) { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (compilation == null) + { throw new ArgumentNullException(nameof(compilation)); + } if (openNamespaces == null) + { throw new ArgumentNullException(nameof(openNamespaces)); + } if (!compilation.GlobalSymbols.ContainsResolutions) + { return Array.Empty(); + } return compilation.GlobalSymbols.AccessibleTypes() .Where(type => IsAccessibleAsUnqualifiedName(type.QualifiedName, currentNamespace, openNamespaces)) @@ -394,10 +469,14 @@ private static IEnumerable GetTypeCompletions( private static IEnumerable GetNamedItemCompletions(CompilationUnit compilation) { if (compilation == null) + { throw new ArgumentNullException(nameof(compilation)); + } if (!compilation.GlobalSymbols.ContainsResolutions) + { return Array.Empty(); + } return compilation.GlobalSymbols.AccessibleTypes() .SelectMany(type => ExtractItems(type.TypeItems)) .Where(item => item.IsNamed) @@ -420,12 +499,18 @@ private static IEnumerable GetGlobalNamespaceCompletions( CompilationUnit compilation, string prefix = "") { if (compilation == null) + { throw new ArgumentNullException(nameof(compilation)); + } if (prefix == null) + { throw new ArgumentNullException(nameof(prefix)); + } if (prefix.Length != 0 && !prefix.EndsWith(".")) + { prefix += "."; + } return compilation.GlobalSymbols.NamespaceNames() .Where(name => name.Value.StartsWith(prefix)) @@ -452,19 +537,31 @@ private static IEnumerable GetNamespaceAliasCompletions( FileContentManager file, CompilationUnit compilation, Position position, string prefix = "") { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (compilation == null) + { throw new ArgumentNullException(nameof(compilation)); + } if (!Utils.IsValidPosition(position)) + { throw new ArgumentException(nameof(position)); + } if (prefix == null) + { throw new ArgumentNullException(nameof(prefix)); + } if (prefix.Length != 0 && !prefix.EndsWith(".")) + { prefix += "."; + } var @namespace = file.TryGetNamespaceAt(position); if (@namespace == null || !compilation.GlobalSymbols.NamespaceExists(NonNullable.New(@namespace))) + { return Array.Empty(); + } return compilation .GetOpenDirectives(NonNullable.New(@namespace))[file.FileName] .Where(open => open.Item2 != null && open.Item2.StartsWith(prefix)) @@ -489,13 +586,19 @@ private static string TryGetDocumentation( CompilationUnit compilation, CompletionItemData data, CompletionItemKind kind, bool useMarkdown) { if (compilation == null) + { throw new ArgumentNullException(nameof(compilation)); + } if (data == null) + { throw new ArgumentNullException(nameof(data)); + } if (data.QualifiedName == null || data.SourceFile == null || !compilation.GlobalSymbols.NamespaceExists(data.QualifiedName.Namespace)) + { return null; + } switch (kind) { @@ -504,7 +607,9 @@ private static string TryGetDocumentation( var result = compilation.GlobalSymbols.TryGetCallable( data.QualifiedName, data.QualifiedName.Namespace, NonNullable.New(data.SourceFile)); if (!(result is ResolutionResult.Found callable)) + { return null; + } var signature = callable.Item.PrintSignature(); var documentation = callable.Item.Documentation.PrintSummary(useMarkdown); return signature.Trim() + "\n\n" + documentation.Trim(); @@ -529,18 +634,26 @@ private static IEnumerable GetOpenNamespaces( FileContentManager file, CompilationUnit compilation, Position position) { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (compilation == null) + { throw new ArgumentNullException(nameof(compilation)); + } if (!Utils.IsValidPosition(position)) + { throw new ArgumentException(nameof(position)); + } var @namespace = file.TryGetNamespaceAt(position); if (@namespace == null || !compilation.GlobalSymbols.NamespaceExists(NonNullable.New(@namespace))) + { return Array.Empty(); + } return compilation .GetOpenDirectives(NonNullable.New(@namespace))[file.FileName] - .Where(open => open.Item2 == null) // Only include open directives without an alias. + .Where(open => open.Item2 == null) // Only include open directives without an alias. .Select(open => open.Item1.Value) .Concat(new[] { @namespace }); } @@ -554,19 +667,29 @@ private static IEnumerable GetOpenNamespaces( private static string GetSymbolNamespacePath(FileContentManager file, Position position) { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (!Utils.IsValidPosition(position)) + { throw new ArgumentException(nameof(position)); + } var fragment = file.TryGetFragmentAt(position, out _, includeEnd: true); if (fragment == null) + { return null; + } var startAt = GetTextIndexFromPosition(fragment, position); var match = Utils.QualifiedSymbolRTL.Match(fragment.Text, startAt); if (match.Success && match.Index + match.Length == startAt && match.Value.LastIndexOf('.') != -1) + { return match.Value.Substring(0, match.Value.LastIndexOf('.')); + } else + { return null; + } } /// @@ -577,9 +700,13 @@ private static string GetSymbolNamespacePath(FileContentManager file, Position p private static int GetTextIndexFromPosition(CodeFragment fragment, Position position) { if (fragment == null) + { throw new ArgumentNullException(nameof(fragment)); + } if (position == null) + { throw new ArgumentNullException(nameof(position)); + } var relativeLine = position.Line - fragment.GetRange().Start.Line; var lines = Utils.SplitLines(fragment.Text).DefaultIfEmpty(""); @@ -605,17 +732,27 @@ private static string ResolveNamespaceAlias( FileContentManager file, CompilationUnit compilation, Position position, string alias) { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (compilation == null) + { throw new ArgumentNullException(nameof(compilation)); + } if (!Utils.IsValidPosition(position)) + { throw new ArgumentException(nameof(position)); + } if (alias == null) + { throw new ArgumentNullException(nameof(alias)); + } var nsName = file.TryGetNamespaceAt(position); if (nsName == null || !compilation.GlobalSymbols.NamespaceExists(NonNullable.New(nsName))) + { return alias; + } return compilation.GlobalSymbols.TryResolveNamespaceAlias( NonNullable.New(alias), NonNullable.New(nsName), file.FileName) ?? alias; } @@ -629,20 +766,28 @@ private static string ResolveNamespaceAlias( private static CodeFragment.TokenIndex GetTokenAtOrBefore(FileContentManager file, Position position) { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (!Utils.IsValidPosition(position)) + { throw new ArgumentException(nameof(position)); + } var line = position.Line; var tokens = file.GetTokenizedLine(line); // If the current line is empty, find the last non-empty line before it. while (tokens.IsEmpty && line > 0) + { tokens = file.GetTokenizedLine(--line); + } var index = tokens.TakeWhile(ContextBuilder.TokensUpTo(position)).Count() - 1; if (index == -1) + { return null; + } return new CodeFragment.TokenIndex(file, line, index); } @@ -654,11 +799,17 @@ private static CodeFragment.TokenIndex GetTokenAtOrBefore(FileContentManager fil private static Position GetDelimiterPosition(FileContentManager file, CodeFragment fragment) { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (fragment == null) + { throw new ArgumentNullException(nameof(fragment)); + } if (fragment.FollowedBy == CodeFragment.MissingDelimiter) + { throw new ArgumentException("Code fragment has a missing delimiter", nameof(fragment)); + } var end = fragment.GetRange().End; var position = file.FragmentEnd(ref end); @@ -668,9 +819,10 @@ private static Position GetDelimiterPosition(FileContentManager file, CodeFragme /// /// Returns true if the fragment has a delimiting character and the given position occurs after it. /// - private static bool IsPositionAfterDelimiter(FileContentManager file, - CodeFragment fragment, - Position position) => + private static bool IsPositionAfterDelimiter( + FileContentManager file, + CodeFragment fragment, + Position position) => fragment.FollowedBy != CodeFragment.MissingDelimiter && GetDelimiterPosition(file, fragment).IsSmallerThan(position); @@ -687,12 +839,18 @@ private static string GetFragmentTextBeforePosition( FileContentManager file, CodeFragment fragment, Position position) { if (file == null) + { throw new ArgumentNullException(nameof(file)); + } if (!Utils.IsValidPosition(position)) + { throw new ArgumentException(nameof(position)); + } if (fragment == null || IsPositionAfterDelimiter(file, fragment, position)) + { return ""; + } return fragment.GetRange().End.IsSmallerThan(position) ? fragment.Text + " " : fragment.Text.Substring(0, GetTextIndexFromPosition(fragment, position)); @@ -702,7 +860,7 @@ private static string GetFragmentTextBeforePosition( /// Returns the namespace part starting at the given starting position and ending at the next dot. /// private static string NextNamespacePart(string @namespace, int start) => - String.Concat(@namespace.Substring(start).TakeWhile(c => c != '.')); + string.Concat(@namespace.Substring(start).TakeWhile(c => c != '.')); /// /// Converts an of s to a @@ -722,9 +880,10 @@ private static CompletionList ToCompletionList(this IEnumerable /// Note: Names that start with "_" are treated as "private;" they are only accessible from the namespace in /// which they are declared. /// - private static bool IsAccessibleAsUnqualifiedName(QsQualifiedName qualifiedName, - string currentNamespace, - IEnumerable openNamespaces) => + private static bool IsAccessibleAsUnqualifiedName( + QsQualifiedName qualifiedName, + string currentNamespace, + IEnumerable openNamespaces) => openNamespaces.Contains(qualifiedName.Namespace.Value) && (!qualifiedName.Name.Value.StartsWith("_") || qualifiedName.Namespace.Value == currentNamespace); } diff --git a/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs b/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs index 5adda7ec1e..ea4fc10124 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs @@ -7,7 +7,6 @@ using System.Linq; using Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures; using Microsoft.Quantum.QsCompiler.DataTypes; -using Microsoft.Quantum.QsCompiler.SymbolManagement; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; @@ -15,7 +14,6 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { internal static class EditorCommands @@ -26,7 +24,10 @@ internal static class EditorCommands /// public static SymbolInformation[] DocumentSymbols(this FileContentManager file) { - if (file == null) return null; + if (file == null) + { + return null; + } var namespaceDeclarations = file.NamespaceDeclarationsSymbolInfo(); var typeDeclarations = file.TypeDeclarationsSymbolInfo(); var callableDeclarations = file.CallableDeclarationsSymbolInfo(); @@ -39,11 +40,17 @@ public static SymbolInformation[] DocumentSymbols(this FileContentManager file) /// public static Location DefinitionLocation(this FileContentManager file, CompilationUnit compilation, Position position) { - var symbolInfo = file?.TryGetQsSymbolInfo(position, true, out CodeFragment _); // includes the end position - if (symbolInfo == null || compilation == null) return null; + var symbolInfo = file?.TryGetQsSymbolInfo(position, true, out CodeFragment _); // includes the end position + if (symbolInfo == null || compilation == null) + { + return null; + } var locals = compilation.TryGetLocalDeclarations(file, position, out var cName, includeDeclaredAtPosition: true); - if (cName == null) return null; + if (cName == null) + { + return null; + } var found = symbolInfo.UsedVariables.Any() @@ -60,34 +67,49 @@ public static Location DefinitionLocation(this FileContentManager file, Compilat } /// - /// Returns an array with all locations where the symbol at the given position - if any - is referenced. + /// Returns an array with all locations where the symbol at the given position - if any - is referenced. /// Returns null if some parameters are unspecified (null), /// or if the specified position is not a valid position within the currently processed file content, /// or if no symbol exists at the specified position at this time. /// public static Location[] SymbolReferences(this FileContentManager file, CompilationUnit compilation, Position position, ReferenceContext context) { - if (file == null) return null; - if (!file.TryGetReferences(compilation, position, out var declLocation, out var locations)) return null; + if (file == null) + { + return null; + } + if (!file.TryGetReferences(compilation, position, out var declLocation, out var locations)) + { + return null; + } return (context?.IncludeDeclaration ?? true) && declLocation != null ? new[] { declLocation }.Concat(locations).ToArray() : locations.ToArray(); } /// - /// Returns the workspace edit that describes the changes to be done if the symbol at the given position - if any - is renamed to the given name. + /// Returns the workspace edit that describes the changes to be done if the symbol at the given position - if any - is renamed to the given name. /// Returns null if no symbol exists at the specified position, /// or if some parameters are unspecified (null), /// or if the specified position is not a valid position within the file. /// public static WorkspaceEdit Rename(this FileContentManager file, CompilationUnit compilation, Position position, string newName) { - if (newName == null || file == null) return null; + if (newName == null || file == null) + { + return null; + } var found = file.TryGetReferences(compilation, position, out var declLocation, out var locations); - if (!found) return null; - if (declLocation != null) locations = new[] { declLocation }.Concat(locations); + if (!found) + { + return null; + } - var changes = locations.ToLookup(loc => loc.Uri, loc => new TextEdit { Range = loc.Range, NewText = newName}); + if (declLocation != null) + { + locations = new[] { declLocation }.Concat(locations); + } + var changes = locations.ToLookup(loc => loc.Uri, loc => new TextEdit { Range = loc.Range, NewText = newName }); return new WorkspaceEdit { DocumentChanges = changes @@ -106,12 +128,15 @@ public static WorkspaceEdit Rename(this FileContentManager file, CompilationUnit /// /// Returns a look-up of workspace edits suggested by the compiler for the given location and context. - /// The key of the look-up is a suitable title for the corresponding edits that can be presented to the user. + /// The key of the look-up is a suitable title for the corresponding edits that can be presented to the user. /// Returns null if any of the given arguments is null or if suitable edits cannot be determined. /// public static ILookup CodeActions(this FileContentManager file, CompilationUnit compilation, LSP.Range range, CodeActionContext context) { - if (range?.Start == null || range.End == null || file == null || !Utils.IsValidRange(range, file)) return null; + if (range?.Start == null || range.End == null || file == null || !Utils.IsValidRange(range, file)) + { + return null; + } var suggestionsForUnknownIds = file.SuggestionsForUnknownIdentifiers(compilation, range.Start.Line, context?.Diagnostics); var suggestionsForAmbiguousIds = file.SuggestionsForAmbiguousIdentifiers(compilation, context?.Diagnostics); var suggestionsForDeprecatedSyntax = file.SuggestionsForDeprecatedSyntax(context?.Diagnostics); @@ -140,11 +165,20 @@ public static DocumentHighlight[] DocumentHighlights(this FileContentManager fil DocumentHighlight AsHighlight(LSP.Range range) => new DocumentHighlight { Range = range, Kind = DocumentHighlightKind.Read }; - if (file == null) return null; - var found = file.TryGetReferences(compilation, position, - out var declLocation, out var locations, + if (file == null) + { + return null; + } + var found = file.TryGetReferences( + compilation, + position, + out var declLocation, + out var locations, limitToSourceFiles: ImmutableHashSet.Create(file.FileName)); - if (!found) return null; + if (!found) + { + return null; + } QsCompilerError.Verify(declLocation == null || declLocation.Uri == file.Uri, "location outside current file"); var highlights = locations.Select(loc => @@ -164,7 +198,10 @@ DocumentHighlight AsHighlight(LSP.Range range) => /// or if the specified position is not a valid position within the currently processed file content, /// or if no token exists at the specified position. /// - public static Hover HoverInformation(this FileContentManager file, CompilationUnit compilation, Position position, + public static Hover HoverInformation( + this FileContentManager file, + CompilationUnit compilation, + Position position, MarkupKind format = MarkupKind.PlainText) { Hover GetHover(string info) => info == null ? null : new Hover @@ -175,12 +212,21 @@ public static Hover HoverInformation(this FileContentManager file, CompilationUn var markdown = format == MarkupKind.Markdown; var symbolInfo = file?.TryGetQsSymbolInfo(position, false, out var _); - if (symbolInfo == null || compilation == null) return null; - if (symbolInfo.UsedLiterals.Any()) return GetHover(symbolInfo.UsedLiterals.Single().LiteralInfo(markdown).Value); + if (symbolInfo == null || compilation == null) + { + return null; + } + if (symbolInfo.UsedLiterals.Any()) + { + return GetHover(symbolInfo.UsedLiterals.Single().LiteralInfo(markdown).Value); + } var locals = compilation.TryGetLocalDeclarations(file, position, out var cName, includeDeclaredAtPosition: true); var nsName = cName?.Namespace.Value ?? file.TryGetNamespaceAt(position); - if (nsName == null) return null; + if (nsName == null) + { + return null; + } // TODO: add hover for functor generators and functor applications // TOOD: add hover for new array expr ? @@ -202,22 +248,31 @@ public static Hover HoverInformation(this FileContentManager file, CompilationUn /// or if no call expression exists at the specified position at this time, /// or if no signature help information can be provided for the call expression at the specified position. /// - public static SignatureHelp SignatureHelp(this FileContentManager file, CompilationUnit compilation, Position position, + public static SignatureHelp SignatureHelp( + this FileContentManager file, + CompilationUnit compilation, + Position position, MarkupKind format = MarkupKind.PlainText) { // getting the relevant token (if any) var fragment = file?.TryGetFragmentAt(position, out var _, includeEnd: true); - if (fragment?.Kind == null || compilation == null) return null; + if (fragment?.Kind == null || compilation == null) + { + return null; + } var fragmentStart = fragment.GetRange().Start; - // getting the overlapping call expressions (if any), and determine the header of the called callable + // getting the overlapping call expressions (if any), and determine the header of the called callable bool OverlapsWithPosition(Tuple symRange) => position.IsWithinRange(DiagnosticTools.GetAbsoluteRange(fragmentStart, symRange), true); var overlappingEx = fragment.Kind.CallExpressions().Where(ex => ex.Range.IsValue && OverlapsWithPosition(ex.Range.Item)).ToList(); - if (!overlappingEx.Any()) return null; + if (!overlappingEx.Any()) + { + return null; + } overlappingEx.Sort((ex1, ex2) => // for nested call expressions, the last expressions (by range) is always the closest one { var (x, y) = (ex1.Range.Item, ex2.Range.Item); @@ -227,7 +282,10 @@ bool OverlapsWithPosition(Tuple symRange) => var nsName = file.TryGetNamespaceAt(position); var (method, args) = overlappingEx.Last().Expression is QsExpressionKind.CallLikeExpression c ? (c.Item1, c.Item2) : (null, null); - if (nsName == null || method == null || args == null) return null; + if (nsName == null || method == null || args == null) + { + return null; + } // getting the called identifier as well as what functors have been applied to it @@ -238,14 +296,20 @@ List FunctorApplications(ref QsExpression ex) ex.Expression is QsExpressionKind.ControlledApplication ctl ? (QsFunctor.Controlled, ctl.Item) : (null, null); var fs = inner == null ? new List() : FunctorApplications(ref inner); - if (next != null) fs.Add(next); + if (next != null) + { + fs.Add(next); + } ex = inner ?? ex; return fs; } var functors = FunctorApplications(ref method); var id = method.Expression as QsExpressionKind.Identifier; - if (id == null) return null; + if (id == null) + { + return null; + } // extracting and adapting the relevant information for the called callable @@ -253,17 +317,19 @@ List FunctorApplications(ref QsExpression ex) if (id.Item1.Symbol is QsSymbolKind.Symbol sym) { methodDecl = - compilation.GlobalSymbols.TryResolveAndGetCallable(sym.Item, - NonNullable.New(nsName), - file.FileName) + compilation.GlobalSymbols.TryResolveAndGetCallable( + sym.Item, + NonNullable.New(nsName), + file.FileName) as ResolutionResult.Found; } else if (id.Item1.Symbol is QsSymbolKind.QualifiedSymbol qualSym) { methodDecl = - compilation.GlobalSymbols.TryGetCallable(new QsQualifiedName(qualSym.Item1, qualSym.Item2), - NonNullable.New(nsName), - file.FileName) + compilation.GlobalSymbols.TryGetCallable( + new QsQualifiedName(qualSym.Item1, qualSym.Item2), + NonNullable.New(nsName), + file.FileName) as ResolutionResult.Found; } @@ -279,31 +345,39 @@ List FunctorApplications(ref QsExpression ex) var ctlQsName = QsLocalSymbol.NewValidName(NonNullable.New(nrCtlApplications == 0 ? "cs" : $"cs{nrCtlApplications}")); argTuple = SyntaxGenerator.WithControlQubits(argTuple, QsNullable>.Null, ctlQsName, QsNullable>.Null); } - + // now that we now what callable is called we need to check which argument should come next bool BeforePosition(Tuple symRange) => DiagnosticTools.GetAbsolutePosition(fragmentStart, symRange.Item2).IsSmallerThan(position); - IEnumerable<(Tuple, string)> ExtractParameterRanges - (QsExpression ex, QsTuple> decl) + IEnumerable<(Tuple, string)> ExtractParameterRanges( + QsExpression ex, QsTuple> decl) { - var Null = ((Tuple)null, (string)null); + var @null = ((Tuple)null, (string)null); IEnumerable<(Tuple, string)> SingleItem(string paramName) { var arg = ex?.Range == null ? ((Tuple)null, paramName) : ex.Range.IsValue ? (ex.Range.Item, paramName) - : Null; // no signature help if there are invalid expressions + : @null; // no signature help if there are invalid expressions return new[] { arg }; } if (decl is QsTuple>.QsTupleItem dItem) - { return SingleItem(dItem.Item.VariableName is QsLocalSymbol.ValidName n ? n.Item.Value : "__argName__"); } + { + return SingleItem(dItem.Item.VariableName is QsLocalSymbol.ValidName n ? n.Item.Value : "__argName__"); + } var declItems = decl as QsTuple>.QsTuple; var exItems = ex?.Expression as QsExpressionKind.ValueTuple; - if (declItems == null) return new[] { Null }; - if (exItems == null && declItems.Item.Length > 1) return SingleItem(decl.PrintArgumentTuple()); + if (declItems == null) + { + return new[] { @null }; + } + if (exItems == null && declItems.Item.Length > 1) + { + return SingleItem(decl.PrintArgumentTuple()); + } var argItems = exItems != null ? exItems.Item : (ex == null ? ImmutableArray.Empty : ImmutableArray.Create(ex)); return argItems.AddRange(Enumerable.Repeat(null, declItems.Item.Length - argItems.Length)) @@ -312,7 +386,10 @@ bool BeforePosition(Tuple symRange) => } var callArgs = ExtractParameterRanges(args, argTuple).ToArray(); - if (id == null || callArgs == null || callArgs.Any(item => item.Item2 == null)) return null; // no signature help if there are invalid expressions + if (id == null || callArgs == null || callArgs.Any(item => item.Item2 == null)) + { + return null; // no signature help if there are invalid expressions + } // finally we can build the signature help information @@ -326,10 +403,16 @@ bool BeforePosition(Tuple symRange) => var signatureLabel = $"{methodDecl.Item.QualifiedName.Name.Value} {argTuple.PrintArgumentTuple()}"; foreach (var f in functors) { - if (f.IsAdjoint) signatureLabel = $"{Keywords.qsAdjointFunctor.id} {signatureLabel}"; - if (f.IsControlled) signatureLabel = $"{Keywords.qsControlledFunctor.id} {signatureLabel}"; + if (f.IsAdjoint) + { + signatureLabel = $"{Keywords.qsAdjointFunctor.id} {signatureLabel}"; + } + if (f.IsControlled) + { + signatureLabel = $"{Keywords.qsControlledFunctor.id} {signatureLabel}"; + } } - + var doc = documentation.PrintSummary(format == MarkupKind.Markdown).Trim(); var info = new SignatureInformation { @@ -339,12 +422,12 @@ bool BeforePosition(Tuple symRange) => }; var precedingArgs = callArgs .TakeWhile(item => item.Item1 == null || BeforePosition(item.Item1)) // skip args that have already been typed or - in the case of inner items - are missing - .Reverse().SkipWhile(item => item.Item1 == null); // don't count missing, i.e. not yet typed items, of the relevant inner argument tuple + .Reverse().SkipWhile(item => item.Item1 == null); // don't count missing, i.e. not yet typed items, of the relevant inner argument tuple return new SignatureHelp { Signatures = new[] { info }, // since we don't support overloading there is just one signature here - ActiveSignature = 0, - ActiveParameter = precedingArgs.Count() + ActiveSignature = 0, + ActiveParameter = precedingArgs.Count() }; } } diff --git a/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs b/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs index de67dc587b..581eddc744 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs @@ -14,7 +14,6 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using QsSymbolInfo = Microsoft.Quantum.QsCompiler.SyntaxProcessing.SyntaxExtensions.SymbolInformation; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { /// @@ -27,8 +26,10 @@ internal static class SymbolInfo /// /// Throws an ArgumentNullException if the given offset or relative range is null. /// - internal static Location AsLocation(NonNullable source, - Tuple offset, Tuple relRange) => + internal static Location AsLocation( + NonNullable source, + Tuple offset, + Tuple relRange) => new Location { Uri = CompilationUnitManager.TryGetUri(source, out var uri) ? uri : null, @@ -79,18 +80,24 @@ public static IEnumerable CallableDeclarationsSymbolInfo(this /// /// Sets the out parameter to the code fragment that overlaps with the given position in the given file - /// if such a fragment exists, or to null otherwise. - /// If an overlapping code fragment exists, returns all symbol declarations, variable, Q# types, and Q# literals + /// if such a fragment exists, or to null otherwise. + /// If an overlapping code fragment exists, returns all symbol declarations, variable, Q# types, and Q# literals /// that *overlap* with the given position as Q# SymbolInformation. - /// Returns null if no such fragment exists, or the given file and/or position is null, or the position is invalid. + /// Returns null if no such fragment exists, or the given file and/or position is null, or the position is invalid. /// - internal static QsSymbolInfo TryGetQsSymbolInfo(this FileContentManager file, - Position position, bool includeEnd, out CodeFragment fragment) + internal static QsSymbolInfo TryGetQsSymbolInfo( + this FileContentManager file, + Position position, + bool includeEnd, + out CodeFragment fragment) { // getting the relevant token (if any) fragment = file?.TryGetFragmentAt(position, out var _, includeEnd); - if (fragment?.Kind == null) return null; + if (fragment?.Kind == null) + { + return null; + } var fragmentStart = fragment.GetRange().Start; // getting the symbol information (if any), and return the overlapping items only @@ -117,20 +124,26 @@ bool OverlapsWithPosition(Tuple symRange) => } /// - /// Searches the given compilation for all references to a globally defined type or callable with the given name, - /// and returns their locations as out parameter. - /// If a set of source files is specified, then the search is limited to the specified files. - /// Returns the location where that type or callable is defined as out parameter, - /// or null if the declaration is not within this compilation unit and the files to which the search has been limited. - /// Returns true if the search completed successfully, and false otherwise. + /// Searches the given compilation for all references to a globally defined type or callable with the given name, + /// and returns their locations as out parameter. + /// If a set of source files is specified, then the search is limited to the specified files. + /// Returns the location where that type or callable is defined as out parameter, + /// or null if the declaration is not within this compilation unit and the files to which the search has been limited. + /// Returns true if the search completed successfully, and false otherwise. /// If the given compilation unit or qualified name is null, returns false without raising an exception. /// - internal static bool TryGetReferences(this CompilationUnit compilation, QsQualifiedName fullName, - out Location declarationLocation, out IEnumerable referenceLocations, + internal static bool TryGetReferences( + this CompilationUnit compilation, + QsQualifiedName fullName, + out Location declarationLocation, + out IEnumerable referenceLocations, IImmutableSet> limitToSourceFiles = null) { (declarationLocation, referenceLocations) = (null, null); - if (compilation == null || fullName == null) return false; + if (compilation == null || fullName == null) + { + return false; + } var emptyDoc = Array.Empty>().ToLookup(i => i, _ => ImmutableArray.Empty); var namespaces = compilation.GetCallables() @@ -151,41 +164,57 @@ internal static bool TryGetReferences(this CompilationUnit compilation, QsQualif } /// - /// Searches the given compilation for all references to the identifier or type at the given position in the given file, - /// and returns their locations as out parameter. - /// If a set of source files is specified, then the search is limited to the specified files. - /// Returns the location where that identifier or type is defined as out parameter, - /// or null if the declaration is not within this compilation unit and the files to which the search has been limited. - /// Returns true if the search completed successfully, and false otherwise. - /// If the given file, compilation unit, or position is null, returns false without raising an exception. + /// Searches the given compilation for all references to the identifier or type at the given position in the given file, + /// and returns their locations as out parameter. + /// If a set of source files is specified, then the search is limited to the specified files. + /// Returns the location where that identifier or type is defined as out parameter, + /// or null if the declaration is not within this compilation unit and the files to which the search has been limited. + /// Returns true if the search completed successfully, and false otherwise. + /// If the given file, compilation unit, or position is null, returns false without raising an exception. /// internal static bool TryGetReferences( - this FileContentManager file, CompilationUnit compilation, Position position, - out Location declarationLocation, out IEnumerable referenceLocations, + this FileContentManager file, + CompilationUnit compilation, + Position position, + out Location declarationLocation, + out IEnumerable referenceLocations, IImmutableSet> limitToSourceFiles = null) { (referenceLocations, declarationLocation) = (null, null); - if (file == null || compilation == null) return false; - var symbolInfo = file.TryGetQsSymbolInfo(position, true, out var fragment); // includes the end position - if (symbolInfo == null || fragment?.Kind is QsFragmentKind.NamespaceDeclaration) return false; + if (file == null || compilation == null) + { + return false; + } + var symbolInfo = file.TryGetQsSymbolInfo(position, true, out var fragment); // includes the end position + if (symbolInfo == null || fragment?.Kind is QsFragmentKind.NamespaceDeclaration) + { + return false; + } var sym = symbolInfo.UsedTypes.Any() && symbolInfo.UsedTypes.Single().Type is QsTypeKind.UserDefinedType udt ? udt.Item : symbolInfo.UsedVariables.Any() ? symbolInfo.UsedVariables.Single() : symbolInfo.DeclaredSymbols.Any() ? symbolInfo.DeclaredSymbols.Single() : null; - if (sym == null) return false; + if (sym == null) + { + return false; + } var implementation = compilation.TryGetSpecializationAt(file, position, out var parentName, out var callablePos, out var specPos); var declarations = implementation?.LocalDeclarationsAt(position.Subtract(specPos), includeDeclaredAtPosition: true); var locals = compilation.PositionedDeclarations(parentName, callablePos, specPos, declarations); var definition = locals.LocalVariable(sym); - if (definition.IsNull) // the given position corresponds to an identifier of a global callable + if (definition.IsNull) { + // the given position corresponds to an identifier of a global callable var nsName = parentName == null ? file.TryGetNamespaceAt(position) : parentName.Namespace.Value; - if (nsName == null) return false; + if (nsName == null) + { + return false; + } var ns = NonNullable.New(nsName); var result = ResolutionResult.NotFound; @@ -206,12 +235,19 @@ internal static bool TryGetReferences( } referenceLocations = Enumerable.Empty(); - if (limitToSourceFiles != null && !limitToSourceFiles.Contains(file.FileName)) return true; + if (limitToSourceFiles != null && !limitToSourceFiles.Contains(file.FileName)) + { + return true; + } var (defOffset, defRange) = (DiagnosticTools.AsPosition(definition.Item.Item2), definition.Item.Item3); - if (defOffset.Equals(callablePos)) // the given position corresponds to a variable declared as part of a callable declaration + if (defOffset.Equals(callablePos)) { - if (!compilation.GetCallables().TryGetValue(parentName, out var parent)) return false; + // the given position corresponds to a variable declared as part of a callable declaration + if (!compilation.GetCallables().TryGetValue(parentName, out var parent)) + { + return false; + } referenceLocations = parent.Specializations .Where(spec => spec.SourceFile.Value == file.FileName.Value) .SelectMany(spec => @@ -220,12 +256,13 @@ spec.Implementation is SpecializationImplementation.Provided impl && spec.Locati : ImmutableHashSet.Empty) .Select(AsLocation); } - else // the given position corresponds to a variable declared as part of a specialization declaration or implementation + else { + // the given position corresponds to a variable declared as part of a specialization declaration or implementation var defStart = DiagnosticTools.GetAbsolutePosition(defOffset, defRange.Item1); var statements = implementation.StatementsAfterDeclaration(defStart.Subtract(specPos)); var scope = new QsScope(statements.ToImmutableArray(), locals); - var rootOffset = DiagnosticTools.AsTuple(specPos); + var rootOffset = DiagnosticTools.AsTuple(specPos); referenceLocations = IdentifierReferences.Find(definition.Item.Item1, scope, file.FileName, rootOffset).Select(AsLocation); } declarationLocation = AsLocation(file.FileName, definition.Item.Item2, defRange); diff --git a/src/QsCompiler/CompilationManager/FileContentManager.cs b/src/QsCompiler/CompilationManager/FileContentManager.cs index 482d3ef1cd..03083093d6 100644 --- a/src/QsCompiler/CompilationManager/FileContentManager.cs +++ b/src/QsCompiler/CompilationManager/FileContentManager.cs @@ -16,10 +16,9 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { - // NOTE: The idea throughout this whole class is that anything inside this class can *only* + // NOTE: The idea throughout this whole class is that anything inside this class can *only* // be modified by the class itself! (not just on a shallow, but on a deep level!) /// @@ -29,38 +28,47 @@ public class FileContentManager : IDisposable { internal readonly Uri Uri; public readonly NonNullable FileName; - private readonly ManagedList Content; - private readonly ManagedList> Tokens; - private readonly FileHeader Header; + private readonly ManagedList content; + private readonly ManagedList> tokens; + private readonly FileHeader header; /// /// list of unprocessed updates that are all limited to the same (single) line /// - private readonly Queue UnprocessedUpdates; + private readonly Queue unprocessedUpdates; + /// /// contains the line numbers in the current content that have been modified /// - private readonly ManagedSortedSet EditedContent; + private readonly ManagedSortedSet editedContent; + /// /// contains the line numbers in the current token list that have been modified /// - private readonly ManagedSortedSet EditedTokens; + private readonly ManagedSortedSet editedTokens; + /// /// contains the qualified names of the callables for which content has been modified /// - private readonly ManagedList EditedCallables; + private readonly ManagedList editedCallables; // properties containing different kinds of diagnostics: - private readonly ManagedList ScopeDiagnostics; - private readonly ManagedList SyntaxDiagnostics; - private readonly ManagedList ContextDiagnostics; - private readonly ManagedList SemanticDiagnostics; - private readonly ManagedList HeaderDiagnostics; + private readonly ManagedList scopeDiagnostics; + private readonly ManagedList syntaxDiagnostics; + private readonly ManagedList contextDiagnostics; + private readonly ManagedList semanticDiagnostics; + private readonly ManagedList headerDiagnostics; + + /// /// used to store partially computed semantic diagnostics until they are ready for publishing - private readonly ManagedList UpdatedSemanticDiagnostics; + /// + private readonly ManagedList updatedSemanticDiagnostics; + + /// /// used to store partially computed header diagnostics until they are ready for publishing - private readonly ManagedList UpdatedHeaderDiagnostics; + /// + private readonly ManagedList updatedHeaderDiagnostics; // locks and other stuff used coordinate: @@ -72,21 +80,24 @@ public class FileContentManager : IDisposable /// /// used to periodically trigger processing the queued changes if no further editing takes place for a while /// - private readonly System.Timers.Timer Timer; - internal void AddTimerTriggeredUpdateEvent() => this.Timer.Start(); + private readonly System.Timers.Timer timer; + + internal void AddTimerTriggeredUpdateEvent() => this.timer.Start(); // events and event handlers /// - /// publish an event to notify all subscribers when the timer for queued changes expires + /// publish an event to notify all subscribers when the timer for queued changes expires /// internal event TimerTriggeredUpdate TimerTriggeredUpdateEvent; + internal delegate Task TimerTriggeredUpdate(Uri file); /// - /// publish an event to notify all subscribers when the entire type checking needs to be re-run + /// publish an event to notify all subscribers when the entire type checking needs to be re-run /// internal event GlobalTypeChecking GlobalTypeCheckingEvent; + internal delegate Task GlobalTypeChecking(); internal void TriggerGlobalTypeChecking() => @@ -100,34 +111,34 @@ internal FileContentManager(Uri uri, NonNullable fileName) this.Uri = uri ?? throw new ArgumentNullException(nameof(uri)); this.FileName = fileName; - this.Content = new ManagedList(this.SyncRoot); - this.Content.Add(CodeLine.Empty()); // each new file per default has one line without text - this.Tokens = new ManagedList>(this.SyncRoot); - this.Tokens.Add(ImmutableArray.Empty); - this.Header = new FileHeader(this.SyncRoot); - - this.UnprocessedUpdates = new Queue(); - this.EditedContent = new ManagedSortedSet(this.SyncRoot); - this.EditedTokens = new ManagedSortedSet(this.SyncRoot); - this.EditedCallables = new ManagedList(this.SyncRoot); - - this.ScopeDiagnostics = new ManagedList(this.SyncRoot); - this.SyntaxDiagnostics = new ManagedList(this.SyncRoot); - this.ContextDiagnostics = new ManagedList(this.SyncRoot); - this.SemanticDiagnostics = new ManagedList(this.SyncRoot); - this.HeaderDiagnostics = new ManagedList(this.SyncRoot); - this.UpdatedSemanticDiagnostics = new ManagedList(this.SyncRoot); - this.UpdatedHeaderDiagnostics = new ManagedList(this.SyncRoot); + this.content = new ManagedList(this.SyncRoot); + this.content.Add(CodeLine.Empty()); // each new file per default has one line without text + this.tokens = new ManagedList>(this.SyncRoot); + this.tokens.Add(ImmutableArray.Empty); + this.header = new FileHeader(this.SyncRoot); + + this.unprocessedUpdates = new Queue(); + this.editedContent = new ManagedSortedSet(this.SyncRoot); + this.editedTokens = new ManagedSortedSet(this.SyncRoot); + this.editedCallables = new ManagedList(this.SyncRoot); + + this.scopeDiagnostics = new ManagedList(this.SyncRoot); + this.syntaxDiagnostics = new ManagedList(this.SyncRoot); + this.contextDiagnostics = new ManagedList(this.SyncRoot); + this.semanticDiagnostics = new ManagedList(this.SyncRoot); + this.headerDiagnostics = new ManagedList(this.SyncRoot); + this.updatedSemanticDiagnostics = new ManagedList(this.SyncRoot); + this.updatedHeaderDiagnostics = new ManagedList(this.SyncRoot); // in order to improve the editor experience it is best to not publish new diagnostics on every keystroke // instead we will only queue changes under certain circumstances - // however, to make sure that queued changes are processed after a certain time of inactivity in the editor + // however, to make sure that queued changes are processed after a certain time of inactivity in the editor // (or rather no file changes) we will use a timer to trigger automatic updates - // -> the timer is started when an update is queued, and reset whenever an update takes place - this.Timer = new System.Timers.Timer(500); - this.Timer.Elapsed += (_, __) => this.TimerTriggeredUpdateEvent?.Invoke(this.Uri); - this.Timer.AutoReset = false; // let's restart manually when we queue changes - this.Timer.Enabled = false; // the timer will be started when a change is queued + // -> the timer is started when an update is queued, and reset whenever an update takes place + this.timer = new System.Timers.Timer(500); + this.timer.Elapsed += (_, __) => this.TimerTriggeredUpdateEvent?.Invoke(this.Uri); + this.timer.AutoReset = false; // let's restart manually when we queue changes + this.timer.Enabled = false; // the timer will be started when a change is queued } /// @@ -135,91 +146,112 @@ internal FileContentManager(Uri uri, NonNullable fileName) /// public void Dispose() { - this.Timer.Dispose(); + this.timer.Dispose(); this.SyncRoot.Dispose(); } - // diagnostics access and management /// /// Returns a copy of the current scope diagnostics. /// internal ImmutableArray CurrentScopeDiagnostics() => - this.ScopeDiagnostics.Get().Select(m => m.Copy()).ToImmutableArray(); + this.scopeDiagnostics.Get().Select(m => m.Copy()).ToImmutableArray(); /// /// Returns a copy of the current syntax diagnostics. /// internal ImmutableArray CurrentSyntaxDiagnostics() => - this.SyntaxDiagnostics.Get().Select(m => m.Copy()).ToImmutableArray(); + this.syntaxDiagnostics.Get().Select(m => m.Copy()).ToImmutableArray(); /// /// Returns a copy of the current context diagnostics. /// internal ImmutableArray CurrentContextDiagnostics() => - this.ContextDiagnostics.Get().Select(m => m.Copy()).ToImmutableArray(); + this.contextDiagnostics.Get().Select(m => m.Copy()).ToImmutableArray(); /// /// Returns a copy of the current header diagnostics. /// internal ImmutableArray CurrentHeaderDiagnostics() => - this.HeaderDiagnostics.Get().Select(m => m.Copy()).ToImmutableArray(); + this.headerDiagnostics.Get().Select(m => m.Copy()).ToImmutableArray(); /// /// Returns a copy of the current semantic diagnostics. /// internal ImmutableArray CurrentSemanticDiagnostics() => - this.SemanticDiagnostics.Get().Select(m => m.Copy()).ToImmutableArray(); - + this.semanticDiagnostics.Get().Select(m => m.Copy()).ToImmutableArray(); /// /// Given the position where the syntax check starts and ends relative to the original file content before the update, and the lineNrChange, /// removes all diagnostics that are no longer valid due to that change, and /// updates the line numbers of the remaining diagnostics if needed. - /// Throws an ArgumentNullException if the given diagnostics to update or if the syntax check delimiters are null. + /// Throws an ArgumentNullException if the given diagnostics to update or if the syntax check delimiters are null. /// Throws an ArgumentException if the given start and end position do not denote a valid range. /// private static void InvalidateOrUpdateBySyntaxCheckDelimeters(ManagedList diagnostics, LSP.Range syntaxCheckDelimiters, int lineNrChange) { - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); - if (!Utils.IsValidRange(syntaxCheckDelimiters)) throw new ArgumentException(nameof(syntaxCheckDelimiters)); + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } + if (!Utils.IsValidRange(syntaxCheckDelimiters)) + { + throw new ArgumentException(nameof(syntaxCheckDelimiters)); + } var (syntaxCheckStart, syntaxCheckEnd) = (syntaxCheckDelimiters.Start, syntaxCheckDelimiters.End); - Diagnostic updateLineNrs(Diagnostic m) => m.SelectByStart(syntaxCheckEnd) ? m.WithUpdatedLineNumber(lineNrChange) : m; + Diagnostic UpdateLineNrs(Diagnostic m) => m.SelectByStart(syntaxCheckEnd) ? m.WithUpdatedLineNumber(lineNrChange) : m; diagnostics.SyncRoot.EnterWriteLock(); try { diagnostics.RemoveAll(m => m.SelectByStart(syntaxCheckStart, syntaxCheckEnd) || m.SelectByEnd(syntaxCheckStart, syntaxCheckEnd)); // remove any Diagnostic overlapping with the updated interval - diagnostics.RemoveAll(m => m.SelectByStart(new Position(0, 0), syntaxCheckStart) && m.SelectByEnd(syntaxCheckEnd)); // these are also no longer valid - if (lineNrChange != 0) diagnostics.Transform(updateLineNrs); + diagnostics.RemoveAll(m => m.SelectByStart(new Position(0, 0), syntaxCheckStart) && m.SelectByEnd(syntaxCheckEnd)); // these are also no longer valid + if (lineNrChange != 0) + { + diagnostics.Transform(UpdateLineNrs); + } + } + finally + { + diagnostics.SyncRoot.ExitWriteLock(); } - finally { diagnostics.SyncRoot.ExitWriteLock(); } } /// - /// Updates the line numbers of diagnostics that start after the syntax check end delimiter in both lists of diagnostics, - /// and removes all diagnostics that overlap with the given range for the syntax check update in the updated diagnostics only. - /// Throws an ArgumentNullException if the given current and/or latest diagnostics are null, or if the syntax check delimiters are null. + /// Updates the line numbers of diagnostics that start after the syntax check end delimiter in both lists of diagnostics, + /// and removes all diagnostics that overlap with the given range for the syntax check update in the updated diagnostics only. + /// Throws an ArgumentNullException if the given current and/or latest diagnostics are null, or if the syntax check delimiters are null. /// Throws an ArgumentException if the given start and end position do not denote a valid range. /// - private static void DelayInvalidateOrUpdate(ManagedList diagnostics, ManagedList updated, - LSP.Range syntaxCheckDelimiters, int lineNrChange) + private static void DelayInvalidateOrUpdate( + ManagedList diagnostics, + ManagedList updated, + LSP.Range syntaxCheckDelimiters, + int lineNrChange) { - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); - if (updated == null) throw new ArgumentNullException(nameof(updated)); + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } + if (updated == null) + { + throw new ArgumentNullException(nameof(updated)); + } InvalidateOrUpdateBySyntaxCheckDelimeters(updated, syntaxCheckDelimiters, lineNrChange); - Diagnostic updateLineNrs(Diagnostic m) => m.SelectByStart(syntaxCheckDelimiters.End) ? m.WithUpdatedLineNumber(lineNrChange) : m; - if (lineNrChange != 0) diagnostics.Transform(updateLineNrs); + Diagnostic UpdateLineNrs(Diagnostic m) => m.SelectByStart(syntaxCheckDelimiters.End) ? m.WithUpdatedLineNumber(lineNrChange) : m; + if (lineNrChange != 0) + { + diagnostics.Transform(UpdateLineNrs); + } } - /// /// Adds the given sequence of scope diagnostics to the current list. /// internal void AddScopeDiagnostics(IEnumerable updates) => - this.ScopeDiagnostics.AddRange(updates); + this.scopeDiagnostics.AddRange(updates); /// /// Given the change specified by start, count and lineNrChange, @@ -232,60 +264,82 @@ internal void AddScopeDiagnostics(IEnumerable updates) => /// private void InvalidateOrUpdateScopeDiagnostics(int start, int count, int lineNrChange) { - if (start < 0) throw new ArgumentOutOfRangeException(nameof(start)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (lineNrChange < -count || start + count + lineNrChange > this.NrLines()) throw new ArgumentOutOfRangeException(nameof(lineNrChange)); + if (start < 0) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + if (lineNrChange < -count || start + count + lineNrChange > this.NrLines()) + { + throw new ArgumentOutOfRangeException(nameof(lineNrChange)); + } var end = start + count; - Diagnostic updateLineNrs(Diagnostic m) => m.SelectByStartLine(end) ? m.WithUpdatedLineNumber(lineNrChange) : m; + Diagnostic UpdateLineNrs(Diagnostic m) => m.SelectByStartLine(end) ? m.WithUpdatedLineNumber(lineNrChange) : m; - this.ScopeDiagnostics.SyncRoot.EnterWriteLock(); + this.scopeDiagnostics.SyncRoot.EnterWriteLock(); try { - this.ScopeDiagnostics.RemoveAll(DiagnosticTools.ErrorType(ErrorCode.MissingBracketError, ErrorCode.MissingStringDelimiterError)); - this.ScopeDiagnostics.RemoveAll(m => m.SelectByStartLine(start, end) || m.SelectByEndLine(start, end)); // remove any Diagnostic overlapping with the updated interval - if (lineNrChange != 0) this.ScopeDiagnostics.Transform(updateLineNrs); + this.scopeDiagnostics.RemoveAll(DiagnosticTools.ErrorType(ErrorCode.MissingBracketError, ErrorCode.MissingStringDelimiterError)); + this.scopeDiagnostics.RemoveAll(m => m.SelectByStartLine(start, end) || m.SelectByEndLine(start, end)); // remove any Diagnostic overlapping with the updated interval + if (lineNrChange != 0) + { + this.scopeDiagnostics.Transform(UpdateLineNrs); + } + } + finally + { + this.scopeDiagnostics.SyncRoot.ExitWriteLock(); } - finally { this.ScopeDiagnostics.SyncRoot.ExitWriteLock(); } } - /// /// Adds the given sequence of syntax diagnostics to the current list. /// internal void AddSyntaxDiagnostics(IEnumerable updates) => - this.SyntaxDiagnostics.AddRange(updates); + this.syntaxDiagnostics.AddRange(updates); /// /// Given the position where the syntax check starts and ends relative to the original file content before the update, and the lineNrChange, /// removes all diagnostics that are no longer valid due to that change, and /// updates the line numbers of the remaining diagnostics if needed. - /// Throws an ArgumentNullException if the given diagnostics to update or the syntax check delimiters are null. + /// Throws an ArgumentNullException if the given diagnostics to update or the syntax check delimiters are null. /// Throws an ArgumentException if the given start and end position do not denote a valid range. /// private void InvalidateOrUpdateSyntaxDiagnostics(LSP.Range syntaxCheckDelimiters, int lineNrChange) => - InvalidateOrUpdateBySyntaxCheckDelimeters(this.SyntaxDiagnostics, syntaxCheckDelimiters, lineNrChange); - + InvalidateOrUpdateBySyntaxCheckDelimeters(this.syntaxDiagnostics, syntaxCheckDelimiters, lineNrChange); /// /// Given the line numbers for which the context diagnostics are now obsolete, - /// removes all context diagnostics that start on a line marked as obsolete, + /// removes all context diagnostics that start on a line marked as obsolete, /// and replaces them with the given sequence of context diagnostics. /// Throws an ArgumentNullException if the if the given sequence of line numbers for which the context diagnostics are obsolete is null. /// Throws an ArgumentOutOfRangeException if that sequence contains a value that is negative. /// internal void UpdateContextDiagnostics(HashSet obsolete, IEnumerable updates) { - if (obsolete == null) throw new ArgumentNullException(nameof(obsolete)); - if (obsolete.Any() && obsolete.Min() < 0) throw new ArgumentOutOfRangeException(nameof(obsolete)); + if (obsolete == null) + { + throw new ArgumentNullException(nameof(obsolete)); + } + if (obsolete.Any() && obsolete.Min() < 0) + { + throw new ArgumentOutOfRangeException(nameof(obsolete)); + } - this.ContextDiagnostics.SyncRoot.EnterWriteLock(); + this.contextDiagnostics.SyncRoot.EnterWriteLock(); try { - this.ContextDiagnostics.RemoveAll(m => obsolete.Contains(m.Range.Start.Line)); - this.ContextDiagnostics.AddRange(updates); + this.contextDiagnostics.RemoveAll(m => obsolete.Contains(m.Range.Start.Line)); + this.contextDiagnostics.AddRange(updates); + } + finally + { + this.contextDiagnostics.SyncRoot.ExitWriteLock(); } - finally { this.ContextDiagnostics.SyncRoot.ExitWriteLock(); } } /// @@ -298,20 +352,35 @@ internal void UpdateContextDiagnostics(HashSet obsolete, IEnumerable private void InvalidateOrUpdateContextDiagnostics(int start, int count, int lineNrChange) { - if (start < 0) throw new ArgumentOutOfRangeException(nameof(start)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (lineNrChange < -count || start + count + lineNrChange > this.NrLines()) throw new ArgumentOutOfRangeException(nameof(lineNrChange)); + if (start < 0) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + if (lineNrChange < -count || start + count + lineNrChange > this.NrLines()) + { + throw new ArgumentOutOfRangeException(nameof(lineNrChange)); + } var end = start + count; - Diagnostic updateLineNrs(Diagnostic m) => m.SelectByStartLine(end) ? m.WithUpdatedLineNumber(lineNrChange) : m; + Diagnostic UpdateLineNrs(Diagnostic m) => m.SelectByStartLine(end) ? m.WithUpdatedLineNumber(lineNrChange) : m; - this.ContextDiagnostics.SyncRoot.EnterWriteLock(); + this.contextDiagnostics.SyncRoot.EnterWriteLock(); try { - this.ContextDiagnostics.RemoveAll(m => m.SelectByStartLine(start, end)); // remove any Diagnostic overlapping with the updated interval - if (lineNrChange != 0) this.ContextDiagnostics.Transform(updateLineNrs); + this.contextDiagnostics.RemoveAll(m => m.SelectByStartLine(start, end)); // remove any Diagnostic overlapping with the updated interval + if (lineNrChange != 0) + { + this.contextDiagnostics.Transform(UpdateLineNrs); + } + } + finally + { + this.contextDiagnostics.SyncRoot.ExitWriteLock(); } - finally { this.ContextDiagnostics.SyncRoot.ExitWriteLock(); } } /// @@ -319,28 +388,27 @@ private void InvalidateOrUpdateContextDiagnostics(int start, int count, int line /// internal void ReplaceHeaderDiagnostics(IEnumerable updates) { - this.UpdatedHeaderDiagnostics.ReplaceAll(updates); - this.HeaderDiagnostics.ReplaceAll(this.UpdatedHeaderDiagnostics); + this.updatedHeaderDiagnostics.ReplaceAll(updates); + this.headerDiagnostics.ReplaceAll(this.updatedHeaderDiagnostics); } /// /// Given the position where the syntax check starts and ends relative to the original file content before the update, and the lineNrChange, /// removes all diagnostics that are no longer valid due to that change, and /// updates the line numbers of the remaining diagnostics if needed. - /// Throws an ArgumentNullException if the given diagnostics to update or the syntax check delimiters are null. + /// Throws an ArgumentNullException if the given diagnostics to update or the syntax check delimiters are null. /// Throws an ArgumentException if the given start and end position do not denote a valid range. /// private void InvalidateOrUpdateHeaderDiagnostics(LSP.Range syntaxCheckDelimiters, int lineNrChange) => - DelayInvalidateOrUpdate(this.HeaderDiagnostics, this.UpdatedHeaderDiagnostics, syntaxCheckDelimiters, lineNrChange); - + DelayInvalidateOrUpdate(this.headerDiagnostics, this.updatedHeaderDiagnostics, syntaxCheckDelimiters, lineNrChange); /// /// Replaces the semantic diagnostics with the given sequence and finalizes the semantic diagnostics update. /// internal void ReplaceSemanticDiagnostics(IEnumerable updates) { - this.UpdatedSemanticDiagnostics.ReplaceAll(updates); - this.SemanticDiagnostics.ReplaceAll(this.UpdatedSemanticDiagnostics); + this.updatedSemanticDiagnostics.ReplaceAll(updates); + this.semanticDiagnostics.ReplaceAll(this.updatedSemanticDiagnostics); } /// @@ -348,20 +416,19 @@ internal void ReplaceSemanticDiagnostics(IEnumerable updates) /// internal void AddAndFinalizeSemanticDiagnostics(IEnumerable updates) { - this.UpdatedSemanticDiagnostics.AddRange(updates); - this.SemanticDiagnostics.ReplaceAll(this.UpdatedSemanticDiagnostics); + this.updatedSemanticDiagnostics.AddRange(updates); + this.semanticDiagnostics.ReplaceAll(this.updatedSemanticDiagnostics); } /// /// Given the position where the syntax check starts and ends relative to the original file content before the update, and the lineNrChange, /// removes all diagnostics that are no longer valid due to that change, and /// updates the line numbers of the remaining diagnostics if needed. - /// Throws an ArgumentNullException if the given diagnostics to update or the syntax check delimiters are null. + /// Throws an ArgumentNullException if the given diagnostics to update or the syntax check delimiters are null. /// Throws an ArgumentException if the given start and end position do not denote a valid range. /// private void InvalidateOrUpdateSemanticDiagnostics(LSP.Range syntaxCheckDelimiters, int lineNrChange) => - DelayInvalidateOrUpdate(this.SemanticDiagnostics, this.UpdatedSemanticDiagnostics, syntaxCheckDelimiters, lineNrChange); - + DelayInvalidateOrUpdate(this.semanticDiagnostics, this.updatedSemanticDiagnostics, syntaxCheckDelimiters, lineNrChange); /// /// Returns all current diagnostic as PublishDiagnosticParams. @@ -384,37 +451,57 @@ public PublishDiagnosticParams Diagnostics() Diagnostics = diagnostics }; } - finally { this.SyncRoot.ExitReadLock(); } + finally + { + this.SyncRoot.ExitReadLock(); + } } - // external access to content & routines related to content updates /// /// Any modification to the returned elements will not be reflected in file. /// - internal IEnumerable GetLines(int index, int count) => this.Content.GetRange(index, count); - internal IEnumerable GetLines() => this.Content.Get(); - internal CodeLine GetLine(int index) => this.Content.GetItem(index); - internal int NrLines() => this.Content.Count(); + internal IEnumerable GetLines(int index, int count) => this.content.GetRange(index, count); + + internal IEnumerable GetLines() => this.content.Get(); + + internal CodeLine GetLine(int index) => this.content.GetItem(index); + + internal int NrLines() => this.content.Count(); /// /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentOutOfRangeException if start and count are not valid for the current file content, where count needs to be at least one. /// Throws an ArgumentException if the replacements do not at least contain one element, or the indentation change is non-zero, - /// or if a replacement does not have a suitable line ending. + /// or if a replacement does not have a suitable line ending. /// private void VerifyContentUpdate(int start, int count, IReadOnlyList replacements) { - if (start < 0 || start >= this.NrLines()) throw new ArgumentOutOfRangeException(nameof(start)); - if (count < 1 || start + count > this.NrLines()) throw new ArgumentOutOfRangeException(nameof(count)); + if (start < 0 || start >= this.NrLines()) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + if (count < 1 || start + count > this.NrLines()) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } // make sure properties are never set to null! - if (replacements == null) throw new ArgumentNullException(nameof(replacements)); - if (replacements.Count == 0) throw new ArgumentException("expecting at least on replacement"); + if (replacements == null) + { + throw new ArgumentNullException(nameof(replacements)); + } + if (replacements.Count == 0) + { + throw new ArgumentException("expecting at least on replacement"); + } // verify that the indentation change is zero var indentationChange = ScopeTracking.GetIndentationChange(this, start + count, replacements.Last()); // fine as long as we have at least one replacement - if (indentationChange != 0) throw new ArgumentException("indentation needs to be zero for continuation"); + if (indentationChange != 0) + { + throw new ArgumentException("indentation needs to be zero for continuation"); + } // Note that the CodeLines themselves are verifies upon initialization - in particular their string delimiters and the positions of excess closing brackets // verify that all lines have the necessary line endings @@ -422,11 +509,15 @@ private void VerifyContentUpdate(int start, int count, IReadOnlyList r { if (start + count == this.NrLines() && nr + 1 == replacements.Count) { - if (replacements[nr].LineEnding != String.Empty || Utils.EndOfLine.Match(replacements[nr].Text).Success) - { throw new ArgumentException("last line in the file must not end in a line ending"); } + if (replacements[nr].LineEnding != string.Empty || Utils.EndOfLine.Match(replacements[nr].Text).Success) + { + throw new ArgumentException("last line in the file must not end in a line ending"); + } + } + else if (replacements[nr].LineEnding == string.Empty || !Utils.EndOfLine.Match(replacements[nr].Text).Success) + { + throw new ArgumentException("missing line ending for a given replacement"); } - else if (replacements[nr].LineEnding == String.Empty || !Utils.EndOfLine.Match(replacements[nr].Text).Success) - { throw new ArgumentException("missing line ending for a given replacement"); } } } @@ -445,7 +536,7 @@ internal void ContentUpdate(int start, int count, IReadOnlyList replac var origFileEnd = this.End(); // update the content (needs to be done first!) and get the syntax check delimiters - this.Content.Replace(start, count, replacements); + this.content.Replace(start, count, replacements); var lineNrChange = replacements.Count - count; var syntaxCheckInUpdated = this.GetSyntaxCheckDelimiters(start, replacements.Count); var syntaxCheckInOriginal = new LSP.Range @@ -454,33 +545,39 @@ internal void ContentUpdate(int start, int count, IReadOnlyList replac End = syntaxCheckInUpdated.End == this.End() ? origFileEnd : syntaxCheckInUpdated.End.WithUpdatedLineNumber(-lineNrChange) }; - // update the tokens and make sure the necessary connections get marked as edited + // update the tokens and make sure the necessary connections get marked as edited // -> needs to be done *before* updating the tracked line numbers! this.RemoveTokensInRange(syntaxCheckInOriginal); // making sure that the corresponding connections are marked as edited - this.Tokens.Replace(start, count, replacements.Select(_ => ImmutableArray.Empty).ToList()); + this.tokens.Replace(start, count, replacements.Select(_ => ImmutableArray.Empty).ToList()); - // update all tracked line numbers - this.EditedContent.InvalidateOrUpdate(start, count, lineNrChange); - this.EditedTokens.InvalidateOrUpdate(start, count, lineNrChange); - this.Header.InvalidateOrUpdate(start, count, lineNrChange); + // update all tracked line numbers + this.editedContent.InvalidateOrUpdate(start, count, lineNrChange); + this.editedTokens.InvalidateOrUpdate(start, count, lineNrChange); + this.header.InvalidateOrUpdate(start, count, lineNrChange); // mark all lines in the range (syntaxCheck.Start.Line, syntaxCheck.End.Line) as edited // -> consider what happens when commenting out the let in "let ..[empty lines] ... valid statment on its own" var startCheck = syntaxCheckInUpdated.Start.Line < start ? syntaxCheckInUpdated.Start.Line + 1 : start; var edited = Enumerable.Range(startCheck, syntaxCheckInUpdated.End.Line - startCheck); QsCompilerError.Verify(edited.Any() || syntaxCheckInUpdated.End.Line == this.NrLines() - 1, "syntax check should not start and end on the same line"); - this.EditedContent.Add(edited.Any() ? edited : new int[] { syntaxCheckInUpdated.End.Line }); - if (this.End().Equals(syntaxCheckInUpdated.End)) this.EditedContent.Add(this.NrLines() - 1); + this.editedContent.Add(edited.Any() ? edited : new int[] { syntaxCheckInUpdated.End.Line }); + if (this.End().Equals(syntaxCheckInUpdated.End)) + { + this.editedContent.Add(this.NrLines() - 1); + } // invalidate diagnostics affected by the change, and update the line numbers for the remaining diagnostics // NOTE: additional context diagnostics need to be removed for all affected connections (-> done upon dequeuing changes) - InvalidateOrUpdateScopeDiagnostics(start, count, lineNrChange); - InvalidateOrUpdateSyntaxDiagnostics(syntaxCheckInOriginal, lineNrChange); - InvalidateOrUpdateContextDiagnostics(start, count, lineNrChange); - InvalidateOrUpdateHeaderDiagnostics(syntaxCheckInOriginal, lineNrChange); - InvalidateOrUpdateSemanticDiagnostics(syntaxCheckInOriginal, lineNrChange); + this.InvalidateOrUpdateScopeDiagnostics(start, count, lineNrChange); + this.InvalidateOrUpdateSyntaxDiagnostics(syntaxCheckInOriginal, lineNrChange); + this.InvalidateOrUpdateContextDiagnostics(start, count, lineNrChange); + this.InvalidateOrUpdateHeaderDiagnostics(syntaxCheckInOriginal, lineNrChange); + this.InvalidateOrUpdateSemanticDiagnostics(syntaxCheckInOriginal, lineNrChange); + } + finally + { + this.SyncRoot.ExitWriteLock(); } - finally { this.SyncRoot.ExitWriteLock(); } } /// @@ -488,29 +585,33 @@ internal void ContentUpdate(int start, int count, IReadOnlyList replac /// NOTE: The changed lines are guaranteed to be returned in ascending order. /// internal SortedSet DequeueContentChanges() => - this.EditedContent.Clear(); - + this.editedContent.Clear(); // external access to tokens & routines related to tokens updates /// /// Any modification to the returned elements will not be reflected in file. /// - internal IEnumerable> GetTokenizedLines(int index, int count) => this.Tokens.GetRange(index, count); - internal IEnumerable> GetTokenizedLines() => this.Tokens.Get(); - internal ImmutableArray GetTokenizedLine(int index) => this.Tokens.GetItem(index); - internal int NrTokenizedLines() => this.Tokens.Count(); + internal IEnumerable> GetTokenizedLines(int index, int count) => this.tokens.GetRange(index, count); + + internal IEnumerable> GetTokenizedLines() => this.tokens.Get(); + + internal ImmutableArray GetTokenizedLine(int index) => this.tokens.GetItem(index); + + internal int NrTokenizedLines() => this.tokens.Count(); /// - /// For each CodeFragment in the given collection, verifies its range against the current Content, + /// For each CodeFragment in the given collection, verifies its range against the current Content, /// verifies that all fragments are ordered according to their range, and - /// verifies that none of the fragments overlap with existing tokens. + /// verifies that none of the fragments overlap with existing tokens. /// Throws an ArgumentException if the verification fails. /// private void VerifyTokenUpdate(IReadOnlyList fragments) { if (fragments.Any(fragment => !Utils.IsValidRange(fragment.GetRange(), this))) - { throw new ArgumentException("the range of the given token to update is not a valid range within the current file content"); } + { + throw new ArgumentException("the range of the given token to update is not a valid range within the current file content"); + } ContextBuilder.VerifyTokenOrdering(fragments); // check that there are no overlapping fragments @@ -521,46 +622,63 @@ private void VerifyTokenUpdate(IReadOnlyList fragments) var existing = Enumerable.Range(min, max - min + 1) .SelectMany(lineNr => this.GetTokenizedLine(lineNr).Select(t => t.WithUpdatedLineNumber(lineNr).GetRange().DiagnosticString() + $": {t.Text}")); throw new ArgumentException("the given fragments to update overlap with existing tokens - \n" + - $"Ranges for updates were: \n{String.Join("\n", fragments.Select(t => t.GetRange().DiagnosticString() + $": {t.Text}"))} \n" + - $"Ranges for existing are: \n{String.Join("\n", existing)}"); + $"Ranges for updates were: \n{string.Join("\n", fragments.Select(t => t.GetRange().DiagnosticString() + $": {t.Text}"))} \n" + + $"Ranges for existing are: \n{string.Join("\n", existing)}"); } } /// - /// Returns an Action as out parameter that replaces the tokens at lineNr - /// with the ones returned by UpdateTokens when called on the current tokens + /// Returns an Action as out parameter that replaces the tokens at lineNr + /// with the ones returned by UpdateTokens when called on the current tokens /// (i.e. the ones after having applied Transformation) on that line. - /// Applies ModifiedTokens to the tokens at the given lineNr to obtain the list of tokens for which to mark all connections as edited. - /// Then constructs and returns an Action as out parameter + /// Applies ModifiedTokens to the tokens at the given lineNr to obtain the list of tokens for which to mark all connections as edited. + /// Then constructs and returns an Action as out parameter /// that adds lineNr as well as all lines containing connections to mark to EditedTokens. /// Throws an ArgumentNullException if UpdatedTokens or ModifiedTokens is null. /// Throws an ArgumentException if any of the values returned by UpdatedTokens or ModifiedTokens is null. /// Throws an ArgumentOutOfRangeException if linrNr is not a valid index for the current Tokens. /// - private void TransformAndMarkEdited(int lineNr, - Func, ImmutableArray> UpdatedTokens, - Func, ImmutableArray> ModifiedTokens, - out Action Transformation, out Action MarkEdited) + private void TransformAndMarkEdited( + int lineNr, + Func, ImmutableArray> updatedTokens, + Func, ImmutableArray> modifiedTokens, + out Action transformation, + out Action markEdited) { - this.Tokens.SyncRoot.EnterWriteLock(); + this.tokens.SyncRoot.EnterWriteLock(); try { - if (UpdatedTokens == null) throw new ArgumentNullException(nameof(UpdatedTokens)); - if (ModifiedTokens == null) throw new ArgumentNullException(nameof(ModifiedTokens)); - if (lineNr < 0 || lineNr >= this.Tokens.Count()) throw new ArgumentOutOfRangeException(nameof(lineNr)); + if (updatedTokens == null) + { + throw new ArgumentNullException(nameof(updatedTokens)); + } + if (modifiedTokens == null) + { + throw new ArgumentNullException(nameof(modifiedTokens)); + } + if (lineNr < 0 || lineNr >= this.tokens.Count()) + { + throw new ArgumentOutOfRangeException(nameof(lineNr)); + } - Transformation = () => + transformation = () => { - var updated = UpdatedTokens(this.GetTokenizedLine(lineNr)); - if (updated == null) throw new ArgumentException($"{nameof(UpdatedTokens)} must not return null"); - this.Tokens.Transform(lineNr, _ => updated); + var updated = updatedTokens(this.GetTokenizedLine(lineNr)); + if (updated == null) + { + throw new ArgumentException($"{nameof(updatedTokens)} must not return null"); + } + this.tokens.Transform(lineNr, _ => updated); }; - var modified = ModifiedTokens(this.GetTokenizedLine(lineNr)); - if (modified == null) throw new ArgumentException($"{nameof(ModifiedTokens)} must not return null"); - MarkEdited = () => + var modified = modifiedTokens(this.GetTokenizedLine(lineNr)); + if (modified == null) + { + throw new ArgumentException($"{nameof(modifiedTokens)} must not return null"); + } + markEdited = () => { - this.EditedTokens.Add(lineNr); + this.editedTokens.Add(lineNr); foreach (var token in modified) { var index = this.GetTokenizedLine(lineNr).FindByValue(token); @@ -568,12 +686,21 @@ private void TransformAndMarkEdited(int lineNr, var tokenIndex = new CodeFragment.TokenIndex(this, lineNr, index); var previous = tokenIndex.PreviousOnScope(); var next = tokenIndex.NextOnScope(); - if (previous != null) this.EditedTokens.Add(previous.Line); - if (next != null) this.EditedTokens.Add(next.Line); + if (previous != null) + { + this.editedTokens.Add(previous.Line); + } + if (next != null) + { + this.editedTokens.Add(next.Line); + } } }; } - finally { this.Tokens.SyncRoot.ExitWriteLock(); } + finally + { + this.tokens.SyncRoot.ExitWriteLock(); + } } /// @@ -587,28 +714,42 @@ private void TransformAndMarkEdited(int lineNr, /// private void RemoveTokensInRange(LSP.Range range) { - this.Tokens.SyncRoot.EnterWriteLock(); + this.tokens.SyncRoot.EnterWriteLock(); try { - if (!Utils.IsValidRange(range)) throw new ArgumentException("invalid range"); // *don't* verify against the current file content - if (range.End.Line >= this.Tokens.Count()) throw new ArgumentOutOfRangeException(nameof(range)); + if (!Utils.IsValidRange(range)) + { + throw new ArgumentException("invalid range"); // *don't* verify against the current file content + } + if (range.End.Line >= this.tokens.Count()) + { + throw new ArgumentOutOfRangeException(nameof(range)); + } - var ApplyTransformations = new List(); - var MarkEdited = new List(); + var applyTransformations = new List(); + var markEdited = new List(); void FilterAndMarkEdited(int index, Func predicate) { - TransformAndMarkEdited(index, + this.TransformAndMarkEdited( + index, tokens => tokens.Where(predicate).ToImmutableArray(), tokens => tokens.Where(x => !predicate(x)).ToImmutableArray(), - out var transformation, out var edited); - ApplyTransformations.Add(transformation); - MarkEdited.Add(edited); + out var transformation, + out var edited); + applyTransformations.Add(transformation); + markEdited.Add(edited); } var (start, end) = (range.Start.Line, range.End.Line); FilterAndMarkEdited(start, ContextBuilder.NotOverlappingWith(range.WithUpdatedLineNumber(-start))); - for (var i = start + 1; i < end; ++i) FilterAndMarkEdited(i, _ => false); // remove all - if (start != end) FilterAndMarkEdited(end, ContextBuilder.TokensAfter(new Position(0, range.End.Character))); + for (var i = start + 1; i < end; ++i) + { + FilterAndMarkEdited(i, _ => false); // remove all + } + if (start != end) + { + FilterAndMarkEdited(end, ContextBuilder.TokensAfter(new Position(0, range.End.Character))); + } var enveloppingFragment = this.TryGetFragmentAt(range.Start, out var _); if (enveloppingFragment != null) @@ -617,49 +758,68 @@ void FilterAndMarkEdited(int index, Func predicate) FilterAndMarkEdited(start, token => !range.Start.IsWithinRange(token.GetRange().WithUpdatedLineNumber(start))); } - // which lines get marked as edited depends on the tokens prior to transformation, - // hence we accumulate all transformations and apply them only at the end - QsCompilerError.RaiseOnFailure(() => - { foreach (var edited in MarkEdited) edited(); } - , $"marking edited in {nameof(RemoveTokensInRange)} failed"); - QsCompilerError.RaiseOnFailure(() => - { foreach (var transformation in ApplyTransformations) transformation(); } - , $"applying transformations in {nameof(RemoveTokensInRange)} failed"); + // which lines get marked as edited depends on the tokens prior to transformation, + // hence we accumulate all transformations and apply them only at the end + QsCompilerError.RaiseOnFailure( + () => + { + foreach (var edited in markEdited) + { + edited(); + } + }, $"marking edited in {nameof(this.RemoveTokensInRange)} failed"); + QsCompilerError.RaiseOnFailure( + () => + { + foreach (var transformation in applyTransformations) + { + transformation(); + } + }, $"applying transformations in {nameof(this.RemoveTokensInRange)} failed"); + } + finally + { + this.tokens.SyncRoot.ExitWriteLock(); } - finally { this.Tokens.SyncRoot.ExitWriteLock(); } } /// /// Removes all Tokens that overlap with any of the given fragments, and adds the given fragments as tokens. /// Attaches end of line comments for the lines on which fragments have been modified to suitable tokens. - /// Verifies the given fragments and + /// Verifies the given fragments and /// throws the corresponding exception if the verification fails. /// Throws an ArgumentNullException if any of the given fragments are null. /// internal void TokensUpdate(IReadOnlyList fragments) { - if (fragments == null || fragments.Any(x => x == null)) throw new ArgumentNullException(nameof(fragments)); - if (!fragments.Any()) return; + if (fragments == null || fragments.Any(x => x == null)) + { + throw new ArgumentNullException(nameof(fragments)); + } + if (!fragments.Any()) + { + return; + } this.SyncRoot.EnterWriteLock(); try { this.VerifyTokenUpdate(fragments); - // update the Header if necessary + // update the Header if necessary var newNSdecl = FileHeader.FilterNamespaceDeclarations(fragments).Select(fragment => fragment.GetRange().Start.Line); var newOpenDir = FileHeader.FilterOpenDirectives(fragments).Select(fragment => fragment.GetRange().Start.Line); var newTypeDecl = FileHeader.FilterTypeDeclarations(fragments).Select(fragment => fragment.GetRange().Start.Line); var newCallableDecl = FileHeader.FilterCallableDeclarations(fragments).Select(fragment => fragment.GetRange().Start.Line); - this.Header.AddNamespaceDeclarations(newNSdecl); // fixme: check that these are disjoint sets... - this.Header.AddOpenDirectives(newOpenDir); - this.Header.AddTypeDeclarations(newTypeDecl); - this.Header.AddCallableDeclarations(newCallableDecl); + this.header.AddNamespaceDeclarations(newNSdecl); // fixme: check that these are disjoint sets... + this.header.AddOpenDirectives(newOpenDir); + this.header.AddTypeDeclarations(newTypeDecl); + this.header.AddCallableDeclarations(newCallableDecl); // update the Tokens - var ApplyTransformations = new List(); - var MarkEdited = new List(); + var applyTransformations = new List(); + var markEdited = new List(); while (fragments.Any()) { var startLine = fragments.First().GetRange().Start.Line; @@ -672,53 +832,79 @@ ImmutableArray MergeAndAttachComments(ImmutableArray { // merge tokens and ... var merged = ContextBuilder.MergeTokens(current, tokens).Select(token => token.ClearComments()).ToList(); - if (!merged.Any()) return merged.ToImmutableArray(); + if (!merged.Any()) + { + return merged.ToImmutableArray(); + } // ... grab all comments associated with the first token and ... var comments = new List(); for (var lineNr = startLine; lineNr > 0 && !this.GetTokenizedLine(--lineNr).Any();) - { comments.Add(this.GetLine(lineNr).EndOfLineComment); } + { + comments.Add(this.GetLine(lineNr).EndOfLineComment); + } comments.Reverse(); merged[0] = merged[0].SetOpeningComments(comments); // will be overwritten again if there is just one token - if (merged.Count > 1) comments.Clear(); + if (merged.Count > 1) + { + comments.Clear(); + } // ... grab all comments associated with the last token var relevantEndLine = startLine + merged[merged.Count - 1].GetRange().End.Line; - if (relevantEndLine != startLine && this.GetTokenizedLine(relevantEndLine).Any()) --relevantEndLine; + if (relevantEndLine != startLine && this.GetTokenizedLine(relevantEndLine).Any()) + { + --relevantEndLine; + } for (var lineNr = startLine; lineNr <= relevantEndLine; ++lineNr) - { comments.Add(this.GetLine(lineNr).EndOfLineComment); } + { + comments.Add(this.GetLine(lineNr).EndOfLineComment); + } merged[merged.Count - 1] = merged[merged.Count - 1].SetOpeningComments(comments); return merged.ToImmutableArray(); } this.TransformAndMarkEdited(startLine, MergeAndAttachComments, _ => tokens, out var transformation, out var edited); - ApplyTransformations.Add(transformation); - MarkEdited.Add(edited); + applyTransformations.Add(transformation); + markEdited.Add(edited); fragments = fragments.SkipWhile(fragment => fragment.GetRange().Start.Line == startLine).ToList(); } - // which lines get marked as edited depends on the tokens after the transformations, - // hence we accumulate all mark-ups and apply them only after applying all transformations - QsCompilerError.RaiseOnFailure(() => - { foreach (var transformation in ApplyTransformations) transformation(); } - , $"applying transformations in {nameof(TokensUpdate)} failed"); - QsCompilerError.RaiseOnFailure(() => - { foreach (var edited in MarkEdited) edited(); } - , $"marking edited in {nameof(TokensUpdate)} failed"); + // which lines get marked as edited depends on the tokens after the transformations, + // hence we accumulate all mark-ups and apply them only after applying all transformations + QsCompilerError.RaiseOnFailure( + () => + { + foreach (var transformation in applyTransformations) + { + transformation(); + } + }, $"applying transformations in {nameof(this.TokensUpdate)} failed"); + QsCompilerError.RaiseOnFailure( + () => + { + foreach (var edited in markEdited) + { + edited(); + } + }, $"marking edited in {nameof(this.TokensUpdate)} failed"); + } + finally + { + this.SyncRoot.ExitWriteLock(); } - finally { this.SyncRoot.ExitWriteLock(); } } /// - /// Returns all lines that have been modified since the last call to DequeueChanges, + /// Returns all lines that have been modified since the last call to DequeueChanges, /// or any lines that contain children of a token on a modified line. /// internal SortedSet DequeueTokenChanges() { - this.EditedTokens.SyncRoot.EnterWriteLock(); + this.editedTokens.SyncRoot.EnterWriteLock(); try { - var currentlyEdited = this.EditedTokens.Clear(); + var currentlyEdited = this.editedTokens.Clear(); IEnumerable GetTokenIndices(int lineNr) => Enumerable.Range(0, this.GetTokenizedLine(lineNr).Length).Select(index => new CodeFragment.TokenIndex(this, lineNr, index)); @@ -726,16 +912,19 @@ internal SortedSet DequeueTokenChanges() var obsolete = currentlyEdited.SelectMany(GetTokenIndices); var children = obsolete.SelectMany(tIndex => tIndex.GetChildren()); - // add all lines containing children to the list of currently edited lines + // add all lines containing children to the list of currently edited lines // -> any issue related to having the wrong parent may now have come up or be resolved for those currentlyEdited.UnionWith(children.Select(tIndex => tIndex.Line).ToList()); return currentlyEdited; } - finally { this.EditedTokens.SyncRoot.ExitWriteLock(); } + finally + { + this.editedTokens.SyncRoot.ExitWriteLock(); + } } /// - /// Adds the given collection of qualified callables names to the list of callables + /// Adds the given collection of qualified callables names to the list of callables /// whose content has been edited since the last call to this routine. /// Invalidates all semantic diagnostics withing the corresponding Ranges. /// @@ -743,19 +932,18 @@ internal void MarkCallableAsContentEdited(IEnumerable<(LSP.Range, QsQualifiedNam { foreach (var (range, callableName) in edited) { - InvalidateOrUpdateBySyntaxCheckDelimeters(this.UpdatedSemanticDiagnostics, range, 0); - this.EditedCallables.Add(callableName); + InvalidateOrUpdateBySyntaxCheckDelimeters(this.updatedSemanticDiagnostics, range, 0); + this.editedCallables.Add(callableName); } } /// - /// Returns a collection of the fully qualified names of the callables - /// whose content has been modified since the last call to this routine. - /// Note that these callable may no longer be defined in the first place. + /// Returns a collection of the fully qualified names of the callables + /// whose content has been modified since the last call to this routine. + /// Note that these callable may no longer be defined in the first place. /// internal IEnumerable DequeueContentEditedCallables() => - this.EditedCallables.Clear(); - + this.editedCallables.Clear(); // methods for tracking and processing file changes @@ -766,19 +954,20 @@ internal IEnumerable DequeueContentEditedCallables() => /// internal bool DequeueUnprocessedChanges(out int lineNr, out string textToInsert) { - if (!this.UnprocessedUpdates.Any()) // empty queue + if (!this.unprocessedUpdates.Any()) { + // empty queue lineNr = -1; textToInsert = null; return false; } - var change = this.UnprocessedUpdates.Peek(); + var change = this.unprocessedUpdates.Peek(); lineNr = change.Range.Start.Line; var newText = this.GetLine(lineNr).Text; - while (this.UnprocessedUpdates.Any()) + while (this.unprocessedUpdates.Any()) { - change = this.UnprocessedUpdates.Dequeue(); + change = this.unprocessedUpdates.Dequeue(); newText = Utils.GetChangedText(newText, change.Range.Start.Character, change.Range.End.Character, change.Text); } textToInsert = newText; @@ -787,35 +976,39 @@ internal bool DequeueUnprocessedChanges(out int lineNr, out string textToInsert) /// /// Calls all updating routines sequentially, wrapping each one in a QsCompilerError.RaiseOnFailure. - /// If no change is given or the given change is null, (only) the currently queued changes are to be processed, + /// If no change is given or the given change is null, (only) the currently queued changes are to be processed, /// otherwise the currently queued changes as well as the given change is processed. /// private void Update(TextDocumentContentChangeEvent change = null) { this.SyncRoot.EnterUpgradeableReadLock(); try - { // We need to enforce that generated type checking diagnostics are consistent with all other diagnostics. - // In particular this means that we cannot have that the type checking runs after the ScopeTracking update completed - // but before the other updates (that generate the data structure based on which the type checking is done) are done. + { + // We need to enforce that generated type checking diagnostics are consistent with all other diagnostics. + // In particular this means that we cannot have that the type checking runs after the ScopeTracking update completed + // but before the other updates (that generate the data structure based on which the type checking is done) are done. QsCompilerError.RaiseOnFailure(() => this.UpdateScopeTacking(change), "error during scope tracking update"); QsCompilerError.RaiseOnFailure(() => this.UpdateLanguageProcessing(), "error during language processing update"); QsCompilerError.RaiseOnFailure(() => this.UpdateContext(), "error during context update"); } - finally { this.SyncRoot.ExitUpgradeableReadLock(); } + finally + { + this.SyncRoot.ExitUpgradeableReadLock(); + } } /// /// Does the semantic verification of everything that has not yet been verified. - /// Updates and all header and semantic diagnostics. - /// Pushes the updated diagnostics if no further computation is needed. - /// If a global type checking is needed, triggers the corresponding event - /// and does not push updates to semantic diagnostic. + /// Updates and all header and semantic diagnostics. + /// Pushes the updated diagnostics if no further computation is needed. + /// If a global type checking is needed, triggers the corresponding event + /// and does not push updates to semantic diagnostic. /// internal void Verify(CompilationUnit compilation) => QsCompilerError.RaiseOnFailure(() => this.UpdateTypeChecking(compilation), "error during type checking update"); /// - /// Clears all Header and SemanticDiagnostics, + /// Clears all Header and SemanticDiagnostics, /// and marks the content of all currently defined callables as edited, such that it will be verified during the next call to Verify. /// internal void ClearVerification() @@ -825,47 +1018,58 @@ internal void ClearVerification() { var edited = this.CallablesWithContentModifications(Enumerable.Range(0, this.NrLines())); this.MarkCallableAsContentEdited(edited); - this.HeaderDiagnostics.Clear(); - this.UpdatedHeaderDiagnostics.Clear(); - this.SemanticDiagnostics.Clear(); - this.UpdatedSemanticDiagnostics.Clear(); + this.headerDiagnostics.Clear(); + this.updatedHeaderDiagnostics.Clear(); + this.semanticDiagnostics.Clear(); + this.updatedSemanticDiagnostics.Clear(); + } + finally + { + this.SyncRoot.ExitUpgradeableReadLock(); } - finally { this.SyncRoot.ExitUpgradeableReadLock(); } } /// /// Given a TextDocumentContentChangeEvent, determines whether it is necessary to immediately update the file, /// updates the file if necessary, and queues the change otherwise. - /// An update is considered necessary if the given change replaces more than one line of the current content, + /// An update is considered necessary if the given change replaces more than one line of the current content, /// or if the inserted text cannot be a symbol or keyword (i.e. includes any whitespace, numbers and/or special characters). - /// Sets the out parameter to true if diagnostics are to be published. + /// Sets the out parameter to true if diagnostics are to be published. /// Throws an ArgumentNullException if the change or any of its fields are null. /// Throws an ArgumentException if the range of the change is invalid. /// internal void PushChange(TextDocumentContentChangeEvent change, out bool publishDiagnostics) { - // NOTE: since there may be still unprocessed changes aggregated in UnprocessedChanges we cannot verify the range of the change against the current file content, + // NOTE: since there may be still unprocessed changes aggregated in UnprocessedChanges we cannot verify the range of the change against the current file content, // however, let's at least check that nothing is null, all entries are positive, and the range start is smaller than or equal to the range end - if (change == null) throw new ArgumentNullException(nameof(change)); - if (!Utils.IsValidRange(change.Range)) throw new ArgumentException("range of the given change is invalid"); - this.Timer.Stop(); // will be restarted if needed + if (change == null) + { + throw new ArgumentNullException(nameof(change)); + } + if (!Utils.IsValidRange(change.Range)) + { + throw new ArgumentException("range of the given change is invalid"); + } + this.timer.Stop(); // will be restarted if needed var start = change.Range.Start.Line; var count = change.Range.End.Line - start + 1; - var line = this.UnprocessedUpdates.Any() ? this.UnprocessedUpdates.Peek().Range.Start.Line : start; + var line = this.unprocessedUpdates.Any() ? this.unprocessedUpdates.Peek().Range.Start.Line : start; publishDiagnostics = true; if (count == 1 && line == start) { - this.UnprocessedUpdates.Enqueue(change); + this.unprocessedUpdates.Enqueue(change); var trimmedText = change.Text.TrimStart(); // tabs etc inserted by the editor come squashed together with the next inserted character - if (change.Text == String.Empty || - trimmedText == "{" || trimmedText == "\"" || // let's not immediately trigger an update for these, hoping the matching one will come right after - trimmedText == "\\" || trimmedText == "/") // ... and the same here + if (change.Text == string.Empty || + // let's not immediately trigger an update for these, hoping the matching one will come right after + trimmedText == "{" || trimmedText == "\"" || + // ... and the same here + trimmedText == "\\" || trimmedText == "/") { - this.Timer.Start(); // we can simply queue this update - no need to actually execute it + this.timer.Start(); // we can simply queue this update - no need to actually execute it publishDiagnostics = false; return; } @@ -873,16 +1077,21 @@ internal void PushChange(TextDocumentContentChangeEvent change, out bool publish // For changes that are part of typing a symbol, the file needs to be updated immediately for code // completion, but diagnostics should be delayed until the user has a chance to finish typing. if (Utils.ValidAsSymbol.IsMatch(trimmedText)) + { publishDiagnostics = false; + } this.Update(); } - else this.Update(change); + else + { + this.Update(change); + } } /// /// Replaces the entire file content with the given text. - /// Forces the update to be processed rather than queued. + /// Forces the update to be processed rather than queued. /// Throws an ArgumentNullException if the given text is null. /// internal void ReplaceFileContent(string text) @@ -890,94 +1099,114 @@ internal void ReplaceFileContent(string text) this.SyncRoot.EnterUpgradeableReadLock(); try { - if (text == null) throw new ArgumentNullException(nameof(text)); + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } var change = new TextDocumentContentChangeEvent { Range = new LSP.Range { Start = new Position(0, 0), End = this.End() }, RangeLength = 0, Text = text }; // fixme: range length is not accurate, but also not used... this.PushChange(change, out bool processed); - if (!processed) this.Flush(); + if (!processed) + { + this.Flush(); + } + } + finally + { + this.SyncRoot.ExitUpgradeableReadLock(); } - finally { this.SyncRoot.ExitUpgradeableReadLock(); } } /// - /// Forces the processing of all currently queued changes, + /// Forces the processing of all currently queued changes, /// *without* calling the constructor onAutomaticUpdate argument with the update. /// internal void Flush() { - this.Timer.Stop(); + this.timer.Stop(); this.Update(); } - // external access to the file header and other file properties /// - /// Given a function returning an int array of line numbers, - /// applies FilterBy to the tokens on any of those lines and returns the TokenIndex of the tokens for which FilterBy returned true as IEnumerable. + /// Given a function returning an int array of line numbers, + /// applies FilterBy to the tokens on any of those lines and returns the TokenIndex of the tokens for which FilterBy returned true as IEnumerable. /// If FilterBy is null, returns an IEnumerable with the token indices for all tokens on the lines specified by GetLineNumbers. /// Returns an empty List if GetLineNumbers is null. /// - private List FilterTokenIndices(Func GetLineNumbers, Func FilterBy = null) + private List FilterTokenIndices(Func getLineNumbers, Func filterBy = null) { - if (GetLineNumbers == null) return new List(); - bool Filter(CodeFragment frag) => FilterBy == null ? true : FilterBy(frag); + if (getLineNumbers == null) + { + return new List(); + } + bool Filter(CodeFragment frag) => filterBy == null ? true : filterBy(frag); this.SyncRoot.EnterReadLock(); try { IEnumerable GetFiltered(int lineNr) => Enumerable.Range(0, this.GetTokenizedLine(lineNr).Length) .Select(i => new CodeFragment.TokenIndex(this, lineNr, i)) - .Where(tokenIndex =>Filter(tokenIndex.GetFragment())); - return GetLineNumbers().SelectMany(GetFiltered).ToList(); + .Where(tokenIndex => Filter(tokenIndex.GetFragment())); + return getLineNumbers().SelectMany(GetFiltered).ToList(); + } + finally + { + this.SyncRoot.ExitReadLock(); } - finally { this.SyncRoot.ExitReadLock(); } } /// - /// Given a function returning an int array of line numbers, - /// applies FilterBy to the fragments on any of those lines and returns the resulting flattened fragment sequence as IEnumerable. - /// Note that the range of the returned fragments is the absolute range in the file. + /// Given a function returning an int array of line numbers, + /// applies FilterBy to the fragments on any of those lines and returns the resulting flattened fragment sequence as IEnumerable. + /// Note that the range of the returned fragments is the absolute range in the file. /// If FilterBy is null, returns an IEnumerable with all fragments on the lines specified by GetLineNumbers. /// Returns an empty List if GetLineNumbers is null. /// - private List FilterFragments(Func GetLineNumbers, Func FilterBy = null) + private List FilterFragments(Func getLineNumbers, Func filterBy = null) { this.SyncRoot.EnterReadLock(); - try { return this.FilterTokenIndices(GetLineNumbers, FilterBy).Select(tokenIndex => tokenIndex.GetFragment()).ToList(); } - finally { this.SyncRoot.ExitReadLock(); } + try + { + return this.FilterTokenIndices(getLineNumbers, filterBy).Select(tokenIndex => tokenIndex.GetFragment()).ToList(); + } + finally + { + this.SyncRoot.ExitReadLock(); + } } /// /// Returns a list with all token indices corresponding to namespace declarations. /// internal List NamespaceDeclarationTokens() => - this.FilterTokenIndices(this.Header.GetNamespaceDeclarations, FileHeader.IsNamespaceDeclaration); + this.FilterTokenIndices(this.header.GetNamespaceDeclarations, FileHeader.IsNamespaceDeclaration); /// /// Returns a list with all token indices corresponding to open directives. /// internal List OpenDirectiveTokens() => - this.FilterTokenIndices(this.Header.GetOpenDirectives, FileHeader.IsOpenDirective); + this.FilterTokenIndices(this.header.GetOpenDirectives, FileHeader.IsOpenDirective); /// /// Returns a list with all token indices corresponding to type declarations. /// internal List TypeDeclarationTokens() => - this.FilterTokenIndices(this.Header.GetTypeDeclarations, FileHeader.IsTypeDeclaration); + this.FilterTokenIndices(this.header.GetTypeDeclarations, FileHeader.IsTypeDeclaration); /// /// Returns a list with all token indices corresponding to operation or function declarations. /// internal List CallableDeclarationTokens() => - this.FilterTokenIndices(this.Header.GetCallableDeclarations, FileHeader.IsCallableDeclaration); + this.FilterTokenIndices(this.header.GetCallableDeclarations, FileHeader.IsCallableDeclaration); /// /// Returns all namespace declarations in the file sorted by the line number they are declared on. /// public IEnumerable<(NonNullable, LSP.Range)> GetNamespaceDeclarations() { - var decl = this.FilterFragments(this.Header.GetNamespaceDeclarations, FileHeader.IsNamespaceDeclaration); + var decl = this.FilterFragments(this.header.GetNamespaceDeclarations, FileHeader.IsNamespaceDeclaration); return decl.Select(fragment => (fragment.Kind.DeclaredNamespaceName(InternalUse.UnknownNamespace), fragment.GetRange())) .Where(tuple => tuple.Item1 != null) .Select(tuple => (NonNullable.New(tuple.Item1), tuple.Item2)); @@ -988,7 +1217,7 @@ private List FilterFragments(Func GetLineNumbers, Func public IEnumerable<(NonNullable, LSP.Range)> GetTypeDeclarations() { - var decl = this.FilterFragments(this.Header.GetTypeDeclarations, FileHeader.IsTypeDeclaration); + var decl = this.FilterFragments(this.header.GetTypeDeclarations, FileHeader.IsTypeDeclaration); return decl.Select(fragment => (fragment.Kind.DeclaredTypeName(null), fragment.GetRange())) .Where(tuple => tuple.Item1 != null) .Select(tuple => (NonNullable.New(tuple.Item1), tuple.Item2)); @@ -999,7 +1228,7 @@ private List FilterFragments(Func GetLineNumbers, Func public IEnumerable<(NonNullable, LSP.Range)> GetCallableDeclarations() { - var decl = this.FilterFragments(this.Header.GetCallableDeclarations, FileHeader.IsCallableDeclaration); + var decl = this.FilterFragments(this.header.GetCallableDeclarations, FileHeader.IsCallableDeclaration); return decl.Select(fragment => (fragment.Kind.DeclaredCallableName(null), fragment.GetRange())) .Where(tuple => tuple.Item1 != null) .Select(tuple => (NonNullable.New(tuple.Item1), tuple.Item2)); diff --git a/src/QsCompiler/CompilationManager/ProcessingQueue.cs b/src/QsCompiler/CompilationManager/ProcessingQueue.cs index f83701857c..85bb7b3520 100644 --- a/src/QsCompiler/CompilationManager/ProcessingQueue.cs +++ b/src/QsCompiler/CompilationManager/ProcessingQueue.cs @@ -4,142 +4,191 @@ using System; using System.Threading.Tasks; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { /// - /// Class allowing to enforce in-order processing of tasks. + /// Class allowing to enforce in-order processing of tasks. /// public class ProcessingQueue { /// /// header string for the message given when an exception if logged /// - private readonly string ExceptionHeader; + private readonly string exceptionHeader; + /// /// used to log exceptions raised during processing /// - private readonly Action LogException; + private readonly Action logException; + /// /// schedulers both for tasks that have to run sequentially and for tasks that can run simultaneously /// - private readonly ConcurrentExclusiveSchedulerPair Scheduler; + private readonly ConcurrentExclusiveSchedulerPair scheduler; public ProcessingQueue(Action exceptionLogger, string exceptionHeader = null) { - this.ExceptionHeader = exceptionHeader ?? "error while running queued task"; - this.LogException = exceptionLogger ?? Console.Error.WriteLine; - this.Scheduler = new ConcurrentExclusiveSchedulerPair(); + this.exceptionHeader = exceptionHeader ?? "error while running queued task"; + this.logException = exceptionLogger ?? Console.Error.WriteLine; + this.scheduler = new ConcurrentExclusiveSchedulerPair(); } - private Task ProcessingTaskAsync(Action action) => + private Task ProcessingTaskAsync(Action action) => new Task(() => { - try { QsCompilerError.RaiseOnFailure(action, this.ExceptionHeader); } - catch (Exception ex) { this.LogException(ex); } + try + { + QsCompilerError.RaiseOnFailure(action, this.exceptionHeader); + } + catch (Exception ex) + { + this.logException(ex); + } }); /// - /// The queue will not accept any more tasks after calling complete. + /// The queue will not accept any more tasks after calling complete. /// public void Complete() => - this.Scheduler.Complete(); - + this.scheduler.Complete(); // non-concurrent routines - i.e. routines that are forced to execute in order /// - /// Enqueues the given Action for exclusive (serialized) execution. - /// Uses the set exception logger to log any exception that occurs during execution. + /// Enqueues the given Action for exclusive (serialized) execution. + /// Uses the set exception logger to log any exception that occurs during execution. /// Throws an ArgumentNullException if the given Action is null. /// public Task QueueForExecutionAsync(Action processing) { - if (processing == null) throw new ArgumentNullException(nameof(processing)); - var processingTask = ProcessingTaskAsync(processing); - processingTask.Start(this.Scheduler.ExclusiveScheduler); + if (processing == null) + { + throw new ArgumentNullException(nameof(processing)); + } + var processingTask = this.ProcessingTaskAsync(processing); + processingTask.Start(this.scheduler.ExclusiveScheduler); return processingTask; } /// /// Executes the given Action synchronously, with no exclusive actions running. - /// Uses the set exception logger to log any exception that occurs during execution. + /// Uses the set exception logger to log any exception that occurs during execution. /// Throws an ArgumentNullException if the given Action is null. - /// NOTE: may deadlock if the given function to execute calls this processing queue. + /// NOTE: may deadlock if the given function to execute calls this processing queue. /// public void QueueForExecution(Action processing) { - if (processing == null) throw new ArgumentNullException(nameof(processing)); - this.QueueForExecution(() => { processing(); return new object(); }, out var _); + if (processing == null) + { + throw new ArgumentNullException(nameof(processing)); + } + this.QueueForExecution( + () => + { + processing(); + return new object(); + }, out _); } /// - /// Executes the given function synchronously without any exclusive tasks running, - /// returning its result as out parameter. - /// Uses the set exception logger to log any exception that occurs during execution. - /// Returns true if the execution succeeded without throwing an exception, and false otherwise. + /// Executes the given function synchronously without any exclusive tasks running, + /// returning its result as out parameter. + /// Uses the set exception logger to log any exception that occurs during execution. + /// Returns true if the execution succeeded without throwing an exception, and false otherwise. /// Throws an ArgumentNullException if the given function to execute is null. - /// NOTE: may deadlock if the given function to execute calls this processing queue. + /// NOTE: may deadlock if the given function to execute calls this processing queue. /// public bool QueueForExecution(Func execute, out T result) { - if (execute == null) throw new ArgumentNullException(nameof(execute)); + if (execute == null) + { + throw new ArgumentNullException(nameof(execute)); + } T res = default(T); var succeeded = true; this.QueueForExecutionAsync(() => { - try { res = execute(); } - catch { succeeded = false; throw; } // will be caught as part of processing + try + { + res = execute(); + } + catch + { + // will be caught as part of processing + succeeded = false; + throw; + } }) .Wait(); result = res; return succeeded; } - // concurrent routines - i.e. routines that may execute at any time in between exclusive tasks /// - /// Enqueues the given Action for concurrent (background) execution. - /// Uses the set exception logger to log any exception that occurs during execution. + /// Enqueues the given Action for concurrent (background) execution. + /// Uses the set exception logger to log any exception that occurs during execution. /// Throws an ArgumentNullException if the given Action is null. /// public Task ConcurrentExecutionAsync(Action processing) { - if (processing == null) throw new ArgumentNullException(nameof(processing)); - var processingTask = ProcessingTaskAsync(processing); - processingTask.Start(this.Scheduler.ConcurrentScheduler); + if (processing == null) + { + throw new ArgumentNullException(nameof(processing)); + } + var processingTask = this.ProcessingTaskAsync(processing); + processingTask.Start(this.scheduler.ConcurrentScheduler); return processingTask; } /// /// Executes the given Action synchronously and concurrently (background). - /// Uses the set exception logger to log any exception that occurs during execution. + /// Uses the set exception logger to log any exception that occurs during execution. /// Throws an ArgumentNullException if the given Action is null. - /// NOTE: may deadlock if the given function to execute calls this processing queue. + /// NOTE: may deadlock if the given function to execute calls this processing queue. /// public void ConcurrentExecution(Action processing) { - if (processing == null) throw new ArgumentNullException(nameof(processing)); - this.ConcurrentExecution(() => { processing(); return new object(); }, out var _); + if (processing == null) + { + throw new ArgumentNullException(nameof(processing)); + } + this.ConcurrentExecution( + () => + { + processing(); + return new object(); + }, out _); } /// /// Executes the given function synchronously and concurrently, returning its result as out parameter. - /// Returns true if the execution succeeded without throwing an exception, and false otherwise. - /// Uses the set exception logger to log any exception that occurs during execution. + /// Returns true if the execution succeeded without throwing an exception, and false otherwise. + /// Uses the set exception logger to log any exception that occurs during execution. /// Throws an ArgumentNullException if the given Action is null. - /// NOTE: may deadlock if the given function to execute calls this processing queue. + /// NOTE: may deadlock if the given function to execute calls this processing queue. /// public bool ConcurrentExecution(Func execute, out T result) { - if (execute == null) throw new ArgumentNullException(nameof(execute)); + if (execute == null) + { + throw new ArgumentNullException(nameof(execute)); + } T res = default(T); var succeeded = true; this.ConcurrentExecutionAsync(() => { - try { res = execute(); } - catch { succeeded = false; throw; } // will be caught as part of processing + try + { + res = execute(); + } + catch + { + // will be caught as part of processing + succeeded = false; + throw; + } }) .Wait(); result = res; diff --git a/src/QsCompiler/CompilationManager/ProjectManager.cs b/src/QsCompiler/CompilationManager/ProjectManager.cs index e5855fdffe..014de4cb2e 100644 --- a/src/QsCompiler/CompilationManager/ProjectManager.cs +++ b/src/QsCompiler/CompilationManager/ProjectManager.cs @@ -5,8 +5,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.IO; +using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.Quantum.QsCompiler.DataTypes; @@ -14,7 +14,6 @@ using Microsoft.Quantum.QsCompiler.ReservedKeywords; using Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { internal class ProjectProperties @@ -23,7 +22,7 @@ internal class ProjectProperties public readonly string OutputPath; public readonly AssemblyConstants.RuntimeCapabilities RuntimeCapabilities; public readonly bool IsExecutable; - public readonly NonNullable ExecutionTarget; + public readonly NonNullable ProcessorArchitecture; public readonly bool ExposeReferencesViaTestNames; public ProjectProperties( @@ -31,14 +30,14 @@ public ProjectProperties( string outputPath, AssemblyConstants.RuntimeCapabilities runtimeCapabilities, bool isExecutable, - NonNullable executionTarget, + NonNullable processorArchitecture, bool loadTestNames) { this.Version = version ?? ""; this.OutputPath = outputPath ?? throw new ArgumentNullException(nameof(outputPath)); this.RuntimeCapabilities = runtimeCapabilities; this.IsExecutable = isExecutable; - this.ExecutionTarget = executionTarget; + this.ProcessorArchitecture = processorArchitecture; this.ExposeReferencesViaTestNames = loadTestNames; } } @@ -57,8 +56,8 @@ internal static ProjectInformation Empty( string outputPath, AssemblyConstants.RuntimeCapabilities runtimeCapabilities) => new ProjectInformation( - version, - outputPath, + version, + outputPath, runtimeCapabilities, false, NonNullable.New("Unspecified"), @@ -72,14 +71,14 @@ public ProjectInformation( string outputPath, AssemblyConstants.RuntimeCapabilities runtimeCapabilities, bool isExecutable, - NonNullable executionTarget, + NonNullable processorArchitecture, bool loadTestNames, IEnumerable sourceFiles, IEnumerable projectReferences, IEnumerable references) { this.Properties = new ProjectProperties( - version, outputPath, runtimeCapabilities, isExecutable, executionTarget, loadTestNames); + version, outputPath, runtimeCapabilities, isExecutable, processorArchitecture, loadTestNames); this.SourceFiles = sourceFiles?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(sourceFiles)); this.ProjectReferences = projectReferences?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(projectReferences)); this.References = references?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(references)); @@ -91,263 +90,332 @@ public class ProjectManager : IDisposable private class Project : IDisposable { public readonly Uri ProjectFile; + public Uri OutputPath { get; private set; } + public ProjectProperties Properties { get; private set; } - private bool IsLoaded; + + private bool isLoaded; /// - /// contains the path of all specified source files, + /// contains the path of all specified source files, /// regardless of whether or not the path is valid, the file exists and could be loaded /// - private ImmutableHashSet SpecifiedSourceFiles; + private ImmutableHashSet specifiedSourceFiles; + /// - /// contains the path to the dlls of all specified references, + /// contains the path to the dlls of all specified references, /// regardless of whether or not the path is valid, the file exists and could be loaded /// - private ImmutableHashSet SpecifiedReferences; + private ImmutableHashSet specifiedReferences; + /// - /// contains the path to the *project* file of all specified project references, + /// contains the path to the *project* file of all specified project references, /// regardless of whether or not the path is valid, and a project with the corresponding uri exists /// - private ImmutableHashSet SpecifiedProjectReferences; + private ImmutableHashSet specifiedProjectReferences; /// /// contains the uris to all source files that have been successfully loaded and are incorporated into the compilation /// - private ImmutableHashSet LoadedSourceFiles; + private ImmutableHashSet loadedSourceFiles; + /// /// contains the keys are the uris to all referenced dlls that have been successfully loaded and are incorporated into the compilation /// - private References LoadedReferences; + private References loadedReferences; + /// /// contains the keys are the uris to the *project file* of all project references that have been successfully loaded and are incorporated into the compilation /// - private References LoadedProjectReferences; + private References loadedProjectReferences; - private readonly ProcessingQueue Processing; - internal readonly CompilationUnitManager Manager; - private readonly Action Log; + private readonly ProcessingQueue processing; + internal readonly CompilationUnitManager Manager; + private readonly Action log; /// /// Returns true if the file with the given uri has been specified to be a source file of this project. /// IMPORTANT: This routine queries the current state of the project and does *not* wait for queued or running tasks to finish! /// - internal bool ContainsSourceFile(Uri sourceFile) => - this.SpecifiedSourceFiles?.Contains(sourceFile) ?? false; + internal bool ContainsSourceFile(Uri sourceFile) => + this.specifiedSourceFiles?.Contains(sourceFile) ?? false; /// /// Returns true if any of the currently specified source files of this project satisfies the given criterion. - /// If no criterion is given, returns true if the list of specified source files is not null. + /// If no criterion is given, returns true if the list of specified source files is not null. /// IMPORTANT: This routine queries the current state of the project and does *not* wait for queued or running tasks to finish! /// internal bool ContainsAnySourceFiles(Func filter = null) => - this.SpecifiedSourceFiles?.Any(filter ?? (_ => true)) ?? false; // keep this as specified, *not* loaded! - - public void Dispose() => - this.Processing.QueueForExecutionAsync(() => this.Manager.Dispose()); + this.specifiedSourceFiles?.Any(filter ?? (_ => true)) ?? false; // keep this as specified, *not* loaded! - private ImmutableArray GeneralDiagnostics; - private ImmutableArray SourceFileDiagnostics; - private ImmutableArray ReferenceDiagnostics; - private ImmutableArray ProjectReferenceDiagnostics; + public void Dispose() => + this.processing.QueueForExecutionAsync(() => this.Manager.Dispose()); + private ImmutableArray generalDiagnostics; + private ImmutableArray sourceFileDiagnostics; + private ImmutableArray referenceDiagnostics; + private ImmutableArray projectReferenceDiagnostics; /// /// Initializes the project for the given project file with the given project information. - /// If an Action for publishing diagnostics is given and not null, + /// If an Action for publishing diagnostics is given and not null, /// that action is called whenever diagnostics for the project have changed and are ready for publishing. - /// Throws an ArgumentNullException if the given project file or project information is null. + /// Throws an ArgumentNullException if the given project file or project information is null. /// - internal Project(Uri projectFile, ProjectInformation projectInfo, - Action onException, Action publishDiagnostics, Action log) + internal Project( + Uri projectFile, + ProjectInformation projectInfo, + Action onException, + Action publishDiagnostics, + Action log) { this.ProjectFile = projectFile ?? throw new ArgumentNullException(nameof(projectFile)); this.SetProjectInformation(projectInfo ?? throw new ArgumentNullException(nameof(projectInfo))); var version = Version.TryParse(projectInfo.Properties.Version, out Version v) ? v : null; - if (projectInfo.Properties.Version.Equals("Latest", StringComparison.InvariantCultureIgnoreCase)) version = new Version(0, 3); + if (projectInfo.Properties.Version.Equals("Latest", StringComparison.InvariantCultureIgnoreCase)) + { + version = new Version(0, 3); + } var ignore = version == null || version < new Version(0, 3) ? true : false; // We track the file contents for unsupported projects in case the files are migrated to newer projects while editing, - // but we don't do any semantic verification, and we don't publish diagnostics for them. - this.Processing = new ProcessingQueue(onException); + // but we don't do any semantic verification, and we don't publish diagnostics for them. + this.processing = new ProcessingQueue(onException); this.Manager = new CompilationUnitManager( onException, ignore ? null : publishDiagnostics, syntaxCheckOnly: ignore, this.Properties.RuntimeCapabilities, this.Properties.IsExecutable, - this.Properties.ExecutionTarget); - this.Log = log ?? ((msg, severity) => Console.WriteLine($"{severity}: {msg}")); + this.Properties.ProcessorArchitecture); + this.log = log ?? ((msg, severity) => Console.WriteLine($"{severity}: {msg}")); - this.LoadedSourceFiles = ImmutableHashSet.Empty; - this.LoadedReferences = References.Empty; - this.LoadedProjectReferences = References.Empty; + this.loadedSourceFiles = ImmutableHashSet.Empty; + this.loadedReferences = References.Empty; + this.loadedProjectReferences = References.Empty; } /// - /// Sets the output path and all specified source files, references and project references - /// to those specified by the given project information. + /// Sets the output path and all specified source files, references and project references + /// to those specified by the given project information. /// Generates a suitable diagnostics if the output uri cannot be determined. - /// Throws an ArgumentNullException if the given project information is null. + /// Throws an ArgumentNullException if the given project information is null. /// private void SetProjectInformation(ProjectInformation projectInfo) { - if (projectInfo == null) throw new ArgumentNullException(nameof(projectInfo)); + if (projectInfo == null) + { + throw new ArgumentNullException(nameof(projectInfo)); + } this.Properties = projectInfo.Properties; - this.IsLoaded = false; + this.isLoaded = false; var outputPath = projectInfo.Properties.OutputPath; - try { outputPath = Path.GetFullPath(outputPath); } - catch { outputPath = null; } + try + { + outputPath = Path.GetFullPath(outputPath); + } + catch + { + outputPath = null; + } var outputUri = Uri.TryCreate(outputPath, UriKind.Absolute, out Uri uri) ? uri : null; this.OutputPath = outputUri; - this.GeneralDiagnostics = this.OutputPath == null + this.generalDiagnostics = this.OutputPath == null ? ImmutableArray.Create(Errors.LoadError(ErrorCode.InvalidProjectOutputPath, new[] { this.ProjectFile.LocalPath }, MessageSource(this.ProjectFile))) : ImmutableArray.Empty; - this.SpecifiedSourceFiles = projectInfo.SourceFiles + this.specifiedSourceFiles = projectInfo.SourceFiles .Select(f => Uri.TryCreate(f, UriKind.Absolute, out uri) ? uri : null) .Where(f => f != null)?.ToImmutableHashSet(); - this.SpecifiedReferences = projectInfo.References + this.specifiedReferences = projectInfo.References .Select(f => Uri.TryCreate(f, UriKind.Absolute, out uri) ? uri : null) .Where(f => f != null)?.ToImmutableHashSet(); - this.SpecifiedProjectReferences = projectInfo.ProjectReferences + this.specifiedProjectReferences = projectInfo.ProjectReferences .Select(f => Uri.TryCreate(f, UriKind.Absolute, out uri) ? uri : null) .Where(f => f != null)?.ToImmutableHashSet(); } /// /// If the project is not yet loaded, loads all specified source file, dll references and project references - /// using the given dictionary to resolve the dll output paths for project references. - /// Generates suitable load diagnostics. - /// Calls the given action RemoveFiles with the uris of all files that are no longer part of this project. + /// using the given dictionary to resolve the dll output paths for project references. + /// Generates suitable load diagnostics. + /// Calls the given action RemoveFiles with the uris of all files that are no longer part of this project. /// Calls the given function GetExistingFileManagers to get all existing managers for the files that are newly part of this project. - /// Does *not* update the content of already existing file managers. - /// Throws an ArgumentNullException if the given dictionary to determine the output path for project references is null. + /// Does *not* update the content of already existing file managers. + /// Throws an ArgumentNullException if the given dictionary to determine the output path for project references is null. /// - private void LoadProject(IDictionary projectOutputPaths, - Func, Uri, IEnumerable> GetExistingFileManagers, Action, Task> RemoveFiles) + private void LoadProject( + IDictionary projectOutputPaths, + Func, Uri, IEnumerable> getExistingFileManagers, + Action, Task> removeFiles) { - if (projectOutputPaths == null) throw new ArgumentNullException(nameof(projectOutputPaths)); - if (this.IsLoaded) return; - this.IsLoaded = true; + if (projectOutputPaths == null) + { + throw new ArgumentNullException(nameof(projectOutputPaths)); + } + if (this.isLoaded) + { + return; + } + this.isLoaded = true; - this.Log($"Loading project '{this.ProjectFile.LocalPath}'.", MessageType.Log); + this.log($"Loading project '{this.ProjectFile.LocalPath}'.", MessageType.Log); if (!this.Manager.EnableVerification) { - this.Log($"The Q# language server functionality is partially disabled for project {this.ProjectFile.LocalPath}. " + + this.log( + $"The Q# language server functionality is partially disabled for project {this.ProjectFile.LocalPath}. " + $"The full functionality will be available after updating the project to Q# version 0.3 or higher.", MessageType.Warning); } - this.LoadReferencedAssembliesAsync(this.SpecifiedReferences.Select(uri => uri.LocalPath), skipVerification: true); - this.LoadProjectReferencesAsync(projectOutputPaths, this.SpecifiedProjectReferences.Select(uri => uri.LocalPath), skipVerification: true); - this.LoadSourceFilesAsync(this.SpecifiedSourceFiles.Select(uri => uri.LocalPath), GetExistingFileManagers, RemoveFiles, skipIfAlreadyLoaded: true) - .ContinueWith(_ => this.Log($"Done loading project '{this.ProjectFile.LocalPath}'", MessageType.Log), TaskScheduler.Default); + this.LoadReferencedAssembliesAsync(this.specifiedReferences.Select(uri => uri.LocalPath), skipVerification: true); + this.LoadProjectReferencesAsync(projectOutputPaths, this.specifiedProjectReferences.Select(uri => uri.LocalPath), skipVerification: true); + this.LoadSourceFilesAsync(this.specifiedSourceFiles.Select(uri => uri.LocalPath), getExistingFileManagers, removeFiles, skipIfAlreadyLoaded: true) + .ContinueWith(_ => this.log($"Done loading project '{this.ProjectFile.LocalPath}'", MessageType.Log), TaskScheduler.Default); this.Manager.PublishDiagnostics(this.CurrentLoadDiagnostics()); } /// - /// Loads the content of all specified source files, dll references and project references, - /// using the given dictionary to resolve the dll output paths for project references. - /// If the project information is specified, updates the project information with the given value before loading. - /// Generates suitable load diagnostics. - /// Calls the given action RemoveFiles with the uris of all files that are no longer part of this project. + /// Loads the content of all specified source files, dll references and project references, + /// using the given dictionary to resolve the dll output paths for project references. + /// If the project information is specified, updates the project information with the given value before loading. + /// Generates suitable load diagnostics. + /// Calls the given action RemoveFiles with the uris of all files that are no longer part of this project. /// Calls the given function GetExistingFileManagers to get all existing managers for the files that are newly part of this project. - /// Does *not* update the content of already existing file managers. - /// Throws an ArgumentNullException if the given dictionary to determine the output path for project references is. + /// Does *not* update the content of already existing file managers. + /// Throws an ArgumentNullException if the given dictionary to determine the output path for project references is. /// - internal Task LoadProjectAsync(IDictionary projectOutputPaths, - Func, Uri, IEnumerable> GetExistingFileManagers, Action, Task> RemoveFiles, + internal Task LoadProjectAsync( + IDictionary projectOutputPaths, + Func, Uri, IEnumerable> getExistingFileManagers, + Action, Task> removeFiles, ProjectInformation projectInfo = null) { - if (projectOutputPaths == null) throw new ArgumentNullException(nameof(projectOutputPaths)); - return this.Processing.QueueForExecutionAsync(() => + if (projectOutputPaths == null) { - if (projectInfo != null) this.SetProjectInformation(projectInfo); - this.LoadProject(projectOutputPaths, GetExistingFileManagers, RemoveFiles); + throw new ArgumentNullException(nameof(projectOutputPaths)); + } + return this.processing.QueueForExecutionAsync(() => + { + if (projectInfo != null) + { + this.SetProjectInformation(projectInfo); + } + this.LoadProject(projectOutputPaths, getExistingFileManagers, removeFiles); }); } - - // private routines used whenever the project itself is updated + // private routines used whenever the project itself is updated // -> need to be called from within appropriately queued routines only! /// - /// Helper function used to generate suitable diagnostics upon project reference loading. + /// Helper function used to generate suitable diagnostics upon project reference loading. /// Returns a function that given the uri to a project files, returns the corresponding output path, /// if the corrsponding entry in the given dictionary indeed exist. - /// If no such entry exists, generates a suitable error messages and adds it to the given list of diagnostics. - /// Throws an ArgumentNullException if the given diagnostics are null, or + /// If no such entry exists, generates a suitable error messages and adds it to the given list of diagnostics. + /// Throws an ArgumentNullException if the given diagnostics are null, or /// if the given dictionary mapping each project files to the corresponding output path of the built project dll is. /// private static Func GetProjectOutputPath(IDictionary projectOutputPaths, List diagnostics) => (projFile) => { - if (projectOutputPaths == null) throw new ArgumentNullException(nameof(projectOutputPaths)); - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); + if (projectOutputPaths == null) + { + throw new ArgumentNullException(nameof(projectOutputPaths)); + } + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } - if (projectOutputPaths.TryGetValue(projFile, out Uri referencedProj)) return referencedProj; + if (projectOutputPaths.TryGetValue(projFile, out Uri referencedProj)) + { + return referencedProj; + } diagnostics.Add(Warnings.LoadWarning(WarningCode.ReferenceToUnknownProject, new[] { projFile.LocalPath }, MessageSource(projFile))); return null; }; /// - /// Loads the given project references from disk using the given mapping + /// Loads the given project references from disk using the given mapping /// to determine the path to the built dll for each project file, - /// and updates the load diagnostics accordingly. - /// If skipVerification is set to true, does push the updated project references to the CompilationUnitManager, + /// and updates the load diagnostics accordingly. + /// If skipVerification is set to true, does push the updated project references to the CompilationUnitManager, /// but suppresses the compilation unit wide type checking that would usually ensue. - /// Otherwise replaces *all* project references in the CompilationUnitManager with the newly loaded ones. + /// Otherwise replaces *all* project references in the CompilationUnitManager with the newly loaded ones. /// Throws an ArgumentNullException if the given sequence of project references is null. /// private Task LoadProjectReferencesAsync( IDictionary projectOutputPaths, IEnumerable projectReferences, bool skipVerification = false) { - if (projectOutputPaths == null) throw new ArgumentNullException(nameof(projectOutputPaths)); - if (projectReferences == null) throw new ArgumentNullException(nameof(projectReferences)); + if (projectOutputPaths == null) + { + throw new ArgumentNullException(nameof(projectOutputPaths)); + } + if (projectReferences == null) + { + throw new ArgumentNullException(nameof(projectReferences)); + } var diagnostics = new List(); - var loadedHeaders = ProjectManager.LoadProjectReferences( - projectReferences, GetProjectOutputPath(projectOutputPaths, diagnostics), - diagnostics.Add, this.Manager.LogException); - - this.LoadedProjectReferences = new References(loadedHeaders, this.Properties.ExposeReferencesViaTestNames); - var importedDeclarations = this.LoadedReferences.CombineWith(this.LoadedProjectReferences, - (code,args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); - this.ProjectReferenceDiagnostics = diagnostics.ToImmutableArray(); + var loadedHeaders = LoadProjectReferences( + projectReferences, + GetProjectOutputPath(projectOutputPaths, diagnostics), + diagnostics.Add, + this.Manager.LogException); + + this.loadedProjectReferences = new References(loadedHeaders, this.Properties.ExposeReferencesViaTestNames); + var importedDeclarations = this.loadedReferences.CombineWith( + this.loadedProjectReferences, + (code, args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); + this.projectReferenceDiagnostics = diagnostics.ToImmutableArray(); return this.Manager.UpdateReferencesAsync(importedDeclarations, suppressVerification: skipVerification); } /// - /// Given a dictionary mapping each project files to the corresponding output path of the built project dll, + /// Given a dictionary mapping each project files to the corresponding output path of the built project dll, /// as well as a uri to the project file of the project reference to reload, reloads that project reference - /// using the given dictionary, and adapting all load diagnostics accordingly. - /// Updates the reloaded reference in the CompilationUnitManager. + /// using the given dictionary, and adapting all load diagnostics accordingly. + /// Updates the reloaded reference in the CompilationUnitManager. /// Publishes the updated load diagnostics using the publisher of the CompilationUnitManager. /// Does nothing if the given project reference is not referenced by this project. - /// Throws an ArgumentNullException if the given dictionary is null. - /// Throws an ArgumentException if the given Uri is not an absolute uri to a file. + /// Throws an ArgumentNullException if the given dictionary is null. + /// Throws an ArgumentException if the given Uri is not an absolute uri to a file. /// private void ReloadProjectReference(IDictionary projectOutputPaths, Uri projectReference) { - if (projectOutputPaths == null) throw new ArgumentNullException(nameof(projectOutputPaths)); - if (!CompilationUnitManager.TryGetFileId(projectReference, out var projRefId)) throw new ArgumentException("expecting an absolute file uri"); - if (!this.SpecifiedProjectReferences.Contains(projectReference) || !this.IsLoaded) return; + if (projectOutputPaths == null) + { + throw new ArgumentNullException(nameof(projectOutputPaths)); + } + if (!CompilationUnitManager.TryGetFileId(projectReference, out var projRefId)) + { + throw new ArgumentException("expecting an absolute file uri"); + } + if (!this.specifiedProjectReferences.Contains(projectReference) || !this.isLoaded) + { + return; + } var diagnostics = new List(); - var loadedHeaders = ProjectManager.LoadProjectReferences( - new string[] { projectReference.LocalPath }, GetProjectOutputPath(projectOutputPaths, diagnostics), - diagnostics.Add, this.Manager.LogException); + var loadedHeaders = LoadProjectReferences( + new string[] { projectReference.LocalPath }, + GetProjectOutputPath(projectOutputPaths, diagnostics), + diagnostics.Add, + this.Manager.LogException); var loaded = new References(loadedHeaders, this.Properties.ExposeReferencesViaTestNames); - QsCompilerError.Verify(!loaded.Declarations.Any() || + QsCompilerError.Verify( + !loaded.Declarations.Any() || (loaded.Declarations.Count == 1 && loaded.Declarations.First().Key.Value == projRefId.Value), - $"loaded references upon loading {projectReference.LocalPath}: {String.Join(", ", loaded.Declarations.Select(r => r.Value))}"); - this.LoadedProjectReferences = this.LoadedProjectReferences.Remove(projRefId).CombineWith(loaded); - var importedDeclarations = this.LoadedReferences.CombineWith(this.LoadedProjectReferences, + $"loaded references upon loading {projectReference.LocalPath}: {string.Join(", ", loaded.Declarations.Select(r => r.Value))}"); + this.loadedProjectReferences = this.loadedProjectReferences.Remove(projRefId).CombineWith(loaded); + var importedDeclarations = this.loadedReferences.CombineWith( + this.loadedProjectReferences, (code, args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); - this.ProjectReferenceDiagnostics = this.ProjectReferenceDiagnostics.RemoveAll(d => + this.projectReferenceDiagnostics = this.projectReferenceDiagnostics.RemoveAll(d => (d.Source == MessageSource(projectReference) && d.Code != WarningCode.DuplicateProjectReference.Code()) || DiagnosticTools.ErrorType(ErrorCode.ConflictInReferences)(d)) .Concat(diagnostics).ToImmutableArray(); @@ -356,52 +424,68 @@ private void ReloadProjectReference(IDictionary projectOutputPaths, Ur } /// - /// Loads the given referenced dlls from disk and updates the load diagnostics accordingly. - /// If skipVerification is set to true, does push the updated references to the CompilationUnitManager, + /// Loads the given referenced dlls from disk and updates the load diagnostics accordingly. + /// If skipVerification is set to true, does push the updated references to the CompilationUnitManager, /// but suppresses the compilation unit wide type checking that would usually ensue. - /// Otherwise replaces *all* references in the CompilationUnitManager with the newly loaded ones. + /// Otherwise replaces *all* references in the CompilationUnitManager with the newly loaded ones. /// Throws an ArgumentNullException if the given sequence of referenced dlls is null. /// private Task LoadReferencedAssembliesAsync(IEnumerable references, bool skipVerification = false) { - if (references == null) throw new ArgumentNullException(nameof(references)); + if (references == null) + { + throw new ArgumentNullException(nameof(references)); + } var diagnostics = new List(); - var loadedHeaders = ProjectManager.LoadReferencedAssemblies(references, - diagnostics.Add, this.Manager.LogException); - - this.LoadedReferences = new References(loadedHeaders, this.Properties.ExposeReferencesViaTestNames); - var importedDeclarations = this.LoadedReferences.CombineWith(this.LoadedProjectReferences, - (code, args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); - this.ReferenceDiagnostics = diagnostics.ToImmutableArray(); + var loadedHeaders = LoadReferencedAssemblies( + references, + diagnostics.Add, + this.Manager.LogException); + + this.loadedReferences = new References(loadedHeaders, this.Properties.ExposeReferencesViaTestNames); + var importedDeclarations = this.loadedReferences.CombineWith( + this.loadedProjectReferences, + (code, args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); + this.referenceDiagnostics = diagnostics.ToImmutableArray(); return this.Manager.UpdateReferencesAsync(importedDeclarations, suppressVerification: skipVerification); } /// - /// Given a uri to the assembly to reload, reloads that reference, updates all load diagnostics accordingly, - /// and updates the reloaded reference in the CompilationUnitManager. + /// Given a uri to the assembly to reload, reloads that reference, updates all load diagnostics accordingly, + /// and updates the reloaded reference in the CompilationUnitManager. /// Publishes the updated load diagnostics using the publisher of the CompilationUnitManager. - /// Does nothing if the given assembly is not referenced by this project. - /// Throws an ArgumentException if the given Uri is not an absolute uri to a file. + /// Does nothing if the given assembly is not referenced by this project. + /// Throws an ArgumentException if the given Uri is not an absolute uri to a file. /// private void ReloadReferencedAssembly(Uri reference) { - if (!CompilationUnitManager.TryGetFileId(reference, out var refId)) throw new ArgumentException("expecting an absolute file uri"); - if (!this.SpecifiedReferences.Contains(reference) || !this.IsLoaded) return; + if (!CompilationUnitManager.TryGetFileId(reference, out var refId)) + { + throw new ArgumentException("expecting an absolute file uri"); + } + if (!this.specifiedReferences.Contains(reference) || !this.isLoaded) + { + return; + } var diagnostics = new List(); - var loadedHeaders = ProjectManager.LoadReferencedAssemblies(new string[] { reference.LocalPath }, - diagnostics.Add, this.Manager.LogException); + var loadedHeaders = LoadReferencedAssemblies( + new string[] { reference.LocalPath }, + diagnostics.Add, + this.Manager.LogException); var loaded = new References(loadedHeaders, this.Properties.ExposeReferencesViaTestNames); - QsCompilerError.Verify(!loaded.Declarations.Any() || + QsCompilerError.Verify( + !loaded.Declarations.Any() || (loaded.Declarations.Count == 1 && loaded.Declarations.First().Key.Value == refId.Value), - $"loaded references upon loading {reference.LocalPath}: {String.Join(", ", loaded.Declarations.Select(r => r.Value))}"); - this.LoadedReferences = this.LoadedReferences.Remove(refId).CombineWith(loaded); - var importedDeclarations = this.LoadedReferences.CombineWith(this.LoadedProjectReferences, + $"loaded references upon loading {reference.LocalPath}: {string.Join(", ", loaded.Declarations.Select(r => r.Value))}"); + this.loadedReferences = this.loadedReferences.Remove(refId).CombineWith(loaded); + var importedDeclarations = this.loadedReferences.CombineWith( + this.loadedProjectReferences, (code, args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); - this.ReferenceDiagnostics = this.ReferenceDiagnostics.RemoveAll(d => + this.referenceDiagnostics = this.referenceDiagnostics.RemoveAll(d => (d.Source == MessageSource(reference) && d.Code != WarningCode.DuplicateBinaryFile.Code()) || DiagnosticTools.ErrorType(ErrorCode.ConflictInReferences)(d)) .Concat(diagnostics).ToImmutableArray(); @@ -410,56 +494,61 @@ private void ReloadReferencedAssembly(Uri reference) } /// - /// Loads the given source files from disk and updates the load diagnostics accordingly. - /// Removes all source files that are no longer specified or loaded from the CompilationUnitManager, - /// and calls the given action RemoveFiles with the corresponding uris. - /// Adds all source files that were not loaded before but are now to the CompilationUnitManager. - /// Calls the given function GetExistingFileManagers to get all existing managers for files that are newly part of this project. - /// If skipIfAlreadyLoaded is set to true, blindly adds those managers to the CompilationUnitManager without updating their content. - /// Otherwise adds a *new* FileContentManager initialized with the content loaded from disk. - /// For all other files, a new FileContentManager is initialized with the content loaded from disk. - /// If skipIfAlreadyLoaded is set to true, the content of the files that were loaded before and are still loaded now + /// Loads the given source files from disk and updates the load diagnostics accordingly. + /// Removes all source files that are no longer specified or loaded from the CompilationUnitManager, + /// and calls the given action RemoveFiles with the corresponding uris. + /// Adds all source files that were not loaded before but are now to the CompilationUnitManager. + /// Calls the given function GetExistingFileManagers to get all existing managers for files that are newly part of this project. + /// If skipIfAlreadyLoaded is set to true, blindly adds those managers to the CompilationUnitManager without updating their content. + /// Otherwise adds a *new* FileContentManager initialized with the content loaded from disk. + /// For all other files, a new FileContentManager is initialized with the content loaded from disk. + /// If skipIfAlreadyLoaded is set to true, the content of the files that were loaded before and are still loaded now /// is *not* updated in the CompilationUnitManager. - /// Otherwise the FileContentManager of all files is replaced by a new one initialized with the content from disk. + /// Otherwise the FileContentManager of all files is replaced by a new one initialized with the content from disk. /// *Always* spawns a compilation unit wide type checking! /// Throws an ArgumentNullException if the given sequence of source files is null. /// - private Task LoadSourceFilesAsync(IEnumerable sourceFiles, - Func, Uri, IEnumerable> GetExistingFileManagers, Action, Task> RemoveFiles, + private Task LoadSourceFilesAsync( + IEnumerable sourceFiles, + Func, Uri, IEnumerable> getExistingFileManagers, + Action, Task> removeFiles, bool skipIfAlreadyLoaded = false) { - if (sourceFiles == null) throw new ArgumentNullException(nameof(sourceFiles)); + if (sourceFiles == null) + { + throw new ArgumentNullException(nameof(sourceFiles)); + } var diagnostics = new List(); - var loaded = ProjectManager.LoadSourceFiles(sourceFiles, diagnostics.Add, this.Manager.LogException); - this.SourceFileDiagnostics = diagnostics.ToImmutableArray(); + var loaded = LoadSourceFiles(sourceFiles, diagnostics.Add, this.Manager.LogException); + this.sourceFileDiagnostics = diagnostics.ToImmutableArray(); - var doNotAdd = skipIfAlreadyLoaded ? this.LoadedSourceFiles : Enumerable.Empty(); + var doNotAdd = skipIfAlreadyLoaded ? this.loadedSourceFiles : Enumerable.Empty(); var addToManager = loaded.Keys.Except(doNotAdd).ToImmutableHashSet(); - var removeFromManager = this.LoadedSourceFiles.Except(loaded.Keys); - this.LoadedSourceFiles = loaded.Keys.ToImmutableHashSet(); + var removeFromManager = this.loadedSourceFiles.Except(loaded.Keys); + this.loadedSourceFiles = loaded.Keys.ToImmutableHashSet(); - var existingFileManagers = GetExistingFileManagers?.Invoke(addToManager, this.ProjectFile)?.ToImmutableHashSet() ?? ImmutableHashSet.Empty; + var existingFileManagers = getExistingFileManagers?.Invoke(addToManager, this.ProjectFile)?.ToImmutableHashSet() ?? ImmutableHashSet.Empty; var knownFilesToAdd = skipIfAlreadyLoaded ? existingFileManagers : ImmutableHashSet.Empty; var newFilesToAdd = CompilationUnitManager.InitializeFileManagers( addToManager.Except(knownFilesToAdd.Select(m => m.Uri)).ToImmutableDictionary(uri => uri, uri => loaded[uri]), - this.Manager.PublishDiagnostics, this.Manager.LogException); + this.Manager.PublishDiagnostics, + this.Manager.LogException); var removal = this.Manager.TryRemoveSourceFilesAsync(removeFromManager, suppressVerification: true); - RemoveFiles?.Invoke(removeFromManager, removal); + removeFiles?.Invoke(removeFromManager, removal); return this.Manager.AddOrUpdateSourceFilesAsync(knownFilesToAdd.Union(newFilesToAdd)); } - /// /// Returns a copy of all current load diagnostics as PublishDiagnosticParams for the project file of this project. /// private PublishDiagnosticParams CurrentLoadDiagnostics() { Diagnostic[] diagnostics = - this.GeneralDiagnostics.Concat( - this.SourceFileDiagnostics).Concat( - this.ProjectReferenceDiagnostics).Concat( - this.ReferenceDiagnostics) + this.generalDiagnostics.Concat( + this.sourceFileDiagnostics).Concat( + this.projectReferenceDiagnostics).Concat( + this.referenceDiagnostics) .Select(d => d.Copy()).ToArray(); return new PublishDiagnosticParams @@ -469,79 +558,101 @@ private PublishDiagnosticParams CurrentLoadDiagnostics() }; } - // routines related to updating the loaded content of the project // -> i.e. asynchronous tasks that are queued into the Processing queue /// - /// Given a dictionary mapping each project files to the corresponding output path of the built project dll, - /// as well as a uri to the assembly to reload, reloads all project references with that output path, and/or any reference to that dll. + /// Given a dictionary mapping each project files to the corresponding output path of the built project dll, + /// as well as a uri to the assembly to reload, reloads all project references with that output path, and/or any reference to that dll. /// Updates the load diagnostics accordingly, and publishes them using the publisher of the CompilationUnitManager. - /// Throws an ArgumentNullException if any of the given arguments is null. + /// Throws an ArgumentNullException if any of the given arguments is null. /// public Task ReloadAssemblyAsync(IDictionary projectOutputPaths, Uri dllPath) { - if (projectOutputPaths == null) throw new ArgumentNullException(nameof(projectOutputPaths)); - if (dllPath == null) throw new ArgumentNullException(nameof(dllPath)); + if (projectOutputPaths == null) + { + throw new ArgumentNullException(nameof(projectOutputPaths)); + } + if (dllPath == null) + { + throw new ArgumentNullException(nameof(dllPath)); + } var projectsWithThatOutputDll = projectOutputPaths.Where(pair => pair.Value == dllPath).Select(pair => pair.Key); - return this.Processing.QueueForExecutionAsync(() => + return this.processing.QueueForExecutionAsync(() => { - var updatedProjectReferences = this.SpecifiedProjectReferences.Intersect(projectsWithThatOutputDll); + var updatedProjectReferences = this.specifiedProjectReferences.Intersect(projectsWithThatOutputDll); foreach (var projFile in updatedProjectReferences) - { this.ReloadProjectReference(projectOutputPaths, projFile); } + { + this.ReloadProjectReference(projectOutputPaths, projFile); + } this.ReloadReferencedAssembly(dllPath); }); } /// - /// Given a dictionary mapping each project files to the corresponding output path of the built project dll, + /// Given a dictionary mapping each project files to the corresponding output path of the built project dll, /// as well as a uri to the project file of the project reference to reload, reloads that project reference - /// using the given dictionary, and adapting all load diagnostics accordingly. - /// Updates the reloaded reference in the CompilationUnitManager. + /// using the given dictionary, and adapting all load diagnostics accordingly. + /// Updates the reloaded reference in the CompilationUnitManager. /// Publishes the updated load diagnostics using the publisher of the CompilationUnitManager. /// Does nothing if the given project reference is not referenced by this project. - /// Throws an ArgumentNullException if any of the given arguments is null. + /// Throws an ArgumentNullException if any of the given arguments is null. /// public Task ReloadProjectReferenceAsync(IDictionary projectOutputPaths, Uri projectReference) { - if (projectOutputPaths == null) throw new ArgumentNullException(nameof(projectOutputPaths)); - if (projectReference == null) throw new ArgumentNullException(nameof(projectReference)); + if (projectOutputPaths == null) + { + throw new ArgumentNullException(nameof(projectOutputPaths)); + } + if (projectReference == null) + { + throw new ArgumentNullException(nameof(projectReference)); + } - return this.Processing.QueueForExecutionAsync(() => + return this.processing.QueueForExecutionAsync(() => this.ReloadProjectReference(projectOutputPaths, projectReference)); } /// - /// Given a uri to source file to reload, reloads that source file, and updates all load diagnostics accordingly, + /// Given a uri to source file to reload, reloads that source file, and updates all load diagnostics accordingly, /// unless that file is open in the editor (i.e. openInEditor does not return null). /// If openInEditor returns null for the given source file, - /// updates the content of the source file in the CompilationUnitManager. + /// updates the content of the source file in the CompilationUnitManager. /// Publishes the updated load diagnostics using the publisher of the CompilationUnitManager. - /// Does nothing if the given source file is open in the editor or not listed as a source file of this project. - /// Throws an ArgumentNullException if the given uri is null. + /// Does nothing if the given source file is open in the editor or not listed as a source file of this project. + /// Throws an ArgumentNullException if the given uri is null. /// public Task ReloadSourceFileAsync(Uri sourceFile, Func openInEditor = null) { - if (sourceFile == null) throw new ArgumentNullException(nameof(sourceFile)); - openInEditor ??= (_ => null); + if (sourceFile == null) + { + throw new ArgumentNullException(nameof(sourceFile)); + } + openInEditor ??= _ => null; - return this.Processing.QueueForExecutionAsync(() => + return this.processing.QueueForExecutionAsync(() => { - if (!this.SpecifiedSourceFiles.Contains(sourceFile) || !this.IsLoaded || openInEditor(sourceFile) != null) return; + if (!this.specifiedSourceFiles.Contains(sourceFile) || !this.isLoaded || openInEditor(sourceFile) != null) + { + return; + } var diagnostics = new List(); - var loaded = ProjectManager.LoadSourceFiles(new string[] { sourceFile.LocalPath }, diagnostics.Add, this.Manager.LogException); + var loaded = LoadSourceFiles(new string[] { sourceFile.LocalPath }, diagnostics.Add, this.Manager.LogException); QsCompilerError.Verify(loaded.Count() <= 1); - this.LoadedSourceFiles = this.LoadedSourceFiles.Remove(sourceFile).Concat(loaded.Keys).ToImmutableHashSet(); - this.SourceFileDiagnostics = this.SourceFileDiagnostics + this.loadedSourceFiles = this.loadedSourceFiles.Remove(sourceFile).Concat(loaded.Keys).ToImmutableHashSet(); + this.sourceFileDiagnostics = this.sourceFileDiagnostics .RemoveAll(d => d.Source == MessageSource(sourceFile) && d.Code != WarningCode.DuplicateSourceFile.Code()) .Concat(diagnostics).ToImmutableArray(); this.Manager.PublishDiagnostics(this.CurrentLoadDiagnostics()); var content = loaded.TryGetValue(sourceFile, out string fileContent) ? fileContent : null; - if (content == null) this.Manager.TryRemoveSourceFileAsync(sourceFile); + if (content == null) + { + this.Manager.TryRemoveSourceFileAsync(sourceFile); + } else { var file = CompilationUnitManager.InitializeFileManager(sourceFile, content, this.Manager.PublishDiagnostics, this.Manager.LogException); @@ -550,33 +661,54 @@ public Task ReloadSourceFileAsync(Uri sourceFile, Func }); } - /// - /// If the given file is a loaded source file of this project, - /// executes the given task for that file on the CompilationUnitManager. - /// Throws an ArgumentNullException if the given uri, the task to execute, - /// or the dictionary to determine the output path for project references is null. + /// If the given file is a loaded source file of this project, + /// executes the given task for that file on the CompilationUnitManager. + /// Throws an ArgumentNullException if the given uri, the task to execute, + /// or the dictionary to determine the output path for project references is null. /// public bool ManagerTask(Uri file, Action executeTask, IDictionary projectOutputPaths) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (executeTask == null) throw new ArgumentNullException(nameof(executeTask)); - if (projectOutputPaths == null) throw new ArgumentNullException(nameof(projectOutputPaths)); - - this.Processing.QueueForExecution(() => + if (file == null) { - if (!this.SpecifiedSourceFiles.Contains(file)) return false; - if (!this.IsLoaded) - { - try { QsCompilerError.RaiseOnFailure(() => this.LoadProject(projectOutputPaths, null, null), $"failed to load {this.ProjectFile.LocalPath}"); } - catch (Exception ex) { this.Manager.LogException(ex); } - } - - if (!this.LoadedSourceFiles.Contains(file)) return false; - executeTask(this.Manager); - return true; + throw new ArgumentNullException(nameof(file)); + } + if (executeTask == null) + { + throw new ArgumentNullException(nameof(executeTask)); } - , out bool didExecute); + if (projectOutputPaths == null) + { + throw new ArgumentNullException(nameof(projectOutputPaths)); + } + + this.processing.QueueForExecution( + () => + { + if (!this.specifiedSourceFiles.Contains(file)) + { + return false; + } + if (!this.isLoaded) + { + try + { + QsCompilerError.RaiseOnFailure(() => this.LoadProject(projectOutputPaths, null, null), $"failed to load {this.ProjectFile.LocalPath}"); + } + catch (Exception ex) + { + this.Manager.LogException(ex); + } + } + + if (!this.loadedSourceFiles.Contains(file)) + { + return false; + } + executeTask(this.Manager); + return true; + }, + out bool didExecute); return didExecute; } @@ -585,299 +717,384 @@ public bool ManagerTask(Uri file, Action executeTask, ID /// Note: this method waits for all currently running or queued tasks to finish before accumulating the diagnostics. /// public PublishDiagnosticParams GetLoadDiagnostics() => - this.Processing.QueueForExecution( - this.CurrentLoadDiagnostics, - out PublishDiagnosticParams param) - ? param : null; + this.processing.QueueForExecution( + this.CurrentLoadDiagnostics, + out PublishDiagnosticParams param) + ? param : null; } - - private readonly ProcessingQueue Load; - private readonly ConcurrentDictionary Projects; - private readonly CompilationUnitManager DefaultManager; + private readonly ProcessingQueue load; + private readonly ConcurrentDictionary projects; + private readonly CompilationUnitManager defaultManager; /// /// called whenever diagnostics within a file have changed and are ready for publishing -> may be null! /// - private readonly Action PublishDiagnostics; + private readonly Action publishDiagnostics; + /// /// used to log exceptions raised during processing -> may be null! /// - private readonly Action LogException; + private readonly Action logException; + /// /// general purpose logging routine used for major loading events -> may be null! /// - private readonly Action Log; + private readonly Action log; /// - /// If an Action for publishing diagnostics is given and not null, + /// If an Action for publishing diagnostics is given and not null, /// that action is called whenever diagnostics for the project have changed and are ready for publishing. - /// Any exceptions caught during processing are logged using the given exception logger. + /// Any exceptions caught during processing are logged using the given exception logger. /// public ProjectManager(Action exceptionLogger, Action log = null, Action publishDiagnostics = null) { - this.Load = new ProcessingQueue(exceptionLogger); - this.Projects = new ConcurrentDictionary(); - this.DefaultManager = new CompilationUnitManager(exceptionLogger, publishDiagnostics, syntaxCheckOnly: true); - this.PublishDiagnostics = publishDiagnostics; - this.LogException = exceptionLogger; - this.Log = log; + this.load = new ProcessingQueue(exceptionLogger); + this.projects = new ConcurrentDictionary(); + this.defaultManager = new CompilationUnitManager(exceptionLogger, publishDiagnostics, syntaxCheckOnly: true); + this.publishDiagnostics = publishDiagnostics; + this.logException = exceptionLogger; + this.log = log; } + /// public void Dispose() => - this.Load.QueueForExecution(() => + this.load.QueueForExecution(() => { - foreach (var project in this.Projects.Values) - { project.Dispose(); } + foreach (var project in this.projects.Values) + { + project.Dispose(); + } }); - /// - /// Returns a function that given the uris of all files that have been added to a project, - /// queries openInEditor to determine which of those files are currently open in the editor. - /// Removes all such files from the default manager and returns their FileContentManagers. - /// Throws an ArgumentNullException if the given function openInEditor is null. + /// Returns a function that given the uris of all files that have been added to a project, + /// queries openInEditor to determine which of those files are currently open in the editor. + /// Removes all such files from the default manager and returns their FileContentManagers. + /// Throws an ArgumentNullException if the given function openInEditor is null. /// private Func, Uri, IEnumerable> MigrateToProject(Func openInEditor) { - if (openInEditor == null) throw new ArgumentNullException(nameof(openInEditor)); + if (openInEditor == null) + { + throw new ArgumentNullException(nameof(openInEditor)); + } return (filesAddedToProject, projFile) => { filesAddedToProject ??= ImmutableHashSet.Empty; var openFiles = filesAddedToProject.Select(openInEditor).Where(m => m != null).ToImmutableArray(); var removals = openFiles.Select(file => { - this.Log($"The file {file.Uri.LocalPath} has been associated with the compilation unit {projFile.LocalPath}.", MessageType.Log); - return this.DefaultManager.TryRemoveSourceFileAsync(file.Uri, publishEmptyDiagnostics: false); // no need to clear diagnostics - new ones will be pushed by the project + this.log($"The file {file.Uri.LocalPath} has been associated with the compilation unit {projFile.LocalPath}.", MessageType.Log); + return this.defaultManager.TryRemoveSourceFileAsync(file.Uri, publishEmptyDiagnostics: false); // no need to clear diagnostics - new ones will be pushed by the project }) .ToArray(); - if (removals.Any()) Task.WaitAll(removals); // we *need* to wait here in order to make sure that change notifications are processed in order!! + if (removals.Any()) + { + Task.WaitAll(removals); // we *need* to wait here in order to make sure that change notifications are processed in order!! + } return openFiles; }; } /// - /// Returns a function that given the uris of all files that have been removed from a project, - /// waits for the given removal task to finish before querying openInEditor - /// to determine which of those files are currently open in the editor. - /// Clears all verifications for those files and adds them to the default manager. - /// The returned Action does nothing if the task passed as argument has been cancelled. - /// The returned Action throws an ObjectDisposedException if the task passed as argument has been disposed. - /// The returned Action throws an ArgumentNullException if the task passed as argument is null. + /// Returns a function that given the uris of all files that have been removed from a project, + /// waits for the given removal task to finish before querying openInEditor + /// to determine which of those files are currently open in the editor. + /// Clears all verifications for those files and adds them to the default manager. + /// The returned Action does nothing if the task passed as argument has been cancelled. + /// The returned Action throws an ObjectDisposedException if the task passed as argument has been disposed. + /// The returned Action throws an ArgumentNullException if the task passed as argument is null. /// Throws an ArgumentNullException if the given function openInEditor is null. /// private Action, Task> MigrateToDefaultManager(Func openInEditor) { - if (openInEditor == null) throw new ArgumentNullException(nameof(openInEditor)); + if (openInEditor == null) + { + throw new ArgumentNullException(nameof(openInEditor)); + } return (filesRemovedFromProject, removal) => { - if (removal.IsCanceled) return; + if (removal.IsCanceled) + { + return; + } filesRemovedFromProject ??= ImmutableHashSet.Empty; Task.WaitAll(removal); // we *need* to wait here in order to make sure that change notifications are processed in order!! var openFiles = filesRemovedFromProject.Select(openInEditor).Where(m => m != null).ToImmutableHashSet(); foreach (var file in openFiles) { - this.Log($"The file {file.Uri.LocalPath} is no longer associated with a compilation unit. Only syntactic diagnostics will be generated.", MessageType.Log); + this.log($"The file {file.Uri.LocalPath} is no longer associated with a compilation unit. Only syntactic diagnostics will be generated.", MessageType.Log); file.ClearVerification(); } - this.DefaultManager.AddOrUpdateSourceFilesAsync(openFiles); + this.defaultManager.AddOrUpdateSourceFilesAsync(openFiles); }; } - // public routines related to tracking compilation units - i.e. routines handling coordination /// - /// Used for initial project loading. + /// Used for initial project loading. /// Note that by calling this routine, all processing will be blocked until loading has finished... /// - public Task LoadProjectsAsync(IEnumerable projectFiles, ProjectInformation.Loader projectLoader, + public Task LoadProjectsAsync( + IEnumerable projectFiles, + ProjectInformation.Loader projectLoader, Func openInEditor = null) { - if (projectFiles == null || projectFiles.Contains(null)) throw new ArgumentNullException(nameof(projectFiles)); - if (projectLoader == null) throw new ArgumentNullException(nameof(projectLoader)); - openInEditor ??= (_ => null); + if (projectFiles == null || projectFiles.Contains(null)) + { + throw new ArgumentNullException(nameof(projectFiles)); + } + if (projectLoader == null) + { + throw new ArgumentNullException(nameof(projectLoader)); + } + openInEditor ??= _ => null; - return this.Load.QueueForExecutionAsync(() => + return this.load.QueueForExecutionAsync(() => { - foreach (var file in projectFiles) // ms build complains if a (design time) build is already in progress... + foreach (var file in projectFiles) { - if (!projectLoader(file, out var info)) continue; - var project = new Project(file, info, this.LogException, this.PublishDiagnostics, this.Log); - this.Projects.AddOrUpdate(file, project, (k, v) => project); + // ms build complains if a (design time) build is already in progress... + if (!projectLoader(file, out var info)) + { + continue; + } + var project = new Project(file, info, this.logException, this.publishDiagnostics, this.log); + this.projects.AddOrUpdate(file, project, (k, v) => project); } - var outputPaths = this.Projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath); + var outputPaths = this.projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath); foreach (var file in projectFiles) { - if (!this.Projects.TryGetValue(file, out var project)) continue; + if (!this.projects.TryGetValue(file, out var project)) + { + continue; + } if (project.ContainsAnySourceFiles(uri => openInEditor(uri) != null)) - { project.LoadProjectAsync(outputPaths, MigrateToProject(openInEditor), null); } + { + project.LoadProjectAsync(outputPaths, this.MigrateToProject(openInEditor), null); + } } }); } /// - /// To be used whenever a project file is added, removed or updated. + /// To be used whenever a project file is added, removed or updated. /// *Not* to be used to update content (will not update content unless it is new/removed content)! /// - public Task ProjectChangedOnDiskAsync(Uri projectFile, ProjectInformation.Loader projectLoader, + public Task ProjectChangedOnDiskAsync( + Uri projectFile, + ProjectInformation.Loader projectLoader, Func openInEditor = null) { - if (projectFile == null) throw new ArgumentNullException(nameof(projectFile)); - if (projectLoader == null) throw new ArgumentNullException(nameof(projectLoader)); - openInEditor ??= (_ => null); + if (projectFile == null) + { + throw new ArgumentNullException(nameof(projectFile)); + } + if (projectLoader == null) + { + throw new ArgumentNullException(nameof(projectLoader)); + } + openInEditor ??= _ => null; // TODO: allow to cancel this task via cancellation token? - return this.Load.QueueForExecutionAsync(() => + return this.load.QueueForExecutionAsync(() => { var loaded = projectLoader(projectFile, out ProjectInformation info); - var existing = this.Projects.TryRemove(projectFile, out Project current) ? current : null; + var existing = this.projects.TryRemove(projectFile, out Project current) ? current : null; if (!loaded) { - existing?.LoadProjectAsync(ImmutableDictionary.Empty, null, MigrateToDefaultManager(openInEditor), + existing?.LoadProjectAsync( + ImmutableDictionary.Empty, + null, + this.MigrateToDefaultManager(openInEditor), ProjectInformation.Empty("Latest", existing.OutputPath.LocalPath, AssemblyConstants.RuntimeCapabilities.Unknown))?.Wait(); // does need to block, or the call to the DefaultManager in ManagerTaskAsync needs to be adapted - if (existing != null) this.ProjectReferenceChangedOnDiskChangeAsync(projectFile); + if (existing != null) + { + this.ProjectReferenceChangedOnDiskChangeAsync(projectFile); + } return; } - var updated = existing ?? new Project(projectFile, info, this.LogException, this.PublishDiagnostics, this.Log); - this.Projects.AddOrUpdate(projectFile, updated, (_, __) => updated); + var updated = existing ?? new Project(projectFile, info, this.logException, this.publishDiagnostics, this.log); + this.projects.AddOrUpdate(projectFile, updated, (_, __) => updated); - // If any of the files that are currently open in the editor is part of the project, - // then we need to make sure to remove them from the default manager before adding them to the project. + // If any of the files that are currently open in the editor is part of the project, + // then we need to make sure to remove them from the default manager before adding them to the project. // Conversely, if a file that is open in the editor is removed from the project, we need to add it to the DefaultManager. - updated.LoadProjectAsync(this.Projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath), - MigrateToProject(openInEditor), MigrateToDefaultManager(openInEditor), info) - .ContinueWith(_ => this.ProjectReferenceChangedOnDiskChangeAsync(projectFile), TaskScheduler.Default); + updated.LoadProjectAsync( + this.projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath), + this.MigrateToProject(openInEditor), + this.MigrateToDefaultManager(openInEditor), + info) + .ContinueWith(_ => this.ProjectReferenceChangedOnDiskChangeAsync(projectFile), TaskScheduler.Default); }); } /// /// To be called whenever one of the tracked projects has been added, removed or updated - /// in order to update all other projects referencing the modified one. + /// in order to update all other projects referencing the modified one. /// private Task ProjectReferenceChangedOnDiskChangeAsync(Uri projFile) { - if (projFile == null) throw new ArgumentNullException(nameof(projFile)); - return this.Load.QueueForExecutionAsync(() => + if (projFile == null) + { + throw new ArgumentNullException(nameof(projFile)); + } + return this.load.QueueForExecutionAsync(() => { - var projectOutputPaths = this.Projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath); - foreach (var project in this.Projects.Values) - { project.ReloadProjectReferenceAsync(projectOutputPaths, projFile); } + var projectOutputPaths = this.projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath); + foreach (var project in this.projects.Values) + { + project.ReloadProjectReferenceAsync(projectOutputPaths, projFile); + } }); } /// /// To be called whenever a dll that may be referenced by one of the tracked projects is added, removed or changed on disk - /// in order to update all projects referencing it accordingly. + /// in order to update all projects referencing it accordingly. /// public Task AssemblyChangedOnDiskAsync(Uri dllPath) { - if (dllPath == null) throw new ArgumentNullException(nameof(dllPath)); - return this.Load.QueueForExecutionAsync(() => + if (dllPath == null) { - var projectOutputPaths = this.Projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath); - foreach (var project in this.Projects.Values) - { project.ReloadAssemblyAsync(projectOutputPaths, dllPath); } + throw new ArgumentNullException(nameof(dllPath)); + } + return this.load.QueueForExecutionAsync(() => + { + var projectOutputPaths = this.projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath); + foreach (var project in this.projects.Values) + { + project.ReloadAssemblyAsync(projectOutputPaths, dllPath); + } }); } /// /// To be called whenever a source file that may belong to one of the tracked projects has changed on disk. - /// For each tracked project reloads the source file from disk and updates the project accordingly, - /// if the modified file is a source file of that project and not open in the editor + /// For each tracked project reloads the source file from disk and updates the project accordingly, + /// if the modified file is a source file of that project and not open in the editor /// (i.e. openInEditor is null or returns null for that file) at the time of execution. /// public Task SourceFileChangedOnDiskAsync(Uri sourceFile, Func openInEditor = null) { - if (sourceFile == null) throw new ArgumentNullException(nameof(sourceFile)); - return this.Load.QueueForExecutionAsync(() => + if (sourceFile == null) { - foreach (var project in this.Projects.Values) - { project.ReloadSourceFileAsync(sourceFile, openInEditor); } + throw new ArgumentNullException(nameof(sourceFile)); + } + return this.load.QueueForExecutionAsync(() => + { + foreach (var project in this.projects.Values) + { + project.ReloadSourceFileAsync(sourceFile, openInEditor); + } }); } - // routines related to querying individual compilation managers (internally and externally) /// - /// Returns the compilation unit manager for the project + /// Returns the compilation unit manager for the project /// if the given file can be uniquely associated with a compilation unit. - /// Returns the DefaultManager otherwise. - /// NOTE: returns null if no CompilationUnitManager exists for the project, or if the given file is null. + /// Returns the DefaultManager otherwise. + /// NOTE: returns null if no CompilationUnitManager exists for the project, or if the given file is null. /// private CompilationUnitManager Manager(Uri file) { - if (file == null) return null; - var includedIn = this.Projects.Values.Where(project => project.ContainsSourceFile(file)); - return includedIn.Count() == 1 - ? includedIn.Single().Manager - : this.DefaultManager; + if (file == null) + { + return null; + } + var includedIn = this.projects.Values.Where(project => project.ContainsSourceFile(file)); + return includedIn.Count() == 1 + ? includedIn.Single().Manager + : this.defaultManager; } /// - /// If the given file can be uniquely associated with a compilation unit, - /// executes the given Action on the CompilationUnitManager of that project (if one exists), passing true as second argument. - /// Executes the given Action on the DefaultManager otherwise, passing false as second argument. - /// Throws an ArgumentNullException if the given Action is null. + /// If the given file can be uniquely associated with a compilation unit, + /// executes the given Action on the CompilationUnitManager of that project (if one exists), passing true as second argument. + /// Executes the given Action on the DefaultManager otherwise, passing false as second argument. + /// Throws an ArgumentNullException if the given Action is null. /// public Task ManagerTaskAsync(Uri file, Action executeTask) { - if (executeTask == null) throw new ArgumentNullException(nameof(executeTask)); - return this.Load.QueueForExecutionAsync(() => + if (executeTask == null) + { + throw new ArgumentNullException(nameof(executeTask)); + } + return this.load.QueueForExecutionAsync(() => { var didExecute = false; var options = new ParallelOptions { TaskScheduler = TaskScheduler.Default }; - var projectOutputPaths = this.Projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath); - Parallel.ForEach(this.Projects.Values, options, project => + var projectOutputPaths = this.projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath); + Parallel.ForEach(this.projects.Values, options, project => { if (project.ManagerTask(file, m => executeTask(m, true), projectOutputPaths)) - { didExecute = true; } + { + didExecute = true; + } }); - if (!didExecute) executeTask(this.DefaultManager, false); + if (!didExecute) + { + executeTask(this.defaultManager, false); + } }); } - // editor commands that require blocking /// - /// Returns the workspace edit that describes the changes to be done if the symbol at the given position - if any - is renamed to the given name. + /// Returns the workspace edit that describes the changes to be done if the symbol at the given position - if any - is renamed to the given name. /// Returns null if no symbol exists at the specified position, /// or if some parameters are unspecified (null), - /// or if the specified position is not a valid position within the file, - /// or if a file affected by the rename operation belongs to several compilation units. + /// or if the specified position is not a valid position within the file, + /// or if a file affected by the rename operation belongs to several compilation units. /// public WorkspaceEdit Rename(RenameParams param, bool versionedChanges) // versionedChanges is unused (WorkspaceEdit contains both Changes and DocumentChanges, but the version nr is null) { - if (param?.TextDocument?.Uri == null) return null; - var success = this.Load.QueueForExecution(() => + if (param?.TextDocument?.Uri == null) { - var options = new ParallelOptions { TaskScheduler = TaskScheduler.Default }; - var projectOutputPaths = this.Projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath); - var results = new ConcurrentBag(); - - Parallel.ForEach(this.Projects.Values, options, project => // the default manager does not support rename operations - { project.ManagerTask(param.TextDocument.Uri, m => results.Add(m.Rename(param)), projectOutputPaths); }); - return results; + return null; + } + var success = this.load.QueueForExecution( + () => + { + var options = new ParallelOptions { TaskScheduler = TaskScheduler.Default }; + var projectOutputPaths = this.projects.ToImmutableDictionary(p => p.Key, p => p.Value.OutputPath); + var results = new ConcurrentBag(); + + Parallel.ForEach(this.projects.Values, options, project => // the default manager does not support rename operations + { + project.ManagerTask(param.TextDocument.Uri, m => results.Add(m.Rename(param)), projectOutputPaths); + }); + return results; + }, + out var edits); + + if (!success) + { + return null; } - , out var edits); - if (!success) return null; try - { // if a file belongs to several compilation units, then this will fail + { + // if a file belongs to several compilation units, then this will fail var changes = edits.SelectMany(edit => edit.Changes) .ToDictionary(pair => pair.Key, pair => pair.Value); var documentChanges = edits.SelectMany(edit => edit.DocumentChanges).ToArray(); return new WorkspaceEdit { Changes = changes, DocumentChanges = documentChanges }; } - catch { return null; } + catch + { + return null; + } } - // routines related to providing information for non-blocking editor commands // -> these commands need to be responsive and therefore won't wait for any processing to finish // -> if the query cannot be processed immediately, they simply return null @@ -885,12 +1102,12 @@ public WorkspaceEdit Rename(RenameParams param, bool versionedChanges) // versio /// /// Returns the source file and position where the item at the given position is declared at, /// if such a declaration exists, and returns null otherwise. - /// Fails silently without logging anything if an exception occurs upon evaluating the query + /// Fails silently without logging anything if an exception occurs upon evaluating the query /// (occasional failures are to be expected as the evaluation is a readonly query running in parallel to the ongoing processing). /// public Location DefinitionLocation(TextDocumentPositionParams param) => - this.Manager(param?.TextDocument?.Uri)?.FileQuery - (param?.TextDocument, (file, c) => file.DefinitionLocation(c, param?.Position), suppressExceptionLogging: true); + this.Manager(param?.TextDocument?.Uri)?.FileQuery( + param?.TextDocument, (file, c) => file.DefinitionLocation(c, param?.Position), suppressExceptionLogging: true); /// /// Returns the signature help information for a call expression if there is such an expression at the specified position. @@ -899,12 +1116,12 @@ public Location DefinitionLocation(TextDocumentPositionParams param) => /// or if the specified position is not a valid position within the currently processed file content, /// or if no call expression exists at the specified position at this time, /// or if no signature help information can be provided for the call expression at the specified position. - /// Fails silently without logging anything if an exception occurs upon evaluating the query + /// Fails silently without logging anything if an exception occurs upon evaluating the query /// (occasional failures are to be expected as the evaluation is a readonly query running in parallel to the ongoing processing). /// public SignatureHelp SignatureHelp(TextDocumentPositionParams param, MarkupKind format = MarkupKind.PlainText) => - this.Manager(param?.TextDocument?.Uri)?.FileQuery - (param?.TextDocument, (file, c) => file.SignatureHelp(c, param?.Position, format), suppressExceptionLogging: true); + this.Manager(param?.TextDocument?.Uri)?.FileQuery( + param?.TextDocument, (file, c) => file.SignatureHelp(c, param?.Position, format), suppressExceptionLogging: true); /// /// Returns information about the item at the specified position as Hover information. @@ -912,12 +1129,12 @@ public SignatureHelp SignatureHelp(TextDocumentPositionParams param, MarkupKind /// or if the specified file is not listed as source file, /// or if the specified position is not a valid position within the currently processed file content, /// or if no token exists at the specified position. - /// Fails silently without logging anything if an exception occurs upon evaluating the query + /// Fails silently without logging anything if an exception occurs upon evaluating the query /// (occasional failures are to be expected as the evaluation is a readonly query running in parallel to the ongoing processing). /// public Hover HoverInformation(TextDocumentPositionParams param, MarkupKind format = MarkupKind.PlainText) => - this.Manager(param?.TextDocument?.Uri)?.FileQuery - (param?.TextDocument, (file, c) => file.HoverInformation(c, param?.Position, format), suppressExceptionLogging: true); + this.Manager(param?.TextDocument?.Uri)?.FileQuery( + param?.TextDocument, (file, c) => file.HoverInformation(c, param?.Position, format), suppressExceptionLogging: true); /// /// Returns an array with all usages of the identifier at the given position (if any) as DocumentHighlights. @@ -925,47 +1142,47 @@ public Hover HoverInformation(TextDocumentPositionParams param, MarkupKind forma /// or if the specified file is not listed as source file, /// or if the specified position is not a valid position within the currently processed file content, /// or if no identifier exists at the specified position at this time. - /// Fails silently without logging anything if an exception occurs upon evaluating the query + /// Fails silently without logging anything if an exception occurs upon evaluating the query /// (occasional failures are to be expected as the evaluation is a readonly query running in parallel to the ongoing processing). /// - public DocumentHighlight[] DocumentHighlights(TextDocumentPositionParams param) => - this.Manager(param?.TextDocument?.Uri)?.FileQuery - (param?.TextDocument, (file, c) => file.DocumentHighlights(c, param?.Position), suppressExceptionLogging: true); + public DocumentHighlight[] DocumentHighlights(TextDocumentPositionParams param) => + this.Manager(param?.TextDocument?.Uri)?.FileQuery( + param?.TextDocument, (file, c) => file.DocumentHighlights(c, param?.Position), suppressExceptionLogging: true); /// - /// Returns an array with all locations where the symbol at the given position - if any - is referenced. + /// Returns an array with all locations where the symbol at the given position - if any - is referenced. /// Returns null if some parameters are unspecified (null), /// or if the specified file is not listed as source file, /// or if the specified position is not a valid position within the currently processed file content, /// or if no symbol exists at the specified position at this time. - /// Fails silently without logging anything if an exception occurs upon evaluating the query + /// Fails silently without logging anything if an exception occurs upon evaluating the query /// (occasional failures are to be expected as the evaluation is a readonly query running in parallel to the ongoing processing). /// public Location[] SymbolReferences(ReferenceParams param) => - this.Manager(param?.TextDocument?.Uri)?.FileQuery - (param?.TextDocument, (file, c) => file.SymbolReferences(c, param?.Position, param.Context), suppressExceptionLogging: true); + this.Manager(param?.TextDocument?.Uri)?.FileQuery( + param?.TextDocument, (file, c) => file.SymbolReferences(c, param?.Position, param.Context), suppressExceptionLogging: true); /// - /// Returns the SymbolInformation for each namespace declaration, + /// Returns the SymbolInformation for each namespace declaration, /// type declaration, and function or operation declaration within the specified file. /// Returns null if given uri is null or if the specified file is not listed as source file. - /// Fails silently without logging anything if an exception occurs upon evaluating the query + /// Fails silently without logging anything if an exception occurs upon evaluating the query /// (occasional failures are to be expected as the evaluation is a readonly query running in parallel to the ongoing processing). /// public SymbolInformation[] DocumentSymbols(DocumentSymbolParams param) => - this.Manager(param?.TextDocument?.Uri)?.FileQuery - (param.TextDocument, (file, _) => file.DocumentSymbols(), suppressExceptionLogging: true); + this.Manager(param?.TextDocument?.Uri)?.FileQuery( + param.TextDocument, (file, _) => file.DocumentSymbols(), suppressExceptionLogging: true); /// /// Returns a look-up of workspace edits suggested by the compiler for the given location and context. - /// The key of the look-up is a suitable title for the corresponding edits that can be presented to the user. + /// The key of the look-up is a suitable title for the corresponding edits that can be presented to the user. /// Returns null if given uri is null or if the specified file is not listed as source file. - /// Fails silently without logging anything if an exception occurs upon evaluating the query + /// Fails silently without logging anything if an exception occurs upon evaluating the query /// (occasional failures are to be expected as the evaluation is a readonly query running in parallel to the ongoing processing). /// public ILookup CodeActions(CodeActionParams param) => - this.Manager(param?.TextDocument?.Uri)?.FileQuery - (param?.TextDocument, (file, c) => file.CodeActions(c, param?.Range, param.Context), suppressExceptionLogging: true); + this.Manager(param?.TextDocument?.Uri)?.FileQuery( + param?.TextDocument, (file, c) => file.CodeActions(c, param?.Range, param.Context), suppressExceptionLogging: true); /// /// Returns a list of suggested completion items for the given location. @@ -996,24 +1213,31 @@ public CompletionItem ResolveCompletion(CompletionItem item, CompletionItemData (_, compilation) => compilation.ResolveCompletion(item, data, format), suppressExceptionLogging: true); - // routines related to querying the state of the project manager // -> these routines will wait for any processing to finish before executing the query /// /// Returns a copy of the current diagnostics generated upon loading. - /// Note: this method waits for all currently running or queued tasks to finish + /// Note: this method waits for all currently running or queued tasks to finish /// before getting the project loading diagnostics by calling FlushAndExecute. /// public IEnumerable GetProjectDiagnostics(Uri projectId) { - if (projectId == null) return null; - this.Load.QueueForExecution(() => + if (projectId == null) { - if (!this.Projects.TryGetValue(projectId, out Project project)) return null; - return project.GetLoadDiagnostics().Diagnostics; + return null; } - , out IEnumerable diagnostics); + this.load.QueueForExecution( + () => + { + if (!this.projects.TryGetValue(projectId, out Project project)) + { + return null; + } + + return project.GetLoadDiagnostics().Diagnostics; + }, + out IEnumerable diagnostics); return diagnostics; } @@ -1022,76 +1246,96 @@ public IEnumerable GetProjectDiagnostics(Uri projectId) /// returns the diagnostics for all source files in that project, but *not* the diagnostics generated upon loading. /// If the given Uri corresponds to a source file in any of the managed projects (including in the DefaultManager), /// returns an array with a single item containing all current diagnostics for the given file. - /// If the given Uri is null, returns the diagnostics for all source files in the default manager. - /// Note: this method waits for all currently running or queued tasks to finish + /// If the given Uri is null, returns the diagnostics for all source files in the default manager. + /// Note: this method waits for all currently running or queued tasks to finish /// before accumulating the diagnostics by calling FlushAndExecute. /// public PublishDiagnosticParams[] GetDiagnostics(Uri file) { - this.Load.QueueForExecution(() => - { - if (file == null) return DefaultManager.GetDiagnostics(); - if (this.Projects.TryGetValue(file, out Project project)) - { return project.Manager?.GetDiagnostics(); } - - // NOTE: the call below prevents any consolidating of the processing queues - // of the project manager and the compilation unit manager (dead locks)! - var manager = this.Manager(file); - return manager?.GetDiagnostics(new TextDocumentIdentifier { Uri = file }); - }, - out PublishDiagnosticParams[] diagnostics); + this.load.QueueForExecution( + () => + { + if (file == null) + { + return this.defaultManager.GetDiagnostics(); + } + if (this.projects.TryGetValue(file, out Project project)) + { + return project.Manager?.GetDiagnostics(); + } + + // NOTE: the call below prevents any consolidating of the processing queues + // of the project manager and the compilation unit manager (dead locks)! + var manager = this.Manager(file); + return manager?.GetDiagnostics(new TextDocumentIdentifier { Uri = file }); + }, + out PublishDiagnosticParams[] diagnostics); return diagnostics; } /// - /// Returns the content (text representation) of the given file, + /// Returns the content (text representation) of the given file, /// if it is listed as source of a project or in the default manager. - /// Returns null if the given file is null. - /// Note: this method waits for all currently running or queued tasks to finish + /// Returns null if the given file is null. + /// Note: this method waits for all currently running or queued tasks to finish /// before getting the file content by calling FlushAndExecute. /// public string[] FileContentInMemory(TextDocumentIdentifier textDocument) { - if (textDocument?.Uri == null) return null; - this.Load.QueueForExecution(() => + if (textDocument?.Uri == null) { - // NOTE: the call below prevents any consolidating of the processing queues - // of the project manager and the compilation unit manager (dead locks)! - var manager = this.Manager(textDocument.Uri); - return manager?.FileContentInMemory(textDocument); + return null; } - , out string[] content); + this.load.QueueForExecution( + () => + { + // NOTE: the call below prevents any consolidating of the processing queues + // of the project manager and the compilation unit manager (dead locks)! + var manager = this.Manager(textDocument.Uri); + return manager?.FileContentInMemory(textDocument); + }, + out string[] content); return content; } - // static routines related to loading the content needed for compilation public static string MessageSource(Uri uri) => - CompilationUnitManager.TryGetFileId(uri, out NonNullable source) ? source.Value + CompilationUnitManager.TryGetFileId(uri, out NonNullable source) ? source.Value : uri != null ? uri.AbsolutePath : throw new ArgumentNullException(nameof(uri)); /// - /// For the given sequence of file names verifies that a file with the corresponding full path exists, + /// For the given sequence of file names verifies that a file with the corresponding full path exists, /// and returns a sequence containing the absolute path for all files that do. /// Sets the corresponding out parameter to a sequence with all duplicate file names, - /// and to a sequence of names for which no such file exists respectively. - /// Filters all file names that are null or only consist of whitespace. - /// Generates suitable diagnostics for duplicate and not found files, and for invalid paths. + /// and to a sequence of names for which no such file exists respectively. + /// Filters all file names that are null or only consist of whitespace. + /// Generates suitable diagnostics for duplicate and not found files, and for invalid paths. /// Logs the generated diagnostics using onDiagnostics if the action has been specified and is not null. /// Catches exceptions related to path errors and logs them using onException if the action has been specified and is not null. - /// Throws an ArgumentNullException if the given sequence of files is null. + /// Throws an ArgumentNullException if the given sequence of files is null. /// - public static IEnumerable FilterFiles(IEnumerable files, - WarningCode duplicateFileWarning, Func FileNotFoundDiagnostic, - out IEnumerable notFound, out IEnumerable duplicates, out IEnumerable<(string, Exception)> invalidPaths, - Action onDiagnostic = null, Action onException = null) + public static IEnumerable FilterFiles( + IEnumerable files, + WarningCode duplicateFileWarning, + Func fileNotFoundDiagnostic, + out IEnumerable notFound, + out IEnumerable duplicates, + out IEnumerable<(string, Exception)> invalidPaths, + Action onDiagnostic = null, + Action onException = null) { - if (files == null) { throw new ArgumentNullException(nameof(files)); } + if (files == null) + { + throw new ArgumentNullException(nameof(files)); + } var exceptions = new List<(string, Exception)>(); Uri WithFullPath(string file) { - try { return new Uri(Path.GetFullPath(file)); } + try + { + return new Uri(Path.GetFullPath(file)); + } catch (Exception ex) { exceptions.Add((file, ex)); @@ -1100,7 +1344,7 @@ Uri WithFullPath(string file) } var uris = files - .Where(file => !String.IsNullOrWhiteSpace(file)) + .Where(file => !string.IsNullOrWhiteSpace(file)) .Select(WithFullPath) .Where(file => file != null).ToList(); invalidPaths = exceptions.ToImmutableArray(); @@ -1108,8 +1352,14 @@ Uri WithFullPath(string file) var distinctSources = new HashSet(uris); notFound = distinctSources.Where(f => !File.Exists(f.LocalPath)).ToImmutableArray(); - foreach (var file in duplicates) onDiagnostic?.Invoke(Warnings.LoadWarning(duplicateFileWarning, new[] { file.LocalPath }, MessageSource(file))); - foreach (var file in notFound) onDiagnostic?.Invoke(FileNotFoundDiagnostic(file.LocalPath, MessageSource(file))); + foreach (var file in duplicates) + { + onDiagnostic?.Invoke(Warnings.LoadWarning(duplicateFileWarning, new[] { file.LocalPath }, MessageSource(file))); + } + foreach (var file in notFound) + { + onDiagnostic?.Invoke(fileNotFoundDiagnostic(file.LocalPath, MessageSource(file))); + } foreach (var (file, ex) in invalidPaths) { onDiagnostic?.Invoke(Errors.LoadError(ErrorCode.InvalidFilePath, new[] { file }, file)); @@ -1120,31 +1370,45 @@ Uri WithFullPath(string file) /// /// Uses FilterFiles to filter the source files specified in the given options (Input), and generates the corresponding errors and warnings. - /// For each valid source file, generates the corrsponding TextDocumentIdentifier and reads the file content from disk. + /// For each valid source file, generates the corrsponding TextDocumentIdentifier and reads the file content from disk. /// Generates a suitable error whenever the file content could not be loaded. /// Calls the given onDiagnostic action on all generated diagnostics. /// Returns the uri and file content for each file that could be loaded. /// Throws an ArgumentNullException if the given sequence of source files is null. /// - public static ImmutableDictionary LoadSourceFiles(IEnumerable sourceFiles, - Action onDiagnostic = null, Action onException = null) + public static ImmutableDictionary LoadSourceFiles( + IEnumerable sourceFiles, + Action onDiagnostic = null, + Action onException = null) { - if (sourceFiles == null) throw new ArgumentNullException(nameof(sourceFiles)); + if (sourceFiles == null) + { + throw new ArgumentNullException(nameof(sourceFiles)); + } string GetFileContent(Uri file) { - try { return File.ReadAllText(file.LocalPath); } + try + { + return File.ReadAllText(file.LocalPath); + } catch (Exception ex) { - onDiagnostic?.Invoke(Errors.LoadError(ErrorCode.CouldNotLoadSourceFile, new[] { file.LocalPath }, MessageSource(file))); + onDiagnostic?.Invoke(Errors.LoadError(ErrorCode.CouldNotLoadSourceFile, new[] { file.LocalPath }, MessageSource(file))); onException?.Invoke(ex); return null; } } static Diagnostic NotFoundDiagnostic(string notFound, string source) => Errors.LoadError(ErrorCode.UnknownSourceFile, new[] { notFound }, source); - var found = FilterFiles(sourceFiles, WarningCode.DuplicateSourceFile, NotFoundDiagnostic, - out IEnumerable notFound, out IEnumerable duplicates, out IEnumerable<(string, Exception)> invalidPaths, - onDiagnostic, onException); + var found = FilterFiles( + sourceFiles, + WarningCode.DuplicateSourceFile, + NotFoundDiagnostic, + out IEnumerable notFound, + out IEnumerable duplicates, + out IEnumerable<(string, Exception)> invalidPaths, + onDiagnostic, + onException); return found .Select(file => (file, GetFileContent(file))) .Where(source => source.Item2 != null) @@ -1156,18 +1420,33 @@ string GetFileContent(Uri file) /// Generates suitable diagostics if the specified assembly could not be found or its content could not be loaded, /// and calls the given onDiagnostic action on all generated diagnostics. /// Catches any thrown exception and calls onException on it if it is specified and not null. - /// Throws an ArgumentNullException if the given uri is null. + /// Throws an ArgumentNullException if the given uri is null. /// - private static References.Headers LoadReferencedDll(Uri asm, bool ignoreDllResources, - Action onDiagnostic = null, Action onException = null) + private static References.Headers LoadReferencedDll( + Uri asm, + bool ignoreDllResources, + Action onDiagnostic = null, + Action onException = null) { - if (asm == null) throw new ArgumentNullException(nameof(asm)); + if (asm == null) + { + throw new ArgumentNullException(nameof(asm)); + } try { - try { AssemblyName.GetAssemblyName(asm.LocalPath); } // will throw if the file is not a valid assembly - catch (FileLoadException) { } // the file is already loaded -> we can ignore that one + try + { + // will throw if the file is not a valid assembly + AssemblyName.GetAssemblyName(asm.LocalPath); + } + catch (FileLoadException) + { + // the file is already loaded -> we can ignore that one + } if (!AssemblyLoader.LoadReferencedAssembly(asm, out var headers, ignoreDllResources)) - { onDiagnostic?.Invoke(Errors.LoadError(ErrorCode.UnrecognizedContentInReference, new[] { asm.LocalPath }, MessageSource(asm))); } + { + onDiagnostic?.Invoke(Errors.LoadError(ErrorCode.UnrecognizedContentInReference, new[] { asm.LocalPath }, MessageSource(asm))); + } return headers; } catch (BadImageFormatException ex) @@ -1185,41 +1464,58 @@ private static References.Headers LoadReferencedDll(Uri asm, bool ignoreDllResou } /// - /// Returns the file id used for the file with the given uri. - /// Raises a QsCompilerError if the id could not be determined. + /// Returns the file id used for the file with the given uri. + /// Raises a QsCompilerError if the id could not be determined. /// - private static NonNullable GetFileId(Uri uri) => - NonNullable.New(QsCompilerError.RaiseOnFailure(() => - CompilationUnitManager.TryGetFileId(uri, out var id) ? id.Value : throw new InvalidOperationException(), + private static NonNullable GetFileId(Uri uri) => + NonNullable.New(QsCompilerError.RaiseOnFailure( + () => CompilationUnitManager.TryGetFileId(uri, out var id) ? id.Value : throw new InvalidOperationException(), "could not determine id for valid uri")); /// /// Uses FilterFiles to filter the given project files, and generates the corresponding errors and warnings. /// For each existing project file, calls GetOutputPath on it to obtain the path to the built dll for the project. - /// For any exception due to a failure of GetOutputPath the given onException action is invoked. - /// A failure of GetOutputPath consists of it throwing an exception, or returning a path that does exist but not correspond to a valid dll. + /// For any exception due to a failure of GetOutputPath the given onException action is invoked. + /// A failure of GetOutputPath consists of it throwing an exception, or returning a path that does exist but not correspond to a valid dll. /// If no file exists at the returned path, generates a suitable error message. /// Calls the given onDiagnostic action on all generated diagnostics. - /// Returns a dictionary that maps each project file for which the corresponding dll content could be loaded to the Q# attributes it contains. + /// Returns a dictionary that maps each project file for which the corresponding dll content could be loaded to the Q# attributes it contains. /// Throws an ArgumentNullException if the given sequence of project references or the given GetOutputPath function is null. /// public static ImmutableDictionary, References.Headers> LoadProjectReferences( - IEnumerable refProjectFiles, Func GetOutputPath, - Action onDiagnostic = null, Action onException = null) + IEnumerable refProjectFiles, + Func getOutputPath, + Action onDiagnostic = null, + Action onException = null) { - if (refProjectFiles == null) throw new ArgumentNullException(nameof(refProjectFiles)); - if (GetOutputPath == null) throw new ArgumentNullException(nameof(GetOutputPath)); + if (refProjectFiles == null) + { + throw new ArgumentNullException(nameof(refProjectFiles)); + } + if (getOutputPath == null) + { + throw new ArgumentNullException(nameof(getOutputPath)); + } References.Headers LoadReferencedDll(Uri asm) => ProjectManager.LoadReferencedDll(asm, false, onException: onException); // any exception here is really a failure of GetOutputPath and will be treated as an unexpected exception static Diagnostic NotFoundDiagnostic(string notFound, string source) => Errors.LoadError(ErrorCode.UnknownProjectReference, new[] { notFound }, source); - var existingProjectFiles = FilterFiles(refProjectFiles, WarningCode.DuplicateProjectReference, NotFoundDiagnostic, - out IEnumerable notFound, out IEnumerable duplicates, out IEnumerable<(string, Exception)> invalidPaths, - onDiagnostic, onException); + var existingProjectFiles = FilterFiles( + refProjectFiles, + WarningCode.DuplicateProjectReference, + NotFoundDiagnostic, + out IEnumerable notFound, + out IEnumerable duplicates, + out IEnumerable<(string, Exception)> invalidPaths, + onDiagnostic, + onException); Uri TryGetOutputPath(Uri projFile) { - try { return GetOutputPath(projFile); } + try + { + return getOutputPath(projFile); + } catch (Exception ex) { onException?.Invoke(ex); @@ -1232,7 +1528,9 @@ Uri TryGetOutputPath(Uri projFile) .ToImmutableDictionary(p => p.Item2, p => p.Item1); // FIXME: take care of different projects having the same output path... var (existingProjectDlls, missingDlls) = projectDlls.Keys.Partition(f => File.Exists(f.LocalPath)); foreach (var projFile in missingDlls.Select(dll => projectDlls[dll])) - { onDiagnostic?.Invoke(Errors.LoadError(ErrorCode.MissingProjectReferenceDll, new[] { projFile.LocalPath }, MessageSource(projFile))); } + { + onDiagnostic?.Invoke(Errors.LoadError(ErrorCode.MissingProjectReferenceDll, new[] { projFile.LocalPath }, MessageSource(projFile))); + } return existingProjectDlls .Select(file => (file, LoadReferencedDll(file))) @@ -1243,23 +1541,35 @@ Uri TryGetOutputPath(Uri projFile) /// /// Uses FilterFiles to filter the given references binary files, and generates the corresponding errors and warnings. /// Ignores any binary files that contain mscorlib.dll or a similar variant in their name. - /// Generates a suitable error message for each binary file that could not be loaded. + /// Generates a suitable error message for each binary file that could not be loaded. /// Calls the given onDiagnostic action on all generated diagnostics. - /// Returns a dictionary that maps each existing dll to the Q# attributes it contains. + /// Returns a dictionary that maps each existing dll to the Q# attributes it contains. /// Throws an ArgumentNullException if the given sequence of referenced binaries is null. /// - public static ImmutableDictionary, References.Headers> LoadReferencedAssemblies(IEnumerable references, - Action onDiagnostic = null, Action onException = null, bool ignoreDllResources = false) + public static ImmutableDictionary, References.Headers> LoadReferencedAssemblies( + IEnumerable references, + Action onDiagnostic = null, + Action onException = null, + bool ignoreDllResources = false) { - if (references == null) throw new ArgumentNullException(nameof(references)); + if (references == null) + { + throw new ArgumentNullException(nameof(references)); + } References.Headers LoadReferencedDll(Uri asm) => ProjectManager.LoadReferencedDll(asm, ignoreDllResources, onDiagnostic, onException); var relevant = references.Where(file => file.IndexOf("mscorlib.dll", StringComparison.InvariantCultureIgnoreCase) < 0); static Diagnostic NotFoundDiagnostic(string notFound, string source) => Warnings.LoadWarning(WarningCode.UnknownBinaryFile, new[] { notFound }, source); - var assembliesToLoad = FilterFiles(relevant, WarningCode.DuplicateBinaryFile, NotFoundDiagnostic, - out IEnumerable notFound, out IEnumerable duplicates, out IEnumerable<(string, Exception)> invalidPaths, - onDiagnostic, onException); + var assembliesToLoad = FilterFiles( + relevant, + WarningCode.DuplicateBinaryFile, + NotFoundDiagnostic, + out IEnumerable notFound, + out IEnumerable duplicates, + out IEnumerable<(string, Exception)> invalidPaths, + onDiagnostic, + onException); return assembliesToLoad .Select(file => (file, LoadReferencedDll(file))) diff --git a/src/QsCompiler/CompilationManager/Properties/AssemblyInfo.cs b/src/QsCompiler/CompilationManager/Properties/AssemblyInfo.cs index 08ea392bdb..a12ed9564a 100644 --- a/src/QsCompiler/CompilationManager/Properties/AssemblyInfo.cs +++ b/src/QsCompiler/CompilationManager/Properties/AssemblyInfo.cs @@ -3,7 +3,6 @@ using System.Runtime.CompilerServices; - // Allow the test assembly to use our internal methods -[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsCompiler" + SigningConstants.PUBLIC_KEY)] -[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsLanguageServer" + SigningConstants.PUBLIC_KEY)] +[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsCompiler" + SigningConstants.PublicKey)] +[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsLanguageServer" + SigningConstants.PublicKey)] diff --git a/src/QsCompiler/CompilationManager/ScopeTracking.cs b/src/QsCompiler/CompilationManager/ScopeTracking.cs index bc670434fa..323ab30e4a 100644 --- a/src/QsCompiler/CompilationManager/ScopeTracking.cs +++ b/src/QsCompiler/CompilationManager/ScopeTracking.cs @@ -7,7 +7,6 @@ using Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures; using Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { public static class ScopeTracking @@ -21,16 +20,31 @@ public static class ScopeTracking /// internal static void VerifyStringDelimiters(string text, IEnumerable delimiters) { - if (text == null) throw new ArgumentNullException(nameof(text)); - if (delimiters == null) throw new ArgumentNullException(nameof(delimiters)); + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } + if (delimiters == null) + { + throw new ArgumentNullException(nameof(delimiters)); + } var last = -2; foreach (int delim in delimiters) { - if (delim <= last) throw new ArgumentException($"the string delimiters need to be positive and sorted in ascending order"); + if (delim <= last) + { + throw new ArgumentException($"the string delimiters need to be positive and sorted in ascending order"); + } last = delim; } - if (last > text.Length) throw new ArgumentException("out of range string delimiter"); - if ((delimiters.Count() & 1) != 0) throw new ArgumentException("expecting an even number of string delimiters"); + if (last > text.Length) + { + throw new ArgumentException("out of range string delimiter"); + } + if ((delimiters.Count() & 1) != 0) + { + throw new ArgumentException("expecting an even number of string delimiters"); + } } /// @@ -41,38 +55,61 @@ internal static void VerifyStringDelimiters(string text, IEnumerable delimi /// internal static void VerifyExcessBracketPositions(CodeLine line, IEnumerable positions) { - if (line == null) throw new ArgumentNullException(nameof(line)); - if (positions == null) throw new ArgumentNullException(nameof(positions)); + if (line == null) + { + throw new ArgumentNullException(nameof(line)); + } + if (positions == null) + { + throw new ArgumentNullException(nameof(positions)); + } var last = -1; foreach (var pos in positions) { - if (pos <= last) throw new ArgumentException($"the excess bracket positions need to be sorted in ascending order"); + if (pos <= last) + { + throw new ArgumentException($"the excess bracket positions need to be sorted in ascending order"); + } last = pos; - if (IndexExcludingStrings(pos, line.StringDelimiters) < 0) throw new ArgumentException($"position for excess bracket is within a string"); + if (IndexExcludingStrings(pos, line.StringDelimiters) < 0) + { + throw new ArgumentException($"position for excess bracket is within a string"); + } + } + if (last >= line.WithoutEnding.Length) + { + throw new ArgumentException("out of range excess bracket position"); } - if (last >= line.WithoutEnding.Length) throw new ArgumentException("out of range excess bracket position"); } /// - /// Computes the updated code line based on the given previous line predecessing it, + /// Computes the updated code line based on the given previous line predecessing it, /// and compares its indentation with the current line at continueAt in the given file. - /// Returns the difference of the new indentation and the current one. + /// Returns the difference of the new indentation and the current one. /// Throws an ArgumentNullException if file is null. /// Throws an ArgumentOutOfRangeException if the given index to continue at is less than zero or more than the number of lines in the given file. /// internal static int GetIndentationChange(FileContentManager file, int continueAt, CodeLine previous) // previous: last element before the one at continueAt { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (continueAt < 0 || continueAt > file.NrLines()) throw new ArgumentOutOfRangeException(nameof(continueAt)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (continueAt < 0 || continueAt > file.NrLines()) + { + throw new ArgumentOutOfRangeException(nameof(continueAt)); + } - if (continueAt == file.NrLines()) return 0; + if (continueAt == file.NrLines()) + { + return 0; + } var continuation = file.GetLine(continueAt); var updatedContinuation = ComputeCodeLines(new string[] { continuation.Text }, previous).Single(); return updatedContinuation.Indentation - continuation.Indentation; } - // private utils for computing text updates /// @@ -81,7 +118,10 @@ internal static int GetIndentationChange(FileContentManager file, int continueAt /// private static bool ContinueString(CodeLine line) { - if (line == null) return false; + if (line == null) + { + return false; + } var delimiters = line.StringDelimiters; return delimiters.Count() != 0 && delimiters.Last() == line.Text.Length; } @@ -92,7 +132,10 @@ private static bool ContinueString(CodeLine line) /// private static IEnumerable ComputeStringDelimiters(string text, bool isContinuation) { - if (text == null) throw new ArgumentNullException(nameof(text)); + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } var nrDelimiters = 0; if (isContinuation) @@ -101,36 +144,50 @@ private static IEnumerable ComputeStringDelimiters(string text, bool isCont yield return -1; } var stringLength = text.Length; - while (text != String.Empty) + while (text != string.Empty) { var commentIndex = (nrDelimiters & 1) == 0 ? text.IndexOf("//") : -1; // only if we are currently not inside a string do we need to check for a potential comment start - if (commentIndex < 0) commentIndex = text.Length; + if (commentIndex < 0) + { + commentIndex = text.Length; + } var index = text.IndexOf('"'); - if ((nrDelimiters & 1) != 0) // if we are currently within a string, we need to ignore string delimiters of the form \" + if ((nrDelimiters & 1) != 0) { - while (index > 0 && text[index - 1] == '\\') { + // if we are currently within a string, we need to ignore string delimiters of the form \" + while (index > 0 && text[index - 1] == '\\') + { var next = text.Substring(index + 1).IndexOf('"'); index = next < 0 ? next : index + 1 + next; } } - if (commentIndex < index) break; // fine also if index = -1 - if (index < 0) text = String.Empty; - else + if (commentIndex < index) + { + break; // fine also if index = -1 + } + if (index < 0) { - ++nrDelimiters; + text = string.Empty; + } + else + { + ++nrDelimiters; yield return index + stringLength - text.Length; text = text.Substring(index + 1); } } - if ((nrDelimiters & 1) != 0) yield return stringLength; + if ((nrDelimiters & 1) != 0) + { + yield return stringLength; + } } - // utils related to filtering irrelevant text for scope and error processing, and parsing private static int StartDelimiter(int delimiter) => delimiter; // used to make sure RemoveStrings and IndexInFullString are in sync + private static int EndDelimiter(int delimiter) => delimiter + 1; /// @@ -140,7 +197,10 @@ private static IEnumerable ComputeStringDelimiters(string text, bool isCont /// private static int IndexExcludingStrings(int indexInFullText, IEnumerable delimiters) { - if (delimiters == null) throw new ArgumentNullException(nameof(delimiters)); + if (delimiters == null) + { + throw new ArgumentNullException(nameof(delimiters)); + } var iter = delimiters.GetEnumerator(); var index = indexInFullText; int GetStart(int pos) => pos < 0 ? 0 : StartDelimiter(pos); @@ -151,7 +211,11 @@ private static int IndexExcludingStrings(int indexInFullText, IEnumerable d index += GetStart(iter.Current); iter.MoveNext(); index -= EndDelimiter(iter.Current); // note: it should not be possible, that we ever have the case iter.Current == text.Length (unless indexInFullText and delimiters mismatch...) - if (indexInFullText <= iter.Current) return -1; // iter.Current, not EndDelimiter(iter.Current) is always correct + if (indexInFullText <= iter.Current) + { + // iter.Current, not EndDelimiter(iter.Current) is always correct + return -1; + } } return index; } @@ -163,16 +227,31 @@ private static int IndexExcludingStrings(int indexInFullText, IEnumerable d /// private static int IndexInRelevantCode(int indexInFullText, CodeLine line) { - if (line == null) throw new ArgumentNullException(nameof(line)); - if (indexInFullText < 0 || line.WithoutEnding.Length <= indexInFullText) return -1; + if (line == null) + { + throw new ArgumentNullException(nameof(line)); + } + if (indexInFullText < 0 || line.WithoutEnding.Length <= indexInFullText) + { + return -1; + } - var index = IndexExcludingStrings(indexInFullText, line.StringDelimiters); - if (index < 0) return index; // saving the trouble of computing the loop below + var index = IndexExcludingStrings(indexInFullText, line.StringDelimiters); + if (index < 0) + { + return index; // saving the trouble of computing the loop below + } foreach (var pos in line.ExcessBracketPositions.Reverse().Select(p => IndexExcludingStrings(p, line.StringDelimiters))) { - if (pos == index) return -1; - if (pos < index) --index; + if (pos == index) + { + return -1; + } + if (pos < index) + { + --index; + } } return index; } @@ -183,7 +262,10 @@ private static int IndexInRelevantCode(int indexInFullText, CodeLine line) /// private static int IndexIncludingStrings(int indexInTrimmed, IEnumerable delimiters) { - if (delimiters == null) throw new ArgumentNullException(nameof(delimiters)); + if (delimiters == null) + { + throw new ArgumentNullException(nameof(delimiters)); + } var iter = delimiters.GetEnumerator(); var index = indexInTrimmed; int GetStart(int pos) => pos < 0 ? 0 : StartDelimiter(pos); @@ -205,8 +287,17 @@ private static int IndexIncludingStrings(int indexInTrimmed, IEnumerable de private static int IndexInFullString(int indexInTrimmed, CodeLine line) { var index = IndexIncludingStrings(indexInTrimmed, line?.StringDelimiters); - foreach (var pos in line.ExcessBracketPositions) if (pos <= index) ++index; - if (index >= line.WithoutEnding.Length) throw new ArgumentException("mismatch between the given index in the relevant code and the given line"); + foreach (var pos in line.ExcessBracketPositions) + { + if (pos <= index) + { + ++index; + } + } + if (index >= line.WithoutEnding.Length) + { + throw new ArgumentException("mismatch between the given index in the relevant code and the given line"); + } return index; } @@ -219,11 +310,11 @@ private static string RemoveStrings(string text, IEnumerable stringDelimite var iter = stringDelimiters.GetEnumerator(); var trimmed = - iter.MoveNext() ? - iter.Current < 0 ? String.Empty : text.Substring(0, StartDelimiter(iter.Current)) : + iter.MoveNext() ? + iter.Current < 0 ? string.Empty : text.Substring(0, StartDelimiter(iter.Current)) : text; - while(iter.MoveNext() && iter.Current < text.Length) + while (iter.MoveNext() && iter.Current < text.Length) { // Note: if modifications here are needed, modify Start- and EndDelimiter to make sure these changes are reflected in IndexInFullString var end = iter.Current == text.Length ? text.Length : EndDelimiter(iter.Current); // end of a substring @@ -237,7 +328,9 @@ private static string RemoveStrings(string text, IEnumerable stringDelimite /// Strips the text of all strings and a potential end of line comment. /// private static string RemoveStringsAndComment(CodeLine line) - { return RemoveStrings(line.WithoutEnding + line.LineEnding, line.StringDelimiters); } + { + return RemoveStrings(line.WithoutEnding + line.LineEnding, line.StringDelimiters); + } /// /// Strips the text of all strings, excess closing brackets, and a potential end of line comment. @@ -247,11 +340,12 @@ private static string RelevantCode(CodeLine line) var delimiters = line?.StringDelimiters; var stripped = RemoveStringsAndComment(line); // will raise an exception if line is null foreach (var index in line.ExcessBracketPositions.Reverse().Select(pos => IndexExcludingStrings(pos, delimiters))) - { stripped = stripped.Remove(index, 1); } + { + stripped = stripped.Remove(index, 1); + } return stripped; } - // utils called upon language processing to get suitable substrings to parse, and related subroutines /// @@ -261,19 +355,36 @@ private static string RelevantCode(CodeLine line) /// private static IEnumerable TruncateStringDelimiters(IEnumerable delimiters, int start, int count) { - if (start < 0) throw new ArgumentOutOfRangeException(nameof(start)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (delimiters == null) throw new ArgumentNullException(nameof(delimiters)); + if (start < 0) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + if (delimiters == null) + { + throw new ArgumentNullException(nameof(delimiters)); + } var nrDelim = 0; delimiters = delimiters.SkipWhile(delim => delim < start && ++nrDelim > 0); - if (delimiters.Any() && (nrDelim & 1) != 0) yield return -1; + if (delimiters.Any() && (nrDelim & 1) != 0) + { + yield return -1; + } var iter = delimiters.TakeWhile(delim => delim < start + count).GetEnumerator(); for (nrDelim = 0; iter.MoveNext(); ++nrDelim) - { yield return iter.Current - start; } + { + yield return iter.Current - start; + } - if ((nrDelim & 1) != 0) yield return count; + if ((nrDelim & 1) != 0) + { + yield return count; + } } /// @@ -284,57 +395,75 @@ private static IEnumerable TruncateStringDelimiters(IEnumerable delimi /// Throws an ArgumentOutOfRangeException if start and count do not define a valid range in the text of the given line. /// Throws an ArgumentNullException if any of the given arguments is null. /// - internal static int FindInCode(this CodeLine line, Func FindIndex, bool ignoreExcessBrackets = true) + internal static int FindInCode(this CodeLine line, Func findIndex, bool ignoreExcessBrackets = true) { - if (FindIndex == null) throw new ArgumentNullException(nameof(FindIndex)); - if (ignoreExcessBrackets) return IndexInFullString(FindIndex(RelevantCode(line)), line); // fine also for index = -1 - else return IndexIncludingStrings(FindIndex(RemoveStringsAndComment(line)), line.StringDelimiters); + if (findIndex == null) + { + throw new ArgumentNullException(nameof(findIndex)); + } + if (ignoreExcessBrackets) + { + return IndexInFullString(findIndex(RelevantCode(line)), line); // fine also for index = -1 + } + else + { + return IndexIncludingStrings(findIndex(RemoveStringsAndComment(line)), line.StringDelimiters); + } } /// - /// Returns the result of FindIndex applied to the text on the substring of length count starting at start + /// Returns the result of FindIndex applied to the text on the substring of length count starting at start /// when ignoring end of line comments, content within strings, and - if ingoreExcessBrackets is set - excessive closing brackets. /// Important: This function returns the index relative to the original text, not the substring. /// If the value returned by FindIndex is smaller than zero it is returned unchanged. /// Throws an ArgumentOutOfRangeException if start and count do not define a valid range in the text of the given line. /// Throws an ArgumentNullException if any of the given arguments is null. /// - internal static int FindInCode(this CodeLine line, Func FindIndex, int start, int count, bool ignoreExcessBrackets = true) + internal static int FindInCode(this CodeLine line, Func findIndex, int start, int count, bool ignoreExcessBrackets = true) { var truncatedDelims = TruncateStringDelimiters(line?.StringDelimiters, start, count); // TruncateStringDelimiters will throw if line is null var truncatedText = line.Text.Substring(start, count); // will throw if start and count are out of range - var truncatedExcessClosings = + var truncatedExcessClosings = line.ExcessBracketPositions .Where(pos => start <= pos && pos < start + count) .Select(pos => pos - start); var shiftedCommentIndex = line.WithoutEnding.Length - start; var truncatedLine = new CodeLine(truncatedText, truncatedDelims, shiftedCommentIndex < count ? shiftedCommentIndex : count, 0, truncatedExcessClosings); // line indentation is irrelevant here - var foundIndex = FindInCode(truncatedLine, FindIndex, ignoreExcessBrackets); + var foundIndex = FindInCode(truncatedLine, findIndex, ignoreExcessBrackets); return foundIndex < 0 ? foundIndex : start + foundIndex; } /// - /// Givent a position, verifies that the position is within the given file, and - /// returns the effective indentation (i.e. the indentation when ignoring excess brackets throughout the file) + /// Givent a position, verifies that the position is within the given file, and + /// returns the effective indentation (i.e. the indentation when ignoring excess brackets throughout the file) /// at that position (i.e. not including the char at the given position). /// internal static int IndentationAt(this FileContentManager file, Position pos) { - if (!Utils.IsValidPosition(pos, file)) throw new ArgumentException("given position is not within file"); + if (!Utils.IsValidPosition(pos, file)) + { + throw new ArgumentException("given position is not within file"); + } var line = file.GetLine(pos.Line); var index = pos.Character; // check if the given position is within a string or a comment, or denotes an excess bracket, // and find the next closest position that isn't - if (index >= line.WithoutEnding.Length) return line.FinalIndentation(); // not necessary, but saves doing the rest of the computation + if (index >= line.WithoutEnding.Length) + { + return line.FinalIndentation(); // not necessary, but saves doing the rest of the computation + } index = line.FindInCode(trimmed => trimmed.Length - 1, 0, pos.Character); // if the given position is within a string, then this is the most convenient way to get the closest position that isn't.. - if (index < 0) return line.Indentation; // perfectly valid scenario (if there is no relevant code before the given position) + if (index < 0) + { + return line.Indentation; // perfectly valid scenario (if there is no relevant code before the given position) + } // check how much the indentation changes in all valid (i.e. non-comment, non-string, non-excess-brackets) code up to that point // NOTE: per specification, this routine returns the indentation not incuding the char at the given position, // but if this char was within non-code text, then we need to include this char ... see (*) - var indexInCode = IndexInRelevantCode(index, line); + var indexInCode = IndexInRelevantCode(index, line); QsCompilerError.Verify(indexInCode >= 0, "index in code should be positive"); var code = RelevantCode(line).Substring(0, index < pos.Character ? indexInCode + 1 : indexInCode); // (*) - yep, that's a bit awkward, but the cleanest I can come up with right now var indentation = line.Indentation + NrIndents(code) - NrUnindents(code); @@ -342,19 +471,24 @@ internal static int IndentationAt(this FileContentManager file, Position pos) return indentation; } - // utils for computing indentations and excess closings private static int NrIndents(string text) { - if (text == null) throw new ArgumentNullException(nameof(text)); - return (text.Length - text.Replace("{", "").Length); + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } + return text.Length - text.Replace("{", "").Length; } private static int NrUnindents(string text) { - if (text == null) throw new ArgumentNullException(nameof(text)); - return (text.Length - text.Replace("}", "").Length); + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } + return text.Length - text.Replace("}", "").Length; } /// @@ -368,7 +502,7 @@ private static int FinalIndentation(this CodeLine line) } /// - /// Computes the number of excess brackets on the given line based in the given line number + /// Computes the number of excess brackets on the given line based in the given line number /// and the list of lines containing the excess closings before that line. /// Throws an ArgumentNullException if any of the arguments is null. /// @@ -376,17 +510,25 @@ private static int[] ComputeExcessClosings(CodeLine line, int effectiveIndent) { var additionalExcessClosings = new List(); var relevantText = RemoveStringsAndComment(line); - for (var unprocessed = relevantText; unprocessed != String.Empty; ) + for (var unprocessed = relevantText; unprocessed != string.Empty;) { var nextOpen = unprocessed.IndexOf('{'); - if (nextOpen < 0) nextOpen = unprocessed.Length; + if (nextOpen < 0) + { + nextOpen = unprocessed.Length; + } var nextClose = unprocessed.IndexOf('}'); - if (nextClose < 0) nextClose = unprocessed.Length; + if (nextClose < 0) + { + nextClose = unprocessed.Length; + } if (nextClose < nextOpen) { if (--effectiveIndent + additionalExcessClosings.Count() < 0) - { additionalExcessClosings.Add(IndexIncludingStrings(nextClose + relevantText.Length - unprocessed.Length, line.StringDelimiters)); } + { + additionalExcessClosings.Add(IndexIncludingStrings(nextClose + relevantText.Length - unprocessed.Length, line.StringDelimiters)); + } unprocessed = unprocessed.Substring(nextClose + 1); } else if (nextOpen < nextClose) @@ -394,16 +536,18 @@ private static int[] ComputeExcessClosings(CodeLine line, int effectiveIndent) ++effectiveIndent; unprocessed = unprocessed.Substring(nextOpen + 1); } - else unprocessed = String.Empty; + else + { + unprocessed = string.Empty; + } } return additionalExcessClosings.ToArray(); } - // computing the objects needed to update the content in the editor state /// - /// Based on the previous line, initializes the new CodeLines for the given texts + /// Based on the previous line, initializes the new CodeLines for the given texts /// with suitable string delimiters and the correct end of line comment position, /// leaving the indentation at its default value and the excess brackets uncomputed. /// The previous line being null or not provided indicates that there is no previous line. @@ -411,7 +555,10 @@ private static int[] ComputeExcessClosings(CodeLine line, int effectiveIndent) /// private static IEnumerable InitializeCodeLines(IEnumerable texts, CodeLine previousLine = null) { - if (texts == null) throw new ArgumentNullException(nameof(texts)); + if (texts == null) + { + throw new ArgumentNullException(nameof(texts)); + } var continueString = ContinueString(previousLine); foreach (string text in texts) { @@ -419,7 +566,7 @@ private static IEnumerable InitializeCodeLines(IEnumerable tex var delimiters = ComputeStringDelimiters(text, continueString); var commentStart = IndexIncludingStrings(RemoveStrings(text, delimiters).IndexOf("//"), delimiters.ToArray()); - // initializes the code line with a default indentation of zero, that will be set to a suitable value during the computation of the excess brackets + // initializes the code line with a default indentation of zero, that will be set to a suitable value during the computation of the excess brackets var line = new CodeLine(text, delimiters, commentStart < 0 ? text.Length : commentStart); continueString = ContinueString(line); yield return line; @@ -428,13 +575,16 @@ private static IEnumerable InitializeCodeLines(IEnumerable tex /// /// Given the initial indentation of a sequence of CodeLines, and a sequence of code lines with the correct string delimiters set, - /// computes and sets the correct indentation level and excess bracket positions for each line. + /// computes and sets the correct indentation level and excess bracket positions for each line. /// The previous line being null or not provided indicates that there is no previous line. /// Throws an ArgumentNullExceptions if the given texts are null. /// private static IEnumerable SetIndentations(IEnumerable lines, int currentIndentation) { - if (lines == null) throw new ArgumentNullException(nameof(lines)); + if (lines == null) + { + throw new ArgumentNullException(nameof(lines)); + } foreach (var line in lines) { var updated = line.SetIndentation(currentIndentation); @@ -443,12 +593,17 @@ private static IEnumerable SetIndentations(IEnumerable lines var nrIndents = NrIndents(stripped); var nrUnindents = NrUnindents(stripped); - if (currentIndentation - nrUnindents < 0) // in this case it is possible (but not necessarily the case) that there are excess brackets - { // if closings occur before openings, then we can have excess brackets even when the indentation is larger than zero + if (currentIndentation - nrUnindents < 0) + { + // in this case it is possible (but not necessarily the case) that there are excess brackets + // if closings occur before openings, then we can have excess brackets even when the indentation is larger than zero updated = updated.SetExcessBrackets(ComputeExcessClosings(updated, currentIndentation)); currentIndentation += updated.ExcessBracketPositions.Count(); } - else updated = updated.SetExcessBrackets(new List().AsReadOnly()); + else + { + updated = updated.SetExcessBrackets(new List().AsReadOnly()); + } currentIndentation += nrIndents - nrUnindents; QsCompilerError.Verify(currentIndentation >= 0, "initial indentation should always be larger or equal to zero"); @@ -462,7 +617,9 @@ private static IEnumerable SetIndentations(IEnumerable lines /// Throws an ArgumentNullExceptions if the given texts are null. /// private static IEnumerable ComputeCodeLines(IEnumerable texts, CodeLine previousLine = null) - { return SetIndentations(InitializeCodeLines(texts, previousLine), previousLine == null ? 0 : previousLine.FinalIndentation()); } + { + return SetIndentations(InitializeCodeLines(texts, previousLine), previousLine == null ? 0 : previousLine.FinalIndentation()); + } /// /// Returns an enumerable sequence of new CodeLines when the initial indentation of the sequence is initialIndentation, @@ -470,7 +627,9 @@ private static IEnumerable ComputeCodeLines(IEnumerable texts, /// Throws an ArgumentNullException if the given sequence of code lines is null. /// private static List GetUpdatedLines(this IEnumerable lines, int initialIndentation) - { return SetIndentations(lines, initialIndentation).ToList(); } + { + return SetIndentations(lines, initialIndentation).ToList(); + } /// /// Computes the excess closing and scope error updates for the given replacements at the position specified by start and count in the given file. @@ -479,13 +638,28 @@ private static List GetUpdatedLines(this IEnumerable lines, /// Throws an ArgumentException if replacements does not at least contain one CodeLine. /// Throws an ArgumentOutOfRangeException if the range defined by start and count is not within the given file, where count needs to be at least one. /// - private static IEnumerable ComputeUpdates(FileContentManager file, int start, int count, CodeLine[] replacements) + private static IEnumerable ComputeUpdates(FileContentManager file, int start, int count, CodeLine[] replacements) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (replacements == null) throw new ArgumentNullException(nameof(replacements)); - if (start < 0 || start >= file.NrLines()) throw new ArgumentOutOfRangeException(nameof(start)); - if (count < 1 || start + count > file.NrLines()) throw new ArgumentOutOfRangeException(nameof(count)); - if (replacements.Length == 0) throw new ArgumentException("replacements cannot be empty"); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (replacements == null) + { + throw new ArgumentNullException(nameof(replacements)); + } + if (start < 0 || start >= file.NrLines()) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + if (count < 1 || start + count > file.NrLines()) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + if (replacements.Length == 0) + { + throw new ArgumentException("replacements cannot be empty"); + } var continueAtInFile = start + count; var remainingLines = file.GetLines(continueAtInFile, file.NrLines() - continueAtInFile); @@ -493,18 +667,24 @@ private static IEnumerable ComputeUpdates(FileContentManager file, int var indentationChange = GetIndentationChange(file, continueAtInFile, replacements.Last()); var requiresStringDelimiterUpdate = ContinueString(file.GetLine(continueAtInFile - 1)) ^ ContinueString(replacements.Last()); if (requiresStringDelimiterUpdate) - // we need to recompute everything if the interpretation of what is code and what is a string changes... - // since the interpretation of the remaining file changed, we need to update the entire file from start onwards - { return ComputeCodeLines(remainingLines.Select(line => line.Text), replacements.Last()).ToList(); } + { + // we need to recompute everything if the interpretation of what is code and what is a string changes... + // since the interpretation of the remaining file changed, we need to update the entire file from start onwards + return ComputeCodeLines(remainingLines.Select(line => line.Text), replacements.Last()).ToList(); + } else if (indentationChange != 0) - // if the replacements has more effective closing brackets (not just excess closings!) than the current part that will be replaced has, - // then we need check the text of the remaining file as well in order to compute the correct update - // if it has less (indentationChange > 0), then we could in principle simplify things somewhat by simply discarding the corresponding number of excess closing brackets - { return remainingLines.GetUpdatedLines(remainingLines.First().Indentation + indentationChange); } - else return null; + { + // if the replacements has more effective closing brackets (not just excess closings!) than the current part that will be replaced has, + // then we need check the text of the remaining file as well in order to compute the correct update + // if it has less (indentationChange > 0), then we could in principle simplify things somewhat by simply discarding the corresponding number of excess closing brackets + return remainingLines.GetUpdatedLines(remainingLines.First().Indentation + indentationChange); + } + else + { + return null; + } } - // routines used to compute scope diagnostics updates /// @@ -515,11 +695,23 @@ private static IEnumerable ComputeUpdates(FileContentManager file, int /// private static IEnumerable CheckForMissingClosings(this FileContentManager file) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (file.NrLines() == 0) throw new ArgumentException("the number of lines in a file can never be zero"); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (file.NrLines() == 0) + { + throw new ArgumentException("the number of lines in a file can never be zero"); + } var lastLine = file.GetLine(file.NrLines() - 1); - if (lastLine.FinalIndentation() > 0) yield return Errors.MissingClosingBracketError(file.FileName.Value, new Position (file.NrLines()-1, lastLine.Text.Length)); - if (ContinueString(lastLine)) yield return Errors.MissingStringDelimiterError(file.FileName.Value, new Position (file.NrLines() - 1, lastLine.Text.Length)); + if (lastLine.FinalIndentation() > 0) + { + yield return Errors.MissingClosingBracketError(file.FileName.Value, new Position(file.NrLines() - 1, lastLine.Text.Length)); + } + if (ContinueString(lastLine)) + { + yield return Errors.MissingStringDelimiterError(file.FileName.Value, new Position(file.NrLines() - 1, lastLine.Text.Length)); + } } /// @@ -529,10 +721,16 @@ private static IEnumerable CheckForMissingClosings(this FileContentM /// private static IEnumerable ComputeScopeDiagnostics(this FileContentManager file, int start, int count) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } foreach (var line in file.GetLines(start, count)) { - foreach (var pos in line.ExcessBracketPositions) yield return Errors.ExcessBracketError(file.FileName.Value, new Position (start, pos)); + foreach (var pos in line.ExcessBracketPositions) + { + yield return Errors.ExcessBracketError(file.FileName.Value, new Position(start, pos)); + } ++start; } } @@ -543,8 +741,10 @@ private static IEnumerable ComputeScopeDiagnostics(this FileContentM /// Throws an ArgumentOutOfRangeException if start is not within file. /// private static IEnumerable ComputeScopeDiagnostics(this FileContentManager file, int start) - { return ComputeScopeDiagnostics(file, start, file == null ? 0 : file.NrLines() - start); } // will raise an exception if file is null - + { + // will raise an exception if file is null + return ComputeScopeDiagnostics(file, start, file == null ? 0 : file.NrLines() - start); + } // the actual update routine @@ -554,41 +754,57 @@ private static IEnumerable ComputeScopeDiagnostics(this FileContentM /// private static void Update(this FileContentManager file, int start, int count, IEnumerable newText) { - CodeLine[] replacements = QsCompilerError.RaiseOnFailure(() => - ComputeCodeLines(newText, start > 0 ? file.GetLine(start - 1) : null).ToArray(), + CodeLine[] replacements = QsCompilerError.RaiseOnFailure( + () => ComputeCodeLines(newText, start > 0 ? file.GetLine(start - 1) : null).ToArray(), "scope tracking update failed during computing the replacements"); - IEnumerable updateRemaining = QsCompilerError.RaiseOnFailure(() => - ComputeUpdates(file, start, count, replacements), + IEnumerable updateRemaining = QsCompilerError.RaiseOnFailure( + () => ComputeUpdates(file, start, count, replacements), "scope tracking update failed during computing the updates"); - QsCompilerError.RaiseOnFailure(() => - { - if (updateRemaining == null) file.ContentUpdate(start, count, replacements); - else file.ContentUpdate(start, file.NrLines() - start, replacements.Concat(updateRemaining).ToArray()); - }, "the proposed ContentUpdate failed"); + QsCompilerError.RaiseOnFailure( + () => + { + if (updateRemaining == null) + { + file.ContentUpdate(start, count, replacements); + } + else + { + file.ContentUpdate(start, file.NrLines() - start, replacements.Concat(updateRemaining).ToArray()); + } + }, "the proposed ContentUpdate failed"); - QsCompilerError.RaiseOnFailure(() => - { - if (updateRemaining == null) file.AddScopeDiagnostics(file.ComputeScopeDiagnostics(start, replacements.Length)); - else file.AddScopeDiagnostics(file.ComputeScopeDiagnostics(start)); - file.AddScopeDiagnostics(file.CheckForMissingClosings()); - }, "updating the scope diagnostics failed"); + QsCompilerError.RaiseOnFailure( + () => + { + if (updateRemaining == null) + { + file.AddScopeDiagnostics(file.ComputeScopeDiagnostics(start, replacements.Length)); + } + else + { + file.AddScopeDiagnostics(file.ComputeScopeDiagnostics(start)); + } + file.AddScopeDiagnostics(file.CheckForMissingClosings()); + }, "updating the scope diagnostics failed"); } - - // routine(s) called by the FileContentManager upon updating a file + // routine(s) called by the FileContentManager upon updating a file /// /// Attempts to compute an incremental update for the change specified by start, count and newText, and updates file accordingly. - /// The given argument newText replaces the entire lines from start to (but not including) start + count. - /// If the given change is null, then (only) the currently queued unprocessed changes are processed. + /// The given argument newText replaces the entire lines from start to (but not including) start + count. + /// If the given change is null, then (only) the currently queued unprocessed changes are processed. /// Throws an ArgumentNullException if file is null. /// Any other exceptions should be throws (and caught, and possibly re-thrown) during the updating. /// internal static void UpdateScopeTacking(this FileContentManager file, TextDocumentContentChangeEvent change) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } /// /// Replaces the lines in the range [start, end] with those for the given text. @@ -597,27 +813,33 @@ void ComputeUpdate(int start, int end, string text) { QsCompilerError.Verify(start >= 0 && end >= start && end < file.NrLines(), "invalid range for update"); - // since both LF and CR in VS cause a line break on their own, + // since both LF and CR in VS cause a line break on their own, // we need to check if the change causes subequent CR LF to merge into a single line break if (text.StartsWith(Utils.LF) && start > 0 && file.GetLine(start - 1).Text.EndsWith(Utils.CR)) - { text = file.GetLine(--start).Text + text; } + { + text = file.GetLine(--start).Text + text; + } // we need to check if the change causes the next line to merge with the (last) changed line if (end + 1 < file.NrLines() && !Utils.EndOfLine.Match(text).Success) - { text = text + file.GetLine(++end).Text; } + { + text = text + file.GetLine(++end).Text; + } var newLines = Utils.SplitLines(text); var count = end - start + 1; - // note that the last line in the file won't end with a line break, + // note that the last line in the file won't end with a line break, // and is hence only captured by SplitLines if it is not empty - // -> we therefore manually add the last line in the file if it is empty + // -> we therefore manually add the last line in the file if it is empty if (newLines.Length == 0 || // the case if the file will be empty after the update (start + count == file.NrLines() && Utils.EndOfLine.Match(newLines.Last()).Success)) - { newLines = newLines.Concat(new string[] { String.Empty }).ToArray(); } + { + newLines = newLines.Concat(new string[] { string.Empty }).ToArray(); + } QsCompilerError.Verify(newLines.Any(), "should have at least one line to replace"); - file.Update(start, count, newLines); + file.Update(start, count, newLines); } file.SyncRoot.EnterUpgradeableReadLock(); @@ -625,13 +847,20 @@ void ComputeUpdate(int start, int end, string text) { // process the currently queued changes if necessary if (file.DequeueUnprocessedChanges(out int start, out string text)) - { ComputeUpdate(start, start, text); } + { + ComputeUpdate(start, start, text); + } // process the given change if necessary if (change != null) - { ComputeUpdate(change.Range.Start.Line, change.Range.End.Line, Utils.GetTextChangedLines(file, change)); } + { + ComputeUpdate(change.Range.Start.Line, change.Range.End.Line, Utils.GetTextChangedLines(file, change)); + } + } + finally + { + file.SyncRoot.ExitUpgradeableReadLock(); } - finally { file.SyncRoot.ExitUpgradeableReadLock(); } } } } diff --git a/src/QsCompiler/CompilationManager/TextProcessor.cs b/src/QsCompiler/CompilationManager/TextProcessor.cs index ce0b8421d1..150842fe17 100644 --- a/src/QsCompiler/CompilationManager/TextProcessor.cs +++ b/src/QsCompiler/CompilationManager/TextProcessor.cs @@ -9,7 +9,6 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { /// @@ -20,14 +19,17 @@ internal static class TextProcessor // routines used for a first round of checking for syntactic errors related to how statements can be formatted (e.g. a non-empty statement ending in '}' is certainly incorrect) /// - /// For any fragment where the code only consistes of whitespace, + /// For any fragment where the code only consistes of whitespace, /// adds a warning to the returned diagnostics if such a fragment terminates in a semicolon, /// adds an error to the returned diagnostics if it ends in and opening bracket. /// Throws an ArgumentNullException if the given diagnostics or fragments are null. /// private static IEnumerable CheckForEmptyFragments(this IEnumerable fragments, string filename) { - if (fragments == null) throw new ArgumentNullException(nameof(fragments)); + if (fragments == null) + { + throw new ArgumentNullException(nameof(fragments)); + } // opting to not complain about semicolons not following code anywhere in the file (i.e. on any scope) var diagnostics = fragments.Where(snippet => snippet.Text.Length == 0 && snippet.FollowedBy == ';') .Select(snippet => Warnings.EmptyStatementWarning(filename, snippet.GetRange().End)) @@ -42,16 +44,21 @@ private static IEnumerable CheckForEmptyFragments(this IEnumerable - private static IEnumerable CheckFragmentDelimiters (this CodeFragment fragment, string filename) + private static IEnumerable CheckFragmentDelimiters(this CodeFragment fragment, string filename) { - if (fragment?.Kind == null) throw new ArgumentException("missing specification of the fragment kind"); - var code = fragment.Kind.InvalidEnding; + if (fragment?.Kind == null) + { + throw new ArgumentException("missing specification of the fragment kind"); + } + var code = fragment.Kind.InvalidEnding; if (Diagnostics.ExpectedEnding(code) != fragment.FollowedBy) - { yield return Errors.InvalidFragmentEnding(filename, code, fragment.GetRange().End); } + { + yield return Errors.InvalidFragmentEnding(filename, code, fragment.GetRange().End); + } } /// - /// Calls the Q# parser on each fragment, splitting one fragment into several if necessary + /// Calls the Q# parser on each fragment, splitting one fragment into several if necessary /// (i.e. modifies the list of given fragments!). /// Fragments for which the code only consists of whitespace are left unchanged (i.e. the Kind remains set to null). /// Adds a suitable error to the returned diagnostics for each fragment that cannot be processed. @@ -59,7 +66,10 @@ private static IEnumerable CheckFragmentDelimiters (this CodeFragmen /// private static IEnumerable ParseCode(ref List fragments, string filename) { - if (fragments == null) throw new ArgumentNullException(nameof(fragments)); + if (fragments == null) + { + throw new ArgumentNullException(nameof(fragments)); + } var processedFragments = new List(fragments.Count()); var diagnostics = new List(); @@ -70,9 +80,13 @@ private static IEnumerable ParseCode(ref List fragment for (var outputIndex = 0; outputIndex < outputs.Length; ++outputIndex) { var output = outputs[outputIndex]; - var fragmentRange = DiagnosticTools.GetAbsoluteRange(snippetStart, output.Range); - var fragment = new CodeFragment(snippet.Indentation, fragmentRange, output.Text.Value, - outputIndex == outputs.Length - 1 ? snippet.FollowedBy : CodeFragment.MissingDelimiter, output.Kind); + var fragmentRange = DiagnosticTools.GetAbsoluteRange(snippetStart, output.Range); + var fragment = new CodeFragment( + snippet.Indentation, + fragmentRange, + output.Text.Value, + outputIndex == outputs.Length - 1 ? snippet.FollowedBy : CodeFragment.MissingDelimiter, + output.Kind); processedFragments.Add(fragment); var checkEnding = true; // if there is already a diagnostic overlapping with the ending, then don't bother checking the ending @@ -83,18 +97,26 @@ private static IEnumerable ParseCode(ref List fragment var fragmentEnd = fragment.GetRange().End; var diagnosticGoesUpToFragmentEnd = fragmentEnd.IsWithinRange(generated.Range) || fragmentEnd.Equals(generated.Range.End); - if (fragmentDiagnostic.Diagnostic.IsError && diagnosticGoesUpToFragmentEnd) checkEnding = false; + if (fragmentDiagnostic.Diagnostic.IsError && diagnosticGoesUpToFragmentEnd) + { + checkEnding = false; + } + } + if (checkEnding) + { + diagnostics.AddRange(fragment.CheckFragmentDelimiters(filename)); } - if (checkEnding) diagnostics.AddRange(fragment.CheckFragmentDelimiters(filename)); } - if (outputs.Length == 0) processedFragments.Add(snippet); // keep empty fragments around (note that the kind is set to null in this case!) + if (outputs.Length == 0) + { + processedFragments.Add(snippet); // keep empty fragments around (note that the kind is set to null in this case!) + } } QsCompilerError.RaiseOnFailure(() => ContextBuilder.VerifyTokenOrdering(processedFragments), "processed fragments are not ordered properly and/or overlap"); fragments = processedFragments; return diagnostics; } - // private utils related to extracting file content /// @@ -105,26 +127,36 @@ private static IEnumerable ParseCode(ref List fragment private static string GetCodeSnippet(this FileContentManager file, LSP.Range range) { if (!Utils.IsValidRange(range, file)) + { throw new ArgumentException($"cannot extract code snippet for the given range \n range: {range.DiagnosticString()}"); + } string CodeLine(CodeLine line) => line.WithoutEnding + line.LineEnding; var start = range.Start.Line; var count = range.End.Line - start + 1; var firstLine = CodeLine(file.GetLine(start)); - if (count == 1) return firstLine.Substring(range.Start.Character, range.End.Character - range.Start.Character); + if (count == 1) + { + return firstLine.Substring(range.Start.Character, range.End.Character - range.Start.Character); + } var lastLine = CodeLine(file.GetLine(range.End.Line)); var prepend = firstLine.Substring(range.Start.Character); var append = lastLine.Substring(0, range.End.Character); var middle = file.GetLines(start + 1, count - 2).Select(CodeLine).ToArray(); - if (middle.Length == 0) return Utils.JoinLines(new string[] { prepend, append }); - else return Utils.JoinLines(new string[] { prepend, Utils.JoinLines(middle), append }); // Note: use JoinLines here to get accurate position infos for errors + if (middle.Length == 0) + { + return Utils.JoinLines(new string[] { prepend, append }); + } + else + { + return Utils.JoinLines(new string[] { prepend, Utils.JoinLines(middle), append }); // Note: use JoinLines here to get accurate position infos for errors + } } - - // private utils for determining suitable ranges to parse + // private utils for determining suitable ranges to parse private static readonly Func StatementEndDelimiters = code => code.LastIndexOfAny(CodeFragment.DelimitingChars.ToArray()); @@ -133,32 +165,40 @@ private static string GetCodeSnippet(this FileContentManager file, LSP.Range ran code => code.IndexOfAny(CodeFragment.DelimitingChars.ToArray()); /// - /// Finds the position of the statement end closest to the end of the line, + /// Finds the position of the statement end closest to the end of the line, /// ignoring strings and comments, but not excess brackets. /// private static int StatementEnd(this CodeLine line) - { return line.FindInCode(StatementEndDelimiters, false); } + { + return line.FindInCode(StatementEndDelimiters, false); + } /// - /// Finds the position of the statement start closest to the beginning of the line, + /// Finds the position of the statement start closest to the beginning of the line, /// ignoring strings and comments, but not excess brackets. /// private static int StatementStart(this CodeLine line) - { return line.FindInCode(StatementStartDelimiters, false); } + { + return line.FindInCode(StatementStartDelimiters, false); + } /// - /// Finds the position of the statement end closest to the end of the line fragment defined by start and count, + /// Finds the position of the statement end closest to the end of the line fragment defined by start and count, /// ignoring strings and comments, but not excess brackets. /// private static int StatementEnd(this CodeLine line, int start, int count) - { return line.FindInCode(StatementEndDelimiters, start, count, false); } + { + return line.FindInCode(StatementEndDelimiters, start, count, false); + } /// - /// Finds the position of the statement start closest to the beginning of the line fragment after start, + /// Finds the position of the statement start closest to the beginning of the line fragment after start, /// ignoring strings and comments, but not excess brackets. /// private static int StatementStart(this CodeLine line, int start) - { return line.FindInCode(StatementStartDelimiters, start, line.WithoutEnding.Length - start, false); } + { + return line.FindInCode(StatementStartDelimiters, start, line.WithoutEnding.Length - start, false); + } /// /// Returns the Position after the last character in the file (including comments). @@ -166,46 +206,66 @@ private static int StatementStart(this CodeLine line, int start) /// public static Position End(this FileContentManager file) { - if (file == null || file.NrLines() == 0) throw new ArgumentNullException(nameof(file), "file is null or empty"); + if (file == null || file.NrLines() == 0) + { + throw new ArgumentNullException(nameof(file), "file is null or empty"); + } return new Position(file.NrLines() - 1, file.GetLine(file.NrLines() - 1).Text.Length); } /// - /// Returns the Position right after where the last relevant (i.e. non-comment) code in the file ends, + /// Returns the Position right after where the last relevant (i.e. non-comment) code in the file ends, /// or the position (0,0) if no such line exists. /// Throws an ArgumentNullException if file is null or does not contain any lines. /// private static Position LastInFile(FileContentManager file) { - if (file == null || file.NrLines() == 0) throw new ArgumentNullException(nameof(file), "file is null or missing content"); + if (file == null || file.NrLines() == 0) + { + throw new ArgumentNullException(nameof(file), "file is null or missing content"); + } var endIndex = file.NrLines(); - while (endIndex-- > 0 && file.GetLine(endIndex).WithoutEnding.Trim().Length == 0); - return endIndex < 0 ? new Position(0,0) : new Position(endIndex, file.GetLine(endIndex).WithoutEnding.Length); + while (endIndex-- > 0 && file.GetLine(endIndex).WithoutEnding.Trim().Length == 0) + { + } + return endIndex < 0 ? new Position(0, 0) : new Position(endIndex, file.GetLine(endIndex).WithoutEnding.Length); } /// /// Returns the position right after where the fragment containing the given position ends. /// If the closest previous ending was on the last character in a line, then the returned position is on the same line after the last character. /// Updates the given position to point to the first character in the fragment that contains code. - /// Throws an ArgumentException if the given position is not smaller than the position after the last piece of code in the file (given by LastInFile). + /// Throws an ArgumentException if the given position is not smaller than the position after the last piece of code in the file (given by LastInFile). /// Throws an ArgumentNullException if any of the arguments is null. /// Throws an ArgumentException if the given position is not within file. /// internal static Position FragmentEnd(this FileContentManager file, ref Position current) { var lastInFile = LastInFile(file); - if (!Utils.IsValidPosition(current, file)) throw new ArgumentException("given position is not within file"); - if (lastInFile.IsSmallerThanOrEqualTo(current)) throw new ArgumentException("no fragment exists at the given position"); + if (!Utils.IsValidPosition(current, file)) + { + throw new ArgumentException("given position is not within file"); + } + if (lastInFile.IsSmallerThanOrEqualTo(current)) + { + throw new ArgumentException("no fragment exists at the given position"); + } var text = file.GetLine(current.Line).WithoutEnding; if (current.Character > text.Length || text.Substring(current.Character).TrimEnd().Length == 0) { - var trimmed = String.Empty; - while (trimmed.Length == 0 && ++current.Line < file.NrLines()) trimmed = file.GetLine(current.Line).WithoutEnding.TrimStart(); - if (current.Line < file.NrLines()) current.Character = file.GetLine(current.Line).WithoutEnding.Length - trimmed.Length; + var trimmed = string.Empty; + while (trimmed.Length == 0 && ++current.Line < file.NrLines()) + { + trimmed = file.GetLine(current.Line).WithoutEnding.TrimStart(); + } + if (current.Line < file.NrLines()) + { + current.Character = file.GetLine(current.Line).WithoutEnding.Length - trimmed.Length; + } else { - current = lastInFile; + current = lastInFile; return lastInFile; } } @@ -213,7 +273,9 @@ internal static Position FragmentEnd(this FileContentManager file, ref Position var endIndex = current.Line; var endChar = file.GetLine(endIndex).StatementStart(current.Character); while (endChar < 0 && ++endIndex < file.NrLines()) - { endChar = file.GetLine(endIndex).StatementStart(); } + { + endChar = file.GetLine(endIndex).StatementStart(); + } return endIndex < file.NrLines() ? new Position(endIndex, endChar + 1) : lastInFile; } @@ -226,11 +288,16 @@ internal static Position FragmentEnd(this FileContentManager file, ref Position /// private static Position PositionAfterPrevious(this FileContentManager file, Position current) { - if (!Utils.IsValidPosition(current, file)) throw new ArgumentException("given position is not within file"); + if (!Utils.IsValidPosition(current, file)) + { + throw new ArgumentException("given position is not within file"); + } var startIndex = current.Line; var startChar = file.GetLine(startIndex).StatementEnd(0, current.Character); while (startChar < 0 && startIndex-- > 0) - { startChar = file.GetLine(startIndex).StatementEnd(); } + { + startChar = file.GetLine(startIndex).StatementEnd(); + } return startIndex < 0 ? new Position(0, 0) : new Position(startIndex, startChar + 1); } @@ -243,8 +310,14 @@ private static Position PositionAfterPrevious(this FileContentManager file, Posi private static IEnumerable FragmentsToProcess(this FileContentManager file, SortedSet changedLines) { // NOTE: I suggest not to touch this routine unless absolutely necessary...(things *will* break) - if (file == null) throw new ArgumentNullException(nameof(file)); - if (changedLines == null) throw new ArgumentNullException(nameof(changedLines)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (changedLines == null) + { + throw new ArgumentNullException(nameof(changedLines)); + } var iter = changedLines.GetEnumerator(); var lastInFile = LastInFile(file); @@ -252,28 +325,35 @@ private static IEnumerable FragmentsToProcess(this FileContentMana Position processed = new Position(0, 0); while (iter.MoveNext()) { - QsCompilerError.Verify(0 <= iter.Current && iter.Current < file.NrLines(), "index out of range for changed line"); + QsCompilerError.Verify(iter.Current >= 0 && iter.Current < file.NrLines(), "index out of range for changed line"); if (processed.Line < iter.Current) { var statementStart = file.PositionAfterPrevious(new Position(iter.Current, 0)); - if (processed.IsSmallerThan(statementStart)) processed = statementStart; + if (processed.IsSmallerThan(statementStart)) + { + processed = statementStart; + } } while (processed.Line <= iter.Current && processed.IsSmallerThan(lastInFile)) { processed = processed.Copy(); // because we don't want to modify the ending of the previous code fragment ... - var nextEnding = file.FragmentEnd(ref processed); + var nextEnding = file.FragmentEnd(ref processed); var extractedPiece = file.GetCodeSnippet(new LSP.Range { Start = processed, End = nextEnding }); - // constructing the CodeFragment - + // constructing the CodeFragment - // NOTE: its Range.End is the position of the delimiting char (if such a char exists), i.e. the position right after Code ends - if (extractedPiece.Length > 0) // length = 0 can occur e.g. if the last piece of code in the file does not terminate with a statement ending + // length = 0 can occur e.g. if the last piece of code in the file does not terminate with a statement ending + if (extractedPiece.Length > 0) { var code = file.GetLine(nextEnding.Line).ExcessBracketPositions.Contains(nextEnding.Character - 1) ? extractedPiece.Substring(0, extractedPiece.Length - 1) : extractedPiece; - if (code.Length == 0 || !CodeFragment.DelimitingChars.Contains(code.Last())) code = $"{code}{CodeFragment.MissingDelimiter}"; + if (code.Length == 0 || !CodeFragment.DelimitingChars.Contains(code.Last())) + { + code = $"{code}{CodeFragment.MissingDelimiter}"; + } var endChar = nextEnding.Character - (extractedPiece.Length - code.Length) - 1; var codeRange = new LSP.Range { Start = processed, End = new Position(nextEnding.Line, endChar) }; @@ -284,57 +364,74 @@ private static IEnumerable FragmentsToProcess(this FileContentMana } } - // routines called by the file content manager upon updating a file /// - /// Given the start line of a change, and how many lines have been updated from there, + /// Given the start line of a change, and how many lines have been updated from there, /// computes the position where the syntax check will start and end. /// Throws an ArgumentNullException if file is null. /// Throws an ArgumentOutOfRangeException if the range [start, start + count) is not a valid range within the current file content. /// internal static LSP.Range GetSyntaxCheckDelimiters(this FileContentManager file, int start, int count) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (start < 0 || start >= file.NrLines()) throw new ArgumentOutOfRangeException(nameof(start)); - if (count < 0 || start + count > file.NrLines()) throw new ArgumentOutOfRangeException(nameof(count)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (start < 0 || start >= file.NrLines()) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + if (count < 0 || start + count > file.NrLines()) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } // if no piece of code exists before the start of the modifications, then the check effectively starts at the beginning of the file var syntaxCheckStart = file.PositionAfterPrevious(new Position(start, 0)); // position (0,0) if there is no previous fragment // if the modification goes past what is currently the last piece of code, then the effectively the check extends to the end of the file var firstAfterModified = new Position(start + count, 0); - var lastInFile = LastInFile(file); + var lastInFile = LastInFile(file); var syntaxCheckEnd = firstAfterModified.IsSmallerThan(lastInFile) - ? file.FragmentEnd(ref firstAfterModified) + ? file.FragmentEnd(ref firstAfterModified) : file.End(); - return new LSP.Range { Start = syntaxCheckStart, End = lastInFile.IsSmallerThanOrEqualTo(syntaxCheckEnd) ? file.End() : syntaxCheckEnd}; + return new LSP.Range { Start = syntaxCheckStart, End = lastInFile.IsSmallerThanOrEqualTo(syntaxCheckEnd) ? file.End() : syntaxCheckEnd }; } /// /// Dequeues all lines whose content has changed and extracts the code fragments overlapping with those lines that need to be reprocessed. - /// Does nothing if no lines have been modified. + /// Does nothing if no lines have been modified. /// Recomputes and pushes the syntax diagnostics for the extracted fragments and all end-of-file diagnostics otherwise. - /// Processes the extracted fragment and inserts the processed fragments into the corresponding data structure - /// Throws an ArgumentNullException if file is null. + /// Processes the extracted fragment and inserts the processed fragments into the corresponding data structure + /// Throws an ArgumentNullException if file is null. /// - internal static void UpdateLanguageProcessing(this FileContentManager file) + internal static void UpdateLanguageProcessing(this FileContentManager file) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } file.SyncRoot.EnterUpgradeableReadLock(); try { var changedLines = file.DequeueContentChanges(); - if (!changedLines.Any()) return; + if (!changedLines.Any()) + { + return; + } var reprocess = QsCompilerError.RaiseOnFailure(() => file.FragmentsToProcess(changedLines).ToList(), "processing the edited lines failed"); var diagnostics = reprocess.CheckForEmptyFragments(file.FileName.Value) .Concat(ParseCode(ref reprocess, file.FileName.Value)).ToList(); QsCompilerError.RaiseOnFailure(() => file.TokensUpdate(reprocess), "the computed token update failed"); - QsCompilerError.RaiseOnFailure(() => file.AddSyntaxDiagnostics(diagnostics),"updating the SyntaxDiagnostics failed"); + QsCompilerError.RaiseOnFailure(() => file.AddSyntaxDiagnostics(diagnostics), "updating the SyntaxDiagnostics failed"); + } + finally + { + file.SyncRoot.ExitUpgradeableReadLock(); } - finally { file.SyncRoot.ExitUpgradeableReadLock(); } } } } diff --git a/src/QsCompiler/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index 9caeeaf924..a20c8b7d44 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -23,16 +23,19 @@ namespace Microsoft.Quantum.QsCompiler.CompilationBuilder internal static class TypeChecking { /// - /// Given a collections of the token indices that contain the header item, + /// Given a collections of the token indices that contain the header item, /// as well as function that extracts the declaration, builds the corresponding HeaderEntries, - /// throwing the corresponding exceptions if the building fails. - /// Returns all HeaderEntries for which the extracted name of the declaration is valid. + /// throwing the corresponding exceptions if the building fails. + /// Returns all HeaderEntries for which the extracted name of the declaration is valid. /// Returns null if the given collection of tokens is null. /// Throws an ArgumentNullException if the given function for extracting the declaration is null. /// - private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry)> GetHeaderItems(this FileContentManager file, - IEnumerable tokens,Func>> GetDeclaration, string keepInvalid) => - tokens?.Select(tIndex => + private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry)> GetHeaderItems( + this FileContentManager file, + IEnumerable tokens, + Func>> getDeclaration, + string keepInvalid) => + tokens?.Select(tIndex => { var attributes = ImmutableArray.CreateBuilder(); CodeFragment precedingFragment = tIndex.GetFragment(); @@ -44,100 +47,123 @@ internal static class TypeChecking var offset = DiagnosticTools.AsTuple(precedingFragment.GetRange().Start); attributes.Add(new AttributeAnnotation(att.Item1, att.Item2, offset, precedingFragment.Comments)); } - else break; + else + { + break; + } } - var docComments = file.DocumentingComments(tIndex.GetFragment().GetRange().Start); - return (tIndex, HeaderEntry.From(GetDeclaration, tIndex, attributes.ToImmutableArray(), docComments, keepInvalid)); + var docComments = file.DocumentingComments(tIndex.GetFragment().GetRange().Start); + return (tIndex, HeaderEntry.From(getDeclaration, tIndex, attributes.ToImmutableArray(), docComments, keepInvalid)); }) ?.Where(tuple => tuple.Item2 != null).Select(tuple => (tuple.Item1, tuple.Item2.Value)); /// - /// Extracts all documenting comments in the given file preceding the fragment at the given position, - /// ignoring any attribute annotations unless ignorePrecedingAttributes is set to false. - /// Documenting comments may be separated by an empty lines. - /// Strips the preceding triple-slash for the comments, as well as whitespace and the line break at the end. - /// Returns null if no documenting comment is given, or if the documenting comments do not have any non-whitespace content. - /// Throws an ArgumentNullException if the given file or position is null. - /// Throws an ArgumentException if the given position is not a valid position within the given file. + /// Extracts all documenting comments in the given file preceding the fragment at the given position, + /// ignoring any attribute annotations unless ignorePrecedingAttributes is set to false. + /// Documenting comments may be separated by an empty lines. + /// Strips the preceding triple-slash for the comments, as well as whitespace and the line break at the end. + /// Returns null if no documenting comment is given, or if the documenting comments do not have any non-whitespace content. + /// Throws an ArgumentNullException if the given file or position is null. + /// Throws an ArgumentException if the given position is not a valid position within the given file. /// internal static ImmutableArray DocumentingComments(this FileContentManager file, Position pos, bool ignorePrecedingAttributes = true) { - if (!Utils.IsValidPosition(pos, file)) throw new ArgumentException(nameof(pos)); + if (!Utils.IsValidPosition(pos, file)) + { + throw new ArgumentException(nameof(pos)); + } bool RelevantToken(CodeFragment token) => !(ignorePrecedingAttributes && token.Kind is QsFragmentKind.DeclarationAttribute); - bool IsDocCommentLine(string text) => text.StartsWith("///") || text == String.Empty; + bool IsDocCommentLine(string text) => text.StartsWith("///") || text == string.Empty; var lastPreceding = file.GetTokenizedLine(pos.Line) .TakeWhile(ContextBuilder.TokensUpTo(new Position(0, pos.Character))) .LastOrDefault(RelevantToken)?.WithUpdatedLineNumber(pos.Line); for (var lineNr = pos.Line; lastPreceding == null && lineNr-- > 0;) - { lastPreceding = file.GetTokenizedLine(lineNr).LastOrDefault(RelevantToken)?.WithUpdatedLineNumber(lineNr); } + { + lastPreceding = file.GetTokenizedLine(lineNr).LastOrDefault(RelevantToken)?.WithUpdatedLineNumber(lineNr); + } var firstRelevant = lastPreceding == null ? 0 : lastPreceding.GetRange().End.Line + 1; return file.GetLines(firstRelevant, pos.Line > firstRelevant ? pos.Line - firstRelevant : 0) .Select(line => line.Text.Trim()).Where(IsDocCommentLine).Select(line => line.TrimStart('/')) - .SkipWhile(text => String.IsNullOrWhiteSpace(text)).Reverse() - .SkipWhile(text => String.IsNullOrWhiteSpace(text)).Reverse() + .SkipWhile(text => string.IsNullOrWhiteSpace(text)).Reverse() + .SkipWhile(text => string.IsNullOrWhiteSpace(text)).Reverse() .ToImmutableArray(); } /// - /// Returns the HeaderItems corresponding to all namespaces declared in the given file, or null if the given file is null. + /// Returns the HeaderItems corresponding to all namespaces declared in the given file, or null if the given file is null. /// For namespaces with an invalid namespace name the symbol name in the header item will be set to an UnknownNamespace. /// private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry)> GetNamespaceHeaderItems(this FileContentManager file) => file.GetHeaderItems(file?.NamespaceDeclarationTokens(), frag => frag.Kind.DeclaredNamespace(), ReservedKeywords.InternalUse.UnknownNamespace); /// - /// Returns the HeaderItems corresponding to all open directives with a valid name in the given file, or null if the given file is null. + /// Returns the HeaderItems corresponding to all open directives with a valid name in the given file, or null if the given file is null. /// - private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry<(string, QsRangeInfo)>)> GetOpenDirectivesHeaderItems - (this FileContentManager file) => file.GetHeaderItems(file?.OpenDirectiveTokens(), frag => - { - var dir = frag.Kind.OpenedNamespace(); - if (dir.IsNull) return QsNullable>.Null; - QsNullable> OpenedAs(string a, QsRangeInfo r) => - QsNullable>.NewValue(new Tuple(dir.Item.Item1, (a, r))); + private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry<(string, QsRangeInfo)>)> GetOpenDirectivesHeaderItems( + this FileContentManager file) => file.GetHeaderItems( + file?.OpenDirectiveTokens(), + frag => + { + var dir = frag.Kind.OpenedNamespace(); + if (dir.IsNull) + { + return QsNullable>.Null; + } + QsNullable> OpenedAs(string a, QsRangeInfo r) => + QsNullable>.NewValue(new Tuple(dir.Item.Item1, (a, r))); - var alias = dir.Item.Item2; - if (alias.IsNull) return OpenedAs(null, QsRangeInfo.Null); - var aliasName = alias.Item.Symbol.AsDeclarationName(null); - QsCompilerError.Verify(aliasName != null || alias.Item.Symbol.IsInvalidSymbol, "could not extract namespace short name"); - return OpenedAs(aliasName, alias.Item.Range); - } - , null); + var alias = dir.Item.Item2; + if (alias.IsNull) + { + return OpenedAs(null, QsRangeInfo.Null); + } + var aliasName = alias.Item.Symbol.AsDeclarationName(null); + QsCompilerError.Verify(aliasName != null || alias.Item.Symbol.IsInvalidSymbol, "could not extract namespace short name"); + return OpenedAs(aliasName, alias.Item.Range); + }, + null); /// - /// Returns the HeaderItems corresponding to all type declarations with a valid name in the given file, or null if the given file is null. + /// Returns the HeaderItems corresponding to all type declarations with a valid name in the given file, or null if the given file is null. /// - private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>>>)> GetTypeDeclarationHeaderItems - (this FileContentManager file) => file.GetHeaderItems(file?.TypeDeclarationTokens(), frag => frag.Kind.DeclaredType(), null); + private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>>>)> GetTypeDeclarationHeaderItems( + this FileContentManager file) => file.GetHeaderItems(file?.TypeDeclarationTokens(), frag => frag.Kind.DeclaredType(), null); /// /// Returns the HeaderItems corresponding to all callable declarations with a valid name in the given file, or null if the given file is null. /// - private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>)> GetCallableDeclarationHeaderItems - (this FileContentManager file) => file.GetHeaderItems(file?.CallableDeclarationTokens(), frag => frag.Kind.DeclaredCallable(), null); + private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>)> GetCallableDeclarationHeaderItems( + this FileContentManager file) => file.GetHeaderItems(file?.CallableDeclarationTokens(), frag => frag.Kind.DeclaredCallable(), null); /// /// Given the HeaderEntry of the parent, defines a function that extracts the specialization declaration /// for a CodeFragment that contains a specialization, that can be used to build a HeaderEntry for the specialization. - /// The symbol saved in that HeaderEntry then is the name of the specialized callable, - /// and its declaration contains the specialization kind as well as the range info for the specialization intro. + /// The symbol saved in that HeaderEntry then is the name of the specialized callable, + /// and its declaration contains the specialization kind as well as the range info for the specialization intro. /// The function returns Null if the Kind of the given fragment is null. /// - private static QsNullable)>> SpecializationDeclaration - (HeaderEntry> parent, CodeFragment fragment) + private static QsNullable)>> SpecializationDeclaration( + HeaderEntry> parent, CodeFragment fragment) { var specDecl = fragment.Kind?.DeclaredSpecialization(); - var Null = QsNullable)>>.Null; - if (!specDecl.HasValue || specDecl.Value.IsNull) return Null; + var @null = QsNullable)>>.Null; + if (!specDecl.HasValue || specDecl.Value.IsNull) + { + return @null; + } var (specKind, generator) = (specDecl.Value.Item.Item1.Item1, specDecl.Value.Item.Item1.Item2); - if (specKind == null) return Null; + if (specKind == null) + { + return @null; + } var introRange = fragment.Kind.IsControlledAdjointDeclaration ? Parsing.HeaderDelimiters(2).Invoke(fragment.Text) : Parsing.HeaderDelimiters(1).Invoke(fragment.Text); var parentSymbol = parent.PositionedSymbol; - var sym = new QsSymbol(QsSymbolKind.NewSymbol(parentSymbol.Item1), + var sym = new QsSymbol( + QsSymbolKind.NewSymbol(parentSymbol.Item1), QsRangeInfo.NewValue(parentSymbol.Item2)); var returnTuple = new Tuple)>(sym, (specKind, generator, introRange)); return QsNullable)>>.NewValue(returnTuple); @@ -150,8 +176,14 @@ internal static ImmutableArray DocumentingComments(this FileContentManag /// private static T ContainingParent(Position pos, IReadOnlyCollection<(Position, T)> items) { - if (!Utils.IsValidPosition(pos)) throw new ArgumentException(nameof(pos)); - if (items == null) throw new ArgumentNullException(nameof(items)); + if (!Utils.IsValidPosition(pos)) + { + throw new ArgumentException(nameof(pos)); + } + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } var preceding = items.TakeWhile(tuple => tuple.Item1.IsSmallerThan(pos)); return preceding.Any() @@ -160,26 +192,39 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position } /// - /// Calls the given function on each of the given items to add, + /// Calls the given function on each of the given items to add, /// and adds the returned diagnostics to the given list of diagnostics. /// Returns a List of the token indices and the corresponding header items for which no errors were generated. /// Throws an ArgumentNullException if the given diagnostics or items, or the function to add them is null. /// - private static List<(TItem, HeaderEntry)> AddItems( + private static List<(TItem, HeaderEntry)> AddItems( IEnumerable<(TItem, HeaderEntry)> itemsToAdd, - Func, Tuple>, THeader, ImmutableArray, ImmutableArray, QsCompilerDiagnostic[]> Add, - string fileName, List diagnostics) + Func, Tuple>, THeader, ImmutableArray, ImmutableArray, QsCompilerDiagnostic[]> add, + string fileName, + List diagnostics) { - if (itemsToAdd == null) throw new ArgumentNullException(nameof(itemsToAdd)); - if (Add == null) throw new ArgumentNullException(nameof(Add)); - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); + if (itemsToAdd == null) + { + throw new ArgumentNullException(nameof(itemsToAdd)); + } + if (add == null) + { + throw new ArgumentNullException(nameof(add)); + } + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } var itemsToCompile = new List<(TItem, HeaderEntry)>(); foreach (var (tIndex, headerItem) in itemsToAdd) { var position = headerItem.GetPosition(); - var messages = Add(position, headerItem.PositionedSymbol, headerItem.Declaration, headerItem.Attributes, headerItem.Documentation).ToList(); - if (!messages.Any(msg => msg.Diagnostic.IsError)) itemsToCompile.Add((tIndex, headerItem)); + var messages = add(position, headerItem.PositionedSymbol, headerItem.Declaration, headerItem.Attributes, headerItem.Documentation).ToList(); + if (!messages.Any(msg => msg.Diagnostic.IsError)) + { + itemsToCompile.Add((tIndex, headerItem)); + } diagnostics.AddRange(messages.Select(msg => Diagnostics.Generate(fileName, msg, position))); } return itemsToCompile; @@ -187,23 +232,35 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position /// /// Updates the given compilation with the information about all globally declared types and callables in the given file. - /// Adds the generated diagnostics to the given list of diagnostics. - /// Returns a lookup for all callables that are to be included in the compilation, - /// with either a list of the token indices that contain its specializations to be included in the compilation, + /// Adds the generated diagnostics to the given list of diagnostics. + /// Returns a lookup for all callables that are to be included in the compilation, + /// with either a list of the token indices that contain its specializations to be included in the compilation, /// or a list consisting of the token index of the callable declaration, if the declaration does not contain any specializations. /// Note: This routine assumes that all empty or invalid fragments have been excluded from compilation prior to calling this routine. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an InvalidOperationException if the given file is not at least read-locked, + /// Throws an InvalidOperationException if the given file is not at least read-locked, /// since the returned token indices will only be valid until the next write operation that affects the tokens in the file. /// Throws an InvalidOperationException if the lock for the given compilation cannot be set because a dependent lock is the gating lock ("outermost lock"). /// - internal static ImmutableDictionary)> UpdateGlobalSymbols - (this FileContentManager file, CompilationUnit compilation, List diagnostics) + internal static ImmutableDictionary)> UpdateGlobalSymbols( + this FileContentManager file, CompilationUnit compilation, List diagnostics) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); - if (!file.SyncRoot.IsAtLeastReadLockHeld()) throw new InvalidOperationException("file needs to be locked in order to update global symbols"); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } + if (!file.SyncRoot.IsAtLeastReadLockHeld()) + { + throw new InvalidOperationException("file needs to be locked in order to update global symbols"); + } compilation.EnterUpgradeableReadLock(); try @@ -220,12 +277,16 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position // add documenting comments to the namespace declarations foreach (var header in namespaceHeaders) - { distinctNamespaces[header.Item2.SymbolName].AddDocumentation(file.FileName, header.Item2.Documentation); } + { + distinctNamespaces[header.Item2.SymbolName].AddDocumentation(file.FileName, header.Item2.Documentation); + } // add all type declarations - var typesToCompile = AddItems(file.GetTypeDeclarationHeaderItems(), - (pos, name, decl, att, doc) => (ContainingParent(pos, namespaces)).TryAddType(file.FileName, Location(pos, name.Item2), name, decl.Item2, att, decl.Item1, doc), - file.FileName.Value, diagnostics); + var typesToCompile = AddItems( + file.GetTypeDeclarationHeaderItems(), + (pos, name, decl, att, doc) => ContainingParent(pos, namespaces).TryAddType(file.FileName, Location(pos, name.Item2), name, decl.Item2, att, decl.Item1, doc), + file.FileName.Value, + diagnostics); var tokensToCompile = new List<(QsQualifiedName, (QsComments, IEnumerable))>(); foreach (var headerItem in typesToCompile) @@ -235,9 +296,11 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position } // add all callable declarations - var callablesToCompile = AddItems(file.GetCallableDeclarationHeaderItems(), - (pos, name, decl, att, doc) => (ContainingParent(pos, namespaces)).TryAddCallableDeclaration(file.FileName, Location(pos, name.Item2), name, Tuple.Create(decl.Item1, decl.Item3), att, decl.Item2, doc), - file.FileName.Value, diagnostics); + var callablesToCompile = AddItems( + file.GetCallableDeclarationHeaderItems(), + (pos, name, decl, att, doc) => ContainingParent(pos, namespaces).TryAddCallableDeclaration(file.FileName, Location(pos, name.Item2), name, Tuple.Create(decl.Item1, decl.Item3), att, decl.Item2, doc), + file.FileName.Value, + diagnostics); // add all callable specilizations -> TOOD: needs to be adapted for specializations outside the declaration body (not yet supported) foreach (var headerItem in callablesToCompile) @@ -251,28 +314,42 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position // push the modified namespace back into the symbol table, while adding all open directives foreach (var ns in distinctNamespaces.Values) - { compilation.GlobalSymbols.AddOrReplaceNamespace(ns); } + { + compilation.GlobalSymbols.AddOrReplaceNamespace(ns); + } return tokensToCompile.ToImmutableDictionary(entry => entry.Item1, entry => entry.Item2); } - finally { compilation.ExitUpgradeableReadLock(); } + finally + { + compilation.ExitUpgradeableReadLock(); + } } /// - /// Given the HeaderItem for a callable declaration, as well as the Namespace and the name of the source file it is declared in, + /// Given the HeaderItem for a callable declaration, as well as the Namespace and the name of the source file it is declared in, /// adds all specializations defined within the declaration body to the given namespace. /// If the declaration body consists of only statements, adds the specialization corresponding to the default body. /// Adds the diagnostics generated during the process to the given list of diagnostics. /// If the given callable declaration contains specializations, /// returns a list of the token indices that contain the specializations to be included in the compilation. - /// If the given callable does not contain any specializations, + /// If the given callable does not contain any specializations, /// returns a list of token indices containing only the token of the callable declaration. /// Throws an ArgumentNullException if either the given namespace or diagnostics are null. /// - private static List AddSpecializationsToNamespace(FileContentManager file, Namespace ns, - (CodeFragment.TokenIndex, HeaderEntry>) parent, List diagnostics) + private static List AddSpecializationsToNamespace( + FileContentManager file, + Namespace ns, + (CodeFragment.TokenIndex, HeaderEntry>) parent, + List diagnostics) { - if (ns == null) throw new ArgumentNullException(nameof(ns)); - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); + if (ns == null) + { + throw new ArgumentNullException(nameof(ns)); + } + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } var contentToCompile = new List(); var callableDecl = parent.Item2.Declaration; var parentName = parent.Item2.PositionedSymbol; @@ -287,9 +364,14 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position var (specKind, generator, introRange) = headerItem.Declaration; var location = new QsLocation(DiagnosticTools.AsTuple(position), introRange); var messages = ns.TryAddCallableSpecialization(specKind, file.FileName, location, parentName, generator, headerItem.Attributes, headerItem.Documentation); - if (!messages.Any(msg => msg.Diagnostic.IsError)) contentToCompile.Add(tIndex); + if (!messages.Any(msg => msg.Diagnostic.IsError)) + { + contentToCompile.Add(tIndex); + } foreach (var msg in messages) - { diagnostics.Add(Diagnostics.Generate(file.FileName.Value, msg, position)); } + { + diagnostics.Add(Diagnostics.Generate(file.FileName.Value, msg, position)); + } } // verify that either no specialization occurs, or that no other fragment than specializations occur @@ -311,10 +393,16 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position var location = new QsLocation(DiagnosticTools.AsTuple(parent.Item2.GetPosition()), parentName.Item2); var genRange = QsRangeInfo.NewValue(location.Range); // set to the range of the callable name var omittedSymbol = new QsSymbol(QsSymbolKind.OmittedSymbols, QsRangeInfo.Null); - var generatorKind = QsSpecializationGeneratorKind.NewUserDefinedImplementation(omittedSymbol); + var generatorKind = QsSpecializationGeneratorKind.NewUserDefinedImplementation(omittedSymbol); var generator = new QsSpecializationGenerator(QsNullable>.Null, generatorKind, genRange); - var messages = ns.TryAddCallableSpecialization(QsSpecializationKind.QsBody, - file.FileName, location, parentName, generator, ImmutableArray.Empty, ImmutableArray.Empty); + var messages = ns.TryAddCallableSpecialization( + QsSpecializationKind.QsBody, + file.FileName, + location, + parentName, + generator, + ImmutableArray.Empty, + ImmutableArray.Empty); QsCompilerError.Verify(!messages.Any(), "compiler returned diagnostic(s) for automatically inserted specialization"); contentToCompile.Add(parent.Item1); } @@ -322,17 +410,23 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position } /// - /// Resolves all type declarations, callable declarations and callable specializations in the given NamespacesManager, + /// Resolves all type declarations, callable declarations and callable specializations in the given NamespacesManager, /// and verifies that the type declarations do not have any cyclic dependencies. - /// If no fileName is given or the given fileName is null, + /// If no fileName is given or the given fileName is null, /// adds all diagnostics generated during resolution and verification to the given list of diagnostics. - /// If the given fileName is not null, adds only the diagnostics for the file with that name to the given list of diagnostics. - /// Throws an ArgumentNullException if the given NamespaceManager or list of diagnostics is null. + /// If the given fileName is not null, adds only the diagnostics for the file with that name to the given list of diagnostics. + /// Throws an ArgumentNullException if the given NamespaceManager or list of diagnostics is null. /// internal static void ResolveGlobalSymbols(NamespaceManager symbols, List diagnostics, string fileName = null) { - if (symbols == null) throw new ArgumentNullException(nameof(symbols)); - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); + if (symbols == null) + { + throw new ArgumentNullException(nameof(symbols)); + } + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } var declDiagnostics = symbols.ResolveAll(BuiltIn.NamespacesToAutoOpen); var cycleDiagnostics = SyntaxProcessing.SyntaxTree.CheckDefinedTypesForCycles(symbols.DefinedTypes()); @@ -343,29 +437,50 @@ void AddDiagnostics(NonNullable source, IEnumerable.New(fileName); - if (declDiagnostics.Contains(key)) AddDiagnostics(key, declDiagnostics[key]); - if (cycleDiagnostics.Contains(key)) AddDiagnostics(key, cycleDiagnostics[key]); + if (declDiagnostics.Contains(key)) + { + AddDiagnostics(key, declDiagnostics[key]); + } + if (cycleDiagnostics.Contains(key)) + { + AddDiagnostics(key, cycleDiagnostics[key]); + } } else { - foreach (var grouping in declDiagnostics) AddDiagnostics(grouping.Key, grouping); - foreach (var grouping in cycleDiagnostics) AddDiagnostics(grouping.Key, grouping); + foreach (var grouping in declDiagnostics) + { + AddDiagnostics(grouping.Key, grouping); + } + foreach (var grouping in cycleDiagnostics) + { + AddDiagnostics(grouping.Key, grouping); + } } } /// /// Updates the symbol information in the given compilation unit with all (validly placed) open directives in the given file, - /// and adds the generated diagnostics for the given file *only* to the given list of diagnostics. + /// and adds the generated diagnostics for the given file *only* to the given list of diagnostics. /// Throws an ArgumentNullException if the given compilation unit, the given file or the given diagnostics are null. /// Throws an InvalidOperationException if the lock for the given compilation cannot be set because a dependent lock is the gating lock ("outermost lock"). /// internal static void ImportGlobalSymbols(this FileContentManager file, CompilationUnit compilation, List diagnostics) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } - // While in principle the file does not need to be externally locked for this routine to evaluate correctly, + // While in principle the file does not need to be externally locked for this routine to evaluate correctly, // it is to be expected that the file is indeed locked, such that the information about open directives is consistent with the one on header items - // of course there is no way to verify that even if the file is locked, let's still call QsCompilerError.Verify on the file lock. QsCompilerError.Verify(file.SyncRoot.IsAtLeastReadLockHeld(), "file should be locked when calling ImportAndResolveGlobalSymbols"); @@ -373,26 +488,41 @@ internal static void ImportGlobalSymbols(this FileContentManager file, Compilati try { var namespaces = file.GetNamespaceHeaderItems().Select(tuple => (tuple.Item2.GetPosition(), tuple.Item2.SymbolName)).ToList(); - AddItems(file.GetOpenDirectivesHeaderItems(), + AddItems( + file.GetOpenDirectivesHeaderItems(), (pos, name, alias, _, __) => compilation.GlobalSymbols.AddOpenDirective(name.Item1, name.Item2, alias.Item1, alias.Item2, ContainingParent(pos, namespaces), file.FileName), - file.FileName.Value, diagnostics); + file.FileName.Value, + diagnostics); + } + finally + { + file.SyncRoot.ExitReadLock(); } - finally { file.SyncRoot.ExitReadLock(); } } /// - /// Builds a FragmentTree containting the given grouping of token indices for a certain parent. - /// Assumes that all given token indices are associated with the given file. + /// Builds a FragmentTree containting the given grouping of token indices for a certain parent. + /// Assumes that all given token indices are associated with the given file. /// Throws an ArgumentNullException if the given file or any of the given groupings is null. - /// Throws an InvalidOperationException if the given file is not at least read-locked, - /// since token indices are only ever valid until the next write operation to the file they are associated with. + /// Throws an InvalidOperationException if the given file is not at least read-locked, + /// since token indices are only ever valid until the next write operation to the file they are associated with. /// - internal static ImmutableDictionary GetDeclarationTrees(this FileContentManager file, + internal static ImmutableDictionary GetDeclarationTrees( + this FileContentManager file, ImmutableDictionary)> content) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (content == null) throw new ArgumentNullException(nameof(content)); - if (!file.SyncRoot.IsAtLeastReadLockHeld()) throw new InvalidOperationException("file needs to be locked in order to build the FragmentTrees from the given token indices"); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + if (!file.SyncRoot.IsAtLeastReadLockHeld()) + { + throw new InvalidOperationException("file needs to be locked in order to build the FragmentTrees from the given token indices"); + } IEnumerable ChildrenToCompile(CodeFragment.TokenIndex token) => token.GetChildren(deep: false).Where(child => child.GetFragment().IncludeInCompilation); @@ -400,7 +530,7 @@ internal static void ImportGlobalSymbols(this FileContentManager file, Compilati IReadOnlyList BuildNodes(IEnumerable tokens, Position rootPos) => tokens?.Select(token => { - var fragment = token.GetFragmentWithClosingComments(); + var fragment = token.GetFragmentWithClosingComments(); var parentPos = rootPos ?? fragment.GetRange().Start; return new FragmentTree.TreeNode(fragment, BuildNodes(ChildrenToCompile(token), parentPos), parentPos); }) @@ -413,23 +543,32 @@ internal static void ImportGlobalSymbols(this FileContentManager file, Compilati } /// - /// Updates and resolves all global symbols in each given file, + /// Updates and resolves all global symbols in each given file, /// and modifies the given CompilationUnit accordingly in the process. - /// Updates and pushes all HeaderDiagnostics in the given files, - /// and clears all semantic diagnostics without pushing them. - /// If the given Action for publishing diagnostics is not null, - /// invokes it for the diagnostics of each given file after updating them. - /// Throws an ArgumentNullException if the given compilation is null, + /// Updates and pushes all HeaderDiagnostics in the given files, + /// and clears all semantic diagnostics without pushing them. + /// If the given Action for publishing diagnostics is not null, + /// invokes it for the diagnostics of each given file after updating them. + /// Throws an ArgumentNullException if the given compilation is null, /// or if the given files, or any file contained in files, is null. /// - internal static ImmutableDictionary UpdateGlobalSymbolsFor - (this CompilationUnit compilation, IEnumerable files) + internal static ImmutableDictionary UpdateGlobalSymbolsFor( + this CompilationUnit compilation, IEnumerable files) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); - if (files == null || files.Contains(null)) throw new ArgumentNullException(nameof(files)); + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } + if (files == null || files.Contains(null)) + { + throw new ArgumentNullException(nameof(files)); + } compilation.EnterWriteLock(); - foreach (var file in files) file.SyncRoot.EnterUpgradeableReadLock(); + foreach (var file in files) + { + file.SyncRoot.EnterUpgradeableReadLock(); + } try { // get the fragment trees for the declaration content of all files and callables @@ -440,7 +579,9 @@ internal static void ImportGlobalSymbols(this FileContentManager file, Compilati // add all open directives to each file (do not resolve yet) foreach (var file in files) - { file.ImportGlobalSymbols(compilation, diagnostics); } + { + file.ImportGlobalSymbols(compilation, diagnostics); + } // do the resolution for all files, and construct the corresponding diagnostics ResolveGlobalSymbols(compilation.GlobalSymbols, diagnostics); @@ -448,19 +589,23 @@ internal static void ImportGlobalSymbols(this FileContentManager file, Compilati // replace the header diagnostics in each file, and publish them var messages = diagnostics.ToLookup(d => d.Source); foreach (var file in files) - { file.ReplaceHeaderDiagnostics(messages[file.FileName.Value]); } + { + file.ReplaceHeaderDiagnostics(messages[file.FileName.Value]); + } // return the declaration content of all files and callables return content; } finally { - foreach (var file in files) file.SyncRoot.ExitUpgradeableReadLock(); + foreach (var file in files) + { + file.SyncRoot.ExitUpgradeableReadLock(); + } compilation.ExitWriteLock(); } } - // routines for building statements /// @@ -471,12 +616,24 @@ internal static void ImportGlobalSymbols(this FileContentManager file, Compilati /// If the set of required functors is unspecified or null, then the functors to support are determined by the parent scope. /// Throws an ArgumentNullException if the given list of tree nodes, scope context or diagnostics are null. /// - private static QsScope BuildScope(IReadOnlyList nodeContent, - ScopeContext context, List diagnostics, ImmutableHashSet requiredFunctorSupport = null) + private static QsScope BuildScope( + IReadOnlyList nodeContent, + ScopeContext context, + List diagnostics, + ImmutableHashSet requiredFunctorSupport = null) { - if (nodeContent == null) throw new ArgumentNullException(nameof(nodeContent)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); + if (nodeContent == null) + { + throw new ArgumentNullException(nameof(nodeContent)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } var inheritedSymbols = context.Symbols.CurrentDeclarations; context.Symbols.BeginScope(requiredFunctorSupport); @@ -493,13 +650,24 @@ private static QsScope BuildScope(IReadOnlyList nodeConte /// /// Thrown if the given build function, scope context, or diagnostics are null. /// - private static T BuildStatement(FragmentTree.TreeNode node, + private static T BuildStatement( + FragmentTree.TreeNode node, Func, Tuple> build, - ScopeContext context, List diagnostics) + ScopeContext context, + List diagnostics) { - if (build == null) throw new ArgumentNullException(nameof(build)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); + if (build == null) + { + throw new ArgumentNullException(nameof(build)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } var statementPos = node.Fragment.GetRange().Start; var location = new QsLocation(DiagnosticTools.AsTuple(node.GetPositionRelativeToRoot()), node.Fragment.HeaderRange); @@ -522,20 +690,38 @@ private static T BuildStatement(FragmentTree.TreeNode node, /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildUsingStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildUsingStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind is QsFragmentKind.UsingBlockIntro allocate) { context.Symbols.BeginScope(); // pushing a scope such that the declared variables are not available outside the body - var allocationScope = BuildStatement(nodes.Current, - (relPos, ctx) => Statements.NewAllocateScope(nodes.Current.Fragment.Comments, relPos, ctx, allocate.Item1, allocate.Item2), - context, diagnostics); + var allocationScope = BuildStatement( + nodes.Current, + (relPos, ctx) => Statements.NewAllocateScope(nodes.Current.Fragment.Comments, relPos, ctx, allocate.Item1, allocate.Item2), + context, + diagnostics); var body = BuildScope(nodes.Current.Children, context, diagnostics); statement = allocationScope(body); context.Symbols.EndScope(); @@ -560,20 +746,38 @@ private static bool TryBuildUsingStatement(IEnumerator no /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildBorrowStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildBorrowStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind is QsFragmentKind.BorrowingBlockIntro borrow) { context.Symbols.BeginScope(); // pushing a scope such that the declared variables are not available outside the body - var borrowingScope = BuildStatement(nodes.Current, + var borrowingScope = BuildStatement( + nodes.Current, (relPos, ctx) => Statements.NewBorrowScope(nodes.Current.Fragment.Comments, relPos, ctx, borrow.Item1, borrow.Item2), - context, diagnostics); + context, + diagnostics); var body = BuildScope(nodes.Current.Children, context, diagnostics); statement = borrowingScope(body); context.Symbols.EndScope(); @@ -599,13 +803,29 @@ private static bool TryBuildBorrowStatement(IEnumerator n /// Throws an ArgumentException if the given scope context does not currently contain an open scope, /// or if the repeat header is not followed by a until-success clause. /// - private static bool TryBuildRepeatStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildRepeatStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind.IsRepeatIntro) { @@ -617,26 +837,33 @@ private static bool TryBuildRepeatStatement(IEnumerator n if (nodes.MoveNext() && nodes.Current.Fragment.Kind is QsFragmentKind.UntilSuccess untilCond) { inheritedSymbols = context.Symbols.CurrentDeclarations; - var conditionalBlock = BuildStatement(nodes.Current, + var conditionalBlock = BuildStatement( + nodes.Current, (relPos, ctx) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, ctx, untilCond.Item1), - context, diagnostics); - var fixupBody = untilCond.Item2 + context, + diagnostics); + var fixupBody = untilCond.Item2 ? new QsScope(BuildStatements(nodes.Current.Children.GetEnumerator(), context, diagnostics), inheritedSymbols) : new QsScope(ImmutableArray.Empty, inheritedSymbols); var (successCondition, fixupBlock) = conditionalBlock(fixupBody); // here, condition = true implies the block is *not* executed context.Symbols.EndScope(); - statement = BuildStatement(headerNode, + statement = BuildStatement( + headerNode, (relPos, ctx) => { - var repeatBlock = new QsPositionedBlock(repeatBody, QsNullable.NewValue(relPos), headerNode.Fragment.Comments); + var repeatBlock = new QsPositionedBlock(repeatBody, QsNullable.NewValue(relPos), headerNode.Fragment.Comments); return Statements.NewRepeatStatement(ctx.Symbols, repeatBlock, successCondition, fixupBlock); }, - context, diagnostics); + context, + diagnostics); proceed = nodes.MoveNext(); return true; } - else throw new ArgumentException("repeat header needs to be followed by an until-clause and a fixup-block"); + else + { + throw new ArgumentException("repeat header needs to be followed by an until-clause and a fixup-block"); + } } (statement, proceed) = (null, true); return false; @@ -656,20 +883,38 @@ private static bool TryBuildRepeatStatement(IEnumerator n /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildForStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildForStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind is QsFragmentKind.ForLoopIntro forStatement) { context.Symbols.BeginScope(); // pushing a scope such that the declared variables are not available outside the body - var forLoop = BuildStatement(nodes.Current, + var forLoop = BuildStatement( + nodes.Current, (relPos, symbols) => Statements.NewForStatement(nodes.Current.Fragment.Comments, relPos, symbols, forStatement.Item1, forStatement.Item2), - context, diagnostics); + context, + diagnostics); var body = BuildScope(nodes.Current.Children, context, diagnostics); statement = forLoop(body); context.Symbols.EndScope(); @@ -694,20 +939,38 @@ private static bool TryBuildForStatement(IEnumerator node /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildWhileStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildWhileStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind is QsFragmentKind.WhileLoopIntro whileStatement) { context.Symbols.BeginScope(); // pushing a scope such that the declared variables are not available outside the body - var whileLoop = BuildStatement(nodes.Current, + var whileLoop = BuildStatement( + nodes.Current, (relPos, ctx) => Statements.NewWhileStatement(nodes.Current.Fragment.Comments, relPos, ctx, whileStatement.Item), - context, diagnostics); + context, + diagnostics); var body = BuildScope(nodes.Current.Children, context, diagnostics); statement = whileLoop(body); context.Symbols.EndScope(); @@ -732,30 +995,52 @@ private static bool TryBuildWhileStatement(IEnumerator no /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildIfStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildIfStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind is QsFragmentKind.IfClause ifCond) { + var rootPosition = nodes.Current.GetRootPosition(); + // if block - var buildClause = BuildStatement(nodes.Current, + var buildClause = BuildStatement( + nodes.Current, (relPos, ctx) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, ctx, ifCond.Item), - context, diagnostics); + context.WithinIfCondition, + diagnostics); var ifBlock = buildClause(BuildScope(nodes.Current.Children, context, diagnostics)); // elif blocks proceed = nodes.MoveNext(); - var elifBlocks = new List>(); + var elifBlocks = new List>(); while (proceed && nodes.Current.Fragment.Kind is QsFragmentKind.ElifClause elifCond) { - buildClause = BuildStatement(nodes.Current, + buildClause = BuildStatement( + nodes.Current, (relPos, ctx) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, ctx, elifCond.Item), - context, diagnostics); + context.WithinIfCondition, + diagnostics); elifBlocks.Add(buildClause(BuildScope(nodes.Current.Children, context, diagnostics))); proceed = nodes.MoveNext(); } @@ -771,7 +1056,15 @@ private static bool TryBuildIfStatement(IEnumerator nodes proceed = nodes.MoveNext(); } - statement = Statements.NewIfStatement(ifBlock, elifBlocks, elseBlock); + var (ifStatement, ifDiagnostics) = + Statements.NewIfStatement(context, ifBlock.Item1, ifBlock.Item2, elifBlocks, elseBlock); + statement = ifStatement; + diagnostics.AddRange(ifDiagnostics.Select(item => + { + var (relativeOffset, diagnostic) = item; + var offset = DiagnosticTools.GetAbsolutePosition(rootPosition, relativeOffset); + return Diagnostics.Generate(context.Symbols.SourceFile.Value, diagnostic, offset); + })); return true; } (statement, proceed) = (null, true); @@ -792,13 +1085,29 @@ private static bool TryBuildIfStatement(IEnumerator nodes /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildConjugationStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildConjugationStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } QsNullable RelativeLocation(FragmentTree.TreeNode node) => QsNullable.NewValue(new QsLocation(DiagnosticTools.AsTuple(node.GetPositionRelativeToRoot()), node.Fragment.HeaderRange)); @@ -816,13 +1125,21 @@ QsNullable RelativeLocation(FragmentTree.TreeNode node) => var innerTransformation = BuildScope(nodes.Current.Children, context, diagnostics); var inner = new QsPositionedBlock(innerTransformation, RelativeLocation(nodes.Current), nodes.Current.Fragment.Comments); var built = Statements.NewConjugation(outer, inner); - diagnostics.AddRange(built.Item2.Select(msg => Diagnostics.Generate(context.Symbols.SourceFile.Value, msg.Item2, nodes.Current.GetRootPosition()))); + diagnostics.AddRange(built.Item2.Select(item => + { + var (relativeOffset, diagnostic) = item; + var offset = DiagnosticTools.GetAbsolutePosition(nodes.Current.GetRootPosition(), relativeOffset); + return Diagnostics.Generate(context.Symbols.SourceFile.Value, diagnostic, offset); + })); statement = built.Item1; proceed = nodes.MoveNext(); return true; } - else throw new ArgumentException("within-block needs to be followed by an apply-block"); + else + { + throw new ArgumentException("within-block needs to be followed by an apply-block"); + } } (statement, proceed) = (null, true); return false; @@ -842,19 +1159,37 @@ QsNullable RelativeLocation(FragmentTree.TreeNode node) => /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildLetStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildLetStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind is QsFragmentKind.ImmutableBinding letStatement) { - statement = BuildStatement(nodes.Current, + statement = BuildStatement( + nodes.Current, (relPos, ctx) => Statements.NewImmutableBinding(nodes.Current.Fragment.Comments, relPos, ctx, letStatement.Item1, letStatement.Item2), - context, diagnostics); + context, + diagnostics); proceed = nodes.MoveNext(); return true; } @@ -876,19 +1211,37 @@ private static bool TryBuildLetStatement(IEnumerator node /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildMutableStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildMutableStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind is QsFragmentKind.MutableBinding mutableStatement) { - statement = BuildStatement(nodes.Current, + statement = BuildStatement( + nodes.Current, (relPos, ctx) => Statements.NewMutableBinding(nodes.Current.Fragment.Comments, relPos, ctx, mutableStatement.Item1, mutableStatement.Item2), - context, diagnostics); + context, + diagnostics); proceed = nodes.MoveNext(); return true; } @@ -910,19 +1263,37 @@ private static bool TryBuildMutableStatement(IEnumerator /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildSetStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildSetStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind is QsFragmentKind.ValueUpdate setStatement) { - statement = BuildStatement(nodes.Current, + statement = BuildStatement( + nodes.Current, (relPos, ctx) => Statements.NewValueUpdate(nodes.Current.Fragment.Comments, relPos, ctx, setStatement.Item1, setStatement.Item2), - context, diagnostics); + context, + diagnostics); proceed = nodes.MoveNext(); return true; } @@ -944,19 +1315,37 @@ private static bool TryBuildSetStatement(IEnumerator node /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildFailStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildFailStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind is QsFragmentKind.FailStatement failStatement) { - statement = BuildStatement(nodes.Current, - (relPos, ctx) => Statements.NewFailStatement(nodes.Current.Fragment.Comments, relPos, ctx, failStatement.Item), - context, diagnostics); + statement = BuildStatement( + nodes.Current, + (relPos, ctx) => Statements.NewFailStatement(nodes.Current.Fragment.Comments, relPos, ctx, failStatement.Item), + context, + diagnostics); proceed = nodes.MoveNext(); return true; } @@ -978,19 +1367,37 @@ private static bool TryBuildFailStatement(IEnumerator nod /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildReturnStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildReturnStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind is QsFragmentKind.ReturnStatement returnStatement) { - statement = BuildStatement(nodes.Current, + statement = BuildStatement( + nodes.Current, (relPos, ctx) => Statements.NewReturnStatement(nodes.Current.Fragment.Comments, relPos, ctx, returnStatement.Item), - context, diagnostics); + context, + diagnostics); proceed = nodes.MoveNext(); return true; } @@ -1012,19 +1419,37 @@ private static bool TryBuildReturnStatement(IEnumerator n /// Throws an ArgumentNullException if any of the given arguments is null. /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// - private static bool TryBuildExpressionStatement(IEnumerator nodes, - ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) + private static bool TryBuildExpressionStatement( + IEnumerator nodes, + ScopeContext context, + List diagnostics, + out bool proceed, + out QsStatement statement) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } if (nodes.Current.Fragment.Kind is QsFragmentKind.ExpressionStatement expressionStatement) { - statement = BuildStatement(nodes.Current, + statement = BuildStatement( + nodes.Current, (relPos, ctx) => Statements.NewExpressionStatement(nodes.Current.Fragment.Comments, relPos, ctx, expressionStatement.Item), - context, diagnostics); + context, + diagnostics); proceed = nodes.MoveNext(); return true; } @@ -1044,91 +1469,133 @@ private static bool TryBuildExpressionStatement(IEnumerator BuildStatements( IEnumerator nodes, ScopeContext context, List diagnostics) { - if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); - if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); + if (nodes == null) + { + throw new ArgumentNullException(nameof(nodes)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Symbols.AllScopesClosed) + { + throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); + } + if (diagnostics == null) + { + throw new ArgumentException(nameof(diagnostics)); + } var proceed = nodes.MoveNext(); var statements = new List(); while (proceed) { if (nodes.Current.Fragment?.Kind == null) - { throw new ArgumentNullException(nameof(nodes.Current.Fragment), "fragment kind cannot be null"); } - + { + throw new ArgumentNullException(nameof(nodes.Current.Fragment), "fragment kind cannot be null"); + } else if (TryBuildExpressionStatement(nodes, context, diagnostics, out proceed, out QsStatement expressionStatement)) - { statements.Add(expressionStatement); } - + { + statements.Add(expressionStatement); + } else if (TryBuildLetStatement(nodes, context, diagnostics, out proceed, out QsStatement letStatement)) - { statements.Add(letStatement); } - + { + statements.Add(letStatement); + } else if (TryBuildMutableStatement(nodes, context, diagnostics, out proceed, out QsStatement mutableStatement)) - { statements.Add(mutableStatement); } - + { + statements.Add(mutableStatement); + } else if (TryBuildSetStatement(nodes, context, diagnostics, out proceed, out QsStatement setStatement)) - { statements.Add(setStatement); } - + { + statements.Add(setStatement); + } else if (TryBuildFailStatement(nodes, context, diagnostics, out proceed, out QsStatement failStatement)) - { statements.Add(failStatement); } - + { + statements.Add(failStatement); + } else if (TryBuildReturnStatement(nodes, context, diagnostics, out proceed, out QsStatement returnStatement)) - { statements.Add(returnStatement); } - + { + statements.Add(returnStatement); + } else if (TryBuildForStatement(nodes, context, diagnostics, out proceed, out QsStatement forStatement)) - { statements.Add(forStatement); } - + { + statements.Add(forStatement); + } else if (TryBuildWhileStatement(nodes, context, diagnostics, out proceed, out QsStatement whileStatement)) - { statements.Add(whileStatement); } - + { + statements.Add(whileStatement); + } else if (TryBuildIfStatement(nodes, context, diagnostics, out proceed, out QsStatement ifStatement)) - { statements.Add(ifStatement); } - + { + statements.Add(ifStatement); + } else if (TryBuildRepeatStatement(nodes, context, diagnostics, out proceed, out QsStatement repeatStatement)) - { statements.Add(repeatStatement); } - + { + statements.Add(repeatStatement); + } else if (TryBuildConjugationStatement(nodes, context, diagnostics, out proceed, out QsStatement conjugationStatement)) - { statements.Add(conjugationStatement); } - + { + statements.Add(conjugationStatement); + } else if (TryBuildBorrowStatement(nodes, context, diagnostics, out proceed, out QsStatement borrowingStatement)) - { statements.Add(borrowingStatement); } - + { + statements.Add(borrowingStatement); + } else if (TryBuildUsingStatement(nodes, context, diagnostics, out proceed, out QsStatement usingStatement)) - { statements.Add(usingStatement); } - + { + statements.Add(usingStatement); + } else if (nodes.Current.Fragment.Kind.IsInvalidFragment) - { proceed = nodes.MoveNext(); } - - else throw new ArgumentException($"node of kind {nodes.Current.Fragment.Kind} is not a valid statement header"); + { + proceed = nodes.MoveNext(); + } + else + { + throw new ArgumentException($"node of kind {nodes.Current.Fragment.Kind} is not a valid statement header"); + } } return statements.ToImmutableArray(); } /// - /// Builds the user defined implementation based on the (children of the) given specialization root. + /// Builds the user defined implementation based on the (children of the) given specialization root. /// The implementation takes the given argument tuple as argument and needs to support auto-generation for the specified set of functors. - /// Uses the given scope context to resolve the symbols used within the implementation, and generates suitable diagnostics in the process. - /// If necessary, generates suitable diagnostics for functor arguments (only!), which are discriminated by the missing position information - /// for argument variables defined in the callable declaration). - /// If the expected return type for the specialization is not Unit, verifies that all paths return a value or fail, generating suitable diagnostics. + /// Uses the given scope context to resolve the symbols used within the implementation, and generates suitable diagnostics in the process. + /// If necessary, generates suitable diagnostics for functor arguments (only!), which are discriminated by the missing position information + /// for argument variables defined in the callable declaration). + /// If the expected return type for the specialization is not Unit, verifies that all paths return a value or fail, generating suitable diagnostics. /// Adds the generated diagnostics to the given list of diagnostics. - /// Throws an ArgumentNullException if the given argument, the scope context, or diagnostics are null. + /// Throws an ArgumentNullException if the given argument, the scope context, or diagnostics are null. /// private static SpecializationImplementation BuildUserDefinedImplementation( FragmentTree.TreeNode root, - NonNullable sourceFile, + NonNullable sourceFile, QsTuple> argTuple, ImmutableHashSet requiredFunctorSupport, ScopeContext context, List diagnostics) { - if (argTuple == null) throw new ArgumentNullException(nameof(argTuple)); - if (context == null) throw new ArgumentNullException(nameof(context)); - if (requiredFunctorSupport == null) throw new ArgumentNullException(nameof(requiredFunctorSupport)); - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); + if (argTuple == null) + { + throw new ArgumentNullException(nameof(argTuple)); + } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (requiredFunctorSupport == null) + { + throw new ArgumentNullException(nameof(requiredFunctorSupport)); + } + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } // the variable defined on the declaration need to be verified upon building the callable (otherwise we get duplicate diagnostics), // but they need to be pushed *first* such that we get suitable re-declaration errors on those defined on the specialization - // -> the position information is set to null (only) for variables defined in the declaration + // -> the position information is set to null (only) for variables defined in the declaration var (variablesOnDeclation, variablesOnSpecialization) = SyntaxGenerator.ExtractItems(argTuple).Partition(decl => decl.Position.IsNull); context.Symbols.BeginScope(requiredFunctorSupport); @@ -1164,15 +1631,22 @@ private static SpecializationImplementation BuildUserDefinedImplementation( /// /// Given a function that returns the generator directive for a given specialization kind, or null if none has been defined for that kind, - /// determines the necessary functor support required for each operation call within a user defined implementation of the specified specialization. - /// Throws an ArgumentNullException if the given specialization for which to determine the required functor support is null, - /// or if the given function to query the directives is. + /// determines the necessary functor support required for each operation call within a user defined implementation of the specified specialization. + /// Throws an ArgumentNullException if the given specialization for which to determine the required functor support is null, + /// or if the given function to query the directives is. /// - private static IEnumerable RequiredFunctorSupport(QsSpecializationKind spec, - Func directives) + private static IEnumerable RequiredFunctorSupport( + QsSpecializationKind spec, + Func directives) { - if (spec == null) throw new ArgumentNullException(nameof(spec)); - if (directives == null) throw new ArgumentNullException(nameof(directives)); + if (spec == null) + { + throw new ArgumentNullException(nameof(spec)); + } + if (directives == null) + { + throw new ArgumentNullException(nameof(directives)); + } var adjDir = directives(QsSpecializationKind.QsAdjoint); var ctlDir = directives(QsSpecializationKind.QsControlled); @@ -1180,14 +1654,26 @@ private static IEnumerable RequiredFunctorSupport(QsSpecializationKin if (spec.IsQsBody && adjDir != null && !(adjDir.IsSelfInverse || adjDir.IsInvalidGenerator)) { - if (adjDir.IsInvert) yield return QsFunctor.Adjoint; - else QsCompilerError.Raise("expecting adjoint functor generator directive to be 'invert'"); + if (adjDir.IsInvert) + { + yield return QsFunctor.Adjoint; + } + else + { + QsCompilerError.Raise("expecting adjoint functor generator directive to be 'invert'"); + } } if (spec.IsQsBody && ctlDir != null && !ctlDir.IsInvalidGenerator) { - if (ctlDir.IsDistribute) yield return QsFunctor.Controlled; - else QsCompilerError.Raise("expecting controlled functor generator directive to be 'distribute'"); + if (ctlDir.IsDistribute) + { + yield return QsFunctor.Controlled; + } + else + { + QsCompilerError.Raise("expecting controlled functor generator directive to be 'distribute'"); + } } // since a directive for a controlled adjoint specialization requires an auto-generation based on either the adjoint or the controlled version @@ -1195,21 +1681,34 @@ private static IEnumerable RequiredFunctorSupport(QsSpecializationKin if (ctlAdjDir != null && !(ctlAdjDir.IsSelfInverse || ctlAdjDir.IsInvalidGenerator)) { if (ctlAdjDir.IsDistribute) - { if (spec.IsQsAdjoint || (spec.IsQsBody && adjDir != null)) yield return QsFunctor.Controlled; } + { + if (spec.IsQsAdjoint || (spec.IsQsBody && adjDir != null)) + { + yield return QsFunctor.Controlled; + } + } else if (ctlAdjDir.IsInvert) - { if (spec.IsQsControlled || (spec.IsQsBody && ctlDir != null)) yield return QsFunctor.Adjoint; } - else QsCompilerError.Raise("expecting controlled adjoint functor generator directive to be either 'invert' or 'distribute'"); + { + if (spec.IsQsControlled || (spec.IsQsBody && ctlDir != null)) + { + yield return QsFunctor.Adjoint; + } + } + else + { + QsCompilerError.Raise("expecting controlled adjoint functor generator directive to be either 'invert' or 'distribute'"); + } } } /// /// Given the root of a specialization declaration, the signature of the callable it belongs to, /// as well as the argument tuple of that callable, builds and returns the corresponding specialization. - /// If the given root is a callable declaration, a default body specialization with its children as the implementation is returned - - /// provided the children are exclusively valid statements. Fails with the corresponding exception otherwise. - /// Adds the generated diagnostics to the given list of diagnostics. - /// Throws an ArgumentNullException if the parent signature, its argument tuple, - /// the compilation unit, or the given list of diagnostics is null. + /// If the given root is a callable declaration, a default body specialization with its children as the implementation is returned - + /// provided the children are exclusively valid statements. Fails with the corresponding exception otherwise. + /// Adds the generated diagnostics to the given list of diagnostics. + /// Throws an ArgumentNullException if the parent signature, its argument tuple, + /// the compilation unit, or the given list of diagnostics is null. /// Throws an ArgumentException if the given root is neither a specialization declaration, nor a callable declaration, /// or if the callable the specialization belongs to does not support that specialization according to the given NamespaceManager. /// @@ -1221,50 +1720,87 @@ private static ImmutableArray BuildSpecializations( List diagnostics, CancellationToken cancellationToken) { - if (parentSignature == null) throw new ArgumentNullException(nameof(parentSignature)); - if (argTuple == null) throw new ArgumentNullException(nameof(argTuple)); - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); - if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); + if (parentSignature == null) + { + throw new ArgumentNullException(nameof(parentSignature)); + } + if (argTuple == null) + { + throw new ArgumentNullException(nameof(argTuple)); + } + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } + if (diagnostics == null) + { + throw new ArgumentNullException(nameof(diagnostics)); + } + if (cancellationToken.IsCancellationRequested) + { + return ImmutableArray.Empty; + } - if (cancellationToken.IsCancellationRequested) return ImmutableArray.Empty; var definedSpecs = compilation.GlobalSymbols.DefinedSpecializations(new QsQualifiedName(specsRoot.Namespace, specsRoot.Callable)) .ToLookup(s => s.Item2.Kind).ToImmutableDictionary( - specs => specs.Key, + specs => specs.Key, specs => QsCompilerError.RaiseOnFailure(specs.Single, "more than one specialization of the same kind exists")); // currently not supported - QsSpecialization GetSpecialization(SpecializationDeclarationHeader spec, ResolvedSignature signature, - SpecializationImplementation implementation, QsComments comments = null) => - new QsSpecialization(spec.Kind, spec.Parent, spec.Attributes, spec.SourceFile, QsNullable.Null, - spec.TypeArguments, SyntaxGenerator.WithoutRangeInfo(signature), implementation, spec.Documentation, comments ?? QsComments.Empty); - - QsSpecialization BuildSpecialization(QsSpecializationKind kind, ResolvedSignature signature, QsSpecializationGeneratorKind gen, FragmentTree.TreeNode root, - Func>, QsCompilerDiagnostic[]>> buildArg, QsComments comments = null) + QsSpecialization GetSpecialization( + SpecializationDeclarationHeader spec, + ResolvedSignature signature, + SpecializationImplementation implementation, + QsComments comments = null) => + new QsSpecialization( + spec.Kind, + spec.Parent, + spec.Attributes, + spec.SourceFile, + QsNullable.Null, + spec.TypeArguments, + SyntaxGenerator.WithoutRangeInfo(signature), + implementation, + spec.Documentation, + comments ?? QsComments.Empty); + + QsSpecialization BuildSpecialization( + QsSpecializationKind kind, + ResolvedSignature signature, + QsSpecializationGeneratorKind gen, + FragmentTree.TreeNode root, + Func>, QsCompilerDiagnostic[]>> buildArg, + QsComments comments = null) { if (!definedSpecs.TryGetValue(kind, out var defined)) + { throw new ArgumentException($"missing entry for {kind} specialization of {specsRoot.Namespace}.{specsRoot.Callable}"); + } var (directive, spec) = defined; var implementation = directive.IsValue ? SpecializationImplementation.NewGenerated(directive.Item) : null; // a user defined implementation is ignored if it is invalid to specify such (e.g. for self-adjoint or intrinsic operations) - if (implementation == null && gen is QsSpecializationGeneratorKind.UserDefinedImplementation userDefined) + if (implementation == null && gen is QsSpecializationGeneratorKind.UserDefinedImplementation userDefined) { var specPos = root.Fragment.GetRange().Start; var (arg, messages) = buildArg(userDefined.Item); - foreach (var msg in messages) diagnostics.Add(Diagnostics.Generate(spec.SourceFile.Value, msg, specPos)); + foreach (var msg in messages) + { + diagnostics.Add(Diagnostics.Generate(spec.SourceFile.Value, msg, specPos)); + } QsGeneratorDirective GetDirective(QsSpecializationKind k) => definedSpecs.TryGetValue(k, out defined) && defined.Item1.IsValue ? defined.Item1.Item : null; var requiredFunctorSupport = RequiredFunctorSupport(kind, GetDirective).ToImmutableHashSet(); var context = ScopeContext.Create( compilation.GlobalSymbols, compilation.RuntimeCapabilities, - compilation.ExecutionTarget, + compilation.ProcessorArchitecture, spec); implementation = BuildUserDefinedImplementation( root, spec.SourceFile, arg, requiredFunctorSupport, context, diagnostics); QsCompilerError.Verify(context.Symbols.AllScopesClosed, "all scopes should be closed"); } - implementation = implementation ?? SpecializationImplementation.Intrinsic; - return GetSpecialization(spec, signature, implementation, comments); + implementation = implementation ?? SpecializationImplementation.Intrinsic; + return GetSpecialization(spec, signature, implementation, comments); } var parentCharacteristics = parentSignature.Information.Characteristics; // note that if this one is invalid, the corresponding specializations are still compiled @@ -1272,84 +1808,137 @@ QsSpecialization BuildSpecialization(QsSpecializationKind kind, ResolvedSignatur var ctlArgPos = QsNullable>.NewValue(DiagnosticTools.AsTuple(new Position())); // position relative to the start of the specialization, i.e. zero-position var controlledSignature = SyntaxGenerator.BuildControlled(parentSignature); - QsSpecialization BuildSpec (FragmentTree.TreeNode root) + QsSpecialization BuildSpec(FragmentTree.TreeNode root) { - if (cancellationToken.IsCancellationRequested) return null; + if (cancellationToken.IsCancellationRequested) + { + return null; + } bool InvalidCharacteristicsOrSupportedFunctors(params QsFunctor[] functors) => parentCharacteristics.AreInvalid || !functors.Any(f => !supportedFunctors.Contains(f)); - if (!definedSpecs.Values.Any(d => d.Item2.Position is DeclarationHeader.Offset.Defined pos && DiagnosticTools.AsPosition(pos.Item) == root.Fragment.GetRange().Start)) return null; // only process specializations that are valid + if (!definedSpecs.Values.Any(d => d.Item2.Position is DeclarationHeader.Offset.Defined pos && DiagnosticTools.AsPosition(pos.Item) == root.Fragment.GetRange().Start)) + { + return null; // only process specializations that are valid + } - if (FileHeader.IsCallableDeclaration(root.Fragment)) // no specializations have been defined -> one default body + if (FileHeader.IsCallableDeclaration(root.Fragment)) { + // no specializations have been defined -> one default body var genArg = new QsSymbol(QsSymbolKind.OmittedSymbols, QsRangeInfo.Null); var defaultGen = QsSpecializationGeneratorKind.NewUserDefinedImplementation(genArg); return BuildSpecialization(QsSpecializationKind.QsBody, parentSignature, defaultGen, root, argTuple.BuildArgumentBody); } else if (root.Fragment.Kind is QsFragmentKind.BodyDeclaration bodyDecl) { - return BuildSpecialization(QsSpecializationKind.QsBody, parentSignature, bodyDecl.Item.Generator, root, - argTuple.BuildArgumentBody, root.Fragment.Comments); + return BuildSpecialization( + QsSpecializationKind.QsBody, + parentSignature, + bodyDecl.Item.Generator, + root, + argTuple.BuildArgumentBody, + root.Fragment.Comments); } else if (root.Fragment.Kind is QsFragmentKind.AdjointDeclaration adjDecl && InvalidCharacteristicsOrSupportedFunctors(QsFunctor.Adjoint)) { - return BuildSpecialization(QsSpecializationKind.QsAdjoint, parentSignature, adjDecl.Item.Generator, root, - argTuple.BuildArgumentAdjoint, root.Fragment.Comments); + return BuildSpecialization( + QsSpecializationKind.QsAdjoint, + parentSignature, + adjDecl.Item.Generator, + root, + argTuple.BuildArgumentAdjoint, + root.Fragment.Comments); } else if (root.Fragment.Kind is QsFragmentKind.ControlledDeclaration ctlDecl && InvalidCharacteristicsOrSupportedFunctors(QsFunctor.Controlled)) { - return BuildSpecialization(QsSpecializationKind.QsControlled, controlledSignature, ctlDecl.Item.Generator, root, - sym => argTuple.BuildArgumentControlled(sym, ctlArgPos), root.Fragment.Comments); + return BuildSpecialization( + QsSpecializationKind.QsControlled, + controlledSignature, + ctlDecl.Item.Generator, + root, + sym => argTuple.BuildArgumentControlled(sym, ctlArgPos), + root.Fragment.Comments); } else if (root.Fragment.Kind is QsFragmentKind.ControlledAdjointDeclaration ctlAdjDecl && InvalidCharacteristicsOrSupportedFunctors(QsFunctor.Adjoint, QsFunctor.Controlled)) { - return BuildSpecialization(QsSpecializationKind.QsControlledAdjoint, controlledSignature, ctlAdjDecl.Item.Generator, root, - sym => argTuple.BuildArgumentControlledAdjoint(sym, ctlArgPos), root.Fragment.Comments); + return BuildSpecialization( + QsSpecializationKind.QsControlledAdjoint, + controlledSignature, + ctlAdjDecl.Item.Generator, + root, + sym => argTuple.BuildArgumentControlledAdjoint(sym, ctlArgPos), + root.Fragment.Comments); + } + else + { + throw new ArgumentException("the given implementation root is not a valid specialization declaration"); } - else throw new ArgumentException("the given implementation root is not a valid specialization declaration"); } // build the specializations defined in the source code var existing = ImmutableArray.CreateBuilder(); existing.AddRange(specsRoot.Specializations.Select(BuildSpec).Where(s => s != null)); - if (cancellationToken.IsCancellationRequested) return existing.ToImmutableArray(); + if (cancellationToken.IsCancellationRequested) + { + return existing.ToImmutableArray(); + } // additionally we need to build the specializations that are missing in the source code var existingKinds = existing.Select(t => t.Kind).ToImmutableHashSet(); foreach (var (directive, spec) in definedSpecs.Values) { - if (existingKinds.Contains(spec.Kind)) continue; + if (existingKinds.Contains(spec.Kind)) + { + continue; + } var implementation = directive.IsValue ? SpecializationImplementation.NewGenerated(directive.Item) : SpecializationImplementation.Intrinsic; if (spec.Kind.IsQsBody || spec.Kind.IsQsAdjoint) - { existing.Add(GetSpecialization(spec, parentSignature, implementation)); } + { + existing.Add(GetSpecialization(spec, parentSignature, implementation)); + } else if (spec.Kind.IsQsControlled || spec.Kind.IsQsControlledAdjoint) - { existing.Add(GetSpecialization(spec, controlledSignature, implementation)); } - else QsCompilerError.Raise("unknown functor kind"); + { + existing.Add(GetSpecialization(spec, controlledSignature, implementation)); + } + else + { + QsCompilerError.Raise("unknown functor kind"); + } } return existing.ToImmutableArray(); } - - // externally accessible routines for compilation evaluation + // externally accessible routines for compilation evaluation /// /// Given access to a NamespaceManager containing all global declarations and their resolutions via a CompilationUnit, /// type checks each of the given FragmentTrees until the process is cancelled via the given cancellation token. /// For each namespace and callable name that occurs in the given FragmentTrees builds the corresponding QsCallable. - /// Updates the given CompilationUnit with all built callables. Checks all types defined in the NamespaceManager for cycles. + /// Updates the given CompilationUnit with all built callables. Checks all types defined in the NamespaceManager for cycles. /// Returns a list with all accumulated diagnostics. If the request has been cancelled, returns null. - /// Throws an ArgumentNullException if any of the arguments is null. + /// Throws an ArgumentNullException if any of the arguments is null. /// - internal static List RunTypeChecking (CompilationUnit compilation, - ImmutableDictionary roots, CancellationToken cancellationToken) + internal static List RunTypeChecking( + CompilationUnit compilation, + ImmutableDictionary roots, + CancellationToken cancellationToken) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); - if (roots == null) throw new ArgumentNullException(nameof(roots)); - if (cancellationToken == null) throw new ArgumentNullException(nameof(cancellationToken)); + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } + if (roots == null) + { + throw new ArgumentNullException(nameof(roots)); + } + if (cancellationToken == null) + { + throw new ArgumentNullException(nameof(cancellationToken)); + } var diagnostics = new List(); compilation.EnterUpgradeableReadLock(); @@ -1377,17 +1966,20 @@ internal static List RunTypeChecking (CompilationUnit compilation, : throw new ArgumentException("callable to build is no longer present in the given NamespaceManager"); }); - (QsQualifiedName, ImmutableArray) GetSpecializations - (KeyValuePair specsRoot) + (QsQualifiedName, ImmutableArray) GetSpecializations( + KeyValuePair specsRoot) { var info = callableDeclarations[specsRoot.Key]; var specs = BuildSpecializations(specsRoot.Value.Item2, info.Signature, info.ArgumentTuple, compilation, diagnostics, cancellationToken); - return (specsRoot.Key, specs); + return (specsRoot.Key, specs); } QsCallable GetCallable((QsQualifiedName, ImmutableArray) specItems) { - if (cancellationToken.IsCancellationRequested) return null; + if (cancellationToken.IsCancellationRequested) + { + return null; + } var (parent, specs) = specItems; var info = callableDeclarations[parent]; var declaredVariables = SyntaxGenerator.ExtractItems(info.ArgumentTuple); @@ -1411,13 +2003,12 @@ QsCallable GetCallable((QsQualifiedName, ImmutableArray) specI info.Attributes, info.Modifiers, info.SourceFile, - QsNullable.Null, + QsNullable.Null, info.Signature, info.ArgumentTuple, specs, info.Documentation, - roots[parent].Item1 - ); + roots[parent].Item1); } var callables = callableRoots.Select(GetSpecializations).Select(GetCallable).ToImmutableArray(); @@ -1430,41 +2021,53 @@ QsCallable GetCallable((QsQualifiedName, ImmutableArray) specI decl.Value.Type, decl.Value.TypeItems, decl.Value.Documentation, - roots[decl.Key].Item1 - )).ToImmutableArray(); + roots[decl.Key].Item1)) + .ToImmutableArray(); - if (cancellationToken.IsCancellationRequested) return null; + if (cancellationToken.IsCancellationRequested) + { + return null; + } compilation.UpdateCallables(callables); compilation.UpdateTypes(types); return diagnostics; } - finally { compilation.ExitUpgradeableReadLock(); } + finally + { + compilation.ExitUpgradeableReadLock(); + } } /// /// This method serves two entirely independent purposes since they require the exact same logic, despite what I would usually consider good practice. /// The first purpose is the following: - /// Returns all locally declared symbols visible at the given relative position, + /// Returns all locally declared symbols visible at the given relative position, /// assuming that the position corresponds to a piece of code within the given scope. - /// If includeDeclaredAtPosition is set to true, then this includes the symbols declared within the statement at the specified position, - /// even if those symbols are *not* visible after the statement ends (e.g. for-loops or qubit allocations). + /// If includeDeclaredAtPosition is set to true, then this includes the symbols declared within the statement at the specified position, + /// even if those symbols are *not* visible after the statement ends (e.g. for-loops or qubit allocations). /// Note that if the given position does not correspond to a piece of code but rather to whitespace possibly after a scope ending, - /// the returned declarations are not necessarily accurate - they are for any actual piece of code, though. + /// the returned declarations are not necessarily accurate - they are for any actual piece of code, though. /// The second purpose is the following: - /// Returns the statements that follow a local declaration at the given relative position, - /// i.e. the statements for which local variables declared at that position are defined. - /// Whether the given relative position is indeed within a statement that declares local variables is not verified. - /// It is important to note that the returned statements are *not* necessarily the set of statements - /// for which the returned set of local variables are valid! + /// Returns the statements that follow a local declaration at the given relative position, + /// i.e. the statements for which local variables declared at that position are defined. + /// Whether the given relative position is indeed within a statement that declares local variables is not verified. + /// It is important to note that the returned statements are *not* necessarily the set of statements + /// for which the returned set of local variables are valid! /// The given relative position is expected to be relative to the beginning of the specialization declaration - - /// or rather to be consistent with the position information saved for statements. + /// or rather to be consistent with the position information saved for statements. /// Throws an ArgumentException if any of the statements contained in the given scope is not annotated with a valid position, /// or if the given relative position is not a valid position. /// private static (LocalDeclarations, IEnumerable) StatementsAfterAndLocalDeclarationsAt(this QsScope scope, Position relativePosition, bool includeDeclaredAtPosition) { - if (scope == null) throw new ArgumentNullException(nameof(scope)); - if (!Utils.IsValidPosition(relativePosition)) throw new ArgumentException(nameof(relativePosition)); + if (scope == null) + { + throw new ArgumentNullException(nameof(scope)); + } + if (!Utils.IsValidPosition(relativePosition)) + { + throw new ArgumentException(nameof(relativePosition)); + } LocalDeclarations Concat(LocalDeclarations fst, LocalDeclarations snd) => new LocalDeclarations(fst.Variables.Concat(snd.Variables).ToImmutableArray()); @@ -1473,23 +2076,33 @@ bool BeforePosition(QsNullable location) => bool StartsBeforePosition(QsScope body) => body.Statements.Any() && BeforePosition(body.Statements.First().Location); var precedingStatements = scope.Statements.TakeWhile(stm => BeforePosition(stm.Location)).ToImmutableArray(); - if (precedingStatements.Length == 0) return (scope.KnownSymbols, scope.Statements); - var lastPreceding = precedingStatements[precedingStatements.Length-1]; + if (precedingStatements.Length == 0) + { + return (scope.KnownSymbols, scope.Statements); + } + var lastPreceding = precedingStatements[precedingStatements.Length - 1]; QsScope relevantScope = null; if (lastPreceding.Statement is QsStatementKind.QsConditionalStatement condStatement) { var blocks = condStatement.Item.ConditionalBlocks.Select(block => block.Item2); var elseBlock = condStatement.Item.Default.ValueOr(null); - if (elseBlock != null) blocks = blocks.Concat(new[] { elseBlock }); + if (elseBlock != null) + { + blocks = blocks.Concat(new[] { elseBlock }); + } var preceding = blocks.TakeWhile(block => BeforePosition(block.Location)); relevantScope = preceding.Any() ? preceding.Last().Body : null; } if (lastPreceding.Statement is QsStatementKind.QsForStatement forStatement) - { relevantScope = forStatement.Item.Body; } + { + relevantScope = forStatement.Item.Body; + } if (lastPreceding.Statement is QsStatementKind.QsWhileStatement whileStatement) - { relevantScope = whileStatement.Item.Body; } + { + relevantScope = whileStatement.Item.Body; + } if (lastPreceding.Statement is QsStatementKind.QsRepeatStatement repeatStatement) { var allContainedStatements = repeatStatement.Item.RepeatBlock.Body.Statements.Concat(repeatStatement.Item.FixupBlock.Body.Statements).ToImmutableArray(); @@ -1502,12 +2115,17 @@ bool BeforePosition(QsNullable location) => : conjugation.Item.OuterTransformation.Body; } if (lastPreceding.Statement is QsStatementKind.QsQubitScope allocationScope) - { relevantScope = allocationScope.Item.Body; } + { + relevantScope = allocationScope.Item.Body; + } - if (relevantScope != null && StartsBeforePosition(relevantScope)) // the relative position is truly within the child scope - { return relevantScope.StatementsAfterAndLocalDeclarationsAt(relativePosition, includeDeclaredAtPosition); } + if (relevantScope != null && StartsBeforePosition(relevantScope)) + { + // the relative position is truly within the child scope + return relevantScope.StatementsAfterAndLocalDeclarationsAt(relativePosition, includeDeclaredAtPosition); + } - LocalDeclarations AggregateLocalDeclarations(IEnumerable stms) => + LocalDeclarations AggregateLocalDeclarations(IEnumerable stms) => stms.Aggregate(scope.KnownSymbols, (decl, statement) => Concat(decl, statement.SymbolDeclarations)); var symbols = includeDeclaredAtPosition ? relevantScope != null ? relevantScope.KnownSymbols : AggregateLocalDeclarations(precedingStatements) @@ -1519,11 +2137,11 @@ LocalDeclarations AggregateLocalDeclarations(IEnumerable stms) => } /// - /// Returns the statements that follow a local declaration at the given relative position, - /// i.e. the statements for which local variables declared at that position are defined. - /// Whether the given relative position is indeed within a statement that declares local variables is not verified. + /// Returns the statements that follow a local declaration at the given relative position, + /// i.e. the statements for which local variables declared at that position are defined. + /// Whether the given relative position is indeed within a statement that declares local variables is not verified. /// The given relative position is expected to be relative to the beginning of the specialization declaration - - /// or rather to be consistent with the position information saved for statements. + /// or rather to be consistent with the position information saved for statements. /// Throws an ArgumentException if any of the statements contained in the given scope is not annotated with a valid position, /// or if the given relative position is not a valid position. /// @@ -1531,14 +2149,14 @@ internal static IEnumerable StatementsAfterDeclaration(this QsScope StatementsAfterAndLocalDeclarationsAt(scope, relativePosition, false).Item2; /// - /// Returns all locally declared symbols visible at the given relative position, - /// assuming that the position corresponds to a piece of code within the given scope. - /// If includeDeclaredAtPosition is set to true, then this includes the symbols declared within the statement at the specified position, - /// even if those symbols are *not* visible after the statement ends (e.g. for-loops or qubit allocations). + /// Returns all locally declared symbols visible at the given relative position, + /// assuming that the position corresponds to a piece of code within the given scope. + /// If includeDeclaredAtPosition is set to true, then this includes the symbols declared within the statement at the specified position, + /// even if those symbols are *not* visible after the statement ends (e.g. for-loops or qubit allocations). /// The given relative position is expected to be relative to the beginning of the specialization declaration - - /// or rather to be consistent with the position information saved for statements. + /// or rather to be consistent with the position information saved for statements. /// Note that if the given position does not correspond to a piece of code but rather to whitespace possibly after a scope ending, - /// the returned declarations are not necessarily accurate - they are for any actual piece of code, though. + /// the returned declarations are not necessarily accurate - they are for any actual piece of code, though. /// Throws an ArgumentException if any of the statements contained in the given scope is not annotated with a valid position, /// or if the given relative position is not a valid position. /// @@ -1546,11 +2164,11 @@ internal static LocalDeclarations LocalDeclarationsAt(this QsScope scope, Positi StatementsAfterAndLocalDeclarationsAt(scope, relativePosition, includeDeclaredAtPosition).Item1; /// - /// Returns all locally declared symbols visible at the given relative position, - /// assuming that the position corresponds to a piece of code within the given scope. + /// Returns all locally declared symbols visible at the given relative position, + /// assuming that the position corresponds to a piece of code within the given scope. /// The given relative position is expected to be relative to the beginning of the specialization declaration - - /// or rather to be consistent with the position information saved for statements. - /// If the given position lays outside a piece of code e.g. after a scope ending the returned declarations may be inaccurate. + /// or rather to be consistent with the position information saved for statements. + /// If the given position lays outside a piece of code e.g. after a scope ending the returned declarations may be inaccurate. /// Throws an ArgumentException if any of the statements contained in the given scope is not annotated with a valid position, /// or if the given relative position is not a valid position. /// @@ -1564,14 +2182,20 @@ public static LocalDeclarations LocalDeclarationsAt(this QsScope scope, Position /// directly proceeds to do the type checking for the content of the callable that has been modified only. /// If the globally declared types/callables have not changed, but the imported namespaces have, /// directly proceeds to do the type checking for the entire file content, independent on what parts have been changed. - /// If the globally declared types/callables have changed, a global type checking event is triggered, - /// since the type checking for the entire compilation unit and all compilation units depending on it needs to be recomputed. - /// Throws an ArgumentNullException if the given file or compilation unit is null. + /// If the globally declared types/callables have changed, a global type checking event is triggered, + /// since the type checking for the entire compilation unit and all compilation units depending on it needs to be recomputed. + /// Throws an ArgumentNullException if the given file or compilation unit is null. /// internal static void UpdateTypeChecking(this FileContentManager file, CompilationUnit compilation) { - if (file == null) throw new ArgumentNullException(nameof(file)); - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } compilation.EnterWriteLock(); file.SyncRoot.EnterUpgradeableReadLock(); @@ -1590,22 +2214,31 @@ internal static void UpdateTypeChecking(this FileContentManager file, Compilatio file.ReplaceHeaderDiagnostics(diagnostics); if (!sameHeader) - { file.TriggerGlobalTypeChecking(); return; } + { + file.TriggerGlobalTypeChecking(); + return; + } var declarationTrees = file.GetDeclarationTrees( - sameHeader && sameImports + sameHeader && sameImports ? contentTokens.Where(element => editedCallables.Contains(element.Key)).ToImmutableDictionary() : contentTokens); - diagnostics = QsCompilerError.RaiseOnFailure(() => RunTypeChecking(compilation, declarationTrees, new CancellationToken()), "error on running type checking"); - if (sameImports) file.AddAndFinalizeSemanticDiagnostics(diagnostics); // diagnostics have been cleared already for the edited callables (only) - else file.ReplaceSemanticDiagnostics(diagnostics); + diagnostics = QsCompilerError.RaiseOnFailure(() => RunTypeChecking(compilation, declarationTrees, CancellationToken.None), "error on running type checking"); + if (sameImports) + { + file.AddAndFinalizeSemanticDiagnostics(diagnostics); // diagnostics have been cleared already for the edited callables (only) + } + else + { + file.ReplaceSemanticDiagnostics(diagnostics); + } } - finally { + finally + { file.SyncRoot.ExitUpgradeableReadLock(); compilation.ExitWriteLock(); } } - } } diff --git a/src/QsCompiler/CompilationManager/Utils.cs b/src/QsCompiler/CompilationManager/Utils.cs index 6303ffe610..bfd579487f 100644 --- a/src/QsCompiler/CompilationManager/Utils.cs +++ b/src/QsCompiler/CompilationManager/Utils.cs @@ -9,59 +9,66 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { public static class Utils { - // characters interpreted as line breaks by Visual Studio: + // characters interpreted as line breaks by Visual Studio: /// /// unicode for '\n' /// internal const string LF = "\u000A"; + /// /// unicode for '\r' /// internal const string CR = "\u000D"; // officially only giving a line break in combination with subsequent \n, but also causes a line break on its own... + /// /// unicode for a line separator char /// internal const string LS = "\u2028"; + /// /// unicode for a paragraph separator char /// internal const string PS = "\u2029"; + /// /// unicode for a next line char /// internal const string NEL = "\u0085"; + /// /// contains the regex string that matches any char that is not a linebreak /// private static readonly string NonBreakingChar = $"[^{LF}{CR}{LS}{PS}{NEL}]"; + /// /// contains the regex string that matches a line break recognized by VisualStudio /// private static readonly string LineBreak = $"{CR}{LF}|{LF}|{CR}|{LS}|{PS}|{NEL}"; - // utils related to tracking the text content of files /// /// matches everything that could could be used as a symbol /// internal static readonly Regex ValidAsSymbol = new Regex(@"^[\p{L}_]([\p{L}\p{Nd}_]*)$"); + /// /// matches qualified symbols before the starting position (right-to-left), including incomplete qualified /// symbols that end with a dot /// internal static readonly Regex QualifiedSymbolRTL = new Regex(@"([\p{L}_][\p{L}\p{Nd}_]*\.?)+", RegexOptions.RightToLeft); + /// /// matches a line and its line ending, and a *non-empty* line without line ending at the end /// private static readonly Regex EditorLine = new Regex($"({NonBreakingChar}*({LineBreak}))|({NonBreakingChar}+$)"); + /// /// matches a CR, LF, or CRLF occurence at the end of a string /// @@ -76,28 +83,42 @@ public static string[] SplitLines(string text) var lines = new string[matches.Count]; var found = matches.GetEnumerator(); for (var i = 0; found.MoveNext(); i += 1) - { lines[i] = ((Match)found.Current).Value; } + { + lines[i] = ((Match)found.Current).Value; + } return lines; } /// - /// to be used as "counter-piece" to SplitLines + /// to be used as "counter-piece" to SplitLines /// public static string JoinLines(string[] content) => - content == null ? null : String.Join("", content); // *DO NOT MODIFY* how lines are joined - the compiler functionality depends on it! + content == null ? null : string.Join("", content); // *DO NOT MODIFY* how lines are joined - the compiler functionality depends on it! /// /// Given a string, replaces the range [starChar, endChar) with the given string to insert. /// Returns null if the given text is null. /// Throws an ArgumentNullException if the given text to insert is null. - /// Throws an ArgumentOutOfRangeException if the given start and end points do not denote a valid range within the string. + /// Throws an ArgumentOutOfRangeException if the given start and end points do not denote a valid range within the string. /// internal static string GetChangedText(string lineText, int startChar, int endChar, string insert) { - if (lineText == null) return null; - if (insert == null) throw new ArgumentNullException(nameof(insert)); - if (startChar < 0 || startChar > lineText.Length) throw new ArgumentOutOfRangeException(nameof(startChar)); - if (endChar < startChar || endChar > lineText.Length) throw new ArgumentOutOfRangeException(nameof(endChar)); + if (lineText == null) + { + return null; + } + if (insert == null) + { + throw new ArgumentNullException(nameof(insert)); + } + if (startChar < 0 || startChar > lineText.Length) + { + throw new ArgumentOutOfRangeException(nameof(startChar)); + } + if (endChar < startChar || endChar > lineText.Length) + { + throw new ArgumentOutOfRangeException(nameof(endChar)); + } return lineText.Remove(startChar, endChar - startChar).Insert(startChar, insert); } @@ -108,17 +129,22 @@ internal static string GetChangedText(string lineText, int startChar, int endCha /// internal static string GetTextChangedLines(FileContentManager file, TextDocumentContentChangeEvent change) { - if (!Utils.IsValidRange(change.Range, file)) throw new ArgumentOutOfRangeException(nameof(change)); // range can be empty - if (change.Text == null) throw new ArgumentNullException(nameof(change.Text), "the given text change is null"); + if (!IsValidRange(change.Range, file)) + { + throw new ArgumentOutOfRangeException(nameof(change)); // range can be empty + } + if (change.Text == null) + { + throw new ArgumentNullException(nameof(change.Text), "the given text change is null"); + } var first = file.GetLine(change.Range.Start.Line).Text; var last = file.GetLine(change.Range.End.Line).Text; var prepend = first.Substring(0, change.Range.Start.Character); var append = last.Substring(change.Range.End.Character); - return String.Concat(prepend, change.Text, append); + return string.Concat(prepend, change.Text, append); } - // general purpose type extensions /// @@ -126,9 +152,12 @@ internal static string GetTextChangedLines(FileContentManager file, TextDocument /// Returns (null, null) if the given IEnumerable is null. /// Throws an ArgumentNullException if predicate is null. /// - public static (List, List) Partition(this IEnumerable collection, Func predicate) + public static (List, List) Partition(this IEnumerable collection, Func predicate) { - if (predicate == null) throw new ArgumentNullException(nameof(predicate)); + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } return (collection?.Where(predicate).ToList(), collection?.Where(x => !predicate(x)).ToList()); } @@ -138,11 +167,13 @@ public static (List, List) Partition(this IEnumerable collection, Fu /// public static bool IsAtLeastReadLockHeld(this ReaderWriterLockSlim syncRoot) { - if (syncRoot == null) throw new ArgumentNullException(nameof(syncRoot)); + if (syncRoot == null) + { + throw new ArgumentNullException(nameof(syncRoot)); + } return syncRoot.IsReadLockHeld || syncRoot.IsUpgradeableReadLockHeld || syncRoot.IsWriteLockHeld; } - // utils for dealing with positions and ranges /// @@ -151,18 +182,24 @@ public static bool IsAtLeastReadLockHeld(this ReaderWriterLockSlim syncRoot) /// public static bool IsValidPosition(Position pos) { - if (pos == null) throw new ArgumentNullException(nameof(pos)); + if (pos == null) + { + throw new ArgumentNullException(nameof(pos)); + } return pos.Line >= 0 && pos.Character >= 0; } /// - /// Returns true if the given position is valid, i.e. if the line is within the given file, + /// Returns true if the given position is valid, i.e. if the line is within the given file, /// and the character is within the text on that line (including text.Length). /// Throws an ArgumentNullException is an argument is null. /// internal static bool IsValidPosition(Position pos, FileContentManager file) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } return IsValidPosition(pos) && pos.Line < file.NrLines() && pos.Character <= file.GetLine(pos.Line).Text.Length; } @@ -173,7 +210,10 @@ internal static bool IsValidPosition(Position pos, FileContentManager file) /// internal static bool IsSmallerThan(this Position first, Position second) { - if (!IsValidPosition(first) || !IsValidPosition(second)) throw new ArgumentException("invalid position(s) given for comparison"); + if (!IsValidPosition(first) || !IsValidPosition(second)) + { + throw new ArgumentException("invalid position(s) given for comparison"); + } return first.Line < second.Line || (first.Line == second.Line && first.Character < second.Character); } @@ -192,21 +232,27 @@ internal static bool IsSmallerThanOrEqualTo(this Position first, Position second /// internal static bool Overlaps(this LSP.Range range1, LSP.Range range2) { - if (!IsValidRange(range1) || !IsValidRange(range2)) throw new ArgumentException("invalid range given for comparison"); + if (!IsValidRange(range1) || !IsValidRange(range2)) + { + throw new ArgumentException("invalid range given for comparison"); + } var (first, second) = range1.Start.IsSmallerThan(range2.Start) ? (range1, range2) : (range2, range1); return second.Start.IsSmallerThan(first.End); } /// /// Verifies the given position and range, and returns true if the given position lays within the given range. - /// If includeEnd is true then the end point of the range is considered to be part of the range, + /// If includeEnd is true then the end point of the range is considered to be part of the range, /// otherwise the range is considered to include the start but excludes the end point. /// Throws an ArgumentNullException if the given position or range is null. /// Throws an ArgumentException if the given position or range is not valid. /// internal static bool IsWithinRange(this Position pos, LSP.Range range, bool includeEnd = false) { - if (!IsValidPosition(pos) || !IsValidRange(range)) throw new ArgumentException("invalid position or range given for comparison"); + if (!IsValidPosition(pos) || !IsValidRange(range)) + { + throw new ArgumentException("invalid position or range given for comparison"); + } return range.Start.IsSmallerThanOrEqualTo(pos) && (includeEnd ? pos.IsSmallerThanOrEqualTo(range.End) : pos.IsSmallerThan(range.End)); } @@ -215,15 +261,15 @@ internal static bool IsWithinRange(this Position pos, LSP.Range range, bool incl /// Throws an ArgumentNullException if an argument is null. /// public static bool IsValidRange(LSP.Range range) => - IsValidPosition(range?.Start) && IsValidPosition(range.End) && range.Start.IsSmallerThanOrEqualTo(range.End); + IsValidPosition(range?.Start) && IsValidPosition(range.End) && range.Start.IsSmallerThanOrEqualTo(range.End); /// - /// Returns true if the given range is valid, + /// Returns true if the given range is valid, /// i.e. if both start and end are valid positions within the given file, and start is smaller than or equal to end. /// Throws an ArgumentNullException if an argument is null. /// internal static bool IsValidRange(LSP.Range range, FileContentManager file) => - IsValidPosition(range?.Start, file) && IsValidPosition(range.End, file) && range.Start.IsSmallerThanOrEqualTo(range.End); + IsValidPosition(range?.Start, file) && IsValidPosition(range.End, file) && range.Start.IsSmallerThanOrEqualTo(range.End); /// /// Returns the absolute position under the assumption that snd is relative to fst and both positions are zero-based. @@ -232,8 +278,14 @@ internal static bool IsValidRange(LSP.Range range, FileContentManager file) => /// internal static Position Add(this Position fst, Position snd) { - if (!IsValidPosition(fst)) throw new ArgumentException(nameof(fst)); - if (!IsValidPosition(snd)) throw new ArgumentException(nameof(snd)); + if (!IsValidPosition(fst)) + { + throw new ArgumentException(nameof(fst)); + } + if (!IsValidPosition(snd)) + { + throw new ArgumentException(nameof(snd)); + } return new Position(fst.Line + snd.Line, snd.Line == 0 ? fst.Character + snd.Character : snd.Character); } @@ -244,15 +296,23 @@ internal static Position Add(this Position fst, Position snd) /// internal static Position Subtract(this Position fst, Position snd) { - if (!IsValidPosition(fst)) throw new ArgumentException(nameof(fst)); - if (!IsValidPosition(snd)) throw new ArgumentException(nameof(snd)); - if (fst.IsSmallerThan(snd)) throw new ArgumentException(nameof(snd), "the position to subtract from needs to be larger than the position to subract"); + if (!IsValidPosition(fst)) + { + throw new ArgumentException(nameof(fst)); + } + if (!IsValidPosition(snd)) + { + throw new ArgumentException(nameof(snd)); + } + if (fst.IsSmallerThan(snd)) + { + throw new ArgumentException(nameof(snd), "the position to subtract from needs to be larger than the position to subract"); + } var relPos = new Position(fst.Line - snd.Line, fst.Line == snd.Line ? fst.Character - snd.Character : fst.Character); QsCompilerError.Verify(snd.Add(relPos).Equals(fst), "adding the relative position to snd does not equal fst"); return relPos; } - // tools for debugging public static string DiagnosticString(this LSP.Range r) => @@ -260,18 +320,19 @@ public static string DiagnosticString(this LSP.Range r) => internal static string DiagnosticString(FileContentManager file) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } var annotatedContent = new string[file.NrLines()]; for (var lineNr = 0; lineNr < file.NrLines(); ++lineNr) { var line = file.GetLine(lineNr); - //var ending = line.LineEnding == "\r\n" ? "CRLF" : line.LineEnding == "\n" ? "LF" : line.LineEnding == "\r" ? "CR" : "-"; - //var relevantText = ScopeTracking.RelevantCode(line); - var delimString = "[" + String.Join(",", line.StringDelimiters) + "] "; - var prefix = delimString + String.Concat(Enumerable.Repeat("*", line.ExcessBracketPositions.Count())); + var delimString = "[" + string.Join(",", line.StringDelimiters) + "] "; + var prefix = delimString + string.Concat(Enumerable.Repeat("*", line.ExcessBracketPositions.Count())); annotatedContent[lineNr] = $"{prefix}i{line.Indentation}: {line.WithoutEnding}"; } - return Utils.JoinLines(annotatedContent); + return JoinLines(annotatedContent); } } } diff --git a/src/QsCompiler/Compiler/CompilationLoader.cs b/src/QsCompiler/Compiler/CompilationLoader.cs index c25ee83926..71a7b8bc4f 100644 --- a/src/QsCompiler/Compiler/CompilationLoader.cs +++ b/src/QsCompiler/Compiler/CompilationLoader.cs @@ -20,11 +20,10 @@ using Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations; using Microsoft.VisualStudio.LanguageServer.Protocol; using Newtonsoft.Json.Bson; +using static Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants; using MetadataReference = Microsoft.CodeAnalysis.MetadataReference; using OptimizationLevel = Microsoft.CodeAnalysis.OptimizationLevel; -using static Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants; - namespace Microsoft.Quantum.QsCompiler { @@ -50,9 +49,9 @@ public class CompilationTaskEventArgs : EventArgs public CompilationTaskEventArgs(CompilationTaskEventType type, string parentTaskName, string taskName) { - ParentTaskName = parentTaskName; - TaskName = taskName; - Type = type; + this.ParentTaskName = parentTaskName; + this.TaskName = taskName; + this.Type = type; } } @@ -60,28 +59,32 @@ public CompilationTaskEventArgs(CompilationTaskEventType type, string parentTask /// Defines the handler for compilation task events. /// public delegate void CompilationTaskEventHandler(object sender, CompilationTaskEventArgs args); + /// /// Given a load function that loads the content of a sequence of files from disk, /// returns the content for all sources to compile. /// public delegate ImmutableDictionary SourceLoader(Func, ImmutableDictionary> loadFromDisk); + /// /// Given a load function that loads the content of a sequence of referenced assemblies from disk, /// returns the loaded references for the compilation. /// public delegate References ReferenceLoader(Func, References> loadFromDisk); + /// /// Used to raise a compilation task event. /// public static event CompilationTaskEventHandler CompilationTaskEvent; + /// /// If LoadAssembly is not null, it will be used to load the dlls that are search for classes defining rewrite steps. /// public static Func LoadAssembly { get; set; } - /// - /// Sorts the given list of step according to their relative priority give by getPriority. - /// Throws the corresponding exception if is null. + /// + /// Sorts the given list of step according to their relative priority give by getPriority. + /// Throws the corresponding exception if is null. /// internal static void SortRewriteSteps(List steps, Func getPriority) => steps?.Sort((fst, snd) => getPriority(snd) - getPriority(fst)); @@ -96,74 +99,88 @@ public struct Configuration /// The name of the project with a suitable extension will also be used as the name of the generated binary file. /// public string ProjectName; + /// /// If set to true, the syntax tree rewrite step that replaces all generation directives /// for all functor specializations is executed during compilation. /// public bool GenerateFunctorSupport; + /// /// Unless this is set to true, the syntax tree rewrite step that eliminates selective abstractions is executed during compilation. /// In particular, all conjugations are inlined. /// public bool SkipSyntaxTreeTrimming; + /// /// If set to true, the compiler attempts to pre-evaluate the built compilation as much as possible. /// This is an experimental feature that will change over time. /// public bool AttemptFullPreEvaluation; + /// - /// Specifies the capabilities of the runtime. + /// Specifies the capabilities of the runtime. /// The specified capabilities determine what QIR profile to compile to. /// public RuntimeCapabilities RuntimeCapabilities; + /// - /// Specifies whether the project to build is a Q# command line application. - /// If set to true, a warning will be raised if no entry point is defined. + /// Specifies whether the project to build is a Q# command line application. + /// If set to true, a warning will be raised if no entry point is defined. /// If set to false, then defined entry points will be ignored and a warning will be raised. /// public bool IsExecutable; + /// /// Unless this is set to true, all usages of type-parameterized callables are replaced with /// the concrete callable instantiation if an entry point is specified for the compilation. /// Removes all type-parameterizations in the syntax tree. /// public bool SkipMonomorphization; + /// /// If the output folder is not null, /// documentation is generated in the specified folder based on doc comments in the source code. /// public string DocumentationOutputFolder; + /// /// Directory where the compiled binaries will be generated. /// No binaries will be written to disk unless this path is specified and valid. /// public string BuildOutputFolder; + /// /// Output path for the dll containing the compiled binaries. /// No dll will be generated unless this path is specified and valid. /// public string DllOutputPath; + /// /// If set to true, then referenced dlls will be loaded purely based on attributes in the contained C# code. /// Any Q# resources will be ignored. /// public bool LoadReferencesBasedOnGeneratedCsharp; + /// - /// If set to true, then public types and callables declared in referenced assemblies + /// If set to true, then public types and callables declared in referenced assemblies /// are exposed via their test name defined by the corresponding attribute. /// public bool ExposeReferencesViaTestNames; + /// - /// Contains a sequence of tuples with the path to a dotnet dll containing one or more rewrite steps + /// Contains a sequence of tuples with the path to a dotnet dll containing one or more rewrite steps /// (i.e. classes implementing IRewriteStep) and the corresponding output folder. /// The contained rewrite steps will be executed in the defined order and priority at the end of the compilation. /// public IEnumerable<(string, string)> RewriteSteps; + /// /// If set to true, the post-condition for loaded rewrite steps is checked if the corresponding verification is implemented. /// Otherwise post-condition verifications are skipped. /// public bool EnableAdditionalChecks; + /// /// Handle to pass arbitrary constants with which to populate the corresponding dictionary for loaded rewrite steps. /// These values will take precedence over any already existing values that the default constructor sets. @@ -171,6 +188,7 @@ public struct Configuration /// The given dictionary in this configuration is left unchanged in any case. /// public IReadOnlyDictionary AssemblyConstants; + /// /// Paths to the assemblies that contains a syntax tree with target specific implementations for certain functions and operations. /// The functions and operations defined in these assemblies replace the ones declared within the compilation unit. @@ -183,19 +201,19 @@ public struct Configuration /// This is the case if either the build output folder is specified or the dll output path is specified. /// internal bool SerializeSyntaxTree => - BuildOutputFolder != null || DllOutputPath != null; + this.BuildOutputFolder != null || this.DllOutputPath != null; /// /// Indicates whether the compiler will remove if-statements and replace them with calls to appropriate intrinsic operations. /// internal bool ConvertClassicalControl => - RuntimeCapabilities == RuntimeCapabilities.QPRGen1; + this.RuntimeCapabilities == RuntimeCapabilities.QPRGen1; /// /// Indicates whether any paths to assemblies have been specified that may contain target specific decompositions. /// internal bool LoadTargetSpecificDecompositions => - TargetPackageAssemblies != null && TargetPackageAssemblies.Any(); + this.TargetPackageAssemblies != null && this.TargetPackageAssemblies.Any(); /// /// If the ProjectName does not have an ending "proj", appends a .qsproj ending to the project name. @@ -219,7 +237,12 @@ public struct Configuration /// /// used to indicate the status of individual compilation steps /// - public enum Status { NotRun = -1, Succeeded = 0, Failed = 1 } + public enum Status + { + NotRun = -1, + Succeeded = 0, + Failed = 1 + } private class ExecutionStatus { @@ -248,83 +271,94 @@ private bool WasSuccessful(bool run, Status code) => internal bool Success(Configuration options) => this.SourceFileLoading <= 0 && this.ReferenceLoading <= 0 && - WasSuccessful(true, this.Validation) && - WasSuccessful(true, this.PluginLoading) && - WasSuccessful(options.LoadTargetSpecificDecompositions, this.TargetSpecificReplacements) && - WasSuccessful(options.GenerateFunctorSupport, this.FunctorSupport) && - WasSuccessful(options.AttemptFullPreEvaluation, this.PreEvaluation) && - WasSuccessful(!options.SkipSyntaxTreeTrimming, this.TreeTrimming) && - WasSuccessful(options.ConvertClassicalControl, this.ConvertClassicalControl) && - WasSuccessful(options.IsExecutable && !options.SkipMonomorphization, this.Monomorphization) && - WasSuccessful(options.DocumentationOutputFolder != null, this.Documentation) && - WasSuccessful(options.SerializeSyntaxTree, this.Serialization) && - WasSuccessful(options.BuildOutputFolder != null, this.BinaryFormat) && - WasSuccessful(options.DllOutputPath != null, this.DllGeneration) && - this.LoadedRewriteSteps.All(status => WasSuccessful(true, status)); + this.WasSuccessful(true, this.Validation) && + this.WasSuccessful(true, this.PluginLoading) && + this.WasSuccessful(options.LoadTargetSpecificDecompositions, this.TargetSpecificReplacements) && + this.WasSuccessful(options.GenerateFunctorSupport, this.FunctorSupport) && + this.WasSuccessful(options.AttemptFullPreEvaluation, this.PreEvaluation) && + this.WasSuccessful(!options.SkipSyntaxTreeTrimming, this.TreeTrimming) && + this.WasSuccessful(options.ConvertClassicalControl, this.ConvertClassicalControl) && + this.WasSuccessful(options.IsExecutable && !options.SkipMonomorphization, this.Monomorphization) && + this.WasSuccessful(options.DocumentationOutputFolder != null, this.Documentation) && + this.WasSuccessful(options.SerializeSyntaxTree, this.Serialization) && + this.WasSuccessful(options.BuildOutputFolder != null, this.BinaryFormat) && + this.WasSuccessful(options.DllOutputPath != null, this.DllGeneration) && + this.LoadedRewriteSteps.All(status => this.WasSuccessful(true, status)); } /// /// Indicates whether all source files were loaded successfully. /// Source file loading may not be executed if the content was preloaded using methods outside this class. /// - public Status SourceFileLoading => this.CompilationStatus.SourceFileLoading; + public Status SourceFileLoading => this.compilationStatus.SourceFileLoading; + /// /// Indicates whether all references were loaded successfully. /// The loading may not be executed if all references were preloaded using methods outside this class. /// - public Status ReferenceLoading => this.CompilationStatus.ReferenceLoading; + public Status ReferenceLoading => this.compilationStatus.ReferenceLoading; + /// /// Indicates whether all external dlls specifying e.g. rewrite steps /// to perform as part of the compilation have been loaded successfully. /// The status indicates a successful execution if no such external dlls have been specified. /// - public Status PluginLoading => this.CompilationStatus.PluginLoading; + public Status PluginLoading => this.compilationStatus.PluginLoading; + /// /// Indicates whether the compilation unit passed the compiler validation /// that is executed before invoking further rewrite and/or generation steps. /// - public Status Validation => this.CompilationStatus.Validation; + public Status Validation => this.compilationStatus.Validation; + /// /// Indicates whether target specific implementations for functions and operations /// have been used to replace the ones declared within the compilation unit. /// This step is only executed if the specified configuration contains the path to the target package. /// - public Status TargetSpecificReplacements => this.CompilationStatus.TargetSpecificReplacements; + public Status TargetSpecificReplacements => this.compilationStatus.TargetSpecificReplacements; + /// /// Indicates whether all specializations were generated successfully. /// This rewrite step is only executed if the corresponding configuration is specified. /// - public Status FunctorSupport => this.CompilationStatus.FunctorSupport; + public Status FunctorSupport => this.compilationStatus.FunctorSupport; + /// /// Indicates whether the pre-evaluation step executed successfully. /// This rewrite step is only executed if the corresponding configuration is specified. /// - public Status PreEvaluation => this.CompilationStatus.PreEvaluation; + public Status PreEvaluation => this.compilationStatus.PreEvaluation; + /// /// Indicates whether all the type-parameterized callables were resolved to concrete callables. /// This rewrite step is only executed if the corresponding configuration is specified. /// - public Status Monomorphization => this.CompilationStatus.Monomorphization; + public Status Monomorphization => this.compilationStatus.Monomorphization; + /// /// Indicates whether documentation for the compilation was generated successfully. /// This step is only executed if the corresponding configuration is specified. /// - public Status Documentation => this.CompilationStatus.Documentation; + public Status Documentation => this.compilationStatus.Documentation; + /// /// Indicates whether the built compilation could be serialized successfully. /// This step is only executed if either the binary representation or a dll is emitted. /// - public Status Serialization => this.CompilationStatus.Serialization; + public Status Serialization => this.compilationStatus.Serialization; + /// /// Indicates whether a binary representation for the generated syntax tree has been generated successfully. /// This step is only executed if the corresponding configuration is specified. /// - public Status BinaryFormat => this.CompilationStatus.BinaryFormat; + public Status BinaryFormat => this.compilationStatus.BinaryFormat; + /// /// Indicates whether a dll containing the compiled binary has been generated successfully. /// This step is only executed if the corresponding configuration is specified. /// - public Status DllGeneration => this.CompilationStatus.DllGeneration; + public Status DllGeneration => this.compilationStatus.DllGeneration; /// /// Indicates whether all rewrite steps with the given name and loaded from the given source executed successfully. @@ -334,58 +368,66 @@ internal bool Success(Configuration options) => /// public Status LoadedRewriteStep(string name, string source = null) { - var uri = String.IsNullOrWhiteSpace(source) ? null : new Uri(Path.GetFullPath(source)); - bool MatchesQuery(int index) => this.ExternalRewriteSteps[index].Name == name && (source == null || this.ExternalRewriteSteps[index].Origin == uri); - var statuses = this.CompilationStatus.LoadedRewriteSteps.Where((s, i) => MatchesQuery(i)).ToArray(); + var uri = string.IsNullOrWhiteSpace(source) ? null : new Uri(Path.GetFullPath(source)); + bool MatchesQuery(int index) => this.externalRewriteSteps[index].Name == name && (source == null || this.externalRewriteSteps[index].Origin == uri); + var statuses = this.compilationStatus.LoadedRewriteSteps.Where((s, i) => MatchesQuery(i)).ToArray(); return statuses.All(s => s == Status.Succeeded) ? Status.Succeeded : statuses.Any(s => s == Status.Failed) ? Status.Failed : Status.NotRun; } + /// /// Indicates the overall status of all rewrite step from external dlls. /// The status is indicated as success if none of these steps failed. /// - public Status AllLoadedRewriteSteps => this.CompilationStatus.LoadedRewriteSteps.Any(s => s == Status.Failed) ? Status.Failed : Status.Succeeded; + public Status AllLoadedRewriteSteps => this.compilationStatus.LoadedRewriteSteps.Any(s => s == Status.Failed) ? Status.Failed : Status.Succeeded; + /// /// Indicates the overall success of all compilation steps. /// The compilation is indicated as having been successful if all steps that were configured to execute completed successfully. /// - public bool Success => this.CompilationStatus.Success(this.Config); - + public bool Success => this.compilationStatus.Success(this.config); /// /// Logger used to log all diagnostic events during compilation. /// - private readonly ILogger Logger; + private readonly ILogger logger; + /// /// Configuration specifying the compilation steps to execute. /// - private readonly Configuration Config; + private readonly Configuration config; + /// /// Used to track the status of individual compilation steps. /// - private readonly ExecutionStatus CompilationStatus; + private readonly ExecutionStatus compilationStatus; + /// /// Contains all loaded rewrite steps found in the specified plugin dlls, /// where configurable properties such as the output folder have already been initialized to suitable values. /// - private readonly ImmutableArray ExternalRewriteSteps; + private readonly ImmutableArray externalRewriteSteps; /// /// Contains all diagnostics generated upon source file and reference loading. /// All other diagnostics can be accessed via the VerifiedCompilation. /// public ImmutableArray LoadDiagnostics; + /// /// Contains the initial compilation built by the compilation unit manager after verification. /// public readonly CompilationUnitManager.Compilation VerifiedCompilation; + /// /// Contains the built compilation including the syntax tree after executing all configured rewrite steps. /// public readonly QsCompilation CompilationOutput; + /// /// Contains the absolute path where the binary representation of the generated syntax tree has been written to disk. /// public readonly string PathToCompiledBinary; + /// /// Contains the absolute path where the generated dll containing the compiled binary has been written to disk. /// @@ -402,8 +444,7 @@ public Status LoadedRewriteStep(string name, string source = null) /// in the order in which they are executed. /// public ImmutableArray<(Uri, string)> LoadedRewriteSteps => - this.ExternalRewriteSteps.Select(step => (step.Origin, step.Name)).ToImmutableArray(); - + this.externalRewriteSteps.Select(step => (step.Origin, step.Name)).ToImmutableArray(); /// /// Builds the compilation for the source files and references loaded by the given loaders, @@ -413,48 +454,49 @@ public Status LoadedRewriteStep(string name, string source = null) /// public CompilationLoader(SourceLoader loadSources, ReferenceLoader loadReferences, Configuration? options = null, ILogger logger = null) { - RaiseCompilationTaskStart(null, "OverallCompilation"); + this.RaiseCompilationTaskStart(null, "OverallCompilation"); // loading the content to compiler - this.Logger = logger; + this.logger = logger; this.LoadDiagnostics = ImmutableArray.Empty; - this.Config = options ?? new Configuration(); + this.config = options ?? default; Status rewriteStepLoading = Status.Succeeded; - this.ExternalRewriteSteps = RewriteSteps.Load(this.Config, + this.externalRewriteSteps = RewriteSteps.Load( + this.config, d => this.LogAndUpdateLoadDiagnostics(ref rewriteStepLoading, d), ex => this.LogAndUpdate(ref rewriteStepLoading, ex)); - this.PrintLoadedRewriteSteps(this.ExternalRewriteSteps); - this.CompilationStatus = new ExecutionStatus(this.ExternalRewriteSteps); - this.CompilationStatus.PluginLoading = rewriteStepLoading; + this.PrintLoadedRewriteSteps(this.externalRewriteSteps); + this.compilationStatus = new ExecutionStatus(this.externalRewriteSteps); + this.compilationStatus.PluginLoading = rewriteStepLoading; - RaiseCompilationTaskStart("OverallCompilation", "SourcesLoading"); + this.RaiseCompilationTaskStart("OverallCompilation", "SourcesLoading"); var sourceFiles = loadSources?.Invoke(this.LoadSourceFiles) ?? throw new ArgumentNullException("unable to load source files"); - RaiseCompilationTaskEnd("OverallCompilation", "SourcesLoading"); - RaiseCompilationTaskStart("OverallCompilation", "ReferenceLoading"); + this.RaiseCompilationTaskEnd("OverallCompilation", "SourcesLoading"); + this.RaiseCompilationTaskStart("OverallCompilation", "ReferenceLoading"); var references = loadReferences?.Invoke( refs => this.LoadAssemblies( - refs, - loadTestNames: this.Config.ExposeReferencesViaTestNames, - ignoreDllResources: this.Config.LoadReferencesBasedOnGeneratedCsharp)) + refs, + loadTestNames: this.config.ExposeReferencesViaTestNames, + ignoreDllResources: this.config.LoadReferencesBasedOnGeneratedCsharp)) ?? throw new ArgumentNullException("unable to load referenced binary files"); - RaiseCompilationTaskEnd("OverallCompilation", "ReferenceLoading"); + this.RaiseCompilationTaskEnd("OverallCompilation", "ReferenceLoading"); // building the compilation - RaiseCompilationTaskStart("OverallCompilation", "Build"); - this.CompilationStatus.Validation = Status.Succeeded; + this.RaiseCompilationTaskStart("OverallCompilation", "Build"); + this.compilationStatus.Validation = Status.Succeeded; var files = CompilationUnitManager.InitializeFileManagers(sourceFiles, null, this.OnCompilerException); // do *not* live track (i.e. use publishing) here! - var executionTarget = this.Config.AssemblyConstants?.GetValueOrDefault(ExecutionTarget); + var processorArchitecture = this.config.AssemblyConstants?.GetValueOrDefault(AssemblyConstants.ProcessorArchitecture); var compilationManager = new CompilationUnitManager( this.OnCompilerException, - capabilities: this.Config.RuntimeCapabilities, - isExecutable: this.Config.IsExecutable, - executionTarget: NonNullable.New(string.IsNullOrWhiteSpace(executionTarget) + capabilities: this.config.RuntimeCapabilities, + isExecutable: this.config.IsExecutable, + processorArchitecture: NonNullable.New(string.IsNullOrWhiteSpace(processorArchitecture) ? "Unspecified" - : executionTarget)); + : processorArchitecture)); compilationManager.UpdateReferencesAsync(references); compilationManager.AddOrUpdateSourceFilesAsync(files); this.VerifiedCompilation = compilationManager.Build(); @@ -462,112 +504,125 @@ public CompilationLoader(SourceLoader loadSources, ReferenceLoader loadReference compilationManager.Dispose(); foreach (var diag in this.VerifiedCompilation?.Diagnostics() ?? Enumerable.Empty()) - { this.LogAndUpdate(ref this.CompilationStatus.Validation, diag); } + { + this.LogAndUpdate(ref this.compilationStatus.Validation, diag); + } - if (this.Config.IsExecutable && this.CompilationOutput?.EntryPoints.Length == 0) + if (this.config.IsExecutable && this.CompilationOutput?.EntryPoints.Length == 0) { - if (this.Config.RuntimeCapabilities == RuntimeCapabilities.Unknown) this.Logger?.Log(WarningCode.MissingEntryPoint, Array.Empty()); - else this.LogAndUpdate(ref this.CompilationStatus.Validation, ErrorCode.MissingEntryPoint, Array.Empty()); + if (this.config.RuntimeCapabilities == RuntimeCapabilities.Unknown) + { + this.logger?.Log(WarningCode.MissingEntryPoint, Array.Empty()); + } + else + { + this.LogAndUpdate(ref this.compilationStatus.Validation, ErrorCode.MissingEntryPoint, Array.Empty()); + } } if (!Uri.TryCreate(Assembly.GetExecutingAssembly().CodeBase, UriKind.Absolute, out Uri thisDllUri)) - { thisDllUri = new Uri(Path.GetFullPath(".", "CompilationLoader.cs")); } + { + thisDllUri = new Uri(Path.GetFullPath(".", "CompilationLoader.cs")); + } - if (this.Config.LoadTargetSpecificDecompositions) + if (this.config.LoadTargetSpecificDecompositions) { - RaiseCompilationTaskStart("Build", "ReplaceTargetSpecificImplementations"); - this.CompilationOutput = this.ReplaceTargetSpecificImplementations(this.Config.TargetPackageAssemblies, thisDllUri, references.Declarations.Count); - RaiseCompilationTaskEnd("Build", "ReplaceTargetSpecificImplementations"); + this.RaiseCompilationTaskStart("Build", "ReplaceTargetSpecificImplementations"); + this.CompilationOutput = this.ReplaceTargetSpecificImplementations(this.config.TargetPackageAssemblies, thisDllUri, references.Declarations.Count); + this.RaiseCompilationTaskEnd("Build", "ReplaceTargetSpecificImplementations"); } - RaiseCompilationTaskEnd("OverallCompilation", "Build"); + this.RaiseCompilationTaskEnd("OverallCompilation", "Build"); // executing the specified rewrite steps var steps = new List<(int, Func)>(); - if (this.Config.ConvertClassicalControl) + if (this.config.ConvertClassicalControl) { var rewriteStep = new RewriteSteps.LoadedStep(new ClassicallyControlled(), typeof(IRewriteStep), thisDllUri); - steps.Add((rewriteStep.Priority, () => ExecuteAsAtomicTransformation(rewriteStep, ref this.CompilationStatus.ConvertClassicalControl))); + steps.Add((rewriteStep.Priority, () => this.ExecuteAsAtomicTransformation(rewriteStep, ref this.compilationStatus.ConvertClassicalControl))); } - if (this.Config.IsExecutable && !this.Config.SkipMonomorphization) + if (this.config.IsExecutable && !this.config.SkipMonomorphization) { var rewriteStep = new RewriteSteps.LoadedStep(new Monomorphization(), typeof(IRewriteStep), thisDllUri); - steps.Add((rewriteStep.Priority, () => ExecuteAsAtomicTransformation(rewriteStep, ref this.CompilationStatus.Monomorphization))); + steps.Add((rewriteStep.Priority, () => this.ExecuteAsAtomicTransformation(rewriteStep, ref this.compilationStatus.Monomorphization))); } - if (this.Config.GenerateFunctorSupport) + if (this.config.GenerateFunctorSupport) { var rewriteStep = new RewriteSteps.LoadedStep(new FunctorGeneration(), typeof(IRewriteStep), thisDllUri); - steps.Add((rewriteStep.Priority, () => ExecuteAsAtomicTransformation(rewriteStep, ref this.CompilationStatus.FunctorSupport))); + steps.Add((rewriteStep.Priority, () => this.ExecuteAsAtomicTransformation(rewriteStep, ref this.compilationStatus.FunctorSupport))); } - if (!this.Config.SkipSyntaxTreeTrimming) + if (!this.config.SkipSyntaxTreeTrimming) { var rewriteStep = new RewriteSteps.LoadedStep(new ConjugationInlining(), typeof(IRewriteStep), thisDllUri); - steps.Add((rewriteStep.Priority, () => ExecuteAsAtomicTransformation(rewriteStep, ref this.CompilationStatus.TreeTrimming))); + steps.Add((rewriteStep.Priority, () => this.ExecuteAsAtomicTransformation(rewriteStep, ref this.compilationStatus.TreeTrimming))); } - if (this.Config.AttemptFullPreEvaluation) + if (this.config.AttemptFullPreEvaluation) { var rewriteStep = new RewriteSteps.LoadedStep(new FullPreEvaluation(), typeof(IRewriteStep), thisDllUri); - steps.Add((rewriteStep.Priority, () => ExecuteAsAtomicTransformation(rewriteStep, ref this.CompilationStatus.PreEvaluation))); + steps.Add((rewriteStep.Priority, () => this.ExecuteAsAtomicTransformation(rewriteStep, ref this.compilationStatus.PreEvaluation))); } - for (int j = 0; j < this.ExternalRewriteSteps.Length; j++) + for (int j = 0; j < this.externalRewriteSteps.Length; j++) { - var priority = this.ExternalRewriteSteps[j].Priority; - Func Execute(int index) => () => - ExecuteAsAtomicTransformation(this.ExternalRewriteSteps[index], ref this.CompilationStatus.LoadedRewriteSteps[index]); + var priority = this.externalRewriteSteps[j].Priority; + Func Execute(int index) => () => + this.ExecuteAsAtomicTransformation(this.externalRewriteSteps[index], ref this.compilationStatus.LoadedRewriteSteps[index]); steps.Add((priority, Execute(j))); } - RaiseCompilationTaskStart("OverallCompilation", "RewriteSteps"); + this.RaiseCompilationTaskStart("OverallCompilation", "RewriteSteps"); SortRewriteSteps(steps, t => t.Item1); foreach (var (_, rewriteStep) in steps) { this.CompilationOutput = rewriteStep(); } - RaiseCompilationTaskEnd("OverallCompilation", "RewriteSteps"); + this.RaiseCompilationTaskEnd("OverallCompilation", "RewriteSteps"); // generating the compiled binary, dll, and docs - RaiseCompilationTaskStart("OverallCompilation", "OutputGeneration"); + this.RaiseCompilationTaskStart("OverallCompilation", "OutputGeneration"); using (var ms = new MemoryStream()) { - RaiseCompilationTaskStart("OutputGeneration", "SyntaxTreeSerialization"); - var serialized = this.Config.SerializeSyntaxTree && this.SerializeSyntaxTree(ms); - RaiseCompilationTaskEnd("OutputGeneration", "SyntaxTreeSerialization"); - if (serialized && this.Config.BuildOutputFolder != null) + this.RaiseCompilationTaskStart("OutputGeneration", "SyntaxTreeSerialization"); + var serialized = this.config.SerializeSyntaxTree && this.SerializeSyntaxTree(ms); + this.RaiseCompilationTaskEnd("OutputGeneration", "SyntaxTreeSerialization"); + if (serialized && this.config.BuildOutputFolder != null) { - RaiseCompilationTaskStart("OutputGeneration", "BinaryGeneration"); + this.RaiseCompilationTaskStart("OutputGeneration", "BinaryGeneration"); this.PathToCompiledBinary = this.GenerateBinary(ms); - RaiseCompilationTaskEnd("OutputGeneration", "BinaryGeneration"); + this.RaiseCompilationTaskEnd("OutputGeneration", "BinaryGeneration"); } - if (serialized && this.Config.DllOutputPath != null) + if (serialized && this.config.DllOutputPath != null) { - RaiseCompilationTaskStart("OutputGeneration", "DllGeneration"); + this.RaiseCompilationTaskStart("OutputGeneration", "DllGeneration"); this.DllOutputPath = this.GenerateDll(ms); - RaiseCompilationTaskEnd("OutputGeneration", "DllGeneration"); + this.RaiseCompilationTaskEnd("OutputGeneration", "DllGeneration"); } } - if (this.Config.DocumentationOutputFolder != null) + if (this.config.DocumentationOutputFolder != null) { - RaiseCompilationTaskStart("OutputGeneration", "DocumentationGeneration"); - this.CompilationStatus.Documentation = Status.Succeeded; - var docsFolder = Path.GetFullPath(String.IsNullOrWhiteSpace(this.Config.DocumentationOutputFolder) ? "." : this.Config.DocumentationOutputFolder); - void onDocException(Exception ex) => this.LogAndUpdate(ref this.CompilationStatus.Documentation, ex); - var docsGenerated = this.VerifiedCompilation != null && DocBuilder.Run(docsFolder, this.VerifiedCompilation.SyntaxTree.Values, this.VerifiedCompilation.SourceFiles, onException: onDocException); - if (!docsGenerated) this.LogAndUpdate(ref this.CompilationStatus.Documentation, ErrorCode.DocGenerationFailed, Enumerable.Empty()); - RaiseCompilationTaskEnd("OutputGeneration", "DocumentationGeneration"); + this.RaiseCompilationTaskStart("OutputGeneration", "DocumentationGeneration"); + this.compilationStatus.Documentation = Status.Succeeded; + var docsFolder = Path.GetFullPath(string.IsNullOrWhiteSpace(this.config.DocumentationOutputFolder) ? "." : this.config.DocumentationOutputFolder); + void OnDocException(Exception ex) => this.LogAndUpdate(ref this.compilationStatus.Documentation, ex); + var docsGenerated = this.VerifiedCompilation != null && DocBuilder.Run(docsFolder, this.VerifiedCompilation.SyntaxTree.Values, this.VerifiedCompilation.SourceFiles, onException: OnDocException); + if (!docsGenerated) + { + this.LogAndUpdate(ref this.compilationStatus.Documentation, ErrorCode.DocGenerationFailed, Enumerable.Empty()); + } + this.RaiseCompilationTaskEnd("OutputGeneration", "DocumentationGeneration"); } - RaiseCompilationTaskEnd("OverallCompilation", "OutputGeneration"); - RaiseCompilationTaskEnd(null, "OverallCompilation"); + this.RaiseCompilationTaskEnd("OverallCompilation", "OutputGeneration"); + this.RaiseCompilationTaskEnd(null, "OverallCompilation"); } /// @@ -576,7 +631,9 @@ Func Execute(int index) => () => /// Uses the specified logger to log all diagnostic events. /// public CompilationLoader(IEnumerable sources, IEnumerable references, Configuration? options = null, ILogger logger = null) - : this(load => load(sources), load => load(references), options, logger) { } + : this(load => load(sources), load => load(references), options, logger) + { + } /// /// Builds the compilation of the specified source files and the loaded references returned by the given loader, @@ -585,7 +642,9 @@ public CompilationLoader(IEnumerable sources, IEnumerable refere /// Throws an ArgumentNullException if the given loader is null or returns null. /// public CompilationLoader(IEnumerable sources, ReferenceLoader loadReferences, Configuration? options = null, ILogger logger = null) - : this(load => load(sources), loadReferences, options, logger) { } + : this(load => load(sources), loadReferences, options, logger) + { + } /// /// Builds the compilation of the content returned by the given loader and the specified references, @@ -594,8 +653,9 @@ public CompilationLoader(IEnumerable sources, ReferenceLoader loadRefere /// Throws an ArgumentNullException if the given loader is null or returns null. /// public CompilationLoader(SourceLoader loadSources, IEnumerable references, Configuration? options = null, ILogger logger = null) - : this(loadSources, load => load(references), options, logger) { } - + : this(loadSources, load => load(references), options, logger) + { + } // private routines used for logging and status updates @@ -605,8 +665,11 @@ public CompilationLoader(SourceLoader loadSources, IEnumerable reference /// private void LogAndUpdate(ref Status current, Diagnostic d) { - this.Logger?.Log(d); - if (d.IsError()) current = Status.Failed; + this.logger?.Log(d); + if (d.IsError()) + { + current = Status.Failed; + } } /// @@ -614,7 +677,7 @@ private void LogAndUpdate(ref Status current, Diagnostic d) /// private void LogAndUpdate(ref Status current, Exception ex) { - this.Logger?.Log(ex); + this.logger?.Log(ex); current = Status.Failed; } @@ -623,7 +686,7 @@ private void LogAndUpdate(ref Status current, Exception ex) /// private void LogAndUpdate(ref Status current, ErrorCode code, IEnumerable args) { - this.Logger?.Log(code, args); + this.logger?.Log(code, args); current = Status.Failed; } @@ -643,8 +706,8 @@ private void LogAndUpdateLoadDiagnostics(ref Status current, Diagnostic d) /// private void OnCompilerException(Exception ex) { - this.LogAndUpdate(ref this.CompilationStatus.Validation, ErrorCode.UnexpectedCompilerException, Enumerable.Empty()); - this.LogAndUpdate(ref this.CompilationStatus.Validation, ex); + this.LogAndUpdate(ref this.compilationStatus.Validation, ErrorCode.UnexpectedCompilerException, Enumerable.Empty()); + this.LogAndUpdate(ref this.compilationStatus.Validation, ex); } /// @@ -653,11 +716,14 @@ private void OnCompilerException(Exception ex) /// private void PrintResolvedFiles(IEnumerable sourceFiles) { - if (sourceFiles == null) return; + if (sourceFiles == null) + { + return; + } var args = sourceFiles.Any() ? sourceFiles.Select(f => f?.LocalPath).ToArray() : new string[] { "(none)" }; - this.Logger?.Log(InformationCode.CompilingWithSourceFiles, Enumerable.Empty(), messageParam: Formatting.Indent(args).ToArray()); + this.logger?.Log(InformationCode.CompilingWithSourceFiles, Enumerable.Empty(), messageParam: Formatting.Indent(args).ToArray()); } /// @@ -666,11 +732,14 @@ private void PrintResolvedFiles(IEnumerable sourceFiles) /// private void PrintResolvedAssemblies(IEnumerable> assemblies) { - if (assemblies == null) return; + if (assemblies == null) + { + return; + } var args = assemblies.Any() ? assemblies.Select(name => name.Value).ToArray() : new string[] { "(none)" }; - this.Logger?.Log(InformationCode.CompilingWithAssemblies, Enumerable.Empty(), messageParam: Formatting.Indent(args).ToArray()); + this.logger?.Log(InformationCode.CompilingWithAssemblies, Enumerable.Empty(), messageParam: Formatting.Indent(args).ToArray()); } /// @@ -679,14 +748,16 @@ private void PrintResolvedAssemblies(IEnumerable> assemblies /// private void PrintLoadedRewriteSteps(IEnumerable rewriteSteps) { - if (rewriteSteps == null) return; + if (rewriteSteps == null) + { + return; + } var args = rewriteSteps.Any() ? rewriteSteps.Select(step => $"{step.Name} ({step.Origin})").ToArray() : new string[] { "(none)" }; - this.Logger?.Log(InformationCode.LoadedRewriteSteps, Enumerable.Empty(), messageParam: Formatting.Indent(args).ToArray()); + this.logger?.Log(InformationCode.LoadedRewriteSteps, Enumerable.Empty(), messageParam: Formatting.Indent(args).ToArray()); } - // private helper methods used during construction /// @@ -708,13 +779,19 @@ private void RaiseCompilationTaskEnd(string parentTaskName, string taskName) => private QsCompilation ExecuteAsAtomicTransformation(RewriteSteps.LoadedStep rewriteStep, ref Status status) { QsCompilation transformed = null; - if (this.CompilationStatus.Validation != Status.Succeeded) status = Status.NotRun; - else status = this.ExecuteRewriteStep(rewriteStep, this.CompilationOutput, out transformed); + if (this.compilationStatus.Validation != Status.Succeeded) + { + status = Status.NotRun; + } + else + { + status = this.ExecuteRewriteStep(rewriteStep, this.CompilationOutput, out transformed); + } return status == Status.Succeeded ? transformed : this.CompilationOutput; } /// - /// Attempts to load the target package assemblies with the given paths, logging diagnostics + /// Attempts to load the target package assemblies with the given paths, logging diagnostics /// when a path is null or invalid, or loading fails. Logs suitable diagnostics if the loaded dlls /// contains conflicting declarations. Updates the compilation status accordingly. /// Executes the transformation to replace target specific implementations as atomic rewrite step. @@ -724,10 +801,13 @@ private QsCompilation ExecuteAsAtomicTransformation(RewriteSteps.LoadedStep rewr /// private QsCompilation ReplaceTargetSpecificImplementations(IEnumerable paths, Uri rewriteStepOrigin, int nrReferences) { - if (paths == null) throw new ArgumentNullException(nameof(paths)); + if (paths == null) + { + throw new ArgumentNullException(nameof(paths)); + } - void LogError(ErrorCode errCode, string[] args) => this.LogAndUpdate(ref this.CompilationStatus.TargetSpecificReplacements, errCode, args); - void LogException(Exception ex) => this.LogAndUpdate(ref this.CompilationStatus.TargetSpecificReplacements, ex); + void LogError(ErrorCode errCode, string[] args) => this.LogAndUpdate(ref this.compilationStatus.TargetSpecificReplacements, errCode, args); + void LogException(Exception ex) => this.LogAndUpdate(ref this.compilationStatus.TargetSpecificReplacements, ex); (NonNullable, ImmutableArray)? LoadReferences(string path) { @@ -735,7 +815,10 @@ private QsCompilation ReplaceTargetSpecificImplementations(IEnumerable p { var targetDll = Path.GetFullPath(path); var loadSucceeded = AssemblyLoader.LoadReferencedAssembly(targetDll, out var loaded, LogException); - if (loadSucceeded) return (NonNullable.New(path), loaded.Namespaces); + if (loadSucceeded) + { + return (NonNullable.New(path), loaded.Namespaces); + } LogError(ErrorCode.FailedToLoadTargetSpecificDecompositions, new[] { targetDll }); return null; } @@ -745,16 +828,18 @@ private QsCompilation ReplaceTargetSpecificImplementations(IEnumerable p LogException(ex); return null; } - } var natives = paths.Select(LoadReferences).Where(loaded => loaded.HasValue).Select(loaded => loaded.Value).ToArray(); var combinedSuccessfully = References.CombineSyntaxTrees(out var replacements, additionalAssemblies: nrReferences, onError: LogError, natives); - if (!combinedSuccessfully) LogError(ErrorCode.ConflictsInTargetSpecificDecompositions, Array.Empty()); + if (!combinedSuccessfully) + { + LogError(ErrorCode.ConflictsInTargetSpecificDecompositions, Array.Empty()); + } var targetSpecificDecompositions = new QsCompilation(replacements, ImmutableArray.Empty); var rewriteStep = new RewriteSteps.LoadedStep(new IntrinsicResolution(targetSpecificDecompositions), typeof(IRewriteStep), rewriteStepOrigin); - return ExecuteAsAtomicTransformation(rewriteStep, ref this.CompilationStatus.TargetSpecificReplacements); + return this.ExecuteAsAtomicTransformation(rewriteStep, ref this.compilationStatus.TargetSpecificReplacements); } /// @@ -764,8 +849,14 @@ private QsCompilation ReplaceTargetSpecificImplementations(IEnumerable p /// private Status ExecuteRewriteStep(RewriteSteps.LoadedStep rewriteStep, QsCompilation compilation, out QsCompilation transformed) { - if (rewriteStep == null) throw new ArgumentNullException(nameof(rewriteStep)); - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); + if (rewriteStep == null) + { + throw new ArgumentNullException(nameof(rewriteStep)); + } + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } string GetDiagnosticsCode(DiagnosticSeverity severity) => rewriteStep.Name == "CsharpGeneration" && severity == DiagnosticSeverity.Error ? Errors.Code(ErrorCode.CsharpGenerationGeneratedError) : @@ -804,24 +895,35 @@ void LogDiagnostics(ref Status status) } var transformationFailed = rewriteStep.ImplementsTransformation && (!rewriteStep.Transformation(compilation, out transformed) || transformed == null); - var postconditionFailed = this.Config.EnableAdditionalChecks && rewriteStep.ImplementsPostconditionVerification && !rewriteStep.PostconditionVerification(transformed); + var postconditionFailed = this.config.EnableAdditionalChecks && rewriteStep.ImplementsPostconditionVerification && !rewriteStep.PostconditionVerification(transformed); LogDiagnostics(ref status); - if (transformationFailed) this.LogAndUpdate(ref status, ErrorCode.RewriteStepExecutionFailed, new[] { rewriteStep.Name, messageSource }); - if (postconditionFailed) this.LogAndUpdate(ref status, ErrorCode.PostconditionVerificationFailed, new[] { rewriteStep.Name, messageSource }); + if (transformationFailed) + { + this.LogAndUpdate(ref status, ErrorCode.RewriteStepExecutionFailed, new[] { rewriteStep.Name, messageSource }); + } + if (postconditionFailed) + { + this.LogAndUpdate(ref status, ErrorCode.PostconditionVerificationFailed, new[] { rewriteStep.Name, messageSource }); + } } catch (Exception ex) { this.LogAndUpdate(ref status, ex); var isLoadException = ex is FileLoadException || ex.InnerException is FileLoadException; - if (isLoadException) this.LogAndUpdate(ref status, ErrorCode.FileNotFoundDuringPluginExecution, new[] { rewriteStep.Name, messageSource }); - else this.LogAndUpdate(ref status, ErrorCode.PluginExecutionFailed, new[] { rewriteStep.Name, messageSource }); + if (isLoadException) + { + this.LogAndUpdate(ref status, ErrorCode.FileNotFoundDuringPluginExecution, new[] { rewriteStep.Name, messageSource }); + } + else + { + this.LogAndUpdate(ref status, ErrorCode.PluginExecutionFailed, new[] { rewriteStep.Name, messageSource }); + } transformed = null; } return status; } - // routines for loading from and dumping to files /// @@ -832,32 +934,38 @@ void LogDiagnostics(ref Status status) /// private ImmutableDictionary LoadSourceFiles(IEnumerable sources) { - this.CompilationStatus.SourceFileLoading = 0; - if (sources == null) this.LogAndUpdate(ref this.CompilationStatus.SourceFileLoading, ErrorCode.SourceFilesMissing, Enumerable.Empty()); - void onException(Exception ex) => this.LogAndUpdate(ref this.CompilationStatus.SourceFileLoading, ex); - void onDiagnostic(Diagnostic d) => this.LogAndUpdateLoadDiagnostics(ref this.CompilationStatus.SourceFileLoading, d); - var sourceFiles = ProjectManager.LoadSourceFiles(sources ?? Enumerable.Empty(), onDiagnostic, onException); + this.compilationStatus.SourceFileLoading = 0; + if (sources == null) + { + this.LogAndUpdate(ref this.compilationStatus.SourceFileLoading, ErrorCode.SourceFilesMissing, Enumerable.Empty()); + } + void OnException(Exception ex) => this.LogAndUpdate(ref this.compilationStatus.SourceFileLoading, ex); + void OnDiagnostic(Diagnostic d) => this.LogAndUpdateLoadDiagnostics(ref this.compilationStatus.SourceFileLoading, d); + var sourceFiles = ProjectManager.LoadSourceFiles(sources ?? Enumerable.Empty(), OnDiagnostic, OnException); this.PrintResolvedFiles(sourceFiles.Keys); return sourceFiles; } /// - /// Used to load the content of the specified assembly references from disk. - /// If loadTestNames is set to true, then public types and callables declared in referenced assemblies - /// are exposed via their test name defined by the corresponding attribute. - /// Returns the loaded content of the references. - /// Logs suitable diagnostics in the process and modifies the compilation status accordingly. + /// Used to load the content of the specified assembly references from disk. + /// If loadTestNames is set to true, then public types and callables declared in referenced assemblies + /// are exposed via their test name defined by the corresponding attribute. + /// Returns the loaded content of the references. + /// Logs suitable diagnostics in the process and modifies the compilation status accordingly. /// Prints all loaded files using PrintResolvedAssemblies. /// private References LoadAssemblies(IEnumerable refs, bool loadTestNames, bool ignoreDllResources) { - this.CompilationStatus.ReferenceLoading = 0; - if (refs == null) this.Logger?.Log(WarningCode.ReferencesSetToNull, Enumerable.Empty()); - void onException(Exception ex) => this.LogAndUpdate(ref this.CompilationStatus.ReferenceLoading, ex); - void onDiagnostic(Diagnostic d) => this.LogAndUpdateLoadDiagnostics(ref this.CompilationStatus.ReferenceLoading, d); - var headers = ProjectManager.LoadReferencedAssemblies(refs ?? Enumerable.Empty(), onDiagnostic, onException, ignoreDllResources); - var projId = this.Config.ProjectName == null ? null : Path.ChangeExtension(Path.GetFullPath(this.Config.ProjectNameWithExtension), "qsproj"); - var references = new References(headers, loadTestNames, (code, args) => onDiagnostic(Errors.LoadError(code, args, projId))); + this.compilationStatus.ReferenceLoading = 0; + if (refs == null) + { + this.logger?.Log(WarningCode.ReferencesSetToNull, Enumerable.Empty()); + } + void OnException(Exception ex) => this.LogAndUpdate(ref this.compilationStatus.ReferenceLoading, ex); + void OnDiagnostic(Diagnostic d) => this.LogAndUpdateLoadDiagnostics(ref this.compilationStatus.ReferenceLoading, d); + var headers = ProjectManager.LoadReferencedAssemblies(refs ?? Enumerable.Empty(), OnDiagnostic, OnException, ignoreDllResources); + var projId = this.config.ProjectName == null ? null : Path.ChangeExtension(Path.GetFullPath(this.config.ProjectNameWithExtension), "qsproj"); + var references = new References(headers, loadTestNames, (code, args) => OnDiagnostic(Errors.LoadError(code, args, projId))); this.PrintResolvedAssemblies(references.Declarations.Keys); return references; } @@ -871,23 +979,32 @@ private References LoadAssemblies(IEnumerable refs, bool loadTestNames, /// private bool SerializeSyntaxTree(MemoryStream ms) { - if (ms == null) throw new ArgumentNullException(nameof(ms)); - bool ErrorAndReturn() + void LogError() => this.LogAndUpdate( + ref this.compilationStatus.Serialization, ErrorCode.SerializationFailed, Enumerable.Empty()); + + if (ms == null) { - this.LogAndUpdate(ref this.CompilationStatus.Serialization, ErrorCode.SerializationFailed, Enumerable.Empty()); + throw new ArgumentNullException(nameof(ms)); + } + this.compilationStatus.Serialization = 0; + if (this.CompilationOutput == null) + { + LogError(); return false; } - this.CompilationStatus.Serialization = 0; - if (this.CompilationOutput == null) ErrorAndReturn(); using var writer = new BsonDataWriter(ms) { CloseOutput = false }; var fromSources = this.CompilationOutput.Namespaces.Select(ns => FilterBySourceFile.Apply(ns, s => s.Value.EndsWith(".qs"))); var compilation = new QsCompilation(fromSources.ToImmutableArray(), this.CompilationOutput.EntryPoints); - try { Json.Serializer.Serialize(writer, compilation); } + try + { + Json.Serializer.Serialize(writer, compilation); + } catch (Exception ex) { - this.LogAndUpdate(ref this.CompilationStatus.Serialization, ex); - ErrorAndReturn(); + this.LogAndUpdate(ref this.compilationStatus.Serialization, ex); + LogError(); + return false; } return true; } @@ -904,24 +1021,29 @@ bool ErrorAndReturn() /// private string GenerateBinary(MemoryStream serialization) { - if (serialization == null) throw new ArgumentNullException(nameof(serialization)); - this.CompilationStatus.BinaryFormat = 0; + if (serialization == null) + { + throw new ArgumentNullException(nameof(serialization)); + } + this.compilationStatus.BinaryFormat = 0; - var projId = NonNullable.New(Path.GetFullPath(this.Config.ProjectNameWithExtension ?? Path.GetRandomFileName())); - var outFolder = Path.GetFullPath(String.IsNullOrWhiteSpace(this.Config.BuildOutputFolder) ? "." : this.Config.BuildOutputFolder); + var projId = NonNullable.New(Path.GetFullPath(this.config.ProjectNameWithExtension ?? Path.GetRandomFileName())); + var outFolder = Path.GetFullPath(string.IsNullOrWhiteSpace(this.config.BuildOutputFolder) ? "." : this.config.BuildOutputFolder); var target = GeneratedFile(projId, outFolder, ".bson", ""); try { serialization.Seek(0, SeekOrigin.Begin); using (var file = new FileStream(target, FileMode.Create, FileAccess.Write)) - { serialization.WriteTo(file); } + { + serialization.WriteTo(file); + } return target; } catch (Exception ex) { - this.LogAndUpdate(ref this.CompilationStatus.BinaryFormat, ex); - this.LogAndUpdate(ref this.CompilationStatus.BinaryFormat, ErrorCode.GeneratingBinaryFailed, Enumerable.Empty()); + this.LogAndUpdate(ref this.compilationStatus.BinaryFormat, ex); + this.LogAndUpdate(ref this.compilationStatus.BinaryFormat, ErrorCode.GeneratingBinaryFailed, Enumerable.Empty()); return null; } } @@ -938,11 +1060,14 @@ private string GenerateBinary(MemoryStream serialization) /// private string GenerateDll(MemoryStream serialization) { - if (serialization == null) throw new ArgumentNullException(nameof(serialization)); - this.CompilationStatus.DllGeneration = 0; + if (serialization == null) + { + throw new ArgumentNullException(nameof(serialization)); + } + this.compilationStatus.DllGeneration = 0; - var fallbackFileName = (this.PathToCompiledBinary ?? this.Config.ProjectNameWithExtension) ?? Path.GetRandomFileName(); - var outputPath = Path.GetFullPath(String.IsNullOrWhiteSpace(this.Config.DllOutputPath) ? fallbackFileName : this.Config.DllOutputPath); + var fallbackFileName = (this.PathToCompiledBinary ?? this.config.ProjectNameWithExtension) ?? Path.GetRandomFileName(); + var outputPath = Path.GetFullPath(string.IsNullOrWhiteSpace(this.config.DllOutputPath) ? fallbackFileName : this.config.DllOutputPath); outputPath = Path.ChangeExtension(outputPath, "dll"); MetadataReference CreateReference(string file, int id) => @@ -954,7 +1079,8 @@ MetadataReference CreateReference(string file, int id) => // This checks if that handle is available to merely generate a warning if we can't include the reference. bool CanBeIncluded(NonNullable dll) { - try // no need to throw in case this fails - ignore the reference instead + // no need to throw in case this fails - ignore the reference instead + try { using var stream = File.OpenRead(dll.Value); using var assemblyFile = new PEReader(stream); @@ -963,7 +1089,10 @@ bool CanBeIncluded(NonNullable dll) .Select(metadataReader.GetTypeDefinition) .Any(t => metadataReader.GetString(t.Namespace) == DotnetCoreDll.MetadataNamespace); } - catch { return false; } + catch + { + return false; + } } try @@ -975,32 +1104,34 @@ bool CanBeIncluded(NonNullable dll) foreach (var (dropped, _, _) in references.Where(r => !r.Item3)) { var warning = Warnings.LoadWarning(WarningCode.ReferenceCannotBeIncludedInDll, new[] { dropped.Value }, null); - this.LogAndUpdate(ref this.CompilationStatus.DllGeneration, warning); + this.LogAndUpdate(ref this.compilationStatus.DllGeneration, warning); } var compilation = CodeAnalysis.CSharp.CSharpCompilation.Create( - this.Config.ProjectNameWithoutExtension ?? Path.GetFileNameWithoutExtension(outputPath), + this.config.ProjectNameWithoutExtension ?? Path.GetFileNameWithoutExtension(outputPath), syntaxTrees: new[] { csharpTree }, references: references.Select(r => r.Item2).Append(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)), // if System.Object can't be found a warning is generated - options: new CodeAnalysis.CSharp.CSharpCompilationOptions(outputKind: CodeAnalysis.OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release) - ); + options: new CodeAnalysis.CSharp.CSharpCompilationOptions(outputKind: CodeAnalysis.OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release)); using var outputStream = File.OpenWrite(outputPath); serialization.Seek(0, SeekOrigin.Begin); var astResource = new CodeAnalysis.ResourceDescription(DotnetCoreDll.ResourceName, () => serialization, true); - var result = compilation.Emit(outputStream, + var result = compilation.Emit( + outputStream, options: new CodeAnalysis.Emit.EmitOptions(), - manifestResources: new CodeAnalysis.ResourceDescription[] { astResource } - ); + manifestResources: new CodeAnalysis.ResourceDescription[] { astResource }); var errs = result.Diagnostics.Where(d => d.Severity >= CodeAnalysis.DiagnosticSeverity.Error); - if (errs.Any()) throw new Exception($"error(s) on emitting dll: {Environment.NewLine}{String.Join(Environment.NewLine, errs.Select(d => d.GetMessage()))}"); + if (errs.Any()) + { + throw new Exception($"error(s) on emitting dll: {Environment.NewLine}{string.Join(Environment.NewLine, errs.Select(d => d.GetMessage()))}"); + } return outputPath; } catch (Exception ex) { - this.LogAndUpdate(ref this.CompilationStatus.DllGeneration, ex); - this.LogAndUpdate(ref this.CompilationStatus.DllGeneration, ErrorCode.GeneratingDllFailed, Enumerable.Empty()); + this.LogAndUpdate(ref this.compilationStatus.DllGeneration, ex); + this.LogAndUpdate(ref this.compilationStatus.DllGeneration, ErrorCode.GeneratingDllFailed, Enumerable.Empty()); return null; } } @@ -1029,11 +1160,13 @@ public static bool ReadBinary(Stream stream, out QsCompilation syntaxTree) => public static string GeneratedFile(NonNullable fileId, string outputFolder, string fileEnding, string content = null) { if (!CompilationUnitManager.TryGetUri(fileId, out var file)) - { throw new ArgumentException("the given file id is not consistent with and id generated by the Q# compiler"); } + { + throw new ArgumentException("the given file id is not consistent with and id generated by the Q# compiler"); + } string FullDirectoryName(string dir) => Path.GetFullPath(dir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar); - outputFolder = String.IsNullOrWhiteSpace(outputFolder) ? "." : outputFolder; + outputFolder = string.IsNullOrWhiteSpace(outputFolder) ? "." : outputFolder; var outputUri = new Uri(FullDirectoryName(outputFolder)); var currentDir = new Uri(FullDirectoryName(".")); var relFilePath = currentDir.MakeRelativeUri(file); @@ -1043,8 +1176,14 @@ string FullDirectoryName(string dir) => : Path.GetDirectoryName(outputUri.LocalPath); var targetFile = Path.GetFullPath(Path.Combine(fileDir, Path.GetFileNameWithoutExtension(filePath) + fileEnding)); - if (content == null) return targetFile; - if (!Directory.Exists(fileDir)) Directory.CreateDirectory(fileDir); + if (content == null) + { + return targetFile; + } + if (!Directory.Exists(fileDir)) + { + Directory.CreateDirectory(fileDir); + } File.WriteAllText(targetFile, content); return targetFile; } diff --git a/src/QsCompiler/Compiler/Compiler.csproj b/src/QsCompiler/Compiler/Compiler.csproj index 14f8fc3c7a..2a5f429a9d 100644 --- a/src/QsCompiler/Compiler/Compiler.csproj +++ b/src/QsCompiler/Compiler/Compiler.csproj @@ -3,6 +3,7 @@ netstandard2.1 + Microsoft.Quantum.Compiler Microsoft.Quantum.QsCompiler Microsoft Q# compiler library. @@ -17,7 +18,12 @@ + + + + + diff --git a/src/QsCompiler/Compiler/Compiler.nuspec.template b/src/QsCompiler/Compiler/Compiler.nuspec.template index aaf0f40ae3..d10f53591b 100644 --- a/src/QsCompiler/Compiler/Compiler.nuspec.template +++ b/src/QsCompiler/Compiler/Compiler.nuspec.template @@ -1,5 +1,5 @@ - + Microsoft.Quantum.Compiler $version$ @@ -13,9 +13,12 @@ See: https://docs.microsoft.com/en-us/quantum/relnotes/ https://github.com/microsoft/qsharp-compiler - https://secure.gravatar.com/avatar/bd1f02955b2853ba0a3b1cdc2434e8ec.png + images\qdk-nuget-icon.png $copyright$ Quantum Q# Qsharp + + + \ No newline at end of file diff --git a/src/QsCompiler/Compiler/ExternalRewriteSteps.cs b/src/QsCompiler/Compiler/ExternalRewriteSteps.cs index 3857cb84c5..c3b90a3115 100644 --- a/src/QsCompiler/Compiler/ExternalRewriteSteps.cs +++ b/src/QsCompiler/Compiler/ExternalRewriteSteps.cs @@ -15,67 +15,73 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler { internal static class RewriteSteps { /// - /// Concrete implementation of a rewrite steps with an additional property specifying the dll it was loaded from. + /// Concrete implementation of a rewrite steps with an additional property specifying the dll it was loaded from. /// internal class LoadedStep : IRewriteStep { internal readonly Uri Origin; - private readonly IRewriteStep _SelfAsStep; - private readonly object _SelfAsObject; + private readonly IRewriteStep selfAsStep; + private readonly object selfAsObject; + + private readonly MethodInfo[] interfaceMethods; - private readonly MethodInfo[] _InterfaceMethods; private MethodInfo InterfaceMethod(string name) => - // This choice of filtering the interface methods may seem a bit particular. - // However, unless you know what you are doing, please don't change it. - // If you are sure you know what you are doing, please make sure the loading via reflection works for rewrite steps + // This choice of filtering the interface methods may seem a bit particular. + // However, unless you know what you are doing, please don't change it. + // If you are sure you know what you are doing, please make sure the loading via reflection works for rewrite steps // implemented in both F# or C#, and whether they are compiled against the current compiler version or an older one. - this._InterfaceMethods?.FirstOrDefault(method => method.Name.Split("-").Last() == name); + this.interfaceMethods?.FirstOrDefault(method => method.Name.Split("-").Last() == name); private object GetViaReflection(string name) => - InterfaceMethod($"get_{name}")?.Invoke(_SelfAsObject, null); + this.InterfaceMethod($"get_{name}")?.Invoke(this.selfAsObject, null); private T GetViaReflection(string name) => - (T)InterfaceMethod($"get_{name}")?.Invoke(_SelfAsObject, null); + (T)this.InterfaceMethod($"get_{name}")?.Invoke(this.selfAsObject, null); private void SetViaReflection(string name, T arg) => - InterfaceMethod($"set_{name}")?.Invoke(_SelfAsObject, new object[] { arg }); + this.InterfaceMethod($"set_{name}")?.Invoke(this.selfAsObject, new object[] { arg }); private T InvokeViaReflection(string name, params object[] args) => - (T)InterfaceMethod(name)?.Invoke(_SelfAsObject, args); - + (T)this.InterfaceMethod(name)?.Invoke(this.selfAsObject, args); /// /// Attempts to construct a rewrite step via reflection. - /// Note that the loading via reflection has the consequence that methods may fail on execution. - /// This is e.g. the case if they invoke methods from package references if the corresponding dll - /// has not been copied to output folder of the dll from which the rewrite step is loaded. - /// Throws the corresponding exception if that construction fails. + /// Note that the loading via reflection has the consequence that methods may fail on execution. + /// This is e.g. the case if they invoke methods from package references if the corresponding dll + /// has not been copied to output folder of the dll from which the rewrite step is loaded. + /// Throws the corresponding exception if that construction fails. /// internal LoadedStep(object implementation, Type interfaceType, Uri origin) { this.Origin = origin ?? throw new ArgumentNullException(nameof(origin)); - this._SelfAsObject = implementation ?? throw new ArgumentNullException(nameof(implementation)); + this.selfAsObject = implementation ?? throw new ArgumentNullException(nameof(implementation)); - // Initializing the _InterfaceMethods even if the implementation implements IRewriteStep + // Initializing the _InterfaceMethods even if the implementation implements IRewriteStep // would result in certain properties being loaded via reflection instead of simply being accessed via _SelfAsStep. - if (this._SelfAsObject is IRewriteStep step) this._SelfAsStep = step; - else this._InterfaceMethods = implementation.GetType().GetInterfaceMap(interfaceType).TargetMethods; + if (this.selfAsObject is IRewriteStep step) + { + this.selfAsStep = step; + } + else + { + this.interfaceMethods = implementation.GetType().GetInterfaceMap(interfaceType).TargetMethods; + } - // The Name and Priority need to be fixed throughout the loading, + // The Name and Priority need to be fixed throughout the loading, // so whatever their value is when loaded that's what these values well be as far at the compiler is concerned. - this.Name = _SelfAsStep?.Name ?? this.GetViaReflection(nameof(IRewriteStep.Name)); - this.Priority = _SelfAsStep?.Priority ?? this.GetViaReflection(nameof(IRewriteStep.Priority)); + this.Name = this.selfAsStep?.Name ?? this.GetViaReflection(nameof(IRewriteStep.Name)); + this.Priority = this.selfAsStep?.Priority ?? this.GetViaReflection(nameof(IRewriteStep.Priority)); } - public string Name { get; } + public int Priority { get; } + internal static Diagnostic ConvertDiagnostic(IRewriteStep.Diagnostic diagnostic, Func getCode = null) { var severity = @@ -87,15 +93,18 @@ internal static Diagnostic ConvertDiagnostic(IRewriteStep.Diagnostic diagnostic, var startPosition = diagnostic.Start == null ? null : new Position(diagnostic.Start.Item1, diagnostic.Start.Item2); var endPosition = diagnostic.End == null ? startPosition : new Position(diagnostic.End.Item1, diagnostic.End.Item2); var range = startPosition == null || diagnostic.Source == null ? null : new LSP.Range { Start = startPosition, End = endPosition }; - if (range != null && !Utils.IsValidRange(range)) range = null; + if (range != null && !Utils.IsValidRange(range)) + { + range = null; + } var stageAnnotation = diagnostic.Stage == IRewriteStep.Stage.PreconditionVerification ? $"[{diagnostic.Stage}] " : diagnostic.Stage == IRewriteStep.Stage.PostconditionVerification ? $"[{diagnostic.Stage}] " : ""; - // NOTE: If we change data structure to add or change properties, - // then the cast below in GeneratedDiagnostics needs to be adapted. + // NOTE: If we change data structure to add or change properties, + // then the cast below in GeneratedDiagnostics needs to be adapted. return new Diagnostic { Code = getCode?.Invoke(severity), @@ -108,7 +117,7 @@ internal static Diagnostic ConvertDiagnostic(IRewriteStep.Diagnostic diagnostic, public IDictionary AssemblyConstants { - get => _SelfAsStep?.AssemblyConstants + get => this.selfAsStep?.AssemblyConstants ?? this.GetViaReflection>(nameof(IRewriteStep.AssemblyConstants)); } @@ -116,16 +125,25 @@ public IEnumerable GeneratedDiagnostics { get { - if (_SelfAsStep != null) return _SelfAsStep.GeneratedDiagnostics; + if (this.selfAsStep != null) + { + return this.selfAsStep.GeneratedDiagnostics; + } static bool IEnumerableInterface(Type t) => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>); var enumerable = this.GetViaReflection(nameof(IRewriteStep.GeneratedDiagnostics)) as IEnumerable; var itemType = enumerable?.GetType().GetInterfaces().FirstOrDefault(IEnumerableInterface)?.GetGenericArguments().FirstOrDefault(); - if (itemType == null) return null; + if (itemType == null) + { + return null; + } var diagnostics = ImmutableArray.CreateBuilder(); foreach (var obj in enumerable) { - if (obj == null) continue; + if (obj == null) + { + continue; + } diagnostics.Add(new IRewriteStep.Diagnostic { Severity = (CodeAnalysis.DiagnosticSeverity)itemType.GetProperty(nameof(IRewriteStep.Diagnostic.Severity)).GetValue(obj, null), @@ -139,29 +157,30 @@ public IEnumerable GeneratedDiagnostics } } - public bool ImplementsTransformation { - get => _SelfAsStep?.ImplementsTransformation + get => this.selfAsStep?.ImplementsTransformation ?? this.GetViaReflection(nameof(IRewriteStep.ImplementsTransformation)); } public bool ImplementsPreconditionVerification { - get => _SelfAsStep?.ImplementsPreconditionVerification + get => this.selfAsStep?.ImplementsPreconditionVerification ?? this.GetViaReflection(nameof(IRewriteStep.ImplementsPreconditionVerification)); } public bool ImplementsPostconditionVerification { - get => _SelfAsStep?.ImplementsPostconditionVerification + get => this.selfAsStep?.ImplementsPostconditionVerification ?? this.GetViaReflection(nameof(IRewriteStep.ImplementsPostconditionVerification)); } - public bool Transformation(QsCompilation compilation, out QsCompilation transformed) { - if (_SelfAsStep != null) return _SelfAsStep.Transformation(compilation, out transformed); + if (this.selfAsStep != null) + { + return this.selfAsStep.Transformation(compilation, out transformed); + } var args = new object[] { compilation, null }; var success = this.InvokeViaReflection(nameof(IRewriteStep.Transformation), args); transformed = success ? (QsCompilation)args[1] : compilation; @@ -169,34 +188,38 @@ public bool Transformation(QsCompilation compilation, out QsCompilation transfor } public bool PreconditionVerification(QsCompilation compilation) => - _SelfAsStep?.PreconditionVerification(compilation) + this.selfAsStep?.PreconditionVerification(compilation) ?? this.InvokeViaReflection(nameof(IRewriteStep.PreconditionVerification), compilation); public bool PostconditionVerification(QsCompilation compilation) => - _SelfAsStep?.PostconditionVerification(compilation) + this.selfAsStep?.PostconditionVerification(compilation) ?? this.InvokeViaReflection(nameof(IRewriteStep.PostconditionVerification), compilation); } - /// /// Loads all dlls listed as containing rewrite steps to include in the compilation process in the given configuration. - /// Generates suitable diagnostics if a listed file can't be found or loaded. + /// Generates suitable diagnostics if a listed file can't be found or loaded. /// Finds all types implementing the IRewriteStep interface and loads the corresponding rewrite steps - /// according to the specified priority, where a steps with a higher priority will be listed first in the returned array. - /// If the function onDiagnostic is specified and not null, calls it on all generated diagnostics, - /// and calls onException on all caught exceptions if it is specified and not null. - /// Returns an empty array if the rewrite steps in the given configurations are set to null. + /// according to the specified priority, where a steps with a higher priority will be listed first in the returned array. + /// If the function onDiagnostic is specified and not null, calls it on all generated diagnostics, + /// and calls onException on all caught exceptions if it is specified and not null. + /// Returns an empty array if the rewrite steps in the given configurations are set to null. /// - internal static ImmutableArray Load(CompilationLoader.Configuration config, - Action onDiagnostic = null, Action onException = null) + internal static ImmutableArray Load( + CompilationLoader.Configuration config, + Action onDiagnostic = null, + Action onException = null) { - if (config.RewriteSteps == null) return ImmutableArray.Empty; + if (config.RewriteSteps == null) + { + return ImmutableArray.Empty; + } static Assembly LoadAssembly(string path) => CompilationLoader.LoadAssembly?.Invoke(path) ?? Assembly.LoadFrom(path); Uri WithFullPath(string file) { try { - return String.IsNullOrWhiteSpace(file) ? null : new Uri(Path.GetFullPath(file)); + return string.IsNullOrWhiteSpace(file) ? null : new Uri(Path.GetFullPath(file)); } catch (Exception ex) { @@ -223,12 +246,15 @@ Uri WithFullPath(string file) { var typesInAssembly = LoadAssembly(target.LocalPath).GetTypes(); var exactInterfaceMatches = typesInAssembly.Where(t => typeof(IRewriteStep).IsAssignableFrom(t)); // inherited interface is defined in this exact dll - if (exactInterfaceMatches.Any()) relevantTypes.AddRange(exactInterfaceMatches); + if (exactInterfaceMatches.Any()) + { + relevantTypes.AddRange(exactInterfaceMatches); + } else { - // If the inherited interface is defined in older compiler version, then we can attempt to load the step anyway via reflection. - // However, in this case we have to load the corresponding assembly into the current context, which can have its own issues. - // We hence first check if this may be the case, and if so we proceed to attempt the loading via reflection. + // If the inherited interface is defined in older compiler version, then we can attempt to load the step anyway via reflection. + // However, in this case we have to load the corresponding assembly into the current context, which can have its own issues. + // We hence first check if this may be the case, and if so we proceed to attempt the loading via reflection. static bool IsPossibleMatch(Type t) => t.GetInterfaces().Any(t => t.FullName == typeof(IRewriteStep).FullName); var possibleInterfaceMatches = typesInAssembly.Where(IsPossibleMatch); if (possibleInterfaceMatches.Any()) @@ -249,7 +275,10 @@ Uri WithFullPath(string file) foreach (var exSub in ex.LoaderExceptions) { var msg = exSub.ToString(); - if (msg != null) sb.AppendLine(msg); + if (msg != null) + { + sb.AppendLine(msg); + } if (exSub is FileNotFoundException exFileNotFound && !string.IsNullOrEmpty(exFileNotFound.FusionLog)) { sb.AppendLine("Fusion Log:"); @@ -269,7 +298,7 @@ Uri WithFullPath(string file) var loadedSteps = new List(); foreach (var type in relevantTypes) { - try + try { var instance = Activator.CreateInstance(type); if (instance is IRewriteStep step) @@ -278,15 +307,17 @@ Uri WithFullPath(string file) continue; } - try // we also try to load rewrite steps that have been compiled against a different compiler version + // we also try to load rewrite steps that have been compiled against a different compiler version + try { var interfaceType = type.GetInterfaces().First(t => t.FullName == typeof(IRewriteStep).FullName); var loadedStep = new LoadedStep(instance, interfaceType, target); onDiagnostic?.Invoke(LoadWarning(WarningCode.RewriteStepLoadedViaReflection, loadedStep.Name, target.LocalPath)); loadedSteps.Add(loadedStep); } - catch // we don't log the exception, since it is perfectly possible that we should have ignored this type in the first place + catch { + // we don't log the exception, since it is perfectly possible that we should have ignored this type in the first place onDiagnostic?.Invoke(LoadWarning(WarningCode.FailedToLoadRewriteStepViaReflection, target.LocalPath)); } } @@ -299,9 +330,14 @@ Uri WithFullPath(string file) foreach (var loaded in loadedSteps) { var assemblyConstants = loaded.AssemblyConstants; - if (assemblyConstants == null) continue; + if (assemblyConstants == null) + { + continue; + } foreach (var kvPair in config.AssemblyConstants ?? Enumerable.Empty>()) - { assemblyConstants[kvPair.Key] = kvPair.Value; } + { + assemblyConstants[kvPair.Key] = kvPair.Value; + } // We don't overwrite assembly properties specified by configuration. var defaultOutput = assemblyConstants.TryGetValue(AssemblyConstants.OutputPath, out var path) ? path : null; diff --git a/src/QsCompiler/Compiler/FunctorGeneration.cs b/src/QsCompiler/Compiler/FunctorGeneration.cs index 4a41516deb..2f4e37a765 100644 --- a/src/QsCompiler/Compiler/FunctorGeneration.cs +++ b/src/QsCompiler/Compiler/FunctorGeneration.cs @@ -21,7 +21,8 @@ public static class CodeGeneration /// private static QsTuple> ControlledArg(QsTuple> arg) => arg != null - ? SyntaxGenerator.WithControlQubits(arg, + ? SyntaxGenerator.WithControlQubits( + arg, QsNullable>.Null, QsLocalSymbol.NewValidName(NonNullable.New(InternalUse.ControlQubitsName)), QsNullable>.Null) @@ -34,9 +35,15 @@ private static QsTuple> ControlledArg(Qs /// private static QsSpecialization GetSpecialization(this IEnumerable specs, QsSpecializationKind kind) { - if (specs == null || specs.Any(s => s == null)) throw new ArgumentNullException(nameof(specs)); + if (specs == null || specs.Any(s => s == null)) + { + throw new ArgumentNullException(nameof(specs)); + } specs = specs.Where(spec => spec.Kind == kind); - if (specs.Count() > 1) throw new ArgumentException("several specializations of the given kind exist"); + if (specs.Count() > 1) + { + throw new ArgumentException("several specializations of the given kind exist"); + } return specs.Any() ? specs.Single() : null; } @@ -58,11 +65,20 @@ impl is SpecializationImplementation.Provided content /// private static (QsTuple>, QsScope)? BodyImplementation(this QsCallable callable) { - if (callable == null || callable.Kind.IsTypeConstructor) return null; + if (callable == null || callable.Kind.IsTypeConstructor) + { + return null; + } var noBodyException = new ArgumentException("no implementation provided for body"); var bodyDecl = callable.Specializations.GetSpecialization(QsSpecializationKind.QsBody)?.Implementation ?? throw noBodyException; - if (bodyDecl.IsGenerated) throw new ArgumentException("functor generator directive on body specialization"); - if (bodyDecl.IsExternal || bodyDecl.IsIntrinsic) return null; + if (bodyDecl.IsGenerated) + { + throw new ArgumentException("functor generator directive on body specialization"); + } + if (bodyDecl.IsExternal || bodyDecl.IsIntrinsic) + { + return null; + } return GetContent(bodyDecl) ?? throw noBodyException; } @@ -81,7 +97,10 @@ private static (QsTuple>, QsScope)? Body private static QsCallable BuildAdjoint(this QsCallable callable) { var bodyDecl = BodyImplementation(callable); - if (bodyDecl == null) return callable ?? throw new ArgumentNullException(nameof(callable)); + if (bodyDecl == null) + { + return callable ?? throw new ArgumentNullException(nameof(callable)); + } var adj = callable.Specializations.GetSpecialization(QsSpecializationKind.QsAdjoint); if (adj != null && adj.Implementation is SpecializationImplementation.Generated gen) @@ -89,8 +108,11 @@ private static QsCallable BuildAdjoint(this QsCallable callable) var (bodyArg, bodyImpl) = bodyDecl.Value; void SetImplementation(QsScope impl) => adj = adj.WithImplementation(SpecializationImplementation.NewProvided(bodyArg, impl)); - //if (gen.Item.IsSelfInverse) SetImplementation(bodyImpl); -> nothing to do here, we want to keep this information - if (gen.Item.IsInvert) SetImplementation(bodyImpl.GenerateAdjoint()); + // if (gen.Item.IsSelfInverse) SetImplementation(bodyImpl); -> nothing to do here, we want to keep this information + if (gen.Item.IsInvert) + { + SetImplementation(bodyImpl.GenerateAdjoint()); + } } return callable.WithSpecializations(specs => specs.Select(s => s.Kind == QsSpecializationKind.QsAdjoint ? adj : s).ToImmutableArray()); } @@ -109,14 +131,20 @@ private static QsCallable BuildAdjoint(this QsCallable callable) private static QsCallable BuildControlled(this QsCallable callable) { var bodyDecl = BodyImplementation(callable); - if (bodyDecl == null) return callable ?? throw new ArgumentNullException(nameof(callable)); + if (bodyDecl == null) + { + return callable ?? throw new ArgumentNullException(nameof(callable)); + } var ctl = callable.Specializations.GetSpecialization(QsSpecializationKind.QsControlled); if (ctl != null && ctl.Implementation is SpecializationImplementation.Generated gen) { var (bodyArg, bodyImpl) = bodyDecl.Value; void SetImplementation(QsScope impl) => ctl = ctl.WithImplementation(SpecializationImplementation.NewProvided(ControlledArg(bodyArg), impl)); - if (gen.Item.IsDistribute) SetImplementation(bodyImpl.GenerateControlled()); + if (gen.Item.IsDistribute) + { + SetImplementation(bodyImpl.GenerateControlled()); + } } return callable.WithSpecializations(specs => specs.Select(s => s.Kind == QsSpecializationKind.QsControlled ? ctl : s).ToImmutableArray()); } @@ -138,7 +166,10 @@ private static QsCallable BuildControlled(this QsCallable callable) private static QsCallable BuildControlledAdjoint(this QsCallable callable) { var bodyDecl = BodyImplementation(callable); - if (bodyDecl == null) return callable ?? throw new ArgumentNullException(nameof(callable)); + if (bodyDecl == null) + { + return callable ?? throw new ArgumentNullException(nameof(callable)); + } var ctlAdj = callable.Specializations.GetSpecialization(QsSpecializationKind.QsControlledAdjoint); if (ctlAdj != null && ctlAdj.Implementation is SpecializationImplementation.Generated gen) @@ -173,7 +204,10 @@ private static QsCallable BuildControlledAdjoint(this QsCallable callable) /// public static bool GenerateFunctorSpecializations(QsCompilation compilation, out QsCompilation built, Action onException = null) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } var success = true; var namespaces = compilation.Namespaces.Where(ns => ns != null).Select(ns => { @@ -196,8 +230,10 @@ public static bool GenerateFunctorSpecializations(QsCompilation compilation, out return QsNamespaceElement.NewQsCallable(callableDecl.Item); } } - else return element; - + else + { + return element; + } }); return new QsNamespace(ns.Name, elements.ToImmutableArray(), ns.Documentation); }); @@ -206,4 +242,3 @@ public static bool GenerateFunctorSpecializations(QsCompilation compilation, out } } } - diff --git a/src/QsCompiler/Compiler/Logging.cs b/src/QsCompiler/Compiler/Logging.cs index 998194b70c..d3211dc810 100644 --- a/src/QsCompiler/Compiler/Logging.cs +++ b/src/QsCompiler/Compiler/Logging.cs @@ -9,53 +9,58 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.Quantum.QsCompiler.Diagnostics +namespace Microsoft.Quantum.QsCompiler.Diagnostics { public interface ILogger { void Log(ErrorCode item, IEnumerable args, string source = null, LSP.Range range = null); + void Log(WarningCode item, IEnumerable args, string source = null, LSP.Range range = null); + void Log(InformationCode item, IEnumerable args, string source = null, LSP.Range range = null, params string[] messageParam); void Log(params Diagnostic[] messages); + void Log(Exception ex); } public abstract class LogTracker : ILogger { public DiagnosticSeverity Verbosity { get; set; } + public int NrErrorsLogged { get; private set; } + public int NrWarningsLogged { get; private set; } + public int NrExceptionsLogged { get; private set; } - private readonly int LineNrOffset; - private readonly ImmutableArray NoWarn; + private readonly int lineNrOffset; + private readonly ImmutableArray noWarn; public LogTracker( DiagnosticSeverity verbosity = DiagnosticSeverity.Warning, - IEnumerable noWarn = null, int lineNrOffset = 0) + IEnumerable noWarn = null, + int lineNrOffset = 0) { this.Verbosity = verbosity; this.NrErrorsLogged = 0; this.NrWarningsLogged = 0; this.NrExceptionsLogged = 0; - this.LineNrOffset = lineNrOffset; - this.NoWarn = noWarn?.ToImmutableArray() ?? ImmutableArray.Empty; + this.lineNrOffset = lineNrOffset; + this.noWarn = noWarn?.ToImmutableArray() ?? ImmutableArray.Empty; } - // methods that need to or can be specified by a deriving class /// - /// Called whenever a diagnostic is logged after the diagnostic has been properly processed. + /// Called whenever a diagnostic is logged after the diagnostic has been properly processed. /// - abstract protected internal void Print(Diagnostic msg); + protected internal abstract void Print(Diagnostic msg); /// - /// Called whenever an exception is logged after the exception has been properly tracked. - /// Prints the given exception as Hint if the logger verbosity is sufficiently high. + /// Called whenever an exception is logged after the exception has been properly tracked. + /// Prints the given exception as Hint if the logger verbosity is sufficiently high. /// protected internal virtual void OnException(Exception ex) => this.Output(ex == null ? null : new Diagnostic @@ -64,7 +69,6 @@ protected internal virtual void OnException(Exception ex) => Message = $"{Environment.NewLine}{ex}{Environment.NewLine}" }); - // routines for convenience /// @@ -106,114 +110,144 @@ public void Log(InformationCode code, IEnumerable args, string source = Severity = DiagnosticSeverity.Information, Code = null, // do not show a code for infos Source = source, - Message = $"{DiagnosticItem.Message(code, args ?? Enumerable.Empty())}{Environment.NewLine}{String.Join(Environment.NewLine, messageParam)}", + Message = $"{DiagnosticItem.Message(code, args ?? Enumerable.Empty())}{Environment.NewLine}{string.Join(Environment.NewLine, messageParam)}", Range = range }); /// /// Logs the given diagnostic messages. - /// Ignores any parameter that is null. + /// Ignores any parameter that is null. /// public void Log(params Diagnostic[] messages) { - if (messages == null) return; + if (messages == null) + { + return; + } foreach (var m in messages) { - if (m == null) continue; + if (m == null) + { + continue; + } this.Log(m); } } - // core routines which track logged diagnostics /// - /// Calls Print on the given diagnostic if the verbosity is sufficiently high. - /// Does nothing if the given diagnostic is null. + /// Calls Print on the given diagnostic if the verbosity is sufficiently high. + /// Does nothing if the given diagnostic is null. /// private void Output(Diagnostic msg) - { if (msg?.Severity <= this.Verbosity) this.Print(msg); } + { + if (msg?.Severity <= this.Verbosity) + { + this.Print(msg); + } + } /// /// Increases the error or warning counter if appropriate, and /// prints the given diagnostic ff the logger verbosity is sufficiently high. - /// Before printing, the line numbers are shifted by the offset specified upon initialization. - /// Returns without doing anything if the given diagnostic is a warning that is to be ignored. - /// Throws an ArgumentNullException if the given diagnostic is null. + /// Before printing, the line numbers are shifted by the offset specified upon initialization. + /// Returns without doing anything if the given diagnostic is a warning that is to be ignored. + /// Throws an ArgumentNullException if the given diagnostic is null. /// public void Log(Diagnostic m) { - if (m == null) throw new ArgumentNullException(nameof(m)); + if (m == null) + { + throw new ArgumentNullException(nameof(m)); + } if (m.Severity == DiagnosticSeverity.Warning && CompilationBuilder.Diagnostics.TryGetCode(m.Code, out int code) - && this.NoWarn.Contains(code)) return; + && this.noWarn.Contains(code)) + { + return; + } - if (m.Severity == DiagnosticSeverity.Error) ++this.NrErrorsLogged; - if (m.Severity == DiagnosticSeverity.Warning) ++this.NrWarningsLogged; + if (m.Severity == DiagnosticSeverity.Error) + { + ++this.NrErrorsLogged; + } + if (m.Severity == DiagnosticSeverity.Warning) + { + ++this.NrWarningsLogged; + } - var msg = m.Range == null ? m : m.WithUpdatedLineNumber(LineNrOffset); + var msg = m.Range == null ? m : m.WithUpdatedLineNumber(this.lineNrOffset); this.Output(msg); } /// - /// Increases the exception counter and calls OnException with the given exception. - /// Throws an ArgumentNullException if the given exception is null. + /// Increases the exception counter and calls OnException with the given exception. + /// Throws an ArgumentNullException if the given exception is null. /// public void Log(Exception ex) { - if (ex == null) throw new ArgumentNullException(nameof(ex)); + if (ex == null) + { + throw new ArgumentNullException(nameof(ex)); + } ++this.NrExceptionsLogged; - this.OnException(ex); + this.OnException(ex); } } - public static class Formatting { public static IEnumerable Indent(params string[] items) => items?.Select(msg => $" {msg}"); /// - /// Returns a string that contains all information about the given diagnostic in human readable format. - /// The string contains one-based position information if the range information is not null, + /// Returns a string that contains all information about the given diagnostic in human readable format. + /// The string contains one-based position information if the range information is not null, /// assuming the given position information is zero-based. - /// Throws an ArgumentNullException if the given message is null. + /// Throws an ArgumentNullException if the given message is null. /// public static string HumanReadableFormat(Diagnostic msg) { - if (msg == null) throw new ArgumentNullException(nameof(msg)); - var codeStr = msg.Code == null ? String.Empty : $" {msg.Code}"; + if (msg == null) + { + throw new ArgumentNullException(nameof(msg)); + } + var codeStr = msg.Code == null ? string.Empty : $" {msg.Code}"; var (startLine, startChar) = (msg.Range?.Start?.Line + 1 ?? 0, msg.Range?.Start?.Character + 1 ?? 0); - var source = msg.Source == null ? String.Empty : $"File: {msg.Source} \n"; - var position = msg.Range == null ? String.Empty : $"Position: [ln {startLine}, cn {startChar}] \n"; + var source = msg.Source == null ? string.Empty : $"File: {msg.Source} \n"; + var position = msg.Range == null ? string.Empty : $"Position: [ln {startLine}, cn {startChar}] \n"; var message = $"{source}{position}{msg.Message ?? "no details are available"}"; return msg.Severity == DiagnosticSeverity.Error ? $"\nError{codeStr}: \n{message}" : msg.Severity == DiagnosticSeverity.Warning ? $"\nWarning{codeStr}: \n{message}" : msg.Severity == DiagnosticSeverity.Information ? $"\nInformation{codeStr}: \n{message}" : - String.IsNullOrWhiteSpace(codeStr) ? $"\n{message}" : $"\n[{codeStr.Trim()}] {message}"; + string.IsNullOrWhiteSpace(codeStr) ? $"\n{message}" : $"\n[{codeStr.Trim()}] {message}"; } /// - /// Returns a string that contains all information about the given diagnostic - /// in a format that is detected and processed as a diagnostic by VS and VS Code. - /// The string contains one-based position information if the range information is not null, + /// Returns a string that contains all information about the given diagnostic + /// in a format that is detected and processed as a diagnostic by VS and VS Code. + /// The string contains one-based position information if the range information is not null, /// assuming the given position information is zero-based. - /// Throws an ArgumentNullException if the given message is null. + /// Throws an ArgumentNullException if the given message is null. /// public static string MsBuildFormat(Diagnostic msg) { - if (msg == null) throw new ArgumentNullException(nameof(msg)); - var codeStr = msg.Code == null ? String.Empty : $" {msg.Code}"; + if (msg == null) + { + throw new ArgumentNullException(nameof(msg)); + } + var codeStr = msg.Code == null ? string.Empty : $" {msg.Code}"; var (startLine, startChar) = (msg.Range?.Start?.Line + 1 ?? 0, msg.Range?.Start?.Character + 1 ?? 0); var level = - msg.Severity == DiagnosticSeverity.Error ? ("error") : - msg.Severity == DiagnosticSeverity.Warning ? ("warning") : - ("info"); - var source = msg.Source ?? String.Empty; - var position = msg.Range == null ? String.Empty : $"({startLine},{startChar})"; + msg.Severity == DiagnosticSeverity.Error ? "error" : + msg.Severity == DiagnosticSeverity.Warning ? "warning" : + "info"; + var source = msg.Source ?? string.Empty; + var position = msg.Range == null ? string.Empty : $"({startLine},{startChar})"; return $"{source}{position}: {level}{codeStr}: {msg.Message}"; } } diff --git a/src/QsCompiler/Compiler/MetadataGeneration.cs b/src/QsCompiler/Compiler/MetadataGeneration.cs index e436564f6c..3b04e9929b 100644 --- a/src/QsCompiler/Compiler/MetadataGeneration.cs +++ b/src/QsCompiler/Compiler/MetadataGeneration.cs @@ -9,12 +9,11 @@ using Microsoft.Quantum.QsCompiler.ReservedKeywords; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; - namespace Microsoft.Quantum.QsCompiler { /// - /// The sole purpose of this module is to generate a C# class that explicitly uses referenced Q# content. - /// This is a hack to force that these references are not dropped upon Emit due to being unused. + /// The sole purpose of this module is to generate a C# class that explicitly uses referenced Q# content. + /// This is a hack to force that these references are not dropped upon Emit due to being unused. /// This is needed (only) if we want to build dlls using the command line compiler without relying on the dotnet core project system. /// internal static class MetadataGeneration @@ -24,11 +23,7 @@ public static ArrayTypeSyntax WithOmittedRankSpecifiers(this ArrayTypeSyntax syn SingletonList( ArrayRankSpecifier( SingletonSeparatedList( - OmittedArraySizeExpression() - ) - ) - ) - ); + OmittedArraySizeExpression())))); internal static CodeAnalysis.SyntaxTree GenerateAssemblyMetadata(IEnumerable references) { @@ -39,72 +34,52 @@ internal static CodeAnalysis.SyntaxTree GenerateAssemblyMetadata(IEnumerable TypeOfExpression( QualifiedName( AliasQualifiedName(IdentifierName(alias), IdentifierName(DotnetCoreDll.MetadataNamespace)), - IdentifierName(DotnetCoreDll.MetadataType) - ) - ) - ); + IdentifierName(DotnetCoreDll.MetadataType)))); var dependenciesInitializer = ArrayCreationExpression( - ArrayType(typeName).WithOmittedRankSpecifiers() - ) + ArrayType(typeName).WithOmittedRankSpecifiers()) .WithInitializer( InitializerExpression( SyntaxKind.ArrayInitializerExpression, - SeparatedList(metadataTypeNodes) - ) - ); + SeparatedList(metadataTypeNodes))); var metadataField = FieldDeclaration( VariableDeclaration( - ArrayType(typeName).WithOmittedRankSpecifiers() - ) + ArrayType(typeName).WithOmittedRankSpecifiers()) .WithVariables( SingletonSeparatedList( VariableDeclarator(Identifier(DotnetCoreDll.Dependencies)) .WithInitializer( - EqualsValueClause(dependenciesInitializer) - ) - ) - ) - ) + EqualsValueClause(dependenciesInitializer))))) .WithModifiers( TokenList( Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword), - Token(SyntaxKind.ReadOnlyKeyword) - ) - ); + Token(SyntaxKind.ReadOnlyKeyword))); var classDef = ClassDeclaration(DotnetCoreDll.MetadataType) .WithModifiers( - TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)) - ) + TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword))) .WithMembers( - SingletonList(metadataField) - ); + SingletonList(metadataField)); var namespaceDef = NamespaceDeclaration(IdentifierName(DotnetCoreDll.MetadataNamespace)) .WithMembers( - SingletonList(classDef) - ); + SingletonList(classDef)); var compilation = CompilationUnit() .WithExterns( - List(aliases.Select(Identifier).Select(ExternAliasDirective)) - ) + List(aliases.Select(Identifier).Select(ExternAliasDirective))) .WithMembers( - SingletonList(namespaceDef) - ); + SingletonList(namespaceDef)); return CSharpSyntaxTree.Create(compilation); } diff --git a/src/QsCompiler/Compiler/PluginInterface.cs b/src/QsCompiler/Compiler/PluginInterface.cs index 1b6bf81430..e53ef5b9c7 100644 --- a/src/QsCompiler/Compiler/PluginInterface.cs +++ b/src/QsCompiler/Compiler/PluginInterface.cs @@ -8,44 +8,47 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using VS = Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler { /// - /// Lists the priorities for built-in rewrite steps. + /// Lists the priorities for built-in rewrite steps. /// public static class RewriteStepPriorities { /// - /// Priority of the built-in transformation that replaces - /// if-statements with the corresponding calls to built-in quantum operations if possible. + /// Priority of the built-in transformation that replaces + /// if-statements with the corresponding calls to built-in quantum operations if possible. /// public const int ControlFlowSubstitutions = 1100; + /// - /// Priority of the built-in transformation that replaces - /// all type parametrized callables with concrete instantiations and drops any unused callables. + /// Priority of the built-in transformation that replaces + /// all type parametrized callables with concrete instantiations and drops any unused callables. /// public const int TypeParameterElimination = 1000; + /// - /// Priority of the built-in transformation that replaces - /// all functor generation directives with the corresponding implementation. + /// Priority of the built-in transformation that replaces + /// all functor generation directives with the corresponding implementation. /// public const int GenerationOfFunctorSupport = 600; + /// - /// Priority of the built-in transformation that inlines all conjugations + /// Priority of the built-in transformation that inlines all conjugations /// and thus eliminates that construct from the syntax tree. /// public const int InliningOfConjugations = 500; + /// - /// Priority of the built-in transformation that - /// evaluates classical computations as much as possible. + /// Priority of the built-in transformation that + /// evaluates classical computations as much as possible. /// public const int EvaluationOfClassicalComputations = 100; } public interface IRewriteStep { - public enum Stage + public enum Stage { Unknown = 0, PreconditionVerification = 1, @@ -56,41 +59,46 @@ public enum Stage public struct Diagnostic { /// - /// Indicates the severity of the diagnostic. - /// Generated diagnostics may be prioritized and filtered according to their severity. + /// Indicates the severity of the diagnostic. + /// Generated diagnostics may be prioritized and filtered according to their severity. /// public DiagnosticSeverity Severity { get; set; } + /// - /// Diagnostic message to be displayed to the user. + /// Diagnostic message to be displayed to the user. /// public string Message { get; set; } + /// /// Absolute path of the file where the code that caused the generation of the diagnostic is located. - /// The source is null if the diagnostic is not caused by a piece of source code. + /// The source is null if the diagnostic is not caused by a piece of source code. /// public string Source { get; set; } + /// - /// The stage during which the diagnostic was generated. - /// The stage is set to Unknown if no stage is specified. + /// The stage during which the diagnostic was generated. + /// The stage is set to Unknown if no stage is specified. /// public Stage Stage { get; set; } + /// /// Zero-based position in the source file where the code that caused the generation of the diagnostic starts. - /// The position is null if the diagnostic is not caused by a piece of source code. + /// The position is null if the diagnostic is not caused by a piece of source code. /// public Tuple Start { get; set; } + /// /// Zero-based position in the source file where the code that caused the generation of the diagnostic ends. - /// The position is null if the diagnostic is not caused by a piece of source code. + /// The position is null if the diagnostic is not caused by a piece of source code. /// public Tuple End { get; set; } /// - /// Initializes a new diagnostic. - /// If a diagnostic generated by the Q# compiler is given as argument, the values are initialized accordingly. + /// Initializes a new diagnostic. + /// If a diagnostic generated by the Q# compiler is given as argument, the values are initialized accordingly. /// - public static Diagnostic Create(VS.Diagnostic d = null, Stage stage = Stage.Unknown) => - d == null ? new Diagnostic() : new Diagnostic + public static Diagnostic Create(VS.Diagnostic d = null, Stage stage = Stage.Unknown) => + d == null ? default : new Diagnostic { Severity = d.Severity switch { @@ -108,72 +116,79 @@ public static Diagnostic Create(VS.Diagnostic d = null, Stage stage = Stage.Unkn } /// - /// User facing name identifying the rewrite step used for logging and in diagnostics. + /// User facing name identifying the rewrite step used for logging and in diagnostics. /// public string Name { get; } + /// - /// The priority of the transformation relative to other transformations within the same dll or package. - /// Steps with higher priority will be executed first. + /// The priority of the transformation relative to other transformations within the same dll or package. + /// Steps with higher priority will be executed first. /// public int Priority { get; } + /// - /// Dictionary that will be populated by the Q# compiler when the rewrite step is loaded. + /// Dictionary that will be populated by the Q# compiler when the rewrite step is loaded. /// It contains the assembly constants for the Q# compilation unit on which the rewrite step is acting. /// public IDictionary AssemblyConstants { get; } + /// - /// Contains diagnostics generated by the rewrite step and intended for display to the user. - /// Depending on the specified build configuration, the generated diagnostics may be queried - /// after all implemented interface methods have been executed. + /// Contains diagnostics generated by the rewrite step and intended for display to the user. + /// Depending on the specified build configuration, the generated diagnostics may be queried + /// after all implemented interface methods have been executed. /// - public IEnumerable GeneratedDiagnostics { get; } + public IEnumerable GeneratedDiagnostics { get; } /// - /// If a precondition verification is implemented, that verification is executed prior to executing anything else. - /// If the verification fails, nothing further is executed and the rewrite step is terminated. + /// If a precondition verification is implemented, that verification is executed prior to executing anything else. + /// If the verification fails, nothing further is executed and the rewrite step is terminated. /// public bool ImplementsPreconditionVerification { get; } + /// - /// Indicates whether or not the rewrite step intends to modify the compilation in any form. - /// If a transformation is implemented, then that transformation will be executed only if either - /// no precondition verification is implemented, or the implemented precondition verification succeeds. + /// Indicates whether or not the rewrite step intends to modify the compilation in any form. + /// If a transformation is implemented, then that transformation will be executed only if either + /// no precondition verification is implemented, or the implemented precondition verification succeeds. /// public bool ImplementsTransformation { get; } + /// /// A postcondition verification provides the means for diagnostics generation and detailed checks after transformation. - /// The verification is executed only if the precondition verification passes and after applying the implemented transformation (if any). + /// The verification is executed only if the precondition verification passes and after applying the implemented transformation (if any). /// public bool ImplementsPostconditionVerification { get; } /// - /// Verifies whether a given compilation satisfies the precondition for executing this rewrite step. - /// indicates whether or not this method is implemented. - /// If the precondition verification succeeds, then the invocation of an implemented transformation (if any) - /// with the given compilation should complete without throwing an exception. - /// The precondition verification should never throw an exception, - /// but instead indicate if the precondition is satisfied via the returned value. - /// More detailed information can be provided via logging. + /// Verifies whether a given compilation satisfies the precondition for executing this rewrite step. + /// indicates whether or not this method is implemented. + /// If the precondition verification succeeds, then the invocation of an implemented transformation (if any) + /// with the given compilation should complete without throwing an exception. + /// The precondition verification should never throw an exception, + /// but instead indicate if the precondition is satisfied via the returned value. + /// More detailed information can be provided via logging. /// /// Q# compilation for which to verify the precondition. /// Whether or not the given compilation satisfies the precondition. public bool PreconditionVerification(QsCompilation compilation); + /// - /// Implements a rewrite step transforming a Q# compilation. - /// indicates whether or not this method is implemented. - /// The transformation should complete without throwing an exception - /// if no precondition verification is implemented or the implemented verification passes. + /// Implements a rewrite step transforming a Q# compilation. + /// indicates whether or not this method is implemented. + /// The transformation should complete without throwing an exception + /// if no precondition verification is implemented or the implemented verification passes. /// /// Q# compilation that satisfies the implemented precondition, if any. /// Q# compilation after transformation. This value should not be null if the transformation succeeded. /// Whether or not the transformation succeeded. public bool Transformation(QsCompilation compilation, out QsCompilation transformed); + /// - /// Verifies whether a given compilation satisfies the postcondition after executing the implemented transformation (if any). - /// indicates whether or not this method is implemented. + /// Verifies whether a given compilation satisfies the postcondition after executing the implemented transformation (if any). + /// indicates whether or not this method is implemented. /// The verification may be omitted for performance reasons depending on the build configuration. - /// The postcondition verification should never throw an exception, - /// but instead indicate if the postcondition is satisfied via the returned value. - /// More detailed information can be displayed to the user by generating suitable diagnostics. + /// The postcondition verification should never throw an exception, + /// but instead indicate if the postcondition is satisfied via the returned value. + /// More detailed information can be displayed to the user by generating suitable diagnostics. /// /// Q# compilation after performing the implemented transformation. /// Whether or not the given compilation satisfies the postcondition of the transformation. diff --git a/src/QsCompiler/Compiler/Process.cs b/src/QsCompiler/Compiler/Process.cs index 2d3f800f72..0bce9973a8 100644 --- a/src/QsCompiler/Compiler/Process.cs +++ b/src/QsCompiler/Compiler/Process.cs @@ -7,15 +7,14 @@ using System.Text; using System.Threading; - namespace Microsoft.Quantum.QsCompiler { public static class ProcessRunner { /// - /// Starts the given process and accumulates the received output and error data in the given StringBuilders. + /// Starts the given process and accumulates the received output and error data in the given StringBuilders. /// Returns true if the process completed within the specified time without throwing an exception, and false otherwise. - /// Any thrown exception is returned as out parameter. + /// Any thrown exception is returned as out parameter. /// public static bool Run(Process process, StringBuilder output, StringBuilder error, out Exception ex, int timeout) { @@ -24,14 +23,26 @@ public static bool Run(Process process, StringBuilder output, StringBuilder erro { void AddOutput(object sender, DataReceivedEventArgs e) { - if (e.Data == null) outputWaitHandle.Set(); - else output.AppendLine(e.Data); + if (e.Data == null) + { + outputWaitHandle.Set(); + } + else + { + output.AppendLine(e.Data); + } } void AddError(object sender, DataReceivedEventArgs e) { - if (e.Data == null) errorWaitHandle.Set(); - else error.AppendLine(e.Data); + if (e.Data == null) + { + errorWaitHandle.Set(); + } + else + { + error.AppendLine(e.Data); + } } process.OutputDataReceived += AddOutput; @@ -47,7 +58,10 @@ void AddError(object sender, DataReceivedEventArgs e) && outputWaitHandle.WaitOne() && errorWaitHandle.WaitOne(); } - catch (Exception e) { ex = e; } + catch (Exception e) + { + ex = e; + } finally { // unsubscribe such that the AutoResetEvents are not accessed after disposing @@ -60,15 +74,21 @@ void AddError(object sender, DataReceivedEventArgs e) /// /// Starts and runs a process invoking the given command with the given arguments. - /// If a dictionary of environment variables and their desired values is specified, + /// If a dictionary of environment variables and their desired values is specified, /// sets these environment variables prior to execution and resets them afterwards. - /// Accumulates the received output and error data in the respective StringBuilder and returns them as out parameters. + /// Accumulates the received output and error data in the respective StringBuilder and returns them as out parameters. /// Returns true if the process completed within the specified time without throwing an exception, and false otherwise. - /// Returns the exit code of the process as well as any thrown exception as out parameter. + /// Returns the exit code of the process as well as any thrown exception as out parameter. /// - public static bool Run(string command, string args, - out StringBuilder outstream, out StringBuilder errstream, out int exitCode, out Exception ex, - IDictionary envVariables = null, int timeout = 10000) + public static bool Run( + string command, + string args, + out StringBuilder outstream, + out StringBuilder errstream, + out int exitCode, + out Exception ex, + IDictionary envVariables = null, + int timeout = 10000) { var process = new Process(); process.StartInfo = new ProcessStartInfo @@ -98,9 +118,10 @@ public static bool Run(string command, string args, finally { foreach (var entry in origEnvVariables) - { Environment.SetEnvironmentVariable(entry.Key, entry.Value); } + { + Environment.SetEnvironmentVariable(entry.Key, entry.Value); + } } } } - } diff --git a/src/QsCompiler/Compiler/RewriteSteps/ClassicallyControlled.cs b/src/QsCompiler/Compiler/RewriteSteps/ClassicallyControlled.cs index cbd1a86cf5..01e2b236cf 100644 --- a/src/QsCompiler/Compiler/RewriteSteps/ClassicallyControlled.cs +++ b/src/QsCompiler/Compiler/RewriteSteps/ClassicallyControlled.cs @@ -2,65 +2,68 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.Transformations.ClassicallyControlled; - namespace Microsoft.Quantum.QsCompiler.BuiltInRewriteSteps { /// - /// Replaces if-statements with the corresponding calls to built-in quantum operations if possible. + /// Replaces if-statements with the corresponding calls to built-in quantum operations if possible. /// internal class ClassicallyControlled : IRewriteStep { public string Name => "Classically Controlled"; - public int Priority => RewriteStepPriorities.ControlFlowSubstitutions; + + public int Priority => RewriteStepPriorities.ControlFlowSubstitutions; + public IDictionary AssemblyConstants { get; } + public IEnumerable GeneratedDiagnostics => null; public bool ImplementsPreconditionVerification => true; + public bool ImplementsTransformation => true; + public bool ImplementsPostconditionVerification => false; public ClassicallyControlled() { - AssemblyConstants = new Dictionary(); + this.AssemblyConstants = new Dictionary(); } public bool PreconditionVerification(QsCompilation compilation) { - var controlNs = compilation.Namespaces - .FirstOrDefault(ns => ns.Name.Equals(BuiltIn.ClassicallyControlledNamespace)); - - if (controlNs == null) - { - return false; - } - - var providedOperations = new QsNamespace[] { controlNs } - .Callables() - .Select(c => c.FullName) - .ToHashSet(); - var requiredBuiltIns = new HashSet() - { + var classicallyControlledRequired = ImmutableHashSet.Create( BuiltIn.ApplyIfZero.FullName, BuiltIn.ApplyIfZeroA.FullName, BuiltIn.ApplyIfZeroC.FullName, BuiltIn.ApplyIfZeroCA.FullName, - BuiltIn.ApplyIfOne.FullName, BuiltIn.ApplyIfOneA.FullName, BuiltIn.ApplyIfOneC.FullName, BuiltIn.ApplyIfOneCA.FullName, - BuiltIn.ApplyIfElseR.FullName, BuiltIn.ApplyIfElseRA.FullName, BuiltIn.ApplyIfElseRC.FullName, - BuiltIn.ApplyIfElseRCA.FullName - }; + BuiltIn.ApplyIfElseRCA.FullName); - return requiredBuiltIns.IsSubsetOf(providedOperations); + if (!this.CheckForRequired(compilation, BuiltIn.ClassicallyControlledNamespace, classicallyControlledRequired)) + { + return false; + } + + var cannonRequired = ImmutableHashSet.Create( + BuiltIn.NoOp.FullName); + + if (!this.CheckForRequired(compilation, BuiltIn.CanonNamespace, cannonRequired)) + { + return false; + } + + return true; } public bool Transformation(QsCompilation compilation, out QsCompilation transformed) @@ -73,5 +76,23 @@ public bool PostconditionVerification(QsCompilation compilation) { throw new System.NotImplementedException(); } + + private bool CheckForRequired(QsCompilation compilation, NonNullable namespaceName, ImmutableHashSet requiredBuiltIns) + { + var builtInNs = compilation.Namespaces + .FirstOrDefault(ns => ns.Name.Equals(namespaceName)); + + if (builtInNs == null) + { + return false; + } + + var providedOperations = new QsNamespace[] { builtInNs } + .Callables() + .Select(c => c.FullName) + .ToImmutableHashSet(); + + return requiredBuiltIns.IsSubsetOf(providedOperations); + } } } diff --git a/src/QsCompiler/Compiler/RewriteSteps/ConjugationInlining.cs b/src/QsCompiler/Compiler/RewriteSteps/ConjugationInlining.cs index 4463813812..f95edbc3ac 100644 --- a/src/QsCompiler/Compiler/RewriteSteps/ConjugationInlining.cs +++ b/src/QsCompiler/Compiler/RewriteSteps/ConjugationInlining.cs @@ -13,17 +13,22 @@ namespace Microsoft.Quantum.QsCompiler.BuiltInRewriteSteps internal class ConjugationInlining : IRewriteStep { public string Name => "Conjugation Inlining"; + public int Priority => RewriteStepPriorities.InliningOfConjugations; + public IDictionary AssemblyConstants { get; } + public IEnumerable GeneratedDiagnostics => null; public bool ImplementsPreconditionVerification => false; + public bool ImplementsTransformation => true; + public bool ImplementsPostconditionVerification => false; public ConjugationInlining() { - AssemblyConstants = new Dictionary(); + this.AssemblyConstants = new Dictionary(); } public bool PreconditionVerification(QsCompilation compilation) diff --git a/src/QsCompiler/Compiler/RewriteSteps/FullPreEvaluation.cs b/src/QsCompiler/Compiler/RewriteSteps/FullPreEvaluation.cs index 569ef3d733..8b9ad620f8 100644 --- a/src/QsCompiler/Compiler/RewriteSteps/FullPreEvaluation.cs +++ b/src/QsCompiler/Compiler/RewriteSteps/FullPreEvaluation.cs @@ -13,17 +13,22 @@ namespace Microsoft.Quantum.QsCompiler.BuiltInRewriteSteps internal class FullPreEvaluation : IRewriteStep { public string Name => "Full Pre-Evaluation"; + public int Priority => RewriteStepPriorities.EvaluationOfClassicalComputations; + public IDictionary AssemblyConstants { get; } + public IEnumerable GeneratedDiagnostics => null; public bool ImplementsPreconditionVerification => false; + public bool ImplementsTransformation => true; + public bool ImplementsPostconditionVerification => false; public FullPreEvaluation() { - AssemblyConstants = new Dictionary(); + this.AssemblyConstants = new Dictionary(); } public bool PreconditionVerification(QsCompilation compilation) diff --git a/src/QsCompiler/Compiler/RewriteSteps/FunctorGeneration.cs b/src/QsCompiler/Compiler/RewriteSteps/FunctorGeneration.cs index 69f08d75eb..1903dded15 100644 --- a/src/QsCompiler/Compiler/RewriteSteps/FunctorGeneration.cs +++ b/src/QsCompiler/Compiler/RewriteSteps/FunctorGeneration.cs @@ -5,26 +5,30 @@ using System.Linq; using Microsoft.Quantum.QsCompiler.SyntaxTree; - namespace Microsoft.Quantum.QsCompiler.BuiltInRewriteSteps { /// - /// Replaces all functor generation directives with the corresponding implementation. + /// Replaces all functor generation directives with the corresponding implementation. /// internal class FunctorGeneration : IRewriteStep { public string Name => "Functor Generation"; + public int Priority => RewriteStepPriorities.GenerationOfFunctorSupport; + public IDictionary AssemblyConstants { get; } + public IEnumerable GeneratedDiagnostics => null; public bool ImplementsPreconditionVerification => true; + public bool ImplementsTransformation => true; + public bool ImplementsPostconditionVerification => false; public FunctorGeneration() { - AssemblyConstants = new Dictionary(); + this.AssemblyConstants = new Dictionary(); } public bool PreconditionVerification(QsCompilation compilation) diff --git a/src/QsCompiler/Compiler/RewriteSteps/IntrinsicResolution.cs b/src/QsCompiler/Compiler/RewriteSteps/IntrinsicResolution.cs index 1fbb74feeb..292a86b01f 100644 --- a/src/QsCompiler/Compiler/RewriteSteps/IntrinsicResolution.cs +++ b/src/QsCompiler/Compiler/RewriteSteps/IntrinsicResolution.cs @@ -5,29 +5,33 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.Transformations.IntrinsicResolution; - namespace Microsoft.Quantum.QsCompiler.BuiltInRewriteSteps { /// - /// Replaces any syntax tree element in the compilation with the one in the environment tree given upon construction. + /// Replaces any syntax tree element in the compilation with the one in the environment tree given upon construction. /// internal class IntrinsicResolution : IRewriteStep { public string Name => "Intrinsic Resolution"; + public int Priority => 10; // Not used for built-in transformations like this + public IDictionary AssemblyConstants { get; } + public IEnumerable GeneratedDiagnostics => null; public bool ImplementsPreconditionVerification => false; + public bool ImplementsTransformation => true; + public bool ImplementsPostconditionVerification => false; private QsCompilation Environment { get; } public IntrinsicResolution(QsCompilation environment) { - AssemblyConstants = new Dictionary(); - Environment = environment; + this.AssemblyConstants = new Dictionary(); + this.Environment = environment; } public bool PreconditionVerification(QsCompilation compilation) diff --git a/src/QsCompiler/Compiler/RewriteSteps/Monomorphization.cs b/src/QsCompiler/Compiler/RewriteSteps/Monomorphization.cs index 480fff702c..62be6f0a9f 100644 --- a/src/QsCompiler/Compiler/RewriteSteps/Monomorphization.cs +++ b/src/QsCompiler/Compiler/RewriteSteps/Monomorphization.cs @@ -7,7 +7,6 @@ using Microsoft.Quantum.QsCompiler.Transformations.Monomorphization; using Microsoft.Quantum.QsCompiler.Transformations.Monomorphization.Validation; - namespace Microsoft.Quantum.QsCompiler.BuiltInRewriteSteps { /// @@ -16,17 +15,22 @@ namespace Microsoft.Quantum.QsCompiler.BuiltInRewriteSteps internal class Monomorphization : IRewriteStep { public string Name => "Monomorphization"; + public int Priority => RewriteStepPriorities.TypeParameterElimination; + public IDictionary AssemblyConstants { get; } + public IEnumerable GeneratedDiagnostics => null; public bool ImplementsPreconditionVerification => true; + public bool ImplementsTransformation => true; + public bool ImplementsPostconditionVerification => true; public Monomorphization() { - AssemblyConstants = new Dictionary(); + this.AssemblyConstants = new Dictionary(); } public bool PreconditionVerification(QsCompilation compilation) => compilation.EntryPoints.Any(); @@ -39,8 +43,14 @@ public bool Transformation(QsCompilation compilation, out QsCompilation transfor public bool PostconditionVerification(QsCompilation compilation) { - try { ValidateMonomorphization.Apply(compilation); } - catch { return false; } + try + { + ValidateMonomorphization.Apply(compilation); + } + catch + { + return false; + } return true; } } diff --git a/src/QsCompiler/Core/SyntaxGenerator.fs b/src/QsCompiler/Core/SyntaxGenerator.fs index c33e86a772..93aef3a27a 100644 --- a/src/QsCompiler/Core/SyntaxGenerator.fs +++ b/src/QsCompiler/Core/SyntaxGenerator.fs @@ -76,6 +76,11 @@ module SyntaxGenerator = let UnitValue = AutoGeneratedExpression UnitValue QsTypeKind.UnitType false + /// Creates a typed expression that corresponds to a Bool literal with the given value. + /// Sets the range information for the built expression to Null. + let BoolLiteral value = + AutoGeneratedExpression (BoolLiteral value) QsTypeKind.Bool false + /// Creates a typed expression that corresponds to an Int literal with the given value. /// Sets the range information for the built expression to Null. let IntLiteral v = diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index 49d67ef219..4fded15421 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -167,6 +167,9 @@ type ErrorCode = | ExpectingCallableExpr = 5021 | UnknownIdentifier = 5022 | UnsupportedResultComparison = 5023 + | ResultComparisonNotInOperationIf = 5024 + | ReturnInResultConditionedBlock = 5025 + | SetInResultConditionedBlock = 5026 | CallableRedefinition = 6001 | CallableOverlapWithTypeConstructor = 6002 @@ -544,11 +547,18 @@ type DiagnosticItem = | ErrorCode.ExpectingIterableExpr -> "The type {0} does not support iteration. Expecting an expression of array type or of type Range." | ErrorCode.ExpectingCallableExpr -> "The type of the expression must be a function or operation type. The given expression is of type {0}." | ErrorCode.UnknownIdentifier -> "No identifier with the name \"{0}\" exists." - | ErrorCode.UnsupportedResultComparison -> - // TODO: When the names of the runtime capabilities are finalized, they can be included in the error - // message. - "The execution target {0} does not support comparing measurement results. " + - "Choose an execution target with additional capabilities or avoid result comparisons." + // TODO: When the names of the runtime capabilities are finalized, they can be included in the result + // comparison error messages. + | ErrorCode.UnsupportedResultComparison -> "The target {0} does not support comparing measurement results." + | ErrorCode.ResultComparisonNotInOperationIf -> + "Measurement results cannot be compared here. " + + "The target {0} only supports comparing measurement results as part of the condition of an if- or elif-statement in an operation." + | ErrorCode.ReturnInResultConditionedBlock -> + "A return statement cannot be used here. " + + "The target {0} does not support return statements in conditional blocks that depend on a measurement result." + | ErrorCode.SetInResultConditionedBlock -> + "The variable \"{0}\" cannot be reassigned here. " + + "In conditional blocks that depend on a measurement result, the target {1} only supports reassigning variables that were declared within the block." | ErrorCode.CallableRedefinition -> "Invalid callable declaration. A function or operation with the name \"{0}\" already exists." | ErrorCode.CallableOverlapWithTypeConstructor -> "Invalid callable declaration. A type constructor with the name \"{0}\" already exists." diff --git a/src/QsCompiler/DataStructures/ReservedKeywords.fs b/src/QsCompiler/DataStructures/ReservedKeywords.fs index 3e85d21445..408a9d6a8a 100644 --- a/src/QsCompiler/DataStructures/ReservedKeywords.fs +++ b/src/QsCompiler/DataStructures/ReservedKeywords.fs @@ -234,13 +234,14 @@ module GeneratedAttributes = module AssemblyConstants = let OutputPath = "OutputPath" let AssemblyName = "AssemblyName" - let QsharpOutputType = "ResolvedQsharpOutputType" + let QsharpOutputType = "QsharpOutputType" let QsharpExe = "QsharpExe" let QsharpLibrary = "QsharpLibrary" - let ExecutionTarget = "ResolvedExecutionTarget" + let ProcessorArchitecture = "ProcessorArchitecture" let HoneywellProcessor = "HoneywellProcessor" let IonQProcessor = "IonQProcessor" let QCIProcessor = "QCIProcessor" + let ExecutionTarget = "ExecutionTarget" let DefaultSimulator = "DefaultSimulator" let QuantumSimulator = "QuantumSimulator" let ToffoliSimulator = "ToffoliSimulator" diff --git a/src/QsCompiler/DocumentationParser/DocBuilder.cs b/src/QsCompiler/DocumentationParser/DocBuilder.cs index d89bc989f0..635d00cb0b 100644 --- a/src/QsCompiler/DocumentationParser/DocBuilder.cs +++ b/src/QsCompiler/DocumentationParser/DocBuilder.cs @@ -9,7 +9,6 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using YamlDotNet.RepresentationModel; - namespace Microsoft.Quantum.QsCompiler.Documentation { /// @@ -48,25 +47,25 @@ public DocBuilder(string rootPath, IEnumerable tree, IEnumerable(); - Directory.CreateDirectory(rootDocPath); + Directory.CreateDirectory(this.rootDocPath); - var rootNode = Utils.ReadYamlFile(rootDocPath, Utils.TableOfContents) as YamlSequenceNode ?? new YamlSequenceNode(); + var rootNode = Utils.ReadYamlFile(this.rootDocPath, Utils.TableOfContents) as YamlSequenceNode ?? new YamlSequenceNode(); foreach (var ns in this.namespaces) { try { - ns.WriteItemsToDirectory(rootDocPath, errors); + ns.WriteItemsToDirectory(this.rootDocPath, errors); } catch (AggregateException ex) { errors.AddRange(ex.InnerExceptions); } - Utils.DoTrackingExceptions(() => ns.WriteToFile(rootDocPath), errors); + Utils.DoTrackingExceptions(() => ns.WriteToFile(this.rootDocPath), errors); Utils.DoTrackingExceptions(() => ns.MergeNamespaceIntoToc(rootNode), errors); } - Utils.DoTrackingExceptions(() => Utils.WriteYamlFile(rootNode, rootDocPath, Utils.TableOfContents), errors); + Utils.DoTrackingExceptions(() => Utils.WriteYamlFile(rootNode, this.rootDocPath, Utils.TableOfContents), errors); if (errors.Count > 0) { @@ -83,8 +82,11 @@ public void BuildDocs() /// The compiled namespaces to generate documentation for. /// If specified, documentation is only generated for the specified source files. /// Called on caught exceptions before ignoring them. - public static bool Run(string rootPath, IEnumerable tree, - IEnumerable> sources = null, Action onException = null) + public static bool Run( + string rootPath, + IEnumerable tree, + IEnumerable> sources = null, + Action onException = null) { try { @@ -95,7 +97,10 @@ public static bool Run(string rootPath, IEnumerable tree, catch (Exception ex) { var exceptions = ex is AggregateException agg ? (IEnumerable)agg.InnerExceptions : new[] { ex }; - foreach (var inner in exceptions) onException(inner); + foreach (var inner in exceptions) + { + onException(inner); + } return false; } } diff --git a/src/QsCompiler/DocumentationParser/DocCallable.cs b/src/QsCompiler/DocumentationParser/DocCallable.cs index 47a7a56e56..f9b2cd94ab 100644 --- a/src/QsCompiler/DocumentationParser/DocCallable.cs +++ b/src/QsCompiler/DocumentationParser/DocCallable.cs @@ -9,7 +9,6 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using YamlDotNet.RepresentationModel; - namespace Microsoft.Quantum.QsCompiler.Documentation { /// @@ -28,27 +27,30 @@ internal class DocCallable : DocItem /// /// The name of the namespace where this callable is defined /// The compiled callable - internal DocCallable(string ns, QsCallable callableObj) - : base(ns, callableObj.FullName.Name.Value, - callableObj.Kind.IsFunction ? Utils.FunctionKind : Utils.OperationKind, - callableObj.Documentation, callableObj.Attributes) + internal DocCallable(string ns, QsCallable callableObj) + : base( + ns, + callableObj.FullName.Name.Value, + callableObj.Kind.IsFunction ? Utils.FunctionKind : Utils.OperationKind, + callableObj.Documentation, + callableObj.Attributes) { - syntax = Utils.CallableToSyntax(callableObj); - inputContent = Utils.CallableToArguments(callableObj); - outputType = Utils.ResolvedTypeToString(callableObj.Signature.ReturnType); - functors = new List(); + this.syntax = Utils.CallableToSyntax(callableObj); + this.inputContent = Utils.CallableToArguments(callableObj); + this.outputType = Utils.ResolvedTypeToString(callableObj.Signature.ReturnType); + this.functors = new List(); foreach (var functor in callableObj.Signature.Information.Characteristics.SupportedFunctors.ValueOr(ImmutableHashSet.Empty)) { if (functor.IsAdjoint) { - functors.Add(Functors.Adjoint); + this.functors.Add(Functors.Adjoint); } else if (functor.IsControlled) { - functors.Add(Functors.Controlled); + this.functors.Add(Functors.Controlled); } } - callable = callableObj; + this.callable = callableObj; } /// @@ -61,17 +63,17 @@ YamlNode BuildInputNode() { var inputNode = new YamlMappingNode(); - inputNode.AddStringMapping(Utils.ContentsKey, inputContent); + inputNode.AddStringMapping(Utils.ContentsKey, this.inputContent); var typesNode = new YamlSequenceNode(); inputNode.Add(Utils.TypesListKey, typesNode); - foreach (var declaration in SyntaxGenerator.ExtractItems(callable.ArgumentTuple)) + foreach (var declaration in SyntaxGenerator.ExtractItems(this.callable.ArgumentTuple)) { var argNode = new YamlMappingNode(); var argName = ((QsLocalSymbol.ValidName)declaration.VariableName).Item.Value; argNode.AddStringMapping(Utils.NameKey, argName); - if (comments.Input.TryGetValue(argName, out string summary)) + if (this.comments.Input.TryGetValue(argName, out string summary)) { argNode.AddStringMapping(Utils.SummaryKey, summary); } @@ -86,7 +88,7 @@ YamlNode BuildOutputNode() { var outputNode = new YamlMappingNode(); - outputNode.AddStringMapping(Utils.ContentsKey, outputType); + outputNode.AddStringMapping(Utils.ContentsKey, this.outputType); var typesNode = new YamlSequenceNode(); outputNode.Add(Utils.TypesListKey, typesNode); @@ -98,46 +100,46 @@ YamlNode BuildOutputNode() { outputTypeNode.AddStringMapping(Utils.SummaryKey, this.comments.Output); } - Utils.ResolvedTypeToYaml(callable.Signature.ReturnType, outputTypeNode); + Utils.ResolvedTypeToYaml(this.callable.Signature.ReturnType, outputTypeNode); return outputNode; } var rootNode = new YamlMappingNode(); - rootNode.AddStringMapping(Utils.UidKey, uid); - rootNode.AddStringMapping(Utils.NameKey, name); - rootNode.AddStringMapping(Utils.TypeKey, itemType); - rootNode.AddStringMapping(Utils.NamespaceKey, namespaceName); - if (!string.IsNullOrWhiteSpace(comments.Documentation)) + rootNode.AddStringMapping(Utils.UidKey, this.uid); + rootNode.AddStringMapping(Utils.NameKey, this.name); + rootNode.AddStringMapping(Utils.TypeKey, this.itemType); + rootNode.AddStringMapping(Utils.NamespaceKey, this.namespaceName); + if (!string.IsNullOrWhiteSpace(this.comments.Documentation)) { - rootNode.AddStringMapping(Utils.SummaryKey, comments.Documentation); + rootNode.AddStringMapping(Utils.SummaryKey, this.comments.Documentation); } - if (!string.IsNullOrWhiteSpace(comments.Remarks)) + if (!string.IsNullOrWhiteSpace(this.comments.Remarks)) { - rootNode.AddStringMapping(Utils.RemarksKey, comments.Remarks); + rootNode.AddStringMapping(Utils.RemarksKey, this.comments.Remarks); } - if (!string.IsNullOrWhiteSpace(comments.Example)) + if (!string.IsNullOrWhiteSpace(this.comments.Example)) { - rootNode.AddStringMapping(Utils.ExamplesKey, comments.Example); + rootNode.AddStringMapping(Utils.ExamplesKey, this.comments.Example); } - rootNode.AddStringMapping(Utils.SyntaxKey, syntax); - if (!string.IsNullOrWhiteSpace(comments.References)) + rootNode.AddStringMapping(Utils.SyntaxKey, this.syntax); + if (!string.IsNullOrWhiteSpace(this.comments.References)) { - rootNode.AddStringMapping(Utils.ReferencesKey, comments.References); + rootNode.AddStringMapping(Utils.ReferencesKey, this.comments.References); } rootNode.Add(Utils.InputKey, BuildInputNode()); rootNode.Add(Utils.OutputKey, BuildOutputNode()); - if (comments.TypeParameters.Count > 0) + if (this.comments.TypeParameters.Count > 0) { - rootNode.Add(Utils.TypeParamsKey, Utils.BuildSequenceMappingNode(comments.TypeParameters)); + rootNode.Add(Utils.TypeParamsKey, Utils.BuildSequenceMappingNode(this.comments.TypeParameters)); } - if (functors.Count > 0) + if (this.functors.Count > 0) { - rootNode.Add(Utils.FunctorsKey, Utils.BuildSequenceNode(functors)); + rootNode.Add(Utils.FunctorsKey, Utils.BuildSequenceNode(this.functors)); } - if (comments.SeeAlso.Count > 0) + if (this.comments.SeeAlso.Count > 0) { - rootNode.Add(Utils.SeeAlsoKey, Utils.BuildSequenceNode(comments.SeeAlso)); + rootNode.Add(Utils.SeeAlsoKey, Utils.BuildSequenceNode(this.comments.SeeAlso)); } var doc = new YamlDocument(rootNode); diff --git a/src/QsCompiler/DocumentationParser/DocComment.cs b/src/QsCompiler/DocumentationParser/DocComment.cs index 527ee9a4df..32f2cbad71 100644 --- a/src/QsCompiler/DocumentationParser/DocComment.cs +++ b/src/QsCompiler/DocumentationParser/DocComment.cs @@ -57,7 +57,7 @@ public class DocComment /// The inputs to the item, as a list of symbol/description pairs. /// This is only populated for functions and operations. /// - public Dictionary Input { get; private set; } + public Dictionary Input { get; private set; } /// /// The output from the item. @@ -69,7 +69,7 @@ public class DocComment /// The type parameters for the item, as a list of symbol/description pairs. /// This is only populated for functions and operations. /// - public Dictionary TypeParameters { get; private set; } + public Dictionary TypeParameters { get; private set; } /// /// An example of using the item. @@ -140,7 +140,7 @@ static List>> BreakIntoSections(IEnumerable(); - var result = new List>>(); + var result = new List<(string, List)>(); foreach (var block in blocks) { @@ -150,7 +150,7 @@ static List>> BreakIntoSections(IEnumerable 0) { - result.Add(new ValueTuple>(key, accum)); + result.Add((key, accum)); accum = new List(); } key = GetHeadingText(heading); @@ -168,7 +168,7 @@ static List>> BreakIntoSections(IEnumerable 0) { - result.Add(new ValueTuple>(key, accum)); + result.Add((key, accum)); } return result; @@ -222,7 +222,7 @@ static void ParseMapSection(IEnumerable blocks, Dictionary blocks, Dictionary(); this.References = ""; - var deprecationSummary = String.IsNullOrWhiteSpace(replacement) + var deprecationSummary = string.IsNullOrWhiteSpace(replacement) ? DiagnosticItem.Message(WarningCode.DeprecationWithoutRedirect, new string[] { name }) : DiagnosticItem.Message(WarningCode.DeprecationWithRedirect, new string[] { name, "@\"" + replacement.ToLowerInvariant() + "\"" }); var deprecationDetails = ""; - var text = String.Join("\n", docComments); + var text = string.Join("\n", docComments); // Only parse if there are comments to parse if (!string.IsNullOrWhiteSpace(text)) @@ -279,10 +279,10 @@ static void ParseMapSection(IEnumerable blocks, Dictionary 1) @@ -314,7 +314,7 @@ static void ParseMapSection(IEnumerable blocks, Dictionary 0) && (this.Example == "")) + if (examples.Count > 0 && this.Example == "") { this.Example = ToMarkdown(examples); } @@ -334,14 +334,13 @@ static void ParseMapSection(IEnumerable blocks, Dictionary blocks, Dictionary /// The doc comments from the source code - public DocComment(IEnumerable docComments) : this(docComments, "", false, "") + public DocComment(IEnumerable docComments) + : this(docComments, "", false, "") { - } } } diff --git a/src/QsCompiler/DocumentationParser/DocItem.cs b/src/QsCompiler/DocumentationParser/DocItem.cs index b07de0d4be..00a379b152 100644 --- a/src/QsCompiler/DocumentationParser/DocItem.cs +++ b/src/QsCompiler/DocumentationParser/DocItem.cs @@ -1,15 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Quantum.QsCompiler.SyntaxTokens; -using Microsoft.Quantum.QsCompiler.SyntaxTree; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; -using System.Linq; +using Microsoft.Quantum.QsCompiler.SyntaxTree; using YamlDotNet.RepresentationModel; - namespace Microsoft.Quantum.QsCompiler.Documentation { /// @@ -29,10 +26,12 @@ internal abstract class DocItem /// The item's kind, as a string (Utils.OperationKind, .FunctionKind, or .UdtKind) /// internal string ItemType => this.itemType; + /// /// The unique internal ID of the item /// internal string Uid => this.uid; + /// /// The name of the item /// @@ -45,30 +44,34 @@ internal abstract class DocItem /// The name of the item itself /// The item's kind: operation, function, or UDT /// The source documentation for the item - internal DocItem(string nsName, string itemName, string kind, ImmutableArray documentation, + internal DocItem( + string nsName, + string itemName, + string kind, + ImmutableArray documentation, IEnumerable attributes) { - namespaceName = nsName; - name = itemName; - uid = (namespaceName + "." + name).ToLowerInvariant(); - itemType = kind; + this.namespaceName = nsName; + this.name = itemName; + this.uid = (this.namespaceName + "." + this.name).ToLowerInvariant(); + this.itemType = kind; var res = SymbolResolution.TryFindRedirect(attributes); - deprecated = res.IsValue; - replacement = res.ValueOr(""); - comments = new DocComment(documentation, name, deprecated, replacement); + this.deprecated = res.IsValue; + this.replacement = res.ValueOr(""); + this.comments = new DocComment(documentation, this.name, this.deprecated, this.replacement); } /// /// Returns a YAML node describing this item suitable for inclusion in a namespace description. /// /// A new YAML mapping node that describes this item - internal YamlMappingNode ToNamespaceItem() => Utils.BuildMappingNode(Utils.UidKey, uid, Utils.SummaryKey, comments.Summary); + internal YamlMappingNode ToNamespaceItem() => Utils.BuildMappingNode(Utils.UidKey, this.uid, Utils.SummaryKey, this.comments.Summary); /// /// Returns a YAML node describing this item suitable for inclusion in a table of contents. /// /// A new YAML mapping node that describes this item - internal YamlMappingNode ToTocItem() => Utils.BuildMappingNode(Utils.UidKey, uid, Utils.NameKey, name); + internal YamlMappingNode ToTocItem() => Utils.BuildMappingNode(Utils.UidKey, this.uid, Utils.NameKey, this.name); /// /// Writes a full YAML representation of this item to the given text stream. diff --git a/src/QsCompiler/DocumentationParser/DocNamespace.cs b/src/QsCompiler/DocumentationParser/DocNamespace.cs index 48dcc6485c..a683163016 100644 --- a/src/QsCompiler/DocumentationParser/DocNamespace.cs +++ b/src/QsCompiler/DocumentationParser/DocNamespace.cs @@ -11,7 +11,6 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using YamlDotNet.RepresentationModel; - namespace Microsoft.Quantum.QsCompiler.Documentation { /// @@ -23,8 +22,10 @@ internal class DocNamespace private readonly string uid; private readonly string summary; private readonly List items = new List(); - public string Name => name; - public bool IsNotEmpty => items.Count > 0; + + public string Name => this.name; + + public bool IsNotEmpty => this.items.Count > 0; /// /// Constructs an instance from a compiled namespace and list of source files. @@ -46,7 +47,7 @@ bool IsVisible(NonNullable source, AccessModifier access, NonNullable source, AccessModifier access, NonNullable source, AccessModifier access, NonNullable source, AccessModifier access, NonNullable x.Uid.CompareTo(y.Uid)); + this.items.Sort((x, y) => x.Uid.CompareTo(y.Uid)); } - /// /// Merges a namespace into a TOC node. /// If there is an existing node with the same uid, then it is updated to reflect the data @@ -119,7 +119,10 @@ bool MatchByUid(YamlNode n, string key) mappingNode.Children.TryGetValue(Utils.UidKey, out var uidNode); return (uidNode as YamlScalarNode)?.Value; } - else { return null; } + else + { + return null; + } } string? TryGetName(YamlNode node) @@ -129,15 +132,16 @@ bool MatchByUid(YamlNode n, string key) mappingNode.Children.TryGetValue(Utils.NameKey, out var nameNode); return (nameNode as YamlScalarNode)?.Value; } - else { return null; } + else + { + return null; + } } - int CompareUids(YamlNode node1, YamlNode node2) => - String.Compare( + string.Compare( TryGetUid(node1), - TryGetUid(node2) - ); + TryGetUid(node2)); var namespaceNode = toc.Children?.SingleOrDefault(c => MatchByUid(c, this.uid)) as YamlMappingNode; YamlSequenceNode? itemListNode = null; @@ -163,14 +167,13 @@ int CompareUids(YamlNode node1, YamlNode node2) => namespaceNode.Add(Utils.ItemsKey, itemListNode); } - var itemsByUid = items + var itemsByUid = this.items .GroupBy(item => item.Uid) .ToDictionary( group => group.Key, group => group .Select(item => item.Name) - .Single() - ); + .Single()); // Update itemsByUid with any items that may already exist. foreach (var existingChild in itemListNode.Children) @@ -186,8 +189,7 @@ int CompareUids(YamlNode node1, YamlNode node2) => foreach (var (uid, name) in itemsByUid.OrderBy(item => item.Key)) { itemListNode.Add(Utils.BuildMappingNode( - Utils.NameKey, name, Utils.UidKey, uid - )); + Utils.NameKey, name, Utils.UidKey, uid)); } } @@ -206,7 +208,7 @@ void WriteItem(DocItem i) i.WriteToFile(text); } } - foreach (var item in items) + foreach (var item in this.items) { Utils.DoTrackingExceptions(() => WriteItem(item), errors); } @@ -243,11 +245,11 @@ string ToSequenceKey(string itemTypeName) } rootNode ??= new YamlMappingNode(); - rootNode.AddStringMapping(Utils.UidKey, uid); - rootNode.AddStringMapping(Utils.NameKey, name); - if (!String.IsNullOrEmpty(summary)) + rootNode.AddStringMapping(Utils.UidKey, this.uid); + rootNode.AddStringMapping(Utils.NameKey, this.name); + if (!string.IsNullOrEmpty(this.summary)) { - rootNode.AddStringMapping(Utils.SummaryKey, summary); + rootNode.AddStringMapping(Utils.SummaryKey, this.summary); } var itemTypeNodes = new Dictionary>(); @@ -282,7 +284,7 @@ string ToSequenceKey(string itemTypeName) } // Now add our new items, overwriting if they already exist - foreach (var item in items) + foreach (var item in this.items) { var typeKey = ToSequenceKey(item.ItemType); SortedDictionary typeList; @@ -323,9 +325,9 @@ string ToSequenceKey(string itemTypeName) /// The directory to write the file to internal void WriteToFile(string directoryPath) { - var rootNode = Utils.ReadYamlFile(directoryPath, name) as YamlMappingNode; - var tocFileName = Path.Combine(directoryPath, name + Utils.YamlExtension); - WriteToStream(File.Open(tocFileName, FileMode.Create), rootNode); + var rootNode = Utils.ReadYamlFile(directoryPath, this.name) as YamlMappingNode; + var tocFileName = Path.Combine(directoryPath, this.name + Utils.YamlExtension); + this.WriteToStream(File.Open(tocFileName, FileMode.Create), rootNode); } } } diff --git a/src/QsCompiler/DocumentationParser/DocUdt.cs b/src/QsCompiler/DocumentationParser/DocUdt.cs index ed09727ec9..9f34ccd171 100644 --- a/src/QsCompiler/DocumentationParser/DocUdt.cs +++ b/src/QsCompiler/DocumentationParser/DocUdt.cs @@ -5,7 +5,6 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using YamlDotNet.RepresentationModel; - namespace Microsoft.Quantum.QsCompiler.Documentation { /// @@ -25,8 +24,8 @@ internal class DocUdt : DocItem internal DocUdt(string ns, QsCustomType udt) : base(ns, udt.FullName.Name.Value, Utils.UdtKind, udt.Documentation, udt.Attributes) { - syntax = Utils.CustomTypeToSyntax(udt); - customType = udt; + this.syntax = Utils.CustomTypeToSyntax(udt); + this.customType = udt; } /// @@ -36,36 +35,36 @@ internal DocUdt(string ns, QsCustomType udt) internal override void WriteToFile(TextWriter text) { var rootNode = new YamlMappingNode(); - rootNode.AddStringMapping(Utils.UidKey, uid); - rootNode.AddStringMapping(Utils.NameKey, name); - rootNode.AddStringMapping(Utils.TypeKey, itemType); - rootNode.AddStringMapping(Utils.NamespaceKey, namespaceName); - if (!string.IsNullOrEmpty(comments.Documentation)) + rootNode.AddStringMapping(Utils.UidKey, this.uid); + rootNode.AddStringMapping(Utils.NameKey, this.name); + rootNode.AddStringMapping(Utils.TypeKey, this.itemType); + rootNode.AddStringMapping(Utils.NamespaceKey, this.namespaceName); + if (!string.IsNullOrEmpty(this.comments.Documentation)) { - rootNode.AddStringMapping(Utils.SummaryKey, comments.Documentation); + rootNode.AddStringMapping(Utils.SummaryKey, this.comments.Documentation); } // UDTs get fancy treatment of examples - if (!string.IsNullOrEmpty(comments.Remarks) || !string.IsNullOrEmpty(comments.Example)) + if (!string.IsNullOrEmpty(this.comments.Remarks) || !string.IsNullOrEmpty(this.comments.Example)) { - var rems = comments.Remarks; - if (!string.IsNullOrEmpty(comments.Example)) + var rems = this.comments.Remarks; + if (!string.IsNullOrEmpty(this.comments.Example)) { // \r instead of \n because the YAML.Net serialization doubles \n. // In the file the newline is correct; YAML.Net serializes \r as \n. - rems += "\r\r### Examples\r" + comments.Example; + rems += "\r\r### Examples\r" + this.comments.Example; } rootNode.AddStringMapping(Utils.RemarksKey, rems); } - rootNode.Add(Utils.SyntaxKey, syntax); - if (!string.IsNullOrEmpty(comments.References)) + rootNode.Add(Utils.SyntaxKey, this.syntax); + if (!string.IsNullOrEmpty(this.comments.References)) { - rootNode.AddStringMapping(Utils.ReferencesKey, comments.References); + rootNode.AddStringMapping(Utils.ReferencesKey, this.comments.References); } - if (comments.SeeAlso.Count > 0) + if (this.comments.SeeAlso.Count > 0) { - rootNode.Add(Utils.SeeAlsoKey, Utils.BuildSequenceNode(comments.SeeAlso)); + rootNode.Add(Utils.SeeAlsoKey, Utils.BuildSequenceNode(this.comments.SeeAlso)); } var doc = new YamlDocument(rootNode); diff --git a/src/QsCompiler/DocumentationParser/DocumentationParser.csproj b/src/QsCompiler/DocumentationParser/DocumentationParser.csproj index 2f4ef8ea1a..b6992d5d78 100644 --- a/src/QsCompiler/DocumentationParser/DocumentationParser.csproj +++ b/src/QsCompiler/DocumentationParser/DocumentationParser.csproj @@ -8,7 +8,8 @@ - + + @@ -21,4 +22,8 @@ + + + + diff --git a/src/QsCompiler/DocumentationParser/Properties/AssemblyInfo.cs b/src/QsCompiler/DocumentationParser/Properties/AssemblyInfo.cs index 1062366236..4f44fd476e 100644 --- a/src/QsCompiler/DocumentationParser/Properties/AssemblyInfo.cs +++ b/src/QsCompiler/DocumentationParser/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ using System.Runtime.InteropServices; // Allow the test assembly to use our internal methods -[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsDocumentationParser" + SigningConstants.PUBLIC_KEY)] +[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsDocumentationParser" + SigningConstants.PublicKey)] diff --git a/src/QsCompiler/DocumentationParser/Utils.cs b/src/QsCompiler/DocumentationParser/Utils.cs index e7bca80bc8..fe581a8ec7 100644 --- a/src/QsCompiler/DocumentationParser/Utils.cs +++ b/src/QsCompiler/DocumentationParser/Utils.cs @@ -13,7 +13,6 @@ using Microsoft.Quantum.QsCompiler.Transformations.QsCodeOutput; using YamlDotNet.RepresentationModel; - namespace Microsoft.Quantum.QsCompiler.Documentation { using QsTypeKind = QsTypeKind; @@ -124,6 +123,7 @@ internal static YamlNode BuildSequenceMappingNode(Dictionary pai // TODO: we need to get the current namespace here somehow so that referenced UDT names // don't get expanded with the full namespace name + /// /// Returns the Q# source representation of a resolved type. /// @@ -159,7 +159,7 @@ IEnumerable FlattenType(ResolvedType ty) } } - void CallableCore(ResolvedType inputType, ResolvedType outputType, IEnumerable functors) + void CallableCore(ResolvedType inputType, ResolvedType outputType, IEnumerable functors) { var types = new YamlSequenceNode(); var input = new YamlMappingNode(); @@ -235,7 +235,7 @@ void CallableCore(ResolvedType inputType, ResolvedType outputType, IEnumerable.Empty); @@ -261,7 +261,7 @@ void CallableCore(ResolvedType inputType, ResolvedType outputType, IEnumerable /// The directory where the file should exist. /// The base name of the file, not including the ".yml" extension. - /// internal static YamlNode ReadYamlFile(string rootPath, string fileBaseName) { var yamlReader = new YamlStream(); YamlNode fileNode = null; - DoIgnoringExceptions(() => { + DoIgnoringExceptions(() => + { var fileName = Path.Combine(rootPath, fileBaseName + YamlExtension); using (var readStream = File.OpenText(fileName)) { @@ -393,7 +393,7 @@ internal static void WriteYamlFile(YamlNode rootNode, string rootPath, string fi var fileName = Path.Combine(rootPath, fileBaseName + YamlExtension); using (var text = new StreamWriter(File.Open(fileName, FileMode.Create))) { - text.WriteLine(Utils.AutogenerationWarning); + text.WriteLine(AutogenerationWarning); stream.Save(text, false); } } @@ -411,7 +411,8 @@ internal static void MergeYamlFile(YamlMappingNode map, string rootPath, string { var yamlReader = new YamlStream(); YamlMappingNode fileMap = new YamlMappingNode(); - DoIgnoringExceptions(() => { + DoIgnoringExceptions(() => + { var fileName = Path.Combine(rootPath, fileBaseName + YamlExtension); using (var readStream = File.OpenText(fileName)) { @@ -475,7 +476,6 @@ internal static void DoTrackingExceptions(Action act, List errors) } } - // See https://stackoverflow.com/a/5037815/267841. internal static class SortExtensions { @@ -494,7 +494,7 @@ internal static void AddRange(this IList list, IEnumerable source) } } - // Sorts an IList in place. + // Sorts an IList in place. internal static void Sort(this IList list, Comparison comparison) { ArrayList.Adapter((IList)list).Sort(new ComparisonComparer(comparison)); @@ -505,17 +505,18 @@ internal static void Sort(this IList list, Comparison comparison) internal static IEnumerable OrderBy(this IEnumerable list, Comparison comparison) => list.OrderBy(t => t, new ComparisonComparer(comparison)); } + internal class ComparisonComparer : IComparer, IComparer { - private readonly Comparison _comparison; + private readonly Comparison comparison; internal ComparisonComparer(Comparison comparison) { - _comparison = comparison; + this.comparison = comparison; } - public int Compare(T x, T y) => _comparison(x, y); + public int Compare(T x, T y) => this.comparison(x, y); - public int Compare(object o1, object o2) => _comparison((T)o1, (T)o2); + public int Compare(object o1, object o2) => this.comparison((T)o1, (T)o2); } } diff --git a/src/QsCompiler/LanguageServer/Communication.cs b/src/QsCompiler/LanguageServer/Communication.cs index 8e59df2c8e..f7fa5a8b85 100644 --- a/src/QsCompiler/LanguageServer/Communication.cs +++ b/src/QsCompiler/LanguageServer/Communication.cs @@ -4,7 +4,6 @@ using System.Runtime.Serialization; using Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsLanguageServer { public static class CommandIds @@ -43,14 +42,14 @@ public ProtocolError(int code, string message = null) new ProtocolError(Codes.AwaitingInitialization); } - // If the workaround for ignoring CodeActionKind is no longer needed, - // please also remove the modification in the server's Initialize method - // that sets capabilities.textDocument.codeAction to null. + // If the workaround for ignoring CodeActionKind is no longer needed, + // please also remove the modification in the server's Initialize method + // that sets capabilities.textDocument.codeAction to null. public static class Workarounds { /// - /// This is the exact version as used by earlier versions of the package. - /// We will use this one for the sake of avoiding a bug in the VS Code client + /// This is the exact version as used by earlier versions of the package. + /// We will use this one for the sake of avoiding a bug in the VS Code client /// that will cause an issue for deserializing the CodeActionKind array. /// [DataContract] @@ -75,8 +74,8 @@ public VisualStudio.LanguageServer.Protocol.CodeActionParams ToCodeActionParams( } /// - /// This is the exact version as used by earlier versions of the package. - /// We will use this one for the sake of avoiding a bug in the VS Code client + /// This is the exact version as used by earlier versions of the package. + /// We will use this one for the sake of avoiding a bug in the VS Code client /// that will cause an issue for deserializing the CodeActionKind array. /// [DataContract] @@ -93,4 +92,4 @@ public VisualStudio.LanguageServer.Protocol.CodeActionContext ToCodeActionContex }; } } -} \ No newline at end of file +} diff --git a/src/QsCompiler/LanguageServer/EditorState.cs b/src/QsCompiler/LanguageServer/EditorState.cs index 7c8e5a1fb2..545965c85b 100644 --- a/src/QsCompiler/LanguageServer/EditorState.cs +++ b/src/QsCompiler/LanguageServer/EditorState.cs @@ -13,7 +13,6 @@ using Microsoft.Quantum.QsCompiler.ReservedKeywords; using Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsLanguageServer { /// @@ -22,65 +21,73 @@ namespace Microsoft.Quantum.QsLanguageServer /// internal class EditorState : IDisposable { - private readonly ProjectManager Projects; - private readonly ProjectLoader ProjectLoader; - public void Dispose() => this.Projects.Dispose(); + private readonly ProjectManager projects; + private readonly ProjectLoader projectLoader; + + public void Dispose() => this.projects.Dispose(); - private readonly Action Publish; - private readonly Action, Dictionary> SendTelemetry; + private readonly Action publish; + private readonly Action, Dictionary> sendTelemetry; /// - /// needed to determine if the reality of a source file that has changed on disk is indeed given by the content on disk, + /// needed to determine if the reality of a source file that has changed on disk is indeed given by the content on disk, /// or whether its current state as it is in the editor needs to be preserved /// - private readonly ConcurrentDictionary OpenFiles; - private FileContentManager GetOpenFile(Uri key) => this.OpenFiles.TryGetValue(key, out var file) ? file : null; + private readonly ConcurrentDictionary openFiles; + + private FileContentManager GetOpenFile(Uri key) => this.openFiles.TryGetValue(key, out var file) ? file : null; /// /// any edits in the editor to the listed files (keys) are ignored, while changes on disk are still being processed /// - private readonly ConcurrentDictionary IgnoreEditorUpdatesForFiles; - internal void IgnoreEditorUpdatesFor(Uri uri) => this.IgnoreEditorUpdatesForFiles.TryAdd(uri, new byte()); + private readonly ConcurrentDictionary ignoreEditorUpdatesForFiles; + + internal void IgnoreEditorUpdatesFor(Uri uri) => this.ignoreEditorUpdatesForFiles.TryAdd(uri, default); private static bool ValidFileUri(Uri file) => file != null && file.IsFile && file.IsAbsoluteUri; - private bool IgnoreFile(Uri file) => file == null || this.IgnoreEditorUpdatesForFiles.ContainsKey(file) || file.LocalPath.ToLowerInvariant().Contains("vctmp"); + + private bool IgnoreFile(Uri file) => file == null || this.ignoreEditorUpdatesForFiles.ContainsKey(file) || file.LocalPath.ToLowerInvariant().Contains("vctmp"); /// - /// Calls the given publishDiagnostics Action with the changed diagnostics whenever they have changed, + /// Calls the given publishDiagnostics Action with the changed diagnostics whenever they have changed, /// calls the given onException Action whenever the compiler encounters an internal error, and /// does nothing if the a given action is null. /// - internal EditorState(ProjectLoader projectLoader, - Action publishDiagnostics, Action, Dictionary> sendTelemetry, - Action log, Action onException) + internal EditorState( + ProjectLoader projectLoader, + Action publishDiagnostics, + Action, Dictionary> sendTelemetry, + Action log, + Action onException) { - this.IgnoreEditorUpdatesForFiles = new ConcurrentDictionary(); - this.SendTelemetry = sendTelemetry ?? ((_, __, ___) => { }); - this.Publish = param => + this.ignoreEditorUpdatesForFiles = new ConcurrentDictionary(); + this.sendTelemetry = sendTelemetry ?? ((eventName, properties, measurements) => { }); + this.publish = param => { var onProjFile = param.Uri.AbsolutePath.EndsWith(".csproj", StringComparison.InvariantCultureIgnoreCase); - if (!param.Diagnostics.Any() || this.OpenFiles.ContainsKey(param.Uri) || onProjFile) + if (!param.Diagnostics.Any() || this.openFiles.ContainsKey(param.Uri) || onProjFile) { // Some editors (e.g. Visual Studio) will actually ignore diagnostics for .csproj files. - // Since all errors on project loading are associated with the corresponding project file for publishing, + // Since all errors on project loading are associated with the corresponding project file for publishing, // we need to replace the project file ending before publishing. This issue is naturally resolved once we have our own project files... var parentDir = Path.GetDirectoryName(param.Uri.AbsolutePath); var projFileWithoutExtension = Path.GetFileNameWithoutExtension(param.Uri.AbsolutePath); if (onProjFile && Uri.TryCreate(Path.Combine(parentDir, $"{projFileWithoutExtension}.qsproj"), UriKind.Absolute, out var parentFolder)) - { param.Uri = parentFolder; } + { + param.Uri = parentFolder; + } publishDiagnostics?.Invoke(param); } }; - this.ProjectLoader = projectLoader ?? throw new ArgumentNullException(nameof(projectLoader)); - this.Projects = new ProjectManager(onException, log, this.Publish); - this.OpenFiles = new ConcurrentDictionary(); + this.projectLoader = projectLoader ?? throw new ArgumentNullException(nameof(projectLoader)); + this.projects = new ProjectManager(onException, log, this.publish); + this.openFiles = new ConcurrentDictionary(); } - /// - /// Extracts the EvaluatedInclude for all items of the given type in the given project instance, - /// and returns the combined path of the project directory and the evaluated include. + /// Extracts the EvaluatedInclude for all items of the given type in the given project instance, + /// and returns the combined path of the project directory and the evaluated include. /// private static IEnumerable GetItemsByType(ProjectInstance project, string itemType) => project?.Items @@ -88,28 +95,34 @@ private static IEnumerable GetItemsByType(ProjectInstance project, strin ?.Select(item => Path.Combine(project.Directory, item.EvaluatedInclude)); /// - /// If the given uri corresponds to a C# project file, + /// If the given uri corresponds to a C# project file, /// determines if the project is consistent with a recognized Q# project using the ProjectLoader. - /// Returns the project information containing the outputPath of the project - /// along with the Q# source files as well as all project and dll references as out parameter if it is. - /// Returns null if it isn't, or if the project file itself has been listed as to be ignored. - /// Calls SendTelemetry with suitable data if the project is a recognized Q# project. + /// Returns the project information containing the outputPath of the project + /// along with the Q# source files as well as all project and dll references as out parameter if it is. + /// Returns null if it isn't, or if the project file itself has been listed as to be ignored. + /// Calls SendTelemetry with suitable data if the project is a recognized Q# project. /// internal bool QsProjectLoader(Uri projectFile, out ProjectInformation info) { info = null; - if (projectFile == null || !ValidFileUri(projectFile) || IgnoreFile(projectFile)) return false; - var projectInstance = this.ProjectLoader.TryGetQsProjectInstance(projectFile.LocalPath, out var telemetryProps); - if (projectInstance == null) return false; + if (projectFile == null || !ValidFileUri(projectFile) || this.IgnoreFile(projectFile)) + { + return false; + } + var projectInstance = this.projectLoader.TryGetQsProjectInstance(projectFile.LocalPath, out var telemetryProps); + if (projectInstance == null) + { + return false; + } var outputDir = projectInstance.GetPropertyValue("OutputPath"); var targetFile = projectInstance.GetPropertyValue("TargetFileName"); var outputPath = Path.Combine(projectInstance.Directory, outputDir, targetFile); - var executionTarget = projectInstance.GetPropertyValue("ResolvedQsharpExecutionTarget"); + var processorArchitecture = projectInstance.GetPropertyValue("ResolvedProcessorArchitecture"); var resRuntimeCapability = projectInstance.GetPropertyValue("ResolvedRuntimeCapabilities"); - var runtimeCapabilities = Enum.TryParse(resRuntimeCapability, out AssemblyConstants.RuntimeCapabilities capability) - ? capability + var runtimeCapabilities = Enum.TryParse(resRuntimeCapability, out AssemblyConstants.RuntimeCapabilities capability) + ? capability : AssemblyConstants.RuntimeCapabilities.Unknown; var sourceFiles = GetItemsByType(projectInstance, "QsharpCompile"); @@ -126,13 +139,13 @@ internal bool QsProjectLoader(Uri projectFile, out ProjectInformation info) telemetryMeas["sources"] = sourceFiles.Count(); telemetryMeas["csharpfiles"] = csharpFiles.Count(); telemetryProps["defaultSimulator"] = defaultSimulator; - this.SendTelemetry("project-load", telemetryProps, telemetryMeas); // does not send anything unless the corresponding flag is defined upon compilation + this.sendTelemetry("project-load", telemetryProps, telemetryMeas); // does not send anything unless the corresponding flag is defined upon compilation info = new ProjectInformation( version, outputPath, runtimeCapabilities, isExecutable, - NonNullable.New(string.IsNullOrWhiteSpace(executionTarget) ? "Unspecified" : executionTarget), + NonNullable.New(string.IsNullOrWhiteSpace(processorArchitecture) ? "Unspecified" : processorArchitecture), loadTestNames, sourceFiles, projectReferences, @@ -141,20 +154,20 @@ internal bool QsProjectLoader(Uri projectFile, out ProjectInformation info) } /// - /// For each given uri, loads the corresponding project if the uri contains the project file for a Q# project, + /// For each given uri, loads the corresponding project if the uri contains the project file for a Q# project, /// and publishes suitable diagnostics for it. /// Throws an ArgumentNullException if the given sequence of uris, or if any of the contained uris is null. /// public Task LoadProjectsAsync(IEnumerable projects) => - this.Projects.LoadProjectsAsync(projects, this.QsProjectLoader, GetOpenFile); + this.projects.LoadProjectsAsync(projects, this.QsProjectLoader, this.GetOpenFile); /// - /// If the given uri corresponds to the project file for a Q# project, + /// If the given uri corresponds to the project file for a Q# project, /// updates that project in the list of tracked projects or adds it if needed, and publishes suitable diagnostics for it. /// Throws an ArgumentNullException if the given uri is null. /// public Task ProjectDidChangeOnDiskAsync(Uri project) => - this.Projects.ProjectChangedOnDiskAsync(project, this.QsProjectLoader, GetOpenFile); + this.projects.ProjectChangedOnDiskAsync(project, this.QsProjectLoader, this.GetOpenFile); /// /// Updates all tracked Q# projects that reference the assembly with the given uri @@ -162,90 +175,124 @@ public Task ProjectDidChangeOnDiskAsync(Uri project) => /// Throws an ArgumentNullException if the given uri is null. /// public Task AssemblyDidChangeOnDiskAsync(Uri dllPath) => - this.Projects.AssemblyChangedOnDiskAsync(dllPath); + this.projects.AssemblyChangedOnDiskAsync(dllPath); /// /// To be used whenever a .qs file is changed on disk. - /// Reloads the file from disk and updates the project(s) which list it as souce file - /// if the file is not listed as currently being open in the editor, publishing suitable diagnostics. - /// If the file is listed as being open in the editor, updates all load diagnostics for the file, - /// but does not update the file content, since the editor manages that one. + /// Reloads the file from disk and updates the project(s) which list it as souce file + /// if the file is not listed as currently being open in the editor, publishing suitable diagnostics. + /// If the file is listed as being open in the editor, updates all load diagnostics for the file, + /// but does not update the file content, since the editor manages that one. /// Throws an ArgumentNullException if the given uri is null. /// public Task SourceFileDidChangeOnDiskAsync(Uri sourceFile) => - this.Projects.SourceFileChangedOnDiskAsync(sourceFile, GetOpenFile); - + this.projects.SourceFileChangedOnDiskAsync(sourceFile, this.GetOpenFile); // routines related to tracking the editor state /// /// To be called whenever a file is opened in the editor. /// Does nothing if the given file is listed as to be ignored. - /// Otherwise publishes suitable diagnostics for it. - /// Invokes the given Action showError with a suitable message if the given file cannot be loaded. + /// Otherwise publishes suitable diagnostics for it. + /// Invokes the given Action showError with a suitable message if the given file cannot be loaded. /// Invokes the given Action logError with a suitable message if the given file cannot be associated with a compilation unit, - /// or if the given file is already listed as being open in the editor. - /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. + /// or if the given file is already listed as being open in the editor. + /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. /// Throws an ArgumentNullException if the given content is null. /// - internal Task OpenFileAsync(TextDocumentItem textDocument, - Action showError = null, Action logError = null) + internal Task OpenFileAsync( + TextDocumentItem textDocument, + Action showError = null, + Action logError = null) { - if (!ValidFileUri(textDocument?.Uri)) throw new ArgumentException("invalid text document identifier"); - if (textDocument.Text == null) throw new ArgumentNullException(nameof(textDocument.Text)); - _ = this.Projects.ManagerTaskAsync(textDocument.Uri, (manager, associatedWithProject) => + if (!ValidFileUri(textDocument?.Uri)) + { + throw new ArgumentException("invalid text document identifier"); + } + if (textDocument.Text == null) + { + throw new ArgumentNullException(nameof(textDocument.Text)); + } + _ = this.projects.ManagerTaskAsync(textDocument.Uri, (manager, associatedWithProject) => { - if (IgnoreFile(textDocument.Uri)) return; - var newManager = CompilationUnitManager.InitializeFileManager(textDocument.Uri, textDocument.Text, this.Publish, ex => + if (this.IgnoreFile(textDocument.Uri)) + { + return; + } + var newManager = CompilationUnitManager.InitializeFileManager(textDocument.Uri, textDocument.Text, this.publish, ex => { showError?.Invoke($"Failed to load file '{textDocument.Uri.LocalPath}'", MessageType.Error); manager.LogException(ex); }); - // Currently it is not possible to handle both the behavior of VS and VS Code for changes on disk in a manner that will never fail. - // To mitigate the impact of failures we choose to just log them as info. - var file = this.OpenFiles.GetOrAdd(textDocument.Uri, newManager); - if (file != newManager) // this may be the case (depending on the editor) e.g. when opening a version control diff ... + // Currently it is not possible to handle both the behavior of VS and VS Code for changes on disk in a manner that will never fail. + // To mitigate the impact of failures we choose to just log them as info. + var file = this.openFiles.GetOrAdd(textDocument.Uri, newManager); + // this may be the case (depending on the editor) e.g. when opening a version control diff ... + if (file != newManager) { - showError?.Invoke($"Version control and opening multiple versions of the same file in the editor are currently not supported. \n" + + showError?.Invoke( + $"Version control and opening multiple versions of the same file in the editor are currently not supported. \n" + $"Intellisense has been disable for the file '{textDocument.Uri.LocalPath}'. An editor restart is required to enable intellisense again.", MessageType.Error); #if DEBUG - if (showError == null) logError?.Invoke("Attempting to open a file that is already open in the editor.", MessageType.Error); + if (showError == null) + { + logError?.Invoke("Attempting to open a file that is already open in the editor.", MessageType.Error); + } #endif - this.IgnoreEditorUpdatesFor(textDocument.Uri); - this.OpenFiles.TryRemove(textDocument.Uri, out FileContentManager _); - if (!associatedWithProject) _ = manager.TryRemoveSourceFileAsync(textDocument.Uri); - this.Publish(new PublishDiagnosticParams { Uri = textDocument.Uri, Diagnostics = Array.Empty() }); + this.IgnoreEditorUpdatesFor(textDocument.Uri); + this.openFiles.TryRemove(textDocument.Uri, out FileContentManager _); + if (!associatedWithProject) + { + _ = manager.TryRemoveSourceFileAsync(textDocument.Uri); + } + this.publish(new PublishDiagnosticParams { Uri = textDocument.Uri, Diagnostics = Array.Empty() }); return; } - if (!associatedWithProject) logError?.Invoke( - $"The file {textDocument.Uri.LocalPath} is not associated with a compilation unit. Only syntactic diagnostics are generated." - , MessageType.Info); + if (!associatedWithProject) + { + logError?.Invoke( + $"The file {textDocument.Uri.LocalPath} is not associated with a compilation unit. Only syntactic diagnostics are generated.", + MessageType.Info); + } + _ = manager.AddOrUpdateSourceFileAsync(file); }); // reloading from disk in case we encountered a file already open error above - return this.Projects.SourceFileChangedOnDiskAsync(textDocument.Uri, GetOpenFile); // NOTE: relies on that the manager task is indeed executed first! + return this.projects.SourceFileChangedOnDiskAsync(textDocument.Uri, this.GetOpenFile); // NOTE: relies on that the manager task is indeed executed first! } /// /// To be called whenever a file is changed within the editor (i.e. changes are not necessarily reflected on disk). /// Does nothing if the given file is listed as to be ignored. - /// Throws an ArgumentException if the uri of the text document identifier in the given parameter is null or not an absolute file uri. - /// Throws an ArgumentNullException if the given content changes are null. + /// Throws an ArgumentException if the uri of the text document identifier in the given parameter is null or not an absolute file uri. + /// Throws an ArgumentNullException if the given content changes are null. /// internal Task DidChangeAsync(DidChangeTextDocumentParams param) { - if (!ValidFileUri(param?.TextDocument?.Uri)) throw new ArgumentException("invalid text document identifier"); - if (param.ContentChanges == null) throw new ArgumentNullException(nameof(param.ContentChanges)); - return this.Projects.ManagerTaskAsync(param.TextDocument.Uri, (manager, __) => + if (!ValidFileUri(param?.TextDocument?.Uri)) { - if (IgnoreFile(param.TextDocument.Uri)) return; + throw new ArgumentException("invalid text document identifier"); + } + if (param.ContentChanges == null) + { + throw new ArgumentNullException(nameof(param.ContentChanges)); + } + return this.projects.ManagerTaskAsync(param.TextDocument.Uri, (manager, __) => + { + if (this.IgnoreFile(param.TextDocument.Uri)) + { + return; + } - // Currently it is not possible to handle both the behavior of VS and VS Code for changes on disk in a manner that will never fail. - // To mitigate the impact of failures we choose to ignore them silently. - if (!this.OpenFiles.ContainsKey(param.TextDocument.Uri)) return; + // Currently it is not possible to handle both the behavior of VS and VS Code for changes on disk in a manner that will never fail. + // To mitigate the impact of failures we choose to ignore them silently. + if (!this.openFiles.ContainsKey(param.TextDocument.Uri)) + { + return; + } _ = manager.SourceFileDidChangeAsync(param); // independent on whether the file does or doesn't belong to a project }); } @@ -254,26 +301,38 @@ internal Task DidChangeAsync(DidChangeTextDocumentParams param) /// Used to reload the file content when a file is saved. /// Does nothing if the given file is listed as to be ignored. /// Expects to get the entire content of the file at the time of saving as argument. - /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. + /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. /// Throws an ArgumentNullException if the given content is null. /// internal Task SaveFileAsync(TextDocumentIdentifier textDocument, string fileContent) { - if (!ValidFileUri(textDocument?.Uri)) throw new ArgumentException("invalid text document identifier"); - if (fileContent == null) throw new ArgumentNullException(nameof(fileContent)); - return this.Projects.ManagerTaskAsync(textDocument.Uri, (manager, __) => + if (!ValidFileUri(textDocument?.Uri)) + { + throw new ArgumentException("invalid text document identifier"); + } + if (fileContent == null) + { + throw new ArgumentNullException(nameof(fileContent)); + } + return this.projects.ManagerTaskAsync(textDocument.Uri, (manager, __) => { - if (IgnoreFile(textDocument.Uri)) return; + if (this.IgnoreFile(textDocument.Uri)) + { + return; + } - // Currently it is not possible to handle both the behavior of VS and VS Code for changes on disk in a manner that will never fail. - // To mitigate the impact of failures we choose to ignore them silently and do our best to recover. - if (!this.OpenFiles.TryGetValue(textDocument.Uri, out var file)) + // Currently it is not possible to handle both the behavior of VS and VS Code for changes on disk in a manner that will never fail. + // To mitigate the impact of failures we choose to ignore them silently and do our best to recover. + if (!this.openFiles.TryGetValue(textDocument.Uri, out var file)) { - file = CompilationUnitManager.InitializeFileManager(textDocument.Uri, fileContent, this.Publish, manager.LogException); - this.OpenFiles.TryAdd(textDocument.Uri, file); + file = CompilationUnitManager.InitializeFileManager(textDocument.Uri, fileContent, this.publish, manager.LogException); + this.openFiles.TryAdd(textDocument.Uri, file); _ = manager.AddOrUpdateSourceFileAsync(file); } - else _ = manager.AddOrUpdateSourceFileAsync(file, fileContent); // let's reload the file content on saving + else + { + _ = manager.AddOrUpdateSourceFileAsync(file, fileContent); // let's reload the file content on saving + } }); } @@ -281,42 +340,53 @@ internal Task SaveFileAsync(TextDocumentIdentifier textDocument, string fileCont /// To be called whenever a file is closed in the editor. /// Does nothing if the given file is listed as to be ignored. /// Otherwise the file content is reloaded from disk (in case changes in the editor are discarded without closing), and the diagnostics are updated. - /// Invokes the given Action onError with a suitable message if the given file is not listed as being open in the editor. - /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. + /// Invokes the given Action onError with a suitable message if the given file is not listed as being open in the editor. + /// Throws an ArgumentException if the uri of the given text document identifier is null or not an absolute file uri. /// internal Task CloseFileAsync(TextDocumentIdentifier textDocument, Action onError = null) { - if (!ValidFileUri(textDocument?.Uri)) throw new ArgumentException("invalid text document identifier"); - _ = this.Projects.ManagerTaskAsync(textDocument.Uri, (manager, associatedWithProject) => // needs to be *first* (due to the modification of OpenFiles) + if (!ValidFileUri(textDocument?.Uri)) { - if (IgnoreFile(textDocument.Uri)) return; + throw new ArgumentException("invalid text document identifier"); + } + _ = this.projects.ManagerTaskAsync(textDocument.Uri, (manager, associatedWithProject) => // needs to be *first* (due to the modification of OpenFiles) + { + if (this.IgnoreFile(textDocument.Uri)) + { + return; + } - // Currently it is not possible to handle both the behavior of VS and VS Code for changes on disk in a manner that will never fail. + // Currently it is not possible to handle both the behavior of VS and VS Code for changes on disk in a manner that will never fail. // To mitigate the impact of failures we choose to ignore them silently. - var removed = this.OpenFiles.TryRemove(textDocument.Uri, out FileContentManager __); + var removed = this.openFiles.TryRemove(textDocument.Uri, out _); #if DEBUG - if (!removed) onError?.Invoke($"Attempting to close file '{textDocument.Uri.LocalPath}' that is not currently listed as open in the editor.", MessageType.Error); + if (!removed) + { + onError?.Invoke($"Attempting to close file '{textDocument.Uri.LocalPath}' that is not currently listed as open in the editor.", MessageType.Error); + } #endif - if (!associatedWithProject) _ = manager.TryRemoveSourceFileAsync(textDocument.Uri); - this.Publish(new PublishDiagnosticParams { Uri = textDocument.Uri, Diagnostics = Array.Empty() }); + if (!associatedWithProject) + { + _ = manager.TryRemoveSourceFileAsync(textDocument.Uri); + } + this.publish(new PublishDiagnosticParams { Uri = textDocument.Uri, Diagnostics = Array.Empty() }); }); // When edits are made in a file, but those are discarded by closing the file and hitting "no, don't save", // no notification is sent for the now discarded changes; - // hence we reload the file content from disk upon closing. - return this.Projects.SourceFileChangedOnDiskAsync(textDocument.Uri, GetOpenFile); // NOTE: relies on that the manager task is indeed executed first! + // hence we reload the file content from disk upon closing. + return this.projects.SourceFileChangedOnDiskAsync(textDocument.Uri, this.GetOpenFile); // NOTE: relies on that the manager task is indeed executed first! } - // routines related to providing information for editor commands /// - /// Returns the workspace edit that describes the changes to be done if the symbol at the given position - if any - is renamed to the given name. - /// Returns null if no symbol exists at the specified position, - /// or if the specified uri is not a valid file uri. - /// or if some parameters are unspecified (null) or inconsistent with the tracked editor state. + /// Returns the workspace edit that describes the changes to be done if the symbol at the given position - if any - is renamed to the given name. + /// Returns null if no symbol exists at the specified position, + /// or if the specified uri is not a valid file uri. + /// or if some parameters are unspecified (null) or inconsistent with the tracked editor state. /// public WorkspaceEdit Rename(RenameParams param, bool versionedChanges = false) => - ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.Rename(param, versionedChanges) : null; + ValidFileUri(param?.TextDocument?.Uri) && !this.IgnoreFile(param.TextDocument.Uri) ? this.projects.Rename(param, versionedChanges) : null; /// /// Returns the source file and position where the item at the given position is declared at, @@ -324,7 +394,7 @@ public WorkspaceEdit Rename(RenameParams param, bool versionedChanges = false) = /// Returns null if the given file is listed as to be ignored or if the information cannot be determined at this point. /// public Location DefinitionLocation(TextDocumentPositionParams param) => - ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.DefinitionLocation(param) : null; + ValidFileUri(param?.TextDocument?.Uri) && !this.IgnoreFile(param.TextDocument.Uri) ? this.projects.DefinitionLocation(param) : null; /// /// Returns the signature help information for a call expression if there is such an expression at the specified position. @@ -335,7 +405,7 @@ public Location DefinitionLocation(TextDocumentPositionParams param) => /// or if no signature help information can be provided for the call expression at the specified position. /// public SignatureHelp SignatureHelp(TextDocumentPositionParams param, MarkupKind format = MarkupKind.PlainText) => - ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.SignatureHelp(param, format) : null; + ValidFileUri(param?.TextDocument?.Uri) && !this.IgnoreFile(param.TextDocument.Uri) ? this.projects.SignatureHelp(param, format) : null; /// /// Returns information about the item at the specified position as Hover information. @@ -345,7 +415,7 @@ public SignatureHelp SignatureHelp(TextDocumentPositionParams param, MarkupKind /// or if no token exists at the specified position at this time. /// public Hover HoverInformation(TextDocumentPositionParams param, MarkupKind format = MarkupKind.PlainText) => - ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.HoverInformation(param, format) : null; + ValidFileUri(param?.TextDocument?.Uri) && !this.IgnoreFile(param.TextDocument.Uri) ? this.projects.HoverInformation(param, format) : null; /// /// Returns an array with all usages of the identifier at the given position (if any) as DocumentHighlights. @@ -355,32 +425,32 @@ public Hover HoverInformation(TextDocumentPositionParams param, MarkupKind forma /// or if no identifier exists at the specified position at this time. /// public DocumentHighlight[] DocumentHighlights(TextDocumentPositionParams param) => - ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.DocumentHighlights(param) : null; + ValidFileUri(param?.TextDocument?.Uri) && !this.IgnoreFile(param.TextDocument.Uri) ? this.projects.DocumentHighlights(param) : null; /// - /// Returns an array with all locations where the symbol at the given position - if any - is referenced. + /// Returns an array with all locations where the symbol at the given position - if any - is referenced. /// Returns null if the given file is listed as to be ignored, /// or if some parameters are unspecified (null), /// or if the specified position is not a valid position within the currently processed file content, /// or if no symbol exists at the specified position at this time. /// public Location[] SymbolReferences(ReferenceParams param) => - ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.SymbolReferences(param) : null; + ValidFileUri(param?.TextDocument?.Uri) && !this.IgnoreFile(param.TextDocument.Uri) ? this.projects.SymbolReferences(param) : null; /// /// Returns the SymbolInformation for each namespace declaration, type declaration, and function or operation declaration. /// Returns null if the given file is listed as to be ignored, or if the given parameter or its uri is null. /// public SymbolInformation[] DocumentSymbols(DocumentSymbolParams param) => - ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.DocumentSymbols(param) : null; + ValidFileUri(param?.TextDocument?.Uri) && !this.IgnoreFile(param.TextDocument.Uri) ? this.projects.DocumentSymbols(param) : null; /// /// Returns a look-up of workspace edits suggested by the compiler for the given location and context. - /// The key of the look-up is a suitable title for the corresponding edits that can be presented to the user. + /// The key of the look-up is a suitable title for the corresponding edits that can be presented to the user. /// Returns null if the given file is listed as to be ignored, or if the given parameter or its uri is null. /// public ILookup CodeActions(CodeActionParams param) => - ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.CodeActions(param) : null; + ValidFileUri(param?.TextDocument?.Uri) && !this.IgnoreFile(param.TextDocument.Uri) ? this.projects.CodeActions(param) : null; /// /// Returns a list of suggested completion items for the given location. @@ -388,8 +458,8 @@ public ILookup CodeActions(CodeActionParams param) => /// Returns null if the given file is listed as to be ignored, or if the given parameter or its uri or position is null. /// public CompletionList Completions(TextDocumentPositionParams param) => - ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) - ? this.Projects.Completions(param) + ValidFileUri(param?.TextDocument?.Uri) && !this.IgnoreFile(param.TextDocument.Uri) + ? this.projects.Completions(param) : null; /// @@ -400,12 +470,11 @@ public CompletionList Completions(TextDocumentPositionParams param) => /// ignored. /// internal CompletionItem ResolveCompletion(CompletionItem item, CompletionItemData data, MarkupKind format) => - item != null && ValidFileUri(data?.TextDocument?.Uri) && !IgnoreFile(data.TextDocument.Uri) - ? this.Projects.ResolveCompletion(item, data, format) + item != null && ValidFileUri(data?.TextDocument?.Uri) && !this.IgnoreFile(data.TextDocument.Uri) + ? this.projects.ResolveCompletion(item, data, format) : null; - - // utils to query the editor state server for testing purposes + // utils to query the editor state server for testing purposes // -> explicitly part of this class because any access to the resources may need to be coordinated as well /// @@ -413,7 +482,7 @@ internal CompletionItem ResolveCompletion(CompletionItem item, CompletionItemDat /// -> Method to be used for testing/diagnostic purposes only! /// internal string[] FileContentInMemory(TextDocumentIdentifier textDocument) => - this.Projects.FileContentInMemory(textDocument); + this.projects.FileContentInMemory(textDocument); /// /// Waits for all currently running or queued tasks to finish before getting the diagnostics for the given file. @@ -421,7 +490,7 @@ internal string[] FileContentInMemory(TextDocumentIdentifier textDocument) => /// internal Diagnostic[] FileDiagnostics(TextDocumentIdentifier textDocument) { - var allDiagnostics = this.Projects.GetDiagnostics(textDocument?.Uri); + var allDiagnostics = this.projects.GetDiagnostics(textDocument?.Uri); return allDiagnostics?.Count() == 1 ? allDiagnostics.Single().Diagnostics : null; // count is > 1 if the given uri corresponds to a project file } } diff --git a/src/QsCompiler/LanguageServer/EventQueues.cs b/src/QsCompiler/LanguageServer/EventQueues.cs index e2242ee055..2dd638a840 100644 --- a/src/QsCompiler/LanguageServer/EventQueues.cs +++ b/src/QsCompiler/LanguageServer/EventQueues.cs @@ -11,20 +11,22 @@ using System.Reactive.Subjects; using Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsLanguageServer { public class CoalesceingQueue { private int nrSubscriptions = 0; - private readonly ConcurrentDictionary Subscriptions; + private readonly ConcurrentDictionary subscriptions; public CoalesceingQueue() => - this.Subscriptions = new ConcurrentDictionary(); + this.subscriptions = new ConcurrentDictionary(); public bool Unsubscribe(int id) { - if (!this.Subscriptions.TryRemove(id, out var subscriptions)) return false; + if (!this.subscriptions.TryRemove(id, out var subscriptions)) + { + return false; + } subscriptions.Item1.Dispose(); subscriptions.Item2.Dispose(); return true; @@ -40,16 +42,22 @@ public int Subscribe(IObservable observable, Action> buffer timer.AutoReset = false; timer.Enabled = false; - var eventSubscription = observable.Subscribe(fileEvent => { timer.Stop(); timer.Start(); }); + var eventSubscription = observable.Subscribe(fileEvent => + { + timer.Stop(); + timer.Start(); + }); var bufferedSubscription = observable.Buffer(fileIdle).Subscribe(e => bufferHandler(e)); - var id = ++nrSubscriptions; + var id = ++this.nrSubscriptions; var subscriptions = (eventSubscription, bufferedSubscription); - if (!this.Subscriptions.TryAdd(id, subscriptions)) id = -1; + if (!this.subscriptions.TryAdd(id, subscriptions)) + { + id = -1; + } return id; } - internal static IEnumerable Coalesce(IEnumerable events) { var stacks = new Dictionary>(); // we use one queue for each event source in order to coalesce @@ -66,11 +74,26 @@ internal static IEnumerable Coalesce(IEnumerable events) var last = stack.Pop(); var changedEvent = new FileEvent { FileChangeType = FileChangeType.Changed, Uri = e.Uri }; - if (last.FileChangeType == e.FileChangeType) stack.Push(last); - else if (last.FileChangeType == FileChangeType.Created && e.FileChangeType == FileChangeType.Deleted) stacks.Remove(e.Uri); - else if (last.FileChangeType == FileChangeType.Deleted && e.FileChangeType == FileChangeType.Created) stack.Push(changedEvent); - else if (last.FileChangeType == FileChangeType.Created && e.FileChangeType == FileChangeType.Changed) stack.Push(last); - else if (last.FileChangeType == FileChangeType.Changed && e.FileChangeType == FileChangeType.Deleted) stack.Push(e); + if (last.FileChangeType == e.FileChangeType) + { + stack.Push(last); + } + else if (last.FileChangeType == FileChangeType.Created && e.FileChangeType == FileChangeType.Deleted) + { + stacks.Remove(e.Uri); + } + else if (last.FileChangeType == FileChangeType.Deleted && e.FileChangeType == FileChangeType.Created) + { + stack.Push(changedEvent); + } + else if (last.FileChangeType == FileChangeType.Created && e.FileChangeType == FileChangeType.Changed) + { + stack.Push(last); + } + else if (last.FileChangeType == FileChangeType.Changed && e.FileChangeType == FileChangeType.Deleted) + { + stack.Push(e); + } else { stack.Push(last); @@ -79,6 +102,5 @@ internal static IEnumerable Coalesce(IEnumerable events) } return stacks.Values.SelectMany(stack => stack.Reverse()).ToImmutableArray(); } - } } diff --git a/src/QsCompiler/LanguageServer/FileSystemWatcher.cs b/src/QsCompiler/LanguageServer/FileSystemWatcher.cs index b253c2e014..52b5f4f238 100644 --- a/src/QsCompiler/LanguageServer/FileSystemWatcher.cs +++ b/src/QsCompiler/LanguageServer/FileSystemWatcher.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Concurrent; -using System.Collections.Immutable; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Security.Permissions; @@ -13,54 +13,60 @@ using Microsoft.Quantum.QsCompiler.CompilationBuilder; using Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsLanguageServer { /// - /// This class provides a basic file watcher for the LSP that sends notifications on watched files + /// This class provides a basic file watcher for the LSP that sends notifications on watched files /// when they change on disk (i.e. are added, removed, or edited). /// The implemeneted mechanism is far from perfect and will fail in some cases - - /// in particular I expect it to fail (silently) for autogenerated file system edits generated in rapid succession, - /// especially in combination with large batch edits in the file system. - /// However, all in all splitting out the logic to generate notifications for individual files here (even if it is less than perfect), - /// seems better and overall less error prone than having to deal with less abstraction on the server side. - /// I am fully aware that this mechanism is not a neat solution, and it should probably be revised at some point in the future. + /// in particular I expect it to fail (silently) for autogenerated file system edits generated in rapid succession, + /// especially in combination with large batch edits in the file system. + /// However, all in all splitting out the logic to generate notifications for individual files here (even if it is less than perfect), + /// seems better and overall less error prone than having to deal with less abstraction on the server side. + /// I am fully aware that this mechanism is not a neat solution, and it should probably be revised at some point in the future. /// internal class FileWatcher { - private readonly ConcurrentBag Watchers; - private readonly Action OnException; + private readonly ConcurrentBag watchers; + private readonly Action onException; + private void OnBufferOverflow(object sender, ErrorEventArgs e) { - // Todo: We should at some point implement a mechanism to try and recover from buffer overflows in the file watcher. - try { QsCompilerError.Raise($"buffer overflow in file system watcher: \n{e.GetException()}"); } - catch (Exception ex) { this.OnException(ex); } + // Todo: We should at some point implement a mechanism to try and recover from buffer overflows in the file watcher. + try + { + QsCompilerError.Raise($"buffer overflow in file system watcher: \n{e.GetException()}"); + } + catch (Exception ex) + { + this.onException(ex); + } } /// /// the keys contain the *absolute* uri to a folder, and the values are the set of the *relative* names of all contained files and folders /// - private readonly Dictionary> WatchedDirectories; - private readonly ConcurrentDictionary> GlobPatterns; - private readonly ProcessingQueue Processing; + private readonly Dictionary> watchedDirectories; + private readonly ConcurrentDictionary> globPatterns; + private readonly ProcessingQueue processing; public event FileEventHandler FileEvent; + public delegate void FileEventHandler(FileEvent e); - [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] + [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] public FileWatcher(Action onException = null) { - this.OnException = onException ?? throw new ArgumentNullException(nameof(onException)); - this.Watchers = new ConcurrentBag(); - this.WatchedDirectories = new Dictionary>(); - this.Processing = new ProcessingQueue(this.OnException, "error in file system watcher") ; - this.GlobPatterns = new ConcurrentDictionary>(); + this.onException = onException ?? throw new ArgumentNullException(nameof(onException)); + this.watchers = new ConcurrentBag(); + this.watchedDirectories = new Dictionary>(); + this.processing = new ProcessingQueue(this.onException, "error in file system watcher"); + this.globPatterns = new ConcurrentDictionary>(); } - /// - /// Returns a file system watcher for the given folder and pattern, with the proper event handlers added. - /// IMPORTANT: The returned watcher is disabled and needs to be enabled by setting EnableRaisingEvents to true. + /// Returns a file system watcher for the given folder and pattern, with the proper event handlers added. + /// IMPORTANT: The returned watcher is disabled and needs to be enabled by setting EnableRaisingEvents to true. /// private System.IO.FileSystemWatcher GetWatcher(string folder, string pattern, NotifyFilters notifyOn) { @@ -69,9 +75,9 @@ private System.IO.FileSystemWatcher GetWatcher(string folder, string pattern, No NotifyFilter = notifyOn, Filter = pattern, Path = folder, - // An entry is the buffer is 12 bytes plus the length of the file path times two. - // The default buffer size is 2*4096, which is around 15 events. - InternalBufferSize = 16 * 4096, + // An entry is the buffer is 12 bytes plus the length of the file path times two. + // The default buffer size is 2*4096, which is around 15 events. + InternalBufferSize = 16 * 4096, }; watcher.Error += new ErrorEventHandler(this.OnBufferOverflow); @@ -84,51 +90,62 @@ private System.IO.FileSystemWatcher GetWatcher(string folder, string pattern, No /// /// Initializes the given dictionary with the structure of the given directory as it is currently on disk for the given glob pattern. - /// Returns true if the routine succeeded without throwing an exception and false otherwise. - /// Returns true without doing anything if no directory exists at the given (absolute!) path. + /// Returns true if the routine succeeded without throwing an exception and false otherwise. + /// Returns true without doing anything if no directory exists at the given (absolute!) path. /// private static bool GlobDirectoryStructure(Dictionary> directories, string path, IEnumerable globPatterns) { - if (!Directory.Exists(path)) return true; // successfully completed, but nothing to be done + if (!Directory.Exists(path)) + { + return true; // successfully completed, but nothing to be done + } var root = Uri.TryCreate(path, UriKind.Absolute, out Uri uri) ? uri : null; var success = Directory.EnumerateDirectories(root.LocalPath).TryEnumerate(out var subfolders); success = globPatterns.TryEnumerate( - pattern => Directory.EnumerateFiles(root.LocalPath, pattern, SearchOption.TopDirectoryOnly), out var files) + pattern => Directory.EnumerateFiles(root.LocalPath, pattern, SearchOption.TopDirectoryOnly), out var files) && success; directories[root] = subfolders.Concat(files.SelectMany(items => items)).Select(Path.GetFileName).ToImmutableHashSet(); foreach (var subfolder in subfolders) - { success = GlobDirectoryStructure(directories, subfolder, globPatterns) && success; } + { + success = GlobDirectoryStructure(directories, subfolder, globPatterns) && success; + } return success; } /// - /// Adds suitable listeners to capture all given glob patterns for the given folder, + /// Adds suitable listeners to capture all given glob patterns for the given folder, /// and - if subfolders is set to true - all its subfolders. - /// Does nothing if no folder with the give path exists. + /// Does nothing if no folder with the give path exists. /// - public Task ListenAsync(string folder, bool subfolders, Action>> onInitialState, params string[] globPatterns) + public Task ListenAsync(string folder, bool subfolders, Action>> onInitialState, params string[] globPatterns) { - if (!Directory.Exists(folder)) return Task.CompletedTask; + if (!Directory.Exists(folder)) + { + return Task.CompletedTask; + } folder = folder.TrimEnd(Path.DirectorySeparatorChar); globPatterns = globPatterns.Distinct().ToArray(); - this.GlobPatterns.AddOrUpdate(new Uri(folder), globPatterns, (_, currentPatterns) => currentPatterns.Concat(globPatterns).Distinct().ToArray()); + this.globPatterns.AddOrUpdate(new Uri(folder), globPatterns, (_, currentPatterns) => currentPatterns.Concat(globPatterns).Distinct().ToArray()); var filters = globPatterns.Select(p => (p, NotifyFilters.FileName | NotifyFilters.LastWrite)); - if (subfolders) filters = filters.Concat(new (string, NotifyFilters)[] { (String.Empty, NotifyFilters.DirectoryName) }); + if (subfolders) + { + filters = filters.Concat(new (string, NotifyFilters)[] { (string.Empty, NotifyFilters.DirectoryName) }); + } - return this.Processing.QueueForExecutionAsync(() => + return this.processing.QueueForExecutionAsync(() => { var dictionary = new Dictionary>(); if (subfolders && GlobDirectoryStructure(dictionary, folder, globPatterns)) { foreach (var entry in dictionary) { - var current = this.WatchedDirectories.TryGetValue(entry.Key, out ImmutableHashSet c) ? c : ImmutableHashSet.Empty; - this.WatchedDirectories[entry.Key] = current.Union(entry.Value); + var current = this.watchedDirectories.TryGetValue(entry.Key, out ImmutableHashSet c) ? c : ImmutableHashSet.Empty; + this.watchedDirectories[entry.Key] = current.Union(entry.Value); } onInitialState?.Invoke(dictionary.ToImmutableDictionary()); } @@ -138,12 +155,11 @@ public Task ListenAsync(string folder, bool subfolders, Action items) ? items : ImmutableHashSet.Empty; - this.WatchedDirectories[dir] = current.Add(Path.GetFileName(fullPath)); + var current = this.watchedDirectories.TryGetValue(dir, out ImmutableHashSet items) ? items : ImmutableHashSet.Empty; + this.watchedDirectories[dir] = current.Add(Path.GetFileName(fullPath)); } private void RecurCreated(string fullPath, IDictionary> newDirectories) @@ -164,35 +180,39 @@ private void RecurCreated(string fullPath, IDictionary items)) { - var current = this.WatchedDirectories.TryGetValue(dir, out ImmutableHashSet c) ? c : ImmutableHashSet.Empty; - this.WatchedDirectories[dir] = current.Union(items); + var current = this.watchedDirectories.TryGetValue(dir, out ImmutableHashSet c) ? c : ImmutableHashSet.Empty; + this.watchedDirectories[dir] = current.Union(items); foreach (var item in items) - { this.RecurCreated(Path.Combine(fullPath, item), newDirectories); } + { + this.RecurCreated(Path.Combine(fullPath, item), newDirectories); + } + } + else + { + this.OnCreatedFile(fullPath); } - else this.OnCreatedFile(fullPath); } - // Todo: this routine in particular illustrates the limitations of the current mechanism. - public void OnCreated(object source, FileSystemEventArgs e) + // Todo: this routine in particular illustrates the limitations of the current mechanism. + public void OnCreated(object source, FileSystemEventArgs e) { var directories = new Dictionary>(); if (source is System.IO.FileSystemWatcher watcher && - this.GlobPatterns.TryGetValue(new Uri(watcher.Path), out IEnumerable globPatterns)) + this.globPatterns.TryGetValue(new Uri(watcher.Path), out IEnumerable globPatterns)) { var maxNrTries = 10; // copied directories need some time until they are on disk -> todo: better solution? - while (maxNrTries-- > 0 && !GlobDirectoryStructure(directories, e.FullPath, globPatterns)) + while (maxNrTries-- > 0 && !GlobDirectoryStructure(directories, e.FullPath, globPatterns)) { directories = new Dictionary>(); System.Threading.Thread.Sleep(1000); } } - _ = this.Processing.QueueForExecutionAsync(() => RecurCreated(e.FullPath, directories)); + _ = this.processing.QueueForExecutionAsync(() => this.RecurCreated(e.FullPath, directories)); } - // routines called upon deletion - private void OnDeletedFile(string fullPath) + private void OnDeletedFile(string fullPath) { this.FileEvent?.Invoke(new FileEvent { @@ -201,24 +221,31 @@ private void OnDeletedFile(string fullPath) }); var dir = new Uri(Path.GetDirectoryName(fullPath)); - var knownDir = this.WatchedDirectories.TryGetValue(dir, out ImmutableHashSet items); - if (knownDir) this.WatchedDirectories[dir] = items.Remove(Path.GetFileName(fullPath)); - } + var knownDir = this.watchedDirectories.TryGetValue(dir, out ImmutableHashSet items); + if (knownDir) + { + this.watchedDirectories[dir] = items.Remove(Path.GetFileName(fullPath)); + } + } private void RecurDeleted(string fullPath) { - if (this.WatchedDirectories.TryGetValue(new Uri(fullPath), out ImmutableHashSet items)) + if (this.watchedDirectories.TryGetValue(new Uri(fullPath), out ImmutableHashSet items)) { foreach (var item in items) - { this.RecurDeleted(Path.Combine(fullPath, item)); } - this.WatchedDirectories.Remove(new Uri(fullPath)); + { + this.RecurDeleted(Path.Combine(fullPath, item)); + } + this.watchedDirectories.Remove(new Uri(fullPath)); + } + else + { + this.OnDeletedFile(fullPath); } - else this.OnDeletedFile(fullPath); } public void OnDeleted(object source, FileSystemEventArgs e) => - this.Processing.QueueForExecutionAsync(() => RecurDeleted(e.FullPath)); - + this.processing.QueueForExecutionAsync(() => this.RecurDeleted(e.FullPath)); // routines called upon changing @@ -231,13 +258,18 @@ private void OnChangedFile(string fullPath) => private void RecurChanged(string fullPath) { - if (this.WatchedDirectories.TryGetValue(new Uri(fullPath), out var _)) { } // nothing to do here - else this.OnChangedFile(fullPath); + if (this.watchedDirectories.TryGetValue(new Uri(fullPath), out _)) + { + // nothing to do here + } + else + { + this.OnChangedFile(fullPath); + } } public void OnChanged(object source, FileSystemEventArgs e) => - this.Processing.QueueForExecutionAsync(() => RecurChanged(e.FullPath)); - + this.processing.QueueForExecutionAsync(() => this.RecurChanged(e.FullPath)); // routines called upon renaming @@ -249,17 +281,22 @@ private void OnRenamedFile(string fullPath, string oldFullPath) private void RecurRenamed(string fullPath, string oldFullPath) { - if (this.WatchedDirectories.TryGetValue(new Uri(oldFullPath), out ImmutableHashSet items)) + if (this.watchedDirectories.TryGetValue(new Uri(oldFullPath), out ImmutableHashSet items)) { - this.WatchedDirectories[new Uri(fullPath)] = items; + this.watchedDirectories[new Uri(fullPath)] = items; foreach (var item in items) - { this.RecurRenamed(Path.Combine(fullPath, item), Path.Combine(oldFullPath, item)); } - this.WatchedDirectories.Remove(new Uri(oldFullPath)); + { + this.RecurRenamed(Path.Combine(fullPath, item), Path.Combine(oldFullPath, item)); + } + this.watchedDirectories.Remove(new Uri(oldFullPath)); + } + else + { + this.OnRenamedFile(fullPath, oldFullPath); } - else this.OnRenamedFile(fullPath, oldFullPath); } public void OnRenamed(object source, RenamedEventArgs e) => - this.Processing.QueueForExecutionAsync(() => RecurRenamed(e.FullPath, e.OldFullPath)); + this.processing.QueueForExecutionAsync(() => this.RecurRenamed(e.FullPath, e.OldFullPath)); } } diff --git a/src/QsCompiler/LanguageServer/LanguageServer.cs b/src/QsCompiler/LanguageServer/LanguageServer.cs index 689ac446ff..76726c3f87 100644 --- a/src/QsCompiler/LanguageServer/LanguageServer.cs +++ b/src/QsCompiler/LanguageServer/LanguageServer.cs @@ -6,53 +6,53 @@ using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; -using System.Reactive.Linq; using Microsoft.Quantum.QsCompiler; using Microsoft.Quantum.QsCompiler.CompilationBuilder; -using Microsoft.VisualStudio.LanguageServer.Protocol; +using Microsoft.VisualStudio.LanguageServer.Protocol; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using StreamJsonRpc; - namespace Microsoft.Quantum.QsLanguageServer { public class QsLanguageServer : IDisposable { // properties required for basic functionality - private readonly JsonRpc Rpc; - private readonly ManualResetEvent DisconnectEvent; // used to keep the server running until it is no longer needed - private ManualResetEvent WaitForInit; // set to null after initialization + private readonly JsonRpc rpc; + private readonly ManualResetEvent disconnectEvent; // used to keep the server running until it is no longer needed + private ManualResetEvent waitForInit; // set to null after initialization + internal bool ReadyForExit { get; private set; } - - private readonly System.Timers.Timer InternalErrorTimer; // used to avoid spamming users with a lot of errors at once - private bool ShowInteralErrorMessage = true; // set via timer as needed - private string WorkspaceFolder = null; - private readonly HashSet ProjectsInWorkspace; - private readonly FileWatcher FileWatcher; - private readonly CoalesceingQueue FileEvents; + private readonly System.Timers.Timer internalErrorTimer; // used to avoid spamming users with a lot of errors at once + private bool showInteralErrorMessage = true; // set via timer as needed + + private string workspaceFolder = null; + private readonly HashSet projectsInWorkspace; + private readonly FileWatcher fileWatcher; + private readonly CoalesceingQueue fileEvents; - private string ClientName; - private Version ClientVersion; - private ClientCapabilities ClientCapabilities; - private readonly EditorState EditorState; + private string clientName; + private Version clientVersion; + private ClientCapabilities clientCapabilities; + private readonly EditorState editorState; /// /// Returns true if the client name matches the given name. /// private bool ClientNameIs(string name) => - name != null && name.Equals(this.ClientName, StringComparison.InvariantCultureIgnoreCase); + name != null && name.Equals(this.clientName, StringComparison.InvariantCultureIgnoreCase); /// /// Returns true if the client version is the same as the given version or later, or if the client version is /// unknown. /// private bool ClientVersionIsAtLeast(Version version) => - version == null || this.ClientVersion == null || this.ClientVersion >= version; + version == null || this.clientVersion == null || this.clientVersion >= version; /// /// helper function that selects a markup format from the given array of supported formats @@ -62,57 +62,61 @@ private MarkupKind ChooseFormat(MarkupKind[] supportedFormats) => ? supportedFormats.Contains(MarkupKind.Markdown) ? MarkupKind.Markdown : supportedFormats.First() : MarkupKind.PlainText; - // methods required for basic functionality public QsLanguageServer(Stream sender, Stream reader) { - this.WaitForInit = new ManualResetEvent(false); - this.Rpc = new JsonRpc(sender, reader, this) + this.waitForInit = new ManualResetEvent(false); + this.rpc = new JsonRpc(sender, reader, this) { SynchronizationContext = new QsSynchronizationContext() }; - this.Rpc.StartListening(); - this.DisconnectEvent = new ManualResetEvent(false); - this.Rpc.Disconnected += (object s, JsonRpcDisconnectedEventArgs e) => { this.DisconnectEvent.Set(); }; // let's make the server exit if the stream is disconnected + this.rpc.StartListening(); + this.disconnectEvent = new ManualResetEvent(false); + this.rpc.Disconnected += (object s, JsonRpcDisconnectedEventArgs e) => { this.disconnectEvent.Set(); }; // let's make the server exit if the stream is disconnected this.ReadyForExit = false; - this.InternalErrorTimer = new System.Timers.Timer(60000); - this.InternalErrorTimer.Elapsed += (_, __) => { this.ShowInteralErrorMessage = true; }; - this.InternalErrorTimer.AutoReset = false; + this.internalErrorTimer = new System.Timers.Timer(60000); + this.internalErrorTimer.Elapsed += (_, __) => { this.showInteralErrorMessage = true; }; + this.internalErrorTimer.AutoReset = false; void ProcessFileEvents(IEnumerable e) => this.OnDidChangeWatchedFiles(JToken.Parse(JsonConvert.SerializeObject( new DidChangeWatchedFilesParams { Changes = e.ToArray() }))); var fileEvents = Observable.FromEvent( - handler => this.FileWatcher.FileEvent += handler, - handler => this.FileWatcher.FileEvent -= handler) + handler => this.fileWatcher.FileEvent += handler, + handler => this.fileWatcher.FileEvent -= handler) .Where(e => !e.Uri.LocalPath.EndsWith("tmp", StringComparison.InvariantCultureIgnoreCase) && !e.Uri.LocalPath.EndsWith('~')); - this.ProjectsInWorkspace = new HashSet(); - this.FileWatcher = new FileWatcher(_ => this.LogToWindow($"FileSystemWatcher encountered and error", MessageType.Error)); - this.FileEvents = new CoalesceingQueue(); - this.FileEvents.Subscribe(fileEvents, observable => ProcessFileEvents(observable)); - this.EditorState = new EditorState(new ProjectLoader(this.LogToWindow), - diagnostics => this.PublishDiagnosticsAsync(diagnostics), (name, props, meas) => this.SendTelemetryAsync(name, props, meas), - this.LogToWindow, this.OnInternalError); - this.WaitForInit.Set(); + this.projectsInWorkspace = new HashSet(); + this.fileWatcher = new FileWatcher(_ => this.LogToWindow($"FileSystemWatcher encountered and error", MessageType.Error)); + this.fileEvents = new CoalesceingQueue(); + this.fileEvents.Subscribe(fileEvents, observable => ProcessFileEvents(observable)); + this.editorState = new EditorState( + new ProjectLoader(this.LogToWindow), + diagnostics => this.PublishDiagnosticsAsync(diagnostics), + (name, props, meas) => this.SendTelemetryAsync(name, props, meas), + this.LogToWindow, + this.OnInternalError); + this.waitForInit.Set(); } public void WaitForShutdown() - { this.DisconnectEvent.WaitOne(); } + { + this.disconnectEvent.WaitOne(); + } + /// public void Dispose() { - this.EditorState.Dispose(); - this.Rpc.Dispose(); - this.DisconnectEvent.Dispose(); - this.WaitForInit.Dispose(); + this.editorState.Dispose(); + this.rpc.Dispose(); + this.disconnectEvent.Dispose(); + this.waitForInit.Dispose(); } - // some utils for server -> client communication internal Task NotifyClientAsync(string method, object args) => - this.Rpc.NotifyWithParameterObjectAsync(method, args); // no need to wait for completion + this.rpc.NotifyWithParameterObjectAsync(method, args); // no need to wait for completion internal Task PublishDiagnosticsAsync(PublishDiagnosticParams diagnostics) => this.NotifyClientAsync(Methods.TextDocumentPublishDiagnosticsName, diagnostics); @@ -120,8 +124,10 @@ internal Task PublishDiagnosticsAsync(PublishDiagnosticParams diagnostics) => /// /// does not actually do anything unless the corresponding flag is defined upon compilation /// - internal Task SendTelemetryAsync(string eventName, - Dictionary properties, Dictionary measurements) => + internal Task SendTelemetryAsync( + string eventName, + Dictionary properties, + Dictionary measurements) => #if TELEMETRY this.NotifyClientAsync(Methods.TelemetryEventName, new Dictionary { @@ -142,15 +148,14 @@ internal void OnInternalError(Exception ex) this.LogToWindow($"{line}{ex}{line}", MessageType.Error); var logLocation = "the output window"; // todo: generate a proper error log in a file somewhere var message = "The Q# Language Server has encountered an error. Diagnostics will be reloaded upon saving the file."; - if (this.ShowInteralErrorMessage) + if (this.showInteralErrorMessage) { - this.ShowInteralErrorMessage = false; - this.InternalErrorTimer.Start(); + this.showInteralErrorMessage = false; + this.internalErrorTimer.Start(); this.ShowInWindow($"{message}\nDetails on the encountered error have been logged to {logLocation}.", MessageType.Error); } } - // jsonrpc methods for initialization and shut down private Task InitializeWorkspaceAsync(ImmutableDictionary> folders) @@ -158,39 +163,49 @@ private Task InitializeWorkspaceAsync(ImmutableDictionary entry.Value.Select(name => Path.Combine(entry.Key.LocalPath, name))); var initialProjects = folderItems.Select(item => { - if (!item.EndsWith(".csproj") || !Uri.TryCreate(item, UriKind.Absolute, out Uri uri)) return null; - this.ProjectsInWorkspace.Add(uri); - return uri; + if (!item.EndsWith(".csproj") || !Uri.TryCreate(item, UriKind.Absolute, out Uri uri)) + { + return null; + } + this.projectsInWorkspace.Add(uri); + return uri; }) .Where(fileEvent => fileEvent != null).ToImmutableArray(); - return this.EditorState.LoadProjectsAsync(initialProjects); + return this.editorState.LoadProjectsAsync(initialProjects); } [JsonRpcMethod(Methods.InitializeName)] public object Initialize(JToken arg) { - var doneWithInit = this.WaitForInit?.WaitOne(20000) ?? false; - if (!doneWithInit) return new InitializeError { Retry = true }; + var doneWithInit = this.waitForInit?.WaitOne(20000) ?? false; + if (!doneWithInit) + { + return new InitializeError { Retry = true }; + } arg?.SelectToken("capabilities.textDocument.codeAction")?.Replace(null); // setting this to null for now, since we are not using it and the deserialization causes issues var param = Utils.TryJTokenAs(arg); - this.ClientCapabilities = param.Capabilities; + this.clientCapabilities = param.Capabilities; if (param.InitializationOptions is JObject options) { if (options.TryGetValue("name", out var name)) - this.ClientName = name.ToString(); + { + this.clientName = name.ToString(); + } if (options.TryGetValue("version", out var version) - && !Version.TryParse(version.ToString(), out this.ClientVersion)) - this.ClientVersion = null; + && !Version.TryParse(version.ToString(), out this.clientVersion)) + { + this.clientVersion = null; + } } - bool supportsCompletion = !ClientNameIs("VisualStudio") || ClientVersionIsAtLeast(new Version(16, 3)); - bool useTriggerCharWorkaround = ClientNameIs("VisualStudio") && !ClientVersionIsAtLeast(new Version(16, 4)); + bool supportsCompletion = !this.ClientNameIs("VisualStudio") || this.ClientVersionIsAtLeast(new Version(16, 3)); + bool useTriggerCharWorkaround = this.ClientNameIs("VisualStudio") && !this.ClientVersionIsAtLeast(new Version(16, 4)); var rootUri = param.RootUri ?? (Uri.TryCreate(param?.RootPath, UriKind.Absolute, out Uri uri) ? uri : null); - this.WorkspaceFolder = rootUri != null && rootUri.IsAbsoluteUri && rootUri.IsFile && Directory.Exists(rootUri.LocalPath) ? rootUri.LocalPath : null; - this.LogToWindow($"workspace folder: {this.WorkspaceFolder ?? "(Null)"}", MessageType.Info); - this.FileWatcher.ListenAsync(this.WorkspaceFolder, true, dict => this.InitializeWorkspaceAsync(dict), "*.csproj", "*.dll", "*.qs").Wait(); // not strictly necessary to wait but less confusing + this.workspaceFolder = rootUri != null && rootUri.IsAbsoluteUri && rootUri.IsFile && Directory.Exists(rootUri.LocalPath) ? rootUri.LocalPath : null; + this.LogToWindow($"workspace folder: {this.workspaceFolder ?? "(Null)"}", MessageType.Info); + this.fileWatcher.ListenAsync(this.workspaceFolder, true, dict => this.InitializeWorkspaceAsync(dict), "*.csproj", "*.dll", "*.qs").Wait(); // not strictly necessary to wait but less confusing var capabilities = new ServerCapabilities { @@ -202,7 +217,7 @@ public object Initialize(JToken arg) capabilities.TextDocumentSync.Change = TextDocumentSyncKind.Incremental; capabilities.TextDocumentSync.OpenClose = true; capabilities.TextDocumentSync.Save = new SaveOptions { IncludeText = true }; - capabilities.CodeActionProvider = this.ClientCapabilities?.Workspace?.ApplyEdit ?? true; + capabilities.CodeActionProvider = this.clientCapabilities?.Workspace?.ApplyEdit ?? true; capabilities.DefinitionProvider = true; capabilities.ReferencesProvider = true; capabilities.DocumentSymbolProvider = true; @@ -219,12 +234,15 @@ public object Initialize(JToken arg) useTriggerCharWorkaround ? new[] { " ", ".", "(" } : new[] { ".", "(" }; } - this.WaitForInit = null; + this.waitForInit = null; return new InitializeResult { Capabilities = capabilities }; } [JsonRpcMethod(Methods.InitializedName)] - public void OnInitialized(JToken _) { } // nothing to do here + public void OnInitialized(JToken token) + { + // nothing to do here + } [JsonRpcMethod(Methods.ShutdownName)] public object Shutdown() // shut down and exit is fine even if the server was never initialized @@ -237,180 +255,241 @@ public object Initialize(JToken arg) public void Exit() // shut down and exit is fine even if the server was never initialized { this.Dispose(); - this.DisconnectEvent.Set(); + this.disconnectEvent.Set(); } - // jsonrpc methods called by the language server protocol [JsonRpcMethod(Methods.TextDocumentDidOpenName)] public Task OnTextDocumentDidOpenAsync(JToken arg) { - if (this.WaitForInit != null) return Task.CompletedTask; + if (this.waitForInit != null) + { + return Task.CompletedTask; + } var param = Utils.TryJTokenAs(arg); - return this.EditorState.OpenFileAsync(param.TextDocument, this.ShowInWindow, - this.WorkspaceFolder != null ? this.LogToWindow : (Action)null); + return this.editorState.OpenFileAsync( + param.TextDocument, + this.ShowInWindow, + this.workspaceFolder != null ? this.LogToWindow : (Action)null); } [JsonRpcMethod(Methods.TextDocumentDidCloseName)] public Task OnTextDocumentDidCloseAsync(JToken arg) { - if (this.WaitForInit != null) return Task.CompletedTask; + if (this.waitForInit != null) + { + return Task.CompletedTask; + } var param = Utils.TryJTokenAs(arg); - return this.EditorState.CloseFileAsync(param.TextDocument, this.LogToWindow); + return this.editorState.CloseFileAsync(param.TextDocument, this.LogToWindow); } [JsonRpcMethod(Methods.TextDocumentDidSaveName)] public Task OnTextDocumentDidSaveAsync(JToken arg) { - if (this.WaitForInit != null) return Task.CompletedTask; + if (this.waitForInit != null) + { + return Task.CompletedTask; + } var param = Utils.TryJTokenAs(arg); - return this.EditorState.SaveFileAsync(param.TextDocument, param.Text); + return this.editorState.SaveFileAsync(param.TextDocument, param.Text); } [JsonRpcMethod(Methods.TextDocumentDidChangeName)] public Task OnTextDocumentChangedAsync(JToken arg) { - if (this.WaitForInit != null) return Task.CompletedTask; + if (this.waitForInit != null) + { + return Task.CompletedTask; + } var param = Utils.TryJTokenAs(arg); - return this.EditorState.DidChangeAsync(param); + return this.editorState.DidChangeAsync(param); } [JsonRpcMethod(Methods.TextDocumentRenameName)] public object OnTextDocumentRename(JToken arg) { - if (WaitForInit != null) return ProtocolError.AwaitingInitialization; + if (this.waitForInit != null) + { + return ProtocolError.AwaitingInitialization; + } var param = Utils.TryJTokenAs(arg); - var versionedChanges = this.ClientCapabilities?.Workspace?.WorkspaceEdit?.DocumentChanges ?? false; + var versionedChanges = this.clientCapabilities?.Workspace?.WorkspaceEdit?.DocumentChanges ?? false; try { - return QsCompilerError.RaiseOnFailure(() => - this.EditorState.Rename(param, versionedChanges), - "Rename threw an exception"); + return QsCompilerError.RaiseOnFailure( + () => this.editorState.Rename(param, versionedChanges), + "Rename threw an exception"); + } + catch + { + return null; } - catch { return null; } } [JsonRpcMethod(Methods.TextDocumentDefinitionName)] public object OnTextDocumentDefinition(JToken arg) { - if (WaitForInit != null) return ProtocolError.AwaitingInitialization; + if (this.waitForInit != null) + { + return ProtocolError.AwaitingInitialization; + } var param = Utils.TryJTokenAs(arg); var defaultLocation = new Location { Uri = param?.TextDocument?.Uri, - Range = param?.Position != null + Range = param?.Position != null ? new VisualStudio.LanguageServer.Protocol.Range { Start = param.Position, End = param.Position } : null }; try { - return QsCompilerError.RaiseOnFailure(() => - this.EditorState.DefinitionLocation(param) ?? defaultLocation, - "GoToDefinition threw an exception"); + return QsCompilerError.RaiseOnFailure( + () => this.editorState.DefinitionLocation(param) ?? defaultLocation, + "GoToDefinition threw an exception"); + } + catch + { + return defaultLocation; } - catch { return defaultLocation; } } [JsonRpcMethod(Methods.TextDocumentDocumentHighlightName)] public object OnHighlightRequest(JToken arg) { - if (WaitForInit != null) return ProtocolError.AwaitingInitialization; + if (this.waitForInit != null) + { + return ProtocolError.AwaitingInitialization; + } var param = Utils.TryJTokenAs(arg); try { - return QsCompilerError.RaiseOnFailure(() => - this.EditorState.DocumentHighlights(param) ?? Array.Empty(), - "DocumentHighlight threw an exception"); + return QsCompilerError.RaiseOnFailure( + () => this.editorState.DocumentHighlights(param) ?? Array.Empty(), + "DocumentHighlight threw an exception"); + } + catch + { + return Array.Empty(); } - catch { return Array.Empty(); } } [JsonRpcMethod(Methods.TextDocumentReferencesName)] public object OnTextDocumentReferences(JToken arg) { - if (WaitForInit != null) return ProtocolError.AwaitingInitialization; + if (this.waitForInit != null) + { + return ProtocolError.AwaitingInitialization; + } var param = Utils.TryJTokenAs(arg); try { - return QsCompilerError.RaiseOnFailure(() => - this.EditorState.SymbolReferences(param) ?? Array.Empty(), - "FindReferences threw an exception"); + return QsCompilerError.RaiseOnFailure( + () => this.editorState.SymbolReferences(param) ?? Array.Empty(), + "FindReferences threw an exception"); + } + catch + { + return Array.Empty(); } - catch { return Array.Empty(); } } [JsonRpcMethod(Methods.TextDocumentHoverName)] public object OnHoverRequest(JToken arg) { - if (WaitForInit != null) return ProtocolError.AwaitingInitialization; + if (this.waitForInit != null) + { + return ProtocolError.AwaitingInitialization; + } var param = Utils.TryJTokenAs(arg); - var supportedFormats = this.ClientCapabilities?.TextDocument?.Hover?.ContentFormat; - var format = ChooseFormat(supportedFormats); + var supportedFormats = this.clientCapabilities?.TextDocument?.Hover?.ContentFormat; + var format = this.ChooseFormat(supportedFormats); try { - return QsCompilerError.RaiseOnFailure(() => - this.EditorState.HoverInformation(param, format), - "HoverInformation threw an exception"); + return QsCompilerError.RaiseOnFailure( + () => this.editorState.HoverInformation(param, format), + "HoverInformation threw an exception"); + } + catch + { + return null; } - catch { return null; } } [JsonRpcMethod(Methods.TextDocumentSignatureHelpName)] public Task OnSignatureHelp(JToken arg) { - if (WaitForInit != null) return Task.Run(() => ProtocolError.AwaitingInitialization); + if (this.waitForInit != null) + { + return Task.Run(() => ProtocolError.AwaitingInitialization); + } var param = Utils.TryJTokenAs(arg); - var supportedFormats = this.ClientCapabilities?.TextDocument?.SignatureHelp?.SignatureInformation?.DocumentationFormat; - var format = ChooseFormat(supportedFormats); + var supportedFormats = this.clientCapabilities?.TextDocument?.SignatureHelp?.SignatureInformation?.DocumentationFormat; + var format = this.ChooseFormat(supportedFormats); var task = new Task(() => { - // We need to give the file manager some time to actually process the change first, - // otherwise we will return null. + // We need to give the file manager some time to actually process the change first, + // otherwise we will return null. Thread.Sleep(100); try { - return QsCompilerError.RaiseOnFailure(() => - this.EditorState.SignatureHelp(param, format), - "SignatureHelp threw an exception"); + return QsCompilerError.RaiseOnFailure( + () => this.editorState.SignatureHelp(param, format), + "SignatureHelp threw an exception"); + } + catch + { + return null; } - catch { return null; } }); task.Start(TaskScheduler.Default); - return task; + return task; } [JsonRpcMethod(Methods.TextDocumentDocumentSymbolName)] public object OnTextDocumentSymbol(JToken arg) // list all symbols found in a given text document { - if (WaitForInit != null) return ProtocolError.AwaitingInitialization; + if (this.waitForInit != null) + { + return ProtocolError.AwaitingInitialization; + } var param = Utils.TryJTokenAs(arg); try { - return QsCompilerError.RaiseOnFailure(() => - this.EditorState.DocumentSymbols(param) ?? Array.Empty(), - "DocumentSymbols threw an exception"); + return QsCompilerError.RaiseOnFailure( + () => this.editorState.DocumentSymbols(param) ?? Array.Empty(), + "DocumentSymbols threw an exception"); + } + catch + { + return Array.Empty(); } - catch { return Array.Empty(); } } [JsonRpcMethod(Methods.TextDocumentCompletionName)] public Task OnTextDocumentCompletion(JToken arg) { - if (WaitForInit != null) return Task.Run(() => ProtocolError.AwaitingInitialization); + if (this.waitForInit != null) + { + return Task.Run(() => ProtocolError.AwaitingInitialization); + } var param = Utils.TryJTokenAs(arg); var task = new Task(() => { - // Wait for the file manager to finish processing any changes + // Wait for the file manager to finish processing any changes // that happened right before this completion request. Thread.Sleep(50); try { - return QsCompilerError.RaiseOnFailure(() => - EditorState.Completions(param), - "Completions threw an exception"); + return QsCompilerError.RaiseOnFailure( + () => this.editorState.Completions(param), + "Completions threw an exception"); + } + catch + { + return null; } - catch { return null; } }); task.Start(TaskScheduler.Default); return task; @@ -419,18 +498,24 @@ public Task OnTextDocumentCompletion(JToken arg) [JsonRpcMethod(Methods.TextDocumentCompletionResolveName)] public object OnTextDocumentCompletionResolve(JToken arg) { - if (WaitForInit != null) return ProtocolError.AwaitingInitialization; + if (this.waitForInit != null) + { + return ProtocolError.AwaitingInitialization; + } var param = Utils.TryJTokenAs(arg); - var supportedFormats = this.ClientCapabilities?.TextDocument?.SignatureHelp?.SignatureInformation?.DocumentationFormat; - var format = ChooseFormat(supportedFormats); + var supportedFormats = this.clientCapabilities?.TextDocument?.SignatureHelp?.SignatureInformation?.DocumentationFormat; + var format = this.ChooseFormat(supportedFormats); try { var data = Utils.TryJTokenAs(JToken.FromObject(param?.Data)); - return QsCompilerError.RaiseOnFailure(() => - EditorState.ResolveCompletion(param, data, format), - "ResolveCompletion threw an exception"); + return QsCompilerError.RaiseOnFailure( + () => this.editorState.ResolveCompletion(param, data, format), + "ResolveCompletion threw an exception"); + } + catch + { + return null; } - catch { return null; } } [JsonRpcMethod(Methods.TextDocumentCodeActionName)] @@ -442,44 +527,57 @@ Command BuildCommand(string title, WorkspaceEdit edit) return new Command { Title = title, CommandIdentifier = CommandIds.ApplyEdit, Arguments = new object[] { commandArgs } }; } - if (this.WaitForInit != null) return ProtocolError.AwaitingInitialization; + if (this.waitForInit != null) + { + return ProtocolError.AwaitingInitialization; + } var param = Utils.TryJTokenAs(arg).ToCodeActionParams(); try { - return QsCompilerError.RaiseOnFailure(() => - this.EditorState.CodeActions(param) - ?.SelectMany(vs => vs.Select(v => BuildCommand(vs.Key, v))) - ?? Enumerable.Empty(), - "CodeAction threw an exception") - .ToArray(); + return + QsCompilerError.RaiseOnFailure( + () => this.editorState.CodeActions(param) + ?.SelectMany(vs => vs.Select(v => BuildCommand(vs.Key, v))) + ?? Enumerable.Empty(), + "CodeAction threw an exception") + .ToArray(); + } + catch + { + return Array.Empty(); } - catch { return Array.Empty(); } } [JsonRpcMethod(Methods.WorkspaceExecuteCommandName)] public object OnExecuteCommand(JToken arg) { var param = Utils.TryJTokenAs(arg); - object CastAndExecute(Func command) where A : class => - QsCompilerError.RaiseOnFailure(() => - command(Utils.TryJTokenAs(param.Arguments.Single() as JObject)), // currently all supported commands take a single argument - "ExecuteCommand threw an exception"); + object CastAndExecute(Func command) where T : class => + QsCompilerError.RaiseOnFailure( + () => command(Utils.TryJTokenAs(param.Arguments.Single() as JObject)), // currently all supported commands take a single argument + "ExecuteCommand threw an exception"); try { return - param.Command == CommandIds.ApplyEdit ? CastAndExecute(edit => - this.Rpc.InvokeWithParameterObjectAsync(Methods.WorkspaceApplyEditName, edit)) : - param.Command == CommandIds.FileContentInMemory ? CastAndExecute(this.EditorState.FileContentInMemory) : - param.Command == CommandIds.FileDiagnostics ? CastAndExecute(this.EditorState.FileDiagnostics) : + param.Command == CommandIds.ApplyEdit ? CastAndExecute(edit => + this.rpc.InvokeWithParameterObjectAsync(Methods.WorkspaceApplyEditName, edit)) : + param.Command == CommandIds.FileContentInMemory ? CastAndExecute(this.editorState.FileContentInMemory) : + param.Command == CommandIds.FileDiagnostics ? CastAndExecute(this.editorState.FileDiagnostics) : null; } - catch { return null; } + catch + { + return null; + } } [JsonRpcMethod(Methods.WorkspaceDidChangeWatchedFilesName)] public void OnDidChangeWatchedFiles(JToken arg) { - if (this.WaitForInit != null) return; + if (this.waitForInit != null) + { + return; + } var param = Utils.TryJTokenAs(arg); FileEvent[] PreprocessEvent(FileEvent fileEvent) @@ -489,8 +587,14 @@ FileEvent[] PreprocessEvent(FileEvent fileEvent) if (fileName.EndsWith(".csproj")) { - if (fileEvent.FileChangeType == FileChangeType.Created) this.ProjectsInWorkspace.Add(fileEvent.Uri); - if (fileEvent.FileChangeType == FileChangeType.Deleted) this.ProjectsInWorkspace.Remove(fileEvent.Uri); + if (fileEvent.FileChangeType == FileChangeType.Created) + { + this.projectsInWorkspace.Add(fileEvent.Uri); + } + if (fileEvent.FileChangeType == FileChangeType.Deleted) + { + this.projectsInWorkspace.Remove(fileEvent.Uri); + } } if (fileName.EndsWith(".qs") && fileEvent.FileChangeType != FileChangeType.Changed) { @@ -500,7 +604,7 @@ bool FileIsWithinProjectDir(Uri projFile) QsCompilerError.Verify(projDir != null, "could not determine project directory"); return fileName.StartsWith(projDir.LocalPath); } - var projEvents = this.ProjectsInWorkspace.Where(FileIsWithinProjectDir) + var projEvents = this.projectsInWorkspace.Where(FileIsWithinProjectDir) .Select(projFile => new FileEvent { Uri = projFile, FileChangeType = FileChangeType.Changed }); events = projEvents.Concat(events).ToArray(); } @@ -517,25 +621,32 @@ bool FileIsWithinProjectDir(Uri projFile) foreach (var fileEvent in changes.Where(IsSourceFile)) { - // Unfortunately we have a rather annoying difference between VS and VS Code - - // for VS, deleting a file from disk will close it in the editor whereas for VS Code it won't. + // Unfortunately we have a rather annoying difference between VS and VS Code - + // for VS, deleting a file from disk will close it in the editor whereas for VS Code it won't. // The problem is now that for VS, we do not get a close file notification in that case... - // We hence inject close notifications for VS if a file has been deleted on disk. + // We hence inject close notifications for VS if a file has been deleted on disk. // While this will hopefully cover the most common cases of edits in- and outside the editor, // it is not currently possible to get the correct behavior for all cases! - if (fileEvent.FileChangeType == FileChangeType.Deleted && ClientNameIs("VisualStudio")) + if (fileEvent.FileChangeType == FileChangeType.Deleted && this.ClientNameIs("VisualStudio")) { this.LogToWindow($"The file '{fileEvent.Uri.LocalPath}' has been deleted on disk.", MessageType.Info); - _ = this.EditorState.CloseFileAsync(new TextDocumentIdentifier { Uri = fileEvent.Uri }); + _ = this.editorState.CloseFileAsync(new TextDocumentIdentifier { Uri = fileEvent.Uri }); + } + else + { + _ = this.editorState.SourceFileDidChangeOnDiskAsync(fileEvent.Uri); } - else _ = this.EditorState.SourceFileDidChangeOnDiskAsync(fileEvent.Uri); } foreach (var fileEvent in changes.Where(IsProjectFile)) - { _ = this.EditorState.ProjectDidChangeOnDiskAsync(fileEvent.Uri); } + { + _ = this.editorState.ProjectDidChangeOnDiskAsync(fileEvent.Uri); + } foreach (var fileEvent in changes.Where(IsDll)) - { _ = this.EditorState.AssemblyDidChangeOnDiskAsync(fileEvent.Uri); } + { + _ = this.editorState.AssemblyDidChangeOnDiskAsync(fileEvent.Uri); + } } } } diff --git a/src/QsCompiler/LanguageServer/LanguageServer.csproj b/src/QsCompiler/LanguageServer/LanguageServer.csproj index 0e0992e979..a4d5c4fd9e 100644 --- a/src/QsCompiler/LanguageServer/LanguageServer.csproj +++ b/src/QsCompiler/LanguageServer/LanguageServer.csproj @@ -27,6 +27,7 @@ + @@ -34,4 +35,7 @@ + + + diff --git a/src/QsCompiler/LanguageServer/Program.cs b/src/QsCompiler/LanguageServer/Program.cs index 096c9b3d78..9c925e1b82 100644 --- a/src/QsCompiler/LanguageServer/Program.cs +++ b/src/QsCompiler/LanguageServer/Program.cs @@ -5,11 +5,11 @@ using System.IO; using System.IO.Pipes; using System.Net.Sockets; +using System.Reflection; using CommandLine; using CommandLine.Text; using Microsoft.Build.Locator; - namespace Microsoft.Quantum.QsLanguageServer { public class Server @@ -20,24 +20,39 @@ public class Options protected const string CONNECTION_VIA_SOCKET = "connectionViaSocket"; protected const string CONNECTION_VIA_PIPE = "connectionViaPipe"; - [Option('l', "log", Required = false, Default = null, - HelpText = "Path to log messages to.")] + [Option( + 'l', + "log", + Required = false, + Default = null, + HelpText = "Path to log messages to.")] public string LogFile { get; set; } - [Option('p', "port", Required = true, SetName = CONNECTION_VIA_SOCKET, - HelpText = "Port to use for TCP/IP connections.")] + [Option( + 'p', + "port", + Required = true, + SetName = CONNECTION_VIA_SOCKET, + HelpText = "Port to use for TCP/IP connections.")] public int Port { get; set; } - [Option('w', "writer", Required = true, SetName = CONNECTION_VIA_PIPE, - HelpText = "Named pipe to write to.")] + [Option( + 'w', + "writer", + Required = true, + SetName = CONNECTION_VIA_PIPE, + HelpText = "Named pipe to write to.")] public string WriterPipeName { get; set; } - [Option('r', "reader", Required = true, SetName = CONNECTION_VIA_PIPE, - HelpText = "Named pipe to read from.")] + [Option( + 'r', + "reader", + Required = true, + SetName = CONNECTION_VIA_PIPE, + HelpText = "Named pipe to read from.")] public string ReaderPipeName { get; set; } } - public enum ReturnCode { SUCCESS = 0, @@ -62,7 +77,8 @@ private static int LogAndExit(ReturnCode code, string logFile = null, string mes } public static string Version = - typeof(Server).Assembly.GetName().Version?.ToString(); + typeof(Server).Assembly.GetCustomAttribute()?.InformationalVersion + ?? typeof(Server).Assembly.GetName().Version.ToString(); public static int Main(string[] args) { @@ -70,18 +86,24 @@ public static int Main(string[] args) var options = parser.ParseArguments(args); return options.MapResult( (Options opts) => Run(opts), - (errs => errs.IsVersion() - ? LogAndExit(ReturnCode.SUCCESS, message: Version) - : LogAndExit(ReturnCode.INVALID_ARGUMENTS, message: HelpText.AutoBuild(options)))); + errs => errs.IsVersion() + ? LogAndExit(ReturnCode.SUCCESS, message: Version) + : LogAndExit(ReturnCode.INVALID_ARGUMENTS, message: HelpText.AutoBuild(options))); } private static int Run(Options options) { - if (options == null) return LogAndExit(ReturnCode.MISSING_ARGUMENTS); + if (options == null) + { + return LogAndExit(ReturnCode.MISSING_ARGUMENTS); + } - // In the case where we actually instantiate a server, we need to "configure" the design time build. - // This needs to be done before any MsBuild packages are loaded. - try { MSBuildLocator.RegisterDefaults(); } + // In the case where we actually instantiate a server, we need to "configure" the design time build. + // This needs to be done before any MsBuild packages are loaded. + try + { + MSBuildLocator.RegisterDefaults(); + } catch (Exception ex) { Log("[ERROR] MsBuildLocator could not register defaults.", options.LogFile); @@ -102,19 +124,21 @@ private static int Run(Options options) } Log("Listening...", options.LogFile); - try { server.WaitForShutdown(); } + try + { + server.WaitForShutdown(); + } catch (Exception ex) { Log("[ERROR] Unexpected error.", options.LogFile); return LogAndExit(ReturnCode.UNEXPECTED_ERROR, options.LogFile, ex.ToString()); } - return server.ReadyForExit + return server.ReadyForExit ? LogAndExit(ReturnCode.SUCCESS, options.LogFile) : LogAndExit(ReturnCode.UNEXPECTED_ERROR, options.LogFile); } - private static void Log(object msg, string logFile = null) { if (logFile != null) @@ -122,7 +146,10 @@ private static void Log(object msg, string logFile = null) using var writer = new StreamWriter(logFile, append: true); writer.WriteLine(msg); } - else Console.WriteLine(msg); + else + { + Console.WriteLine(msg); + } } internal static QsLanguageServer ConnectViaNamedPipe(string writerName, string readerName, string logFile = null) @@ -132,9 +159,15 @@ internal static QsLanguageServer ConnectViaNamedPipe(string writerName, string r var readerPipe = new NamedPipeClientStream(readerName); readerPipe.Connect(30000); - if (!readerPipe.IsConnected) Log($"[ERROR] Connection attempted timed out.", logFile); + if (!readerPipe.IsConnected) + { + Log($"[ERROR] Connection attempted timed out.", logFile); + } writerPipe.Connect(30000); - if (!writerPipe.IsConnected) Log($"[ERROR] Connection attempted timed out.", logFile); + if (!writerPipe.IsConnected) + { + Log($"[ERROR] Connection attempted timed out.", logFile); + } return new QsLanguageServer(writerPipe, readerPipe); } @@ -142,7 +175,10 @@ internal static QsLanguageServer ConnectViaSocket(string hostname = "localhost", { Log($"Connecting via socket. {Environment.NewLine}Port number: {port}", logFile); Stream stream = null; - try { stream = new TcpClient(hostname, port)?.GetStream(); } + try + { + stream = new TcpClient(hostname, port)?.GetStream(); + } catch (Exception ex) { Log("[ERROR] Failed to get network stream.", logFile); diff --git a/src/QsCompiler/LanguageServer/ProjectLoader.cs b/src/QsCompiler/LanguageServer/ProjectLoader.cs index e48d05980f..6300e0727f 100644 --- a/src/QsCompiler/LanguageServer/ProjectLoader.cs +++ b/src/QsCompiler/LanguageServer/ProjectLoader.cs @@ -15,20 +15,20 @@ using Microsoft.Build.Utilities; using Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsLanguageServer { /// - /// Note that this class is *not* threadsafe, + /// Note that this class is *not* threadsafe, /// and design time builds will fail if a (design time) build is already in progress. /// internal class ProjectLoader { public readonly Action Log; + public ProjectLoader(Action log = null) => this.Log = log ?? ((_, __) => { }); - // possibly configurable properties + // possibly configurable properties /// /// Returns a dictionary with global properties used to load projects at runtime. @@ -41,28 +41,32 @@ internal static Dictionary GlobalProperties(string targetFramewo ["BuildProjectReferences"] = "false", ["EnableFrameworkPathOverride"] = "false" // otherwise msbuild fails on .net 461 projects }; - if (targetFramework != null) { props["TargetFramework"] = targetFramework; } // necessary for multi-framework projects. + if (targetFramework != null) + { + // necessary for multi-framework projects. + props["TargetFramework"] = targetFramework; + } return props; } - private static readonly Regex TargetFrameworkMoniker = + private static readonly Regex TargetFrameworkMoniker = new Regex(@"(netstandard[1-9]\.[0-9])|(netcoreapp[1-9]\.[0-9])|(net[1-9][0-9][0-9]?)"); - private readonly ImmutableArray SupportedQsFrameworks = + private readonly ImmutableArray supportedQsFrameworks = ImmutableArray.Create("netstandard2.", "netcoreapp2.", "netcoreapp3."); /// - /// Returns true if the given framework is officially supported for Q# projects. + /// Returns true if the given framework is officially supported for Q# projects. /// public bool IsSupportedQsFramework(string framework) => framework != null - ? this.SupportedQsFrameworks.Any(framework.ToLowerInvariant().StartsWith) + ? this.supportedQsFrameworks.Any(framework.ToLowerInvariant().StartsWith) : false; /// - /// contains a list of Properties from the project that we want to track e.g. for telemetry. + /// contains a list of Properties from the project that we want to track e.g. for telemetry. /// - private static readonly IEnumerable PropertiesToTrack = + private static readonly IEnumerable PropertiesToTrack = new string[] { "QsharpLangVersion" }; /// @@ -71,12 +75,11 @@ public bool IsSupportedQsFramework(string framework) => private static bool GeneratePackageInfo(string packageName) => packageName.StartsWith("microsoft.quantum.", StringComparison.InvariantCultureIgnoreCase); - // general purpose routines /// /// Returns all targeted frameworks of the given project. - /// IMPORTANT: currently only supports .net core-style projects. + /// IMPORTANT: currently only supports .net core-style projects. /// private static string[] TargetedFrameworks(Project project) { @@ -84,22 +87,27 @@ private static string[] TargetedFrameworks(Project project) var evaluatedProps = project.Properties.Where(p => p.Name?.ToLowerInvariant()?.StartsWith("targetframework") ?? false); return evaluatedProps .SelectMany(p => TargetFrameworkMoniker.Matches(p.EvaluatedValue.ToLowerInvariant()) - .Where(m => m.Success).Select(m => m.Value) - ).ToArray(); + .Where(m => m.Success).Select(m => m.Value)) + .ToArray(); } /// /// Returns a dictionary with the properties used for design time builds of the project corresponding to the given project file. - /// Chooses a target framework for the build properties according to the given comparison. + /// Chooses a target framework for the build properties according to the given comparison. /// Chooses the first target framework is no comparison is given. - /// Logs a suitable error is no target framework can be determined. - /// Returns a dictionary with additional project information (e.g. for telemetry) as out parameter. - /// Throws an ArgumentException if the given project file is null or does not exist. + /// Logs a suitable error is no target framework can be determined. + /// Returns a dictionary with additional project information (e.g. for telemetry) as out parameter. + /// Throws an ArgumentException if the given project file is null or does not exist. /// - internal IDictionary DesignTimeBuildProperties(string projectFile, - out Dictionary metadata, Comparison preferredFramework = null) + internal IDictionary DesignTimeBuildProperties( + string projectFile, + out Dictionary metadata, + Comparison preferredFramework = null) { - if (!File.Exists(projectFile)) throw new ArgumentException("given project file is null or does not exist", nameof(projectFile)); + if (!File.Exists(projectFile)) + { + throw new ArgumentException("given project file is null or does not exist", nameof(projectFile)); + } (string, Dictionary) FrameworkAndMetadata(Project project) { string GetVersion(ProjectItem item) => item.DirectMetadata @@ -113,13 +121,20 @@ string GetVersion(ProjectItem item) => item.DirectMetadata var projInfo = new Dictionary(); foreach (var (package, version) in packageRefs) - { projInfo[$"pkgref.{package}"] = version; } + { + projInfo[$"pkgref.{package}"] = version; + } foreach (var (name, value) in trackedProperties) - { projInfo[name] = value; } - projInfo["projectNameHash"] = GetProjectNameHash(projectFile); + { + projInfo[name] = value; + } + projInfo["projectNameHash"] = this.GetProjectNameHash(projectFile); var frameworks = TargetedFrameworks(project).ToList(); - if (preferredFramework != null) frameworks.Sort(preferredFramework); + if (preferredFramework != null) + { + frameworks.Sort(preferredFramework); + } return (frameworks.FirstOrDefault(), projInfo); } @@ -141,7 +156,10 @@ internal string GetProjectNameHash(string projectFile) string fileName = Path.GetFileName(projectFile); byte[] data = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(fileName)); var sBuilder = new StringBuilder(); - for (int i = 0; i < data.Length; i++) { sBuilder.Append(data[i].ToString("x2")); } + for (int i = 0; i < data.Length; i++) + { + sBuilder.Append(data[i].ToString("x2")); + } return sBuilder.ToString(); } } @@ -154,90 +172,130 @@ internal string GetProjectNameHash(string projectFile) /// /// Loads the project corresponding to the given project file with the given properties, - /// applies the given query to it, and unloads it. Returns the result of the query. - /// Throws an ArgumentNullException if the given query or properties are null. - /// Throws an ArgumentException if the given project file is null or does not exist. - /// NOTE: unloads the GlobalProjectCollection to force a cache clearing. + /// applies the given query to it, and unloads it. Returns the result of the query. + /// Throws an ArgumentNullException if the given query or properties are null. + /// Throws an ArgumentException if the given project file is null or does not exist. + /// NOTE: unloads the GlobalProjectCollection to force a cache clearing. /// - private static T LoadAndApply(string projectFile, IDictionary properties, Func Query) + private static T LoadAndApply(string projectFile, IDictionary properties, Func query) { - if (Query == null) throw new ArgumentNullException(nameof(Query)); - if (properties == null) throw new ArgumentNullException(nameof(properties)); - if (!File.Exists(projectFile)) throw new ArgumentException("given project file is null or does not exist", nameof(projectFile)); + if (query == null) + { + throw new ArgumentNullException(nameof(query)); + } + if (properties == null) + { + throw new ArgumentNullException(nameof(properties)); + } + if (!File.Exists(projectFile)) + { + throw new ArgumentException("given project file is null or does not exist", nameof(projectFile)); + } Project project = null; try { // Unloading the project unloads the project but *doesn't* clear the cache to be resilient to inconsistent states. - // Hence we actually need to unload all projects, which does make sure the cache is cleared and changes on disk are reflected. + // Hence we actually need to unload all projects, which does make sure the cache is cleared and changes on disk are reflected. // See e.g. https://github.com/Microsoft/msbuild/issues/795 ProjectCollection.GlobalProjectCollection.UnloadAllProjects(); // needed due to the caching behavior of MS build project = new Project(projectFile, properties, ToolLocationHelper.CurrentToolsVersion); - return Query(project); + return query(project); } finally { if (project != null) - { ProjectCollection.GlobalProjectCollection?.UnloadProject(project); } + { + ProjectCollection.GlobalProjectCollection?.UnloadProject(project); + } } } - // routines for loading and processing information from Q# projects specifically /// - /// Loads the project for the given project file, restores all packages, - /// and builds the target ResolveAssemblyReferencesDesignTime, logging suitable errors in the process. + /// Loads the project for the given project file, restores all packages, + /// and builds the target ResolveAssemblyReferencesDesignTime, logging suitable errors in the process. /// If the built project instance is recognized as a valid Q# project by the server, returns the built project instance. - /// Returns null if this is not the case, or if the given project file is null or does not exist. - /// Returns a dictionary with additional project information (e.g. for telemetry) as out parameter. + /// Returns null if this is not the case, or if the given project file is null or does not exist. + /// Returns a dictionary with additional project information (e.g. for telemetry) as out parameter. /// private ProjectInstance QsProjectInstance(string projectFile, out Dictionary metadata) { metadata = new Dictionary(); - if (!File.Exists(projectFile)) return null; + if (!File.Exists(projectFile)) + { + return null; + } var loggers = new ILogger[] { new Utils.MSBuildLogger(this.Log) }; - int preferSupportedFrameworks(string x, string y) => (IsSupportedQsFramework(y) ? 1 : 0) - (IsSupportedQsFramework(x) ? 1 : 0); - var properties = this.DesignTimeBuildProperties(projectFile, out metadata, preferSupportedFrameworks); + int PreferSupportedFrameworks(string x, string y) => (this.IsSupportedQsFramework(y) ? 1 : 0) - (this.IsSupportedQsFramework(x) ? 1 : 0); + var properties = this.DesignTimeBuildProperties(projectFile, out metadata, PreferSupportedFrameworks); // restore project (requires reloading the project after for the restore to take effect) var succeed = LoadAndApply(projectFile, properties, project => project.CreateProjectInstance().Build("Restore", loggers)); - if (!succeed) this.Log($"Failed to restore project '{projectFile}'.", MessageType.Error); + if (!succeed) + { + this.Log($"Failed to restore project '{projectFile}'.", MessageType.Error); + } // build the project instance and returns it if it is indeed a Q# project return LoadAndApply(projectFile, properties, project => { var instance = project.CreateProjectInstance(); succeed = instance.Build("ResolveAssemblyReferencesDesignTime", loggers); - if (!succeed) this.Log($"Failed to resolve assembly references for project '{projectFile}'.", MessageType.Error); + if (!succeed) + { + this.Log($"Failed to resolve assembly references for project '{projectFile}'.", MessageType.Error); + } return instance.Targets.ContainsKey("QsharpCompile") ? instance : null; }); } /// /// Returns the project instance for the given project file with all assembly references resolved, - /// if the given project is recognized as a valid Q# project by the server, and null otherwise. - /// Returns null without logging anything if the given project file does not end in .csproj. - /// Returns a dictionary with additional project information (e.g. for telemetry) as out parameter. + /// if the given project is recognized as a valid Q# project by the server, and null otherwise. + /// Returns null without logging anything if the given project file does not end in .csproj. + /// Returns a dictionary with additional project information (e.g. for telemetry) as out parameter. /// Logs suitable messages using the given log function if the project file cannot be found, or if the design time build fails. - /// Logs whether or not the project is recognized as Q# project. + /// Logs whether or not the project is recognized as Q# project. /// Throws an ArgumentNullException if the given project file is null. /// public ProjectInstance TryGetQsProjectInstance(string projectFile, out Dictionary metadata) { - if (projectFile == null) throw new ArgumentNullException(nameof(projectFile)); + if (projectFile == null) + { + throw new ArgumentNullException(nameof(projectFile)); + } metadata = new Dictionary(); - if (!projectFile.ToLowerInvariant().EndsWith(".csproj")) return null; + if (!projectFile.ToLowerInvariant().EndsWith(".csproj")) + { + return null; + } ProjectInstance instance = null; - try { instance = QsProjectInstance(projectFile, out metadata); } - catch (Exception ex) { this.Log($"Error on loading project '{projectFile}': {ex.Message}.", MessageType.Error); } + try + { + instance = this.QsProjectInstance(projectFile, out metadata); + } + catch (Exception ex) + { + this.Log($"Error on loading project '{projectFile}': {ex.Message}.", MessageType.Error); + } - if (!File.Exists(projectFile)) this.Log($"Could not find project file '{projectFile}'.", MessageType.Warning); - else if (instance == null) this.Log($"Ignoring non-Q# project '{projectFile}'.", MessageType.Log); - else this.Log($"Discovered Q# project '{projectFile}'.", MessageType.Log); + if (!File.Exists(projectFile)) + { + this.Log($"Could not find project file '{projectFile}'.", MessageType.Warning); + } + else if (instance == null) + { + this.Log($"Ignoring non-Q# project '{projectFile}'.", MessageType.Log); + } + else + { + this.Log($"Discovered Q# project '{projectFile}'.", MessageType.Log); + } return instance; } } -} \ No newline at end of file +} diff --git a/src/QsCompiler/LanguageServer/Properties/AssemblyInfo.cs b/src/QsCompiler/LanguageServer/Properties/AssemblyInfo.cs index b0a240fdad..932d13cc8c 100644 --- a/src/QsCompiler/LanguageServer/Properties/AssemblyInfo.cs +++ b/src/QsCompiler/LanguageServer/Properties/AssemblyInfo.cs @@ -4,4 +4,4 @@ using System.Runtime.CompilerServices; // Allow the test assembly to use our internal methods -[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsLanguageServer" + SigningConstants.PUBLIC_KEY)] +[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsLanguageServer" + SigningConstants.PublicKey)] diff --git a/src/QsCompiler/LanguageServer/SynchronizationContext.cs b/src/QsCompiler/LanguageServer/SynchronizationContext.cs index 23137be75a..15c4adcda3 100644 --- a/src/QsCompiler/LanguageServer/SynchronizationContext.cs +++ b/src/QsCompiler/LanguageServer/SynchronizationContext.cs @@ -6,29 +6,36 @@ using Microsoft.Quantum.QsCompiler; using Microsoft.VisualStudio.Threading; - namespace Microsoft.Quantum.QsLanguageServer { /// - /// Used to enforce in-order processing of the communication with the Q# language server. - /// Such a synchronization context is needed since the Q# language server + /// Used to enforce in-order processing of the communication with the Q# language server. + /// Such a synchronization context is needed since the Q# language server /// processes changes incrementally rather than reprocessing entire files each time. /// public class QsSynchronizationContext : SynchronizationContext { private readonly AsyncQueue<(SendOrPostCallback, object)> queued = new AsyncQueue<(SendOrPostCallback, object)>(); + private void ProcessNext() { var gotNext = this.queued.TryDequeue(out (SendOrPostCallback, object) next); QsCompilerError.Verify(gotNext, "nothing to process in the SynchronizationContext"); - if (gotNext) next.Item1(next.Item2); + if (gotNext) + { + next.Item1(next.Item2); + } } + /// public override void Post(SendOrPostCallback fct, object arg) { - if (fct == null) throw new ArgumentNullException(nameof(fct)); - this.queued.Enqueue((fct,arg)); - base.Send(_ => ProcessNext(), null); + if (fct == null) + { + throw new ArgumentNullException(nameof(fct)); + } + this.queued.Enqueue((fct, arg)); + this.Send(_ => this.ProcessNext(), null); } } } diff --git a/src/QsCompiler/LanguageServer/Utils.cs b/src/QsCompiler/LanguageServer/Utils.cs index f351ea298e..206bcb4087 100644 --- a/src/QsCompiler/LanguageServer/Utils.cs +++ b/src/QsCompiler/LanguageServer/Utils.cs @@ -10,17 +10,17 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using Newtonsoft.Json.Linq; - namespace Microsoft.Quantum.QsLanguageServer { public static class Utils { - // language server tools - + // language server tools - // wrapping these into a try .. catch .. to make sure errors don't go unnoticed as they otherwise would public static T TryJTokenAs(JToken arg) where T : class => - QsCompilerError.RaiseOnFailure(() => - arg == null ? throw new ArgumentNullException(nameof(arg)) : arg.ToObject(), "could not cast given JToken"); + QsCompilerError.RaiseOnFailure( + () => arg == null ? throw new ArgumentNullException(nameof(arg)) : arg.ToObject(), + "could not cast given JToken"); private static ShowMessageParams AsMessageParams(string text, MessageType severity) => text == null ? null : new ShowMessageParams { Message = text, MessageType = severity }; @@ -33,8 +33,14 @@ internal static void ShowInWindow(this QsLanguageServer server, string text, Mes { var message = AsMessageParams(text, severity); QsCompilerError.Verify(server != null && message != null, "cannot show message - given server or text was null"); - if (server == null) throw new ArgumentNullException(nameof(server)); - if (message == null) throw new ArgumentNullException(nameof(message)); + if (server == null) + { + throw new ArgumentNullException(nameof(server)); + } + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } _ = server.NotifyClientAsync(Methods.WindowShowMessageName, message); } @@ -46,33 +52,53 @@ internal static void LogToWindow(this QsLanguageServer server, string text, Mess { var message = AsMessageParams(text, severity); QsCompilerError.Verify(server != null && message != null, "cannot log message - given server or text was null"); - if (server == null) throw new ArgumentNullException(nameof(server)); - if (message == null) throw new ArgumentNullException(nameof(message)); + if (server == null) + { + throw new ArgumentNullException(nameof(server)); + } + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } _ = server.NotifyClientAsync(Methods.WindowLogMessageName, message); } - // tools related to project loading and file watching /// - /// Attempts to apply the given mapper to each element in the given sequence. - /// Returns a new sequence consisting of all mapped elements for which the mapping succeeded as out parameter, + /// Attempts to apply the given mapper to each element in the given sequence. + /// Returns a new sequence consisting of all mapped elements for which the mapping succeeded as out parameter, /// as well as a bool indicating whether the mapping succeeded for all elements. - /// The returned out parameter is non-null even if the mapping failed on some elements. + /// The returned out parameter is non-null even if the mapping failed on some elements. /// - internal static bool TryEnumerate(this IEnumerable source, - Func mapper, out ImmutableArray mapped) + internal static bool TryEnumerate( + this IEnumerable source, + Func mapper, + out ImmutableArray mapped) { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (mapper == null) throw new ArgumentNullException(nameof(mapper)); + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (mapper == null) + { + throw new ArgumentNullException(nameof(mapper)); + } var succeeded = true; var enumerator = source.GetEnumerator(); T Try(Func getRes, T fallback) { - try { return getRes(); } - catch { succeeded = false; return fallback; } + try + { + return getRes(); + } + catch + { + succeeded = false; + return fallback; + } } bool TryMoveNext() => Try(enumerator.MoveNext, false); @@ -82,24 +108,27 @@ T Try(Func getRes, T fallback) while (TryMoveNext()) { var evaluated = ApplyToCurrent(); - if (evaluated.Item1) values.Add(evaluated.Item2); - }; + if (evaluated.Item1) + { + values.Add(evaluated.Item2); + } + } mapped = values.ToImmutable(); return succeeded; } /// - /// Attempts to enumerate the given sequence. - /// Returns a new sequence consisting of all elements which could be accessed, + /// Attempts to enumerate the given sequence. + /// Returns a new sequence consisting of all elements which could be accessed, /// as well as a bool indicating whether the enumeration succeeded for all elements. - /// The returned out parameter is non-null even if access failed on some elements. + /// The returned out parameter is non-null even if access failed on some elements. /// internal static bool TryEnumerate(this IEnumerable source, out ImmutableArray enumerated) => source.TryEnumerate(element => element, out enumerated); /// - /// The given log function is applied to all errors and warning + /// The given log function is applied to all errors and warning /// raised by the ms build routine an instance of this class is given to. /// internal class MSBuildLogger : Logger diff --git a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs index 4c62ae08f6..87b0073160 100644 --- a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs @@ -63,7 +63,7 @@ let internal toString (t : ResolvedType) = SyntaxTreeToQsharp.Default.ToCode t /// This subtyping carries over to tuple types containing operations, and callable types containing operations as within their in- and/or output type. /// However, arrays in particular are treated as invariant; /// i.e. an array of operations of type t1 are *not* a subtype of arrays of operations of type t2 even if t1 is a subtype of t2. -let private CommonBaseType addError mismatchErr parent (lhsType : ResolvedType, lhsRange) (rhsType : ResolvedType, rhsRange) : ResolvedType = +let private CommonBaseType addError mismatchErr parent (lhsType : ResolvedType, lhsRange) (rhsType : ResolvedType, rhsRange) : ResolvedType = let raiseError errCode (lhsCond, rhsCond) = if lhsCond then lhsRange |> addError errCode if rhsCond then rhsRange |> addError errCode @@ -251,9 +251,16 @@ let private VerifyEqualityComparison context addError (lhsType, lhsRange) (rhsTy // comparison for any derived type). let argumentError = ErrorCode.ArgumentMismatchInBinaryOp, [toString lhsType; toString rhsType] let baseType = CommonBaseType addError argumentError context.Symbols.Parent (lhsType, lhsRange) (rhsType, rhsRange) + + // This assumes that: + // - Result has no derived types that support equality comparisons. + // - Compound types containing Result (e.g., tuples or arrays of results) do not support equality comparison. match baseType.Resolution with | Result when context.Capabilities = RuntimeCapabilities.QPRGen0 -> - addError (ErrorCode.UnsupportedResultComparison, [context.ExecutionTarget.Value]) rhsRange + addError (ErrorCode.UnsupportedResultComparison, [context.ProcessorArchitecture.Value]) rhsRange + | Result when context.Capabilities = RuntimeCapabilities.QPRGen1 && + not (context.IsInOperation && context.IsInIfCondition) -> + addError (ErrorCode.ResultComparisonNotInOperationIf, [context.ProcessorArchitecture.Value]) rhsRange | _ -> let unsupportedError = ErrorCode.InvalidTypeInEqualityComparison, [toString baseType] VerifyIsOneOf (fun t -> t.supportsEqualityComparison) unsupportedError addError (baseType, rhsRange) |> ignore diff --git a/src/QsCompiler/SyntaxProcessor/ScopeTools.fs b/src/QsCompiler/SyntaxProcessor/ScopeTools.fs index c838e10fa4..931161b264 100644 --- a/src/QsCompiler/SyntaxProcessor/ScopeTools.fs +++ b/src/QsCompiler/SyntaxProcessor/ScopeTools.fs @@ -286,12 +286,14 @@ type ScopeContext<'a> = Symbols : SymbolTracker<'a> /// True if the parent callable for the current scope is an operation. IsInOperation : bool + /// True if the current expression is contained within the condition of an if- or elif-statement. + IsInIfCondition : bool /// The return type of the parent callable for the current scope. ReturnType : ResolvedType /// The runtime capabilities for the compilation unit. Capabilities : RuntimeCapabilities - /// The name of the execution target for the compilation unit. - ExecutionTarget : NonNullable } + /// The name of the processor architecture for the compilation unit. + ProcessorArchitecture : NonNullable } with /// @@ -307,13 +309,18 @@ type ScopeContext<'a> = /// static member Create (nsManager : NamespaceManager) capabilities - executionTarget + processorArchitecture (spec : SpecializationDeclarationHeader) = match nsManager.TryGetCallable spec.Parent (spec.Parent.Namespace, spec.SourceFile) with | Found declaration -> { Symbols = SymbolTracker<'a> (nsManager, spec.SourceFile, spec.Parent) IsInOperation = declaration.Kind = Operation + IsInIfCondition = false ReturnType = StripPositionInfo.Apply declaration.Signature.ReturnType Capabilities = capabilities - ExecutionTarget = executionTarget } + ProcessorArchitecture = processorArchitecture } | _ -> raise <| ArgumentException "The specialization's parent callable does not exist." + + /// Returns a new scope context for an expression that is contained within the condition of an if- or + /// elif-statement. + member this.WithinIfCondition = { this with IsInIfCondition = true } diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 569e285e2e..d27e87572c 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -9,6 +9,7 @@ open System.Collections.Immutable open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.Diagnostics +open Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants open Microsoft.Quantum.QsCompiler.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxProcessing open Microsoft.Quantum.QsCompiler.SyntaxProcessing.Expressions @@ -51,6 +52,75 @@ let private onAutoInvertGenerateError (errCode, range) (symbols : SymbolTracker< if not (symbols.RequiredFunctorSupport.Contains QsFunctor.Adjoint) then [||] else [| range |> QsCompilerDiagnostic.Error errCode |] +/// +/// Returns true if the expression is an equality or inequality comparison between two expressions of type Result. +/// +/// The name of the callable in which the expression occurs. +/// The expression. +let private isResultComparison ({ Expression = expression } : TypedExpression) = + let validType = function + | InvalidType -> None + | kind -> Some kind + let binaryType lhs rhs = validType lhs.ResolvedType.Resolution |> Option.defaultValue rhs.ResolvedType.Resolution + + // This assumes that: + // - Result has no derived types that support equality comparisons. + // - Compound types containing Result (e.g., tuples or arrays of results) do not support equality comparison. + match expression with + | EQ (lhs, rhs) -> binaryType lhs rhs = Result + | NEQ (lhs, rhs) -> binaryType lhs rhs = Result + | _ -> false + +/// Finds the locations where a mutable variable, which was not declared locally in the given scope, is reassigned. The +/// symbol tracker is not modified. Returns the name of the variable and the location of the reassignment. +let private nonLocalUpdates (symbols : SymbolTracker<_>) (scope : QsScope) = + // This assumes that a variable with the same name cannot be re-declared in an inner scope (i.e., shadowing is not + // allowed). + let identifierExists (name, location : QsLocation) = + let symbol = { Symbol = Symbol name; Range = Value location.Range } + match (symbols.ResolveIdentifier ignore symbol |> fst).VariableName with + | InvalidIdentifier -> false + | _ -> true + let accumulator = AccumulateIdentifiers() + accumulator.Statements.OnScope scope |> ignore + accumulator.SharedState.ReassignedVariables + |> Seq.collect (fun grouping -> Seq.map (fun location -> grouping.Key, location) grouping) + |> Seq.filter identifierExists + +/// Verifies that any conditional blocks which depend on a measurement result do not use any language constructs that +/// are not supported by the runtime capabilities. Returns the diagnostics for the blocks. +let private verifyResultConditionalBlocks context condBlocks elseBlock = + // Diagnostics for return statements. + let returnStatements (statement : QsStatement) = statement.ExtractAll <| fun s -> + match s.Statement with + | QsReturnStatement _ -> [s] + | _ -> [] + let returnError (statement : QsStatement) = + let error = ErrorCode.ReturnInResultConditionedBlock, [context.ProcessorArchitecture.Value] + let location = statement.Location.ValueOr { Offset = 0, 0; Range = QsCompilerDiagnostic.DefaultRange } + location.Offset, QsCompilerDiagnostic.Error error location.Range + let returnErrors (block : QsPositionedBlock) = + block.Body.Statements |> Seq.collect returnStatements |> Seq.map returnError + + // Diagnostics for variable reassignments. + let setError (name : NonNullable, location : QsLocation) = + let error = ErrorCode.SetInResultConditionedBlock, [name.Value; context.ProcessorArchitecture.Value] + location.Offset, QsCompilerDiagnostic.Error error location.Range + let setErrors (block : QsPositionedBlock) = nonLocalUpdates context.Symbols block.Body |> Seq.map setError + + let blocks = + elseBlock + |> QsNullable<_>.Map (fun block -> SyntaxGenerator.BoolLiteral true, block) + |> QsNullable<_>.Fold (fun acc x -> x :: acc) [] + |> Seq.append condBlocks + let foldErrors (dependsOnResult, diagnostics) (condition : TypedExpression, block) = + if dependsOnResult || condition.Exists isResultComparison + then true, Seq.concat [returnErrors block; setErrors block; diagnostics] + else false, diagnostics + if context.Capabilities = RuntimeCapabilities.QPRGen1 + then Seq.fold foldErrors (false, Seq.empty) blocks |> snd + else Seq.empty + // utils for building QsStatements from QsFragmentKinds @@ -250,15 +320,21 @@ let NewConditionalBlock comments location context (qsExpr : QsExpression) = let block body = condition, QsPositionedBlock.New comments (Value location) body new BlockStatement<_>(block), Array.concat [errs; autoGenErrs] -/// Given a conditional block for the if-clause of a Q# if-statement, a sequence of conditional blocks for the elif-clauses, -/// as well as optionally a positioned block of Q# statements and its location for the else-clause, builds and returns the complete if-statement. -/// Throws an ArgumentException if the given if-block contains no location information. -let NewIfStatement (ifBlock : TypedExpression * QsPositionedBlock, elifBlocks, elseBlock : QsNullable) = - let location = (snd ifBlock).Location |> function - | Null -> ArgumentException "no location is set for the given if-block" |> raise - | Value loc -> loc - let condBlocks = seq { yield ifBlock; yield! elifBlocks; } - QsConditionalStatement.New (condBlocks, elseBlock) |> QsConditionalStatement |> asStatement QsComments.Empty location LocalDeclarations.Empty +/// Given a conditional block for the if-clause of a Q# if-statement, a sequence of conditional blocks for the elif-clauses, +/// as well as optionally a positioned block of Q# statements and its location for the else-clause, builds and returns the complete if-statement. +/// Throws an ArgumentException if the given if-block contains no location information. +let NewIfStatement context (ifBlock : TypedExpression * QsPositionedBlock) elifBlocks elseBlock = + let location = + match (snd ifBlock).Location with + | Null -> ArgumentException "No location is set for the given if-block." |> raise + | Value location -> location + let condBlocks = Seq.append (Seq.singleton ifBlock) elifBlocks + let statement = + QsConditionalStatement.New (condBlocks, elseBlock) + |> QsConditionalStatement + |> asStatement QsComments.Empty location LocalDeclarations.Empty + let diagnostics = verifyResultConditionalBlocks context condBlocks elseBlock + statement, diagnostics /// Given a positioned block of Q# statements for the repeat-block of a Q# RUS-statement, a typed expression containing the success condition, /// as well as a positioned block of Q# statements for the fixup-block, builds the complete RUS-statement at the given location and returns it. diff --git a/src/QsCompiler/SyntaxProcessor/VerificationTools.fs b/src/QsCompiler/SyntaxProcessor/VerificationTools.fs index 4407a9f73e..ce20143c41 100644 --- a/src/QsCompiler/SyntaxProcessor/VerificationTools.fs +++ b/src/QsCompiler/SyntaxProcessor/VerificationTools.fs @@ -107,5 +107,3 @@ type ResolvedType with | Range -> Some (Int |> ResolvedType.New) | ArrayType bt -> Some bt | _ -> None - - diff --git a/src/QsCompiler/TestTargets/Simulation/Target/Program.cs b/src/QsCompiler/TestTargets/Simulation/Target/Program.cs index 63b12652ff..2902715bd8 100644 --- a/src/QsCompiler/TestTargets/Simulation/Target/Program.cs +++ b/src/QsCompiler/TestTargets/Simulation/Target/Program.cs @@ -8,58 +8,80 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations; - namespace Microsoft.Quantum.QsCompiler.Testing.Simulation { /// /// This project serves as example for defining a rewrite step that can integrated into the compilation process - /// by given it as target to the Q# command line compiler (via -t path/To/Simulation.dll). - /// Any class in this dll that implements the IRewriteStep interface will be detected during compilation, - /// and its transformation and verfication step (if implemented) will be executed. + /// by given it as target to the Q# command line compiler (via -t path/To/Simulation.dll). + /// Any class in this dll that implements the IRewriteStep interface will be detected during compilation, + /// and its transformation and verfication step (if implemented) will be executed. /// public class CsharpGeneration : IRewriteStep { public CsharpGeneration() => this.AssemblyConstants = new Dictionary(); + /// public string Name => "CsharpGeneration"; + + /// public int Priority => 0; + + /// public IDictionary AssemblyConstants { get; } + + /// public IEnumerable GeneratedDiagnostics { get; private set; } + /// public bool ImplementsTransformation => true; + + /// public bool ImplementsPreconditionVerification => false; + + /// public bool ImplementsPostconditionVerification => false; + /// public bool Transformation(QsCompilation compilation, out QsCompilation transformed) { // random "diagnostic" to check if diagnostics loading works - this.GeneratedDiagnostics = new List() { + this.GeneratedDiagnostics = new List() + { new IRewriteStep.Diagnostic { Severity = CodeAnalysis.DiagnosticSeverity.Info, Message = "Invokation of the Q# compiler extension for C# generation to demonstrate execution on the simulation framework.", - }}; + } + }; var success = true; - var outputFolder = this.AssemblyConstants.TryGetValue(ReservedKeywords.AssemblyConstants.OutputPath, out var path) ? path : null; + var outputFolder = this.AssemblyConstants.TryGetValue(ReservedKeywords.AssemblyConstants.OutputPath, out var path) ? path : null; var allSources = GetSourceFiles.Apply(compilation.Namespaces) // also generate the code for referenced libraries... // ... except when they are one of the packages that currently still already contains the C# code (temporary workaround): - .Where(s => !Path.GetFileName(s.Value).StartsWith("Microsoft.Quantum")); + .Where(s => !Path.GetFileName(s.Value).StartsWith("Microsoft.Quantum")); foreach (var source in allSources) { var content = SimulationCode.generate(source, CodegenContext.Create(compilation.Namespaces)); - try { CompilationLoader.GeneratedFile(source, outputFolder ?? this.Name, ".g.cs", content); } - catch { success = false; } + try + { + CompilationLoader.GeneratedFile(source, outputFolder ?? this.Name, ".g.cs", content); + } + catch + { + success = false; + } } transformed = compilation; return success; } + /// public bool PreconditionVerification(QsCompilation compilation) => // todo: we should implement this and check for conjugations and invalid pieces throw new System.NotImplementedException(); + /// public bool PostconditionVerification(QsCompilation compilation) => throw new System.NotImplementedException(); } diff --git a/src/QsCompiler/TestTargets/Simulation/Target/Simulation.csproj b/src/QsCompiler/TestTargets/Simulation/Target/Simulation.csproj index 6b5cd19d8c..2be7baeef4 100644 --- a/src/QsCompiler/TestTargets/Simulation/Target/Simulation.csproj +++ b/src/QsCompiler/TestTargets/Simulation/Target/Simulation.csproj @@ -12,8 +12,13 @@ + + + + + diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index e2c5c20ca8..bb9820c18c 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -28,57 +28,110 @@ let private testName name = QsQualifiedName.New (NonNullable<_>.New "Microsoft.Quantum.Testing.CapabilityVerification", NonNullable<_>.New name) -/// Asserts that the tester allows the given test case. -let private allows (tester : CompilerTests) name = tester.Verify (testName name, List.empty) +/// Asserts that the tester produces the expected error codes for the test case with the given name. +let private expect (tester : CompilerTests) errorCodes name = tester.Verify (testName name, Seq.map Error errorCodes) -/// Asserts that the tester disallows the given test case. -let private restricts (tester : CompilerTests) name = - tester.Verify (testName name, [Error ErrorCode.UnsupportedResultComparison]) - -/// The names of all test cases. -let private all = +/// The names of all "simple" test cases: test cases that have exactly one unsupported result comparison error in +/// QPRGen0, and no errors in Unknown. +let private simpleTests = [ "ResultAsBool" "ResultAsBoolNeq" "ResultAsBoolOp" "ResultAsBoolNeqOp" "ResultAsBoolOpReturnIf" "ResultAsBoolNeqOpReturnIf" + "ResultAsBoolOpReturnIfNested" "ResultAsBoolOpSetIf" "ResultAsBoolNeqOpSetIf" + "ResultAsBoolOpElseSet" + "ElifSet" + "ElifElifSet" + "ElifElseSet" + "SetLocal" + "SetTuple" "EmptyIf" "EmptyIfNeq" + "EmptyIfOp" + "EmptyIfNeqOp" "Reset" "ResetNeq" ] -let [] ``Unknown allows all Result comparison`` () = List.iter (allows unknown) all -let [] ``QPRGen0 restricts all Result comparison`` () = List.iter (restricts gen0) all +[] +let ``Unknown allows all Result comparison`` () = + List.iter (expect unknown []) simpleTests + "SetReusedName" |> expect unknown [ErrorCode.LocalVariableAlreadyExists] + [ "ResultTuple" + "ResultArray" ] + |> List.iter (expect unknown [ErrorCode.InvalidTypeInEqualityComparison]) -[] +[] +let ``QPRGen0 restricts all Result comparison`` () = + List.iter (expect gen0 [ErrorCode.UnsupportedResultComparison]) simpleTests + "SetReusedName" |> expect gen0 [ErrorCode.LocalVariableAlreadyExists; ErrorCode.UnsupportedResultComparison] + [ "ResultTuple" + "ResultArray" ] + |> List.iter (expect unknown [ErrorCode.InvalidTypeInEqualityComparison]) + +[] let ``QPRGen1 restricts Result comparison in functions`` () = - restricts gen1 "ResultAsBool" - restricts gen1 "ResultAsBoolNeq" + [ "ResultAsBool" + "ResultAsBoolNeq" ] + |> List.iter (expect gen1 [ErrorCode.ResultComparisonNotInOperationIf]) + [ "ResultTuple" + "ResultArray" ] + |> List.iter (expect unknown [ErrorCode.InvalidTypeInEqualityComparison]) -[] +[] let ``QPRGen1 restricts non-if Result comparison in operations`` () = - restricts gen1 "ResultAsBoolOp" - restricts gen1 "ResultAsBoolNeqOp" + [ "ResultAsBoolOp" + "ResultAsBoolNeqOp" ] + |> List.iter (expect gen1 [ErrorCode.ResultComparisonNotInOperationIf]) -[] +[] let ``QPRGen1 restricts return from Result if`` () = - restricts gen1 "ResultAsBoolOpReturnIf" - restricts gen1 "ResultAsBoolNeqOpReturnIf" + [ "ResultAsBoolOpReturnIf" + "ResultAsBoolOpReturnIfNested" + "ResultAsBoolNeqOpReturnIf" ] + |> List.iter (expect gen1 <| Seq.replicate 2 ErrorCode.ReturnInResultConditionedBlock) -[] -let ``QPRGen1 restricts mutable set from Result if`` () = - restricts gen1 "ResultAsBoolOpSetIf" - restricts gen1 "ResultAsBoolNeqOpSetIf" +[] +let ``QPRGen1 allows local mutable set from Result if`` () = "SetLocal" |> expect gen1 [] [] -let ``QPRGen1 allows empty Result if`` () = - allows gen1 "EmptyIf" - allows gen1 "EmptyIfNeq" +let ``QPRGen1 restricts non-local mutable set from Result if`` () = + [ "ResultAsBoolOpSetIf" + "ResultAsBoolNeqOpSetIf" + "SetTuple" ] + |> List.iter (expect gen1 [ErrorCode.SetInResultConditionedBlock]) + "SetReusedName" + |> expect gen1 (ErrorCode.LocalVariableAlreadyExists :: List.replicate 2 ErrorCode.SetInResultConditionedBlock) + +[] +let ``QPRGen1 restricts non-local mutable set from Result elif`` () = + [ "ElifSet" + "ElifElifSet" ] + |> List.iter (expect gen1 [ErrorCode.SetInResultConditionedBlock]) + +[] +let ``QPRGen1 restricts non-local mutable set from Result else`` () = + [ "ResultAsBoolOpElseSet" + "ElifElseSet" ] + |> List.iter (expect gen1 [ErrorCode.SetInResultConditionedBlock]) + +[] +let ``QPRGen1 restricts empty Result if function`` () = + [ "EmptyIf" + "EmptyIfNeq" ] + |> List.iter (expect gen1 [ErrorCode.ResultComparisonNotInOperationIf]) + +[] +let ``QPRGen1 allows empty Result if operation`` () = + [ "EmptyIfOp" + "EmptyIfNeqOp" ] + |> List.iter (expect gen1 []) [] let ``QPRGen1 allows operation call from Result if`` () = - allows gen1 "Reset" - allows gen1 "ResetNeq" + [ "Reset" + "ResetNeq" ] + |> List.iter (expect gen1 []) diff --git a/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs b/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs index ea8dae81f3..b9039ce85f 100644 --- a/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs @@ -85,6 +85,7 @@ type LocalVerificationTests () = this.Expect "ApplyAndReassign8" [Error ErrorCode.UpdateOfImmutableIdentifier] this.Expect "ApplyAndReassign9" [Error ErrorCode.UpdateOfArrayItemExpr] this.Expect "ApplyAndReassign10" [Error ErrorCode.UpdateOfArrayItemExpr] + this.Expect "ApplyAndReassign11" [Error ErrorCode.InvalidUseOfReservedKeyword] [] diff --git a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs index 000161c30a..ee8fc25291 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs @@ -11,10 +11,12 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { body intrinsic; } + internal operation NoOp() : Unit { } + function ResultAsBool(result : Result) : Bool { return result == Zero ? false | true; } - + function ResultAsBoolNeq(result : Result) : Bool { return result != One ? false | true; } @@ -22,7 +24,7 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { operation ResultAsBoolOp(result : Result) : Bool { return result == Zero ? false | true; } - + function ResultAsBoolNeqOp(result : Result) : Bool { return result != One ? false | true; } @@ -34,7 +36,7 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { return true; } } - + operation ResultAsBoolNeqOpReturnIf(result : Result) : Bool { if (result != One) { return false; @@ -43,6 +45,24 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { } } + operation ResultAsBoolOpReturnIfNested(result : Result) : Bool { + if (result == Zero) { + let x = 5; + if (x == 5) { + return false; + } else { + fail "error"; + } + } else { + let x = 7; + if (x == 7) { + return true; + } else { + fail "error"; + } + } + } + operation ResultAsBoolOpSetIf(result : Result) : Bool { mutable b = false; if (result == One) { @@ -50,7 +70,7 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { } return b; } - + operation ResultAsBoolNeqOpSetIf(result : Result) : Bool { mutable b = false; if (result != Zero) { @@ -59,11 +79,93 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { return b; } - operation EmptyIf(result : Result) : Unit { + operation ResultAsBoolOpElseSet(result : Result) : Bool { + mutable b = false; + if (result == Zero) { + NoOp(); + } else { + set b = true; + } + return b; + } + + operation ElifSet(result : Result, flag : Bool) : Bool { + mutable b = false; + if (flag) { + set b = true; + } elif (result != Zero) { + set b = true; + } + return b; + } + + operation ElifElifSet(result : Result, flag : Bool) : Bool { + mutable b = false; + if (flag) { + set b = true; + } elif (flag) { + set b = true; + } elif (result != Zero) { + set b = true; + } else { + NoOp(); + } + return b; + } + + operation ElifElseSet(result : Result, flag : Bool) : Bool { + mutable b = false; + if (flag) { + set b = true; + } elif (result == Zero) { + NoOp(); + } else { + set b = true; + } + return b; + } + + operation SetLocal(result : Result) : Unit { + if (result == One) { + mutable b = false; + set b = true; + } + } + + operation SetReusedName(result : Result) : Unit { + mutable b = false; + if (result == One) { + if (true) { + // Re-declaring b is an error, but it shouldn't affect the invalid sets below. + mutable b = false; + set b = true; + } + set b = true; + } + } + + operation SetTuple(result : Result) : Unit { + mutable a = false; + if (result == One) { + mutable b = 0; + mutable c = 0.0; + set (c, (b, a)) = (1.0, (1, true)); + } + } + + function EmptyIf(result : Result) : Unit { if (result == Zero) { } } - operation EmptyIfNeq(result : Result) : Unit { + function EmptyIfNeq(result : Result) : Unit { + if (result != Zero) { } + } + + operation EmptyIfOp(result : Result) : Unit { + if (result == Zero) { } + } + + operation EmptyIfNeqOp(result : Result) : Unit { if (result != Zero) { } } @@ -78,4 +180,15 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { X(q); } } + + // Tuples and arrays currently don't support equality comparison, but result comparison should still be prevented if + // they do. + + function ResultTuple(rr : (Result, Result)) : Bool { + return rr == (One, One) ? true | false; + } + + function ResultArray(rs : Result[]) : Bool { + return rs == [One] ? true | false; + } } diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LocalVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/LocalVerification.qs index 0f1ed0b5ff..aa839ad76a 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/LocalVerification.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/LocalVerification.qs @@ -264,6 +264,10 @@ namespace Microsoft.Quantum.Testing.LocalVerification { set a[0] += 1; } + function ApplyAndReassign11 () : Unit { + set bool = true; + } + // accessing named items in user defined types diff --git a/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs b/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs index 2187681451..2c7e15bb04 100644 --- a/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs @@ -90,15 +90,18 @@ type CompilerTests (compilation : CompilationUnitManager.Compilation) = if other.Any() then NotImplementedException "unknown diagnostics item to verify" |> raise - static member Compile (srcFolder, files, ?references, ?capabilities) = + static member Compile (srcFolder, fileNames, ?references, ?capabilities) = let references = defaultArg references [] let capabilities = defaultArg capabilities RuntimeCapabilities.Unknown - let compileFiles (files : IEnumerable<_>) = - let mgr = new CompilationUnitManager((fun ex -> failwith ex.Message), capabilities = capabilities) - files.ToImmutableDictionary(Path.GetFullPath >> Uri, File.ReadAllText) - |> CompilationUnitManager.InitializeFileManagers - |> mgr.AddOrUpdateSourceFilesAsync - |> ignore - mgr.UpdateReferencesAsync(new References(ProjectManager.LoadReferencedAssemblies(references))) |> ignore - mgr.Build() - files |> Seq.map (fun file -> Path.Combine (srcFolder, file)) |> compileFiles + let paths = fileNames |> Seq.map (fun file -> Path.Combine (srcFolder, file) |> Path.GetFullPath) + let mutable exceptions = [] + use manager = new CompilationUnitManager ((fun e -> exceptions <- e :: exceptions), capabilities = capabilities) + paths.ToImmutableDictionary (Uri, File.ReadAllText) + |> CompilationUnitManager.InitializeFileManagers + |> manager.AddOrUpdateSourceFilesAsync + |> ignore + references |> ProjectManager.LoadReferencedAssemblies |> References |> manager.UpdateReferencesAsync |> ignore + let compilation = manager.Build () + if not <| List.isEmpty exceptions + then exceptions |> List.rev |> AggregateException |> raise + compilation diff --git a/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs b/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs index 294fa006d2..3b522e0f43 100644 --- a/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs +++ b/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Quantum.QsCompiler.DataTypes; -using Microsoft.Quantum.QsCompiler.SyntaxTokens; -using Microsoft.Quantum.QsCompiler.SyntaxTree; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; +using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; +using Microsoft.Quantum.QsCompiler.SyntaxTree; using Xunit; using static Microsoft.Quantum.QsCompiler.Documentation.Testing.Utils; @@ -23,37 +23,40 @@ public class DocParsingTests [Fact] public void ParseDocComments() { - string[] comments = { "# Summary", - "Encodes a multi-qubit Pauli operator represented as an array of", - "single-qubit Pauli operators into an integer.", - "", - "Test second paragraph.", - "", - "# Description", - "This is some text", - "", - "This is some more text", - "", - "# Input", - "## paulies", - "An array of at most 31 single-qubit Pauli operators.", - "", - "# Output", - "An integer uniquely identifying `paulies`, as described below.", - "", - "# Remarks", - "Each Pauli operator can be encoded using two bits:", - "$$", - "\\begin{align}", - "\\boldone \\mapsto 00, \\quad X \\mapsto 01, \\quad Y \\mapsto 11,", - "\\quad Z \\mapsto 10.", - "\\end{align}", - "$$", - "", - "Given an array of Pauli operators `[P0, ..., Pn]`, this function returns an", - "integer with binary expansion formed by concatenating", - "the mappings of each Pauli operator in big-endian order", - "`bits(Pn) ... bits(P0)`." }; + string[] comments = + { + "# Summary", + "Encodes a multi-qubit Pauli operator represented as an array of", + "single-qubit Pauli operators into an integer.", + "", + "Test second paragraph.", + "", + "# Description", + "This is some text", + "", + "This is some more text", + "", + "# Input", + "## paulies", + "An array of at most 31 single-qubit Pauli operators.", + "", + "# Output", + "An integer uniquely identifying `paulies`, as described below.", + "", + "# Remarks", + "Each Pauli operator can be encoded using two bits:", + "$$", + "\\begin{align}", + "\\boldone \\mapsto 00, \\quad X \\mapsto 01, \\quad Y \\mapsto 11,", + "\\quad Z \\mapsto 10.", + "\\end{align}", + "$$", + "", + "Given an array of Pauli operators `[P0, ..., Pn]`, this function returns an", + "integer with binary expansion formed by concatenating", + "the mappings of each Pauli operator in big-endian order", + "`bits(Pn) ... bits(P0)`." + }; var dc = new DocComment(comments); Assert.Equal(comments[1] + "\r" + comments[2] + "\r" + comments[3] + "\r" + comments[4], dc.Summary); Assert.Equal(comments[1] + "\r" + comments[2], dc.ShortSummary); @@ -64,14 +67,14 @@ public void ParseDocComments() Assert.Equal(comments[16], dc.Output); Assert.Empty(dc.TypeParameters); Assert.Equal("", dc.Example); - var remarks = String.Join("\r", comments.AsSpan(19, 12).ToArray()); + var remarks = string.Join("\r", comments.AsSpan(19, 12).ToArray()); Assert.Equal(remarks, dc.Remarks); } [Fact] public void ParseUdt() { - string[] comments = + string[] comments = { "# Summary", "Represents a single primitive term in the set of all dynamical generators, e.g.", @@ -149,15 +152,16 @@ element indexes the subsystem on which the generator acts on. var anonymousItem = QsTuple.NewQsTupleItem(QsTypeItem.NewAnonymous(baseType)); var typeItems = QsTuple.NewQsTuple(ImmutableArray.Create(anonymousItem)); - var generatorIndexType = new QsCustomType(MakeFullName("GeneratorIndex"), - ImmutableArray.Empty, - new Modifiers(AccessModifier.DefaultAccess), - NonNullable.New("GeneratorRepresentation.qs"), - ZeroLocation, - baseType, - typeItems, - comments.ToImmutableArray(), - QsComments.Empty); + var generatorIndexType = new QsCustomType( + MakeFullName("GeneratorIndex"), + ImmutableArray.Empty, + new Modifiers(AccessModifier.DefaultAccess), + NonNullable.New("GeneratorRepresentation.qs"), + ZeroLocation, + baseType, + typeItems, + comments.ToImmutableArray(), + QsComments.Empty); var udt = new DocUdt("Microsoft.Quantum.Canon", generatorIndexType); var stream = new StringWriter(); @@ -307,12 +311,13 @@ of the generator represented by $U$. var qubitArrayType = ResolvedType.New(QsType.NewArrayType(ResolvedType.New(QsType.Qubit))); var unitType = ResolvedType.New(QsType.UnitType); var doubleType = ResolvedType.New(QsType.Double); - var oracleType = ResolvedType.New(QsType.NewUserDefinedType(new UserDefinedType(CanonName, - NonNullable.New("DiscreteOracle"), - QsNullable>.Null))); + var oracleType = ResolvedType.New(QsType.NewUserDefinedType(new UserDefinedType( + CanonName, + NonNullable.New("DiscreteOracle"), + QsNullable>.Null))); var noInfo = CallableInformation.NoInformation; var acFunctors = ResolvedCharacteristics.FromProperties(new[] { OpProperty.Adjointable, OpProperty.Controllable }); - var acInfo = new CallableInformation(acFunctors, InferredCallableInformation.NoInformation); + var acInfo = new CallableInformation(acFunctors, InferredCallableInformation.NoInformation); var qubitToUnitOp = ResolvedType.New(QsType.NewOperation(new SigTypeTuple(qubitArrayType, unitType), noInfo)); var qubitToUnitOpAC = ResolvedType.New(QsType.NewOperation(new SigTypeTuple(qubitArrayType, unitType), acInfo)); var phaseEstArgs = new ResolvedType[] { oracleType, qubitArrayType }.ToImmutableArray(); @@ -324,27 +329,32 @@ of the generator represented by $U$. var argTupleType = ResolvedType.New(QsType.NewTupleType(argTypes)); var signature = new ResolvedSignature(typeParams, argTupleType, doubleType, noInfo); - var args = new List { BuildArgument("statePrepUnitary", qubitToUnitOp), - BuildArgument("adiabaticUnitary", qubitToUnitOp), - BuildArgument("qpeUnitary", qubitToUnitOpAC), - BuildArgument("phaseEstAlgorithm", phaseEstOp), - BuildArgument("qubits", qubitArrayType) } - .ConvertAll(arg => QsTuple.NewQsTupleItem(arg)) - .ToImmutableArray(); + var args = + new List + { + BuildArgument("statePrepUnitary", qubitToUnitOp), + BuildArgument("adiabaticUnitary", qubitToUnitOp), + BuildArgument("qpeUnitary", qubitToUnitOpAC), + BuildArgument("phaseEstAlgorithm", phaseEstOp), + BuildArgument("qubits", qubitArrayType) + } + .ConvertAll(arg => QsTuple.NewQsTupleItem(arg)) + .ToImmutableArray(); var argTuple = QsTuple.NewQsTuple(args); var specs = Array.Empty().ToImmutableArray(); - var qsCallable = new QsCallable(QsCallableKind.Operation, - MakeFullName("AdiabaticStateEnergyUnitary"), - ImmutableArray.Empty, - new Modifiers(AccessModifier.DefaultAccess), - NonNullable.New("Techniques.qs"), - ZeroLocation, - signature, - argTuple, - specs, - comments.ToImmutableArray(), - QsComments.Empty); + var qsCallable = new QsCallable( + QsCallableKind.Operation, + MakeFullName("AdiabaticStateEnergyUnitary"), + ImmutableArray.Empty, + new Modifiers(AccessModifier.DefaultAccess), + NonNullable.New("Techniques.qs"), + ZeroLocation, + signature, + argTuple, + specs, + comments.ToImmutableArray(), + QsComments.Empty); var callable = new DocCallable("Microsoft.Quantum.Canon", qsCallable); var stream = new StringWriter(); @@ -356,11 +366,13 @@ of the generator represented by $U$. [Fact] public void ParseDeprecated() { - string[] comments = { "# Summary", - "This is some text", - "# Deprecated", - "Some other text" - }; + string[] comments = + { + "# Summary", + "This is some text", + "# Deprecated", + "Some other text" + }; string dep = "NewName"; string warning = "> [!WARNING]\n> Deprecated\n"; string warningText = "name has been deprecated. Please use @\"newname\" instead."; diff --git a/src/QsCompiler/Tests.DocGenerator/DocWritingTests.cs b/src/QsCompiler/Tests.DocGenerator/DocWritingTests.cs index 98411b04ad..0375bddf8b 100644 --- a/src/QsCompiler/Tests.DocGenerator/DocWritingTests.cs +++ b/src/QsCompiler/Tests.DocGenerator/DocWritingTests.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Quantum.QsCompiler.DataTypes; -using Microsoft.Quantum.QsCompiler.SyntaxTokens; -using Microsoft.Quantum.QsCompiler.SyntaxTree; using System; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Text; +using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; +using Microsoft.Quantum.QsCompiler.SyntaxTree; using Xunit; using static Microsoft.Quantum.QsCompiler.Documentation.Testing.Utils; @@ -32,34 +32,37 @@ public void ExcludeInaccessible() var source = NonNullable.New("Tests.qs"); var unit = ResolvedType.New(QsType.UnitType); - var signature = new ResolvedSignature(Array.Empty().ToImmutableArray(), - unit, - unit, - CallableInformation.NoInformation); + var signature = new ResolvedSignature( + Array.Empty().ToImmutableArray(), + unit, + unit, + CallableInformation.NoInformation); var argumentTuple = QsTuple.NewQsTuple(ImmutableArray.Create>()); - var callable = new QsCallable(kind: QsCallableKind.Operation, - fullName: MakeFullName(access + "Operation"), - attributes: ImmutableArray.Empty, - modifiers: new Modifiers(access), - sourceFile: source, - location: ZeroLocation, - signature: signature, - argumentTuple: argumentTuple, - specializations: ImmutableArray.Create(), - documentation: ImmutableArray.Create(), - comments: QsComments.Empty); + var callable = new QsCallable( + kind: QsCallableKind.Operation, + fullName: MakeFullName(access + "Operation"), + attributes: ImmutableArray.Empty, + modifiers: new Modifiers(access), + sourceFile: source, + location: ZeroLocation, + signature: signature, + argumentTuple: argumentTuple, + specializations: ImmutableArray.Create(), + documentation: ImmutableArray.Create(), + comments: QsComments.Empty); var typeItems = QsTuple.NewQsTuple( ImmutableArray.Create(QsTuple.NewQsTupleItem(QsTypeItem.NewAnonymous(unit)))); - var type = new QsCustomType(fullName: MakeFullName(access + "Type"), - attributes: ImmutableArray.Empty, - modifiers: new Modifiers(access), - sourceFile: source, - location: ZeroLocation, - type: unit, - typeItems: typeItems, - documentation: ImmutableArray.Create(), - comments: QsComments.Empty); + var type = new QsCustomType( + fullName: MakeFullName(access + "Type"), + attributes: ImmutableArray.Empty, + modifiers: new Modifiers(access), + sourceFile: source, + location: ZeroLocation, + type: unit, + typeItems: typeItems, + documentation: ImmutableArray.Create(), + comments: QsComments.Empty); return new[] { QsNamespaceElement.NewQsCallable(callable), diff --git a/src/QsCompiler/Tests.DocGenerator/Tests.DocGenerator.csproj b/src/QsCompiler/Tests.DocGenerator/Tests.DocGenerator.csproj index 5e10c65b63..a0328017db 100644 --- a/src/QsCompiler/Tests.DocGenerator/Tests.DocGenerator.csproj +++ b/src/QsCompiler/Tests.DocGenerator/Tests.DocGenerator.csproj @@ -12,8 +12,13 @@ + + + + + all diff --git a/src/QsCompiler/Tests.DocGenerator/Utils.cs b/src/QsCompiler/Tests.DocGenerator/Utils.cs index c9badf778a..486e05f453 100644 --- a/src/QsCompiler/Tests.DocGenerator/Utils.cs +++ b/src/QsCompiler/Tests.DocGenerator/Utils.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.SyntaxTree; -using System; namespace Microsoft.Quantum.QsCompiler.Documentation.Testing { @@ -11,10 +11,12 @@ internal static class Utils { internal static readonly Tuple EmptyRange = new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero); + internal static readonly QsNullable ZeroLocation = - QsNullable.NewValue( - new QsLocation(new Tuple(0, 0), - new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero))); + QsNullable.NewValue(new QsLocation( + new Tuple(0, 0), + new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero))); + internal static readonly NonNullable CanonName = NonNullable.New("Microsoft.Quantum.Canon"); internal static QsQualifiedName MakeFullName(string name) diff --git a/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs b/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs index 8ffae6b26a..7e01b4d6d5 100644 --- a/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs +++ b/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs @@ -9,14 +9,12 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.TestTools.UnitTesting; - namespace Microsoft.Quantum.QsLanguageServer.Testing { - [TestClass] public class ProjectLoaderTests { - static ProjectLoaderTests () => + static ProjectLoaderTests() => MSBuildLocator.RegisterDefaults(); private static string ProjectFileName(string project) => @@ -28,7 +26,6 @@ private static string ProjectFileName(string project) => var uri = new Uri(Path.GetFullPath(relativePath)); return (uri.LocalPath, CompilationContext.Load(uri)); } - [TestMethod] public void GetGlobalProperties() @@ -61,12 +58,15 @@ public void FindProjectTargetFramework() void CompareFramework(string project, string expected) { var projectFileName = ProjectFileName(project); - var props = new ProjectLoader().DesignTimeBuildProperties(projectFileName, out var _, (x,y) => (y.Contains('.') ? 1 : 0) - (x.Contains('.') ? 1 : 0)); - if (!props.TryGetValue("TargetFramework", out string actual)) actual = null; + var props = new ProjectLoader().DesignTimeBuildProperties(projectFileName, out var _, (x, y) => (y.Contains('.') ? 1 : 0) - (x.Contains('.') ? 1 : 0)); + if (!props.TryGetValue("TargetFramework", out string actual)) + { + actual = null; + } Assert.AreEqual(expected, actual); } - var testProjects = new(string, string)[] + var testProjects = new (string, string)[] { ("test1", "netcoreapp2.1"), ("test2", "netstandard2.0"), @@ -102,7 +102,7 @@ public void LoadNonQsharpProjects() foreach (var project in invalidProjects) { - var (_, context) = Context(project); + var (_, context) = this.Context(project); Assert.IsNull(context); } } @@ -110,7 +110,7 @@ public void LoadNonQsharpProjects() [TestMethod] public void LoadOutdatedQsharpProject() { - var (projectFile, context) = Context("test9"); + var (projectFile, context) = this.Context("test9"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); Assert.AreEqual("test9.dll", Path.GetFileName(context.Properties.OutputPath)); @@ -130,7 +130,7 @@ public void LoadOutdatedQsharpProject() [TestMethod] public void LoadQsharpCoreLibraries() { - var (projectFile, context) = Context("test3"); + var (projectFile, context) = this.Context("test3"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); Assert.AreEqual("test3.dll", Path.GetFileName(context.Properties.OutputPath)); @@ -149,7 +149,7 @@ public void LoadQsharpCoreLibraries() Assert.IsFalse(context.UsesXunitHelper()); CollectionAssert.AreEquivalent(qsFiles, context.SourceFiles.ToArray()); - (projectFile, context) = Context("test12"); + (projectFile, context) = this.Context("test12"); projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); Assert.AreEqual("test12.dll", Path.GetFileName(context.Properties.OutputPath)); @@ -172,7 +172,7 @@ public void LoadQsharpCoreLibraries() [TestMethod] public void LoadQsharpFrameworkLibrary() { - var (projectFile, context) = Context("test7"); + var (projectFile, context) = this.Context("test7"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); Assert.AreEqual("test7.dll", Path.GetFileName(context.Properties.OutputPath)); @@ -192,7 +192,7 @@ public void LoadQsharpFrameworkLibrary() [TestMethod] public void LoadQsharpConsoleApps() { - var (projectFile, context) = Context("test4"); + var (projectFile, context) = this.Context("test4"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); Assert.AreEqual("test4.dll", Path.GetFileName(context.Properties.OutputPath)); @@ -209,7 +209,7 @@ public void LoadQsharpConsoleApps() Assert.IsTrue(context.UsesProject("test3.csproj")); CollectionAssert.AreEquivalent(qsFiles, context.SourceFiles.ToArray()); - (projectFile, context) = Context("test10"); + (projectFile, context) = this.Context("test10"); projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); Assert.AreEqual("test10.dll", Path.GetFileName(context.Properties.OutputPath)); @@ -224,7 +224,7 @@ public void LoadQsharpConsoleApps() Assert.IsTrue(context.UsesCanon()); CollectionAssert.AreEquivalent(qsFiles, context.SourceFiles.ToArray()); - (projectFile, context) = Context("test11"); + (projectFile, context) = this.Context("test11"); projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); Assert.AreEqual("test11.dll", Path.GetFileName(context.Properties.OutputPath)); @@ -243,7 +243,7 @@ public void LoadQsharpConsoleApps() [TestMethod] public void LoadQsharpUnitTest() { - var (projectFile, context) = Context("test5"); + var (projectFile, context) = this.Context("test5"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); Assert.AreEqual("test5.dll", Path.GetFileName(context.Properties.OutputPath)); @@ -251,7 +251,7 @@ public void LoadQsharpUnitTest() var qsFiles = new string[] { - // Compilation target set to none for "Operation5.qs", + // Compilation target set to none for "Operation5.qs", Path.Combine(projDir, "Tests5.qs"), Path.Combine(projDir, "test.folder", "Operation5.qs") }; @@ -267,7 +267,7 @@ public void LoadQsharpUnitTest() [TestMethod] public void LoadQsharpMultiFrameworkLibrary() { - var (projectFile, context) = Context("test6"); + var (projectFile, context) = this.Context("test6"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); Assert.AreEqual("test6.dll", Path.GetFileName(context.Properties.OutputPath)); @@ -288,7 +288,6 @@ public void LoadQsharpMultiFrameworkLibrary() } } - internal static class CompilationContext { internal static ProjectInformation Load(Uri projectFile) @@ -300,14 +299,16 @@ static void LogOutput(string msg, MessageType level) => } internal static bool UsesDll(this ProjectInformation info, string dll) => info.References.Any(r => r.EndsWith(dll)); + internal static bool UsesProject(this ProjectInformation info, string projectFileName) => info.ProjectReferences.Any(r => r.EndsWith(projectFileName)); // NB: We check whether the project uses either the 0.3–0.5 name (Primitives) or the 0.6– name (Intrinsic). internal static bool UsesIntrinsics(this ProjectInformation info) => info.UsesDll("Microsoft.Quantum.Intrinsic.dll") || info.UsesDll("Microsoft.Quantum.Primitives.dll"); + internal static bool UsesCanon(this ProjectInformation info) => info.UsesDll("Microsoft.Quantum.Canon.dll") || info.UsesDll("Microsoft.Quantum.Standard.dll"); + internal static bool UsesXunitHelper(this ProjectInformation info) => info.UsesDll("Microsoft.Quantum.Simulation.XUnit.dll"); } - -} \ No newline at end of file +} diff --git a/src/QsCompiler/Tests.LanguageServer/TestInput.cs b/src/QsCompiler/Tests.LanguageServer/TestInput.cs index 28e9d58953..0ab413d082 100644 --- a/src/QsCompiler/Tests.LanguageServer/TestInput.cs +++ b/src/QsCompiler/Tests.LanguageServer/TestInput.cs @@ -10,83 +10,98 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Builder = Microsoft.Quantum.QsCompiler.CompilationBuilder.Utils; - namespace Microsoft.Quantum.QsLanguageServer.Testing { internal class RandomInput { private Random rnd; - internal const string testInputDirectory = "RandomInputFiles"; + internal const string TestInputDirectory = "RandomInputFiles"; internal RandomInput(int seed) - { rnd = new Random(seed); } + { + this.rnd = new Random(seed); + } internal RandomInput() - { rnd = new Random(); } + { + this.rnd = new Random(); + } internal int GetRandom() => this.rnd.Next(); private void InsertRandom(ref string current, Func insert, int maxOccurrences = 2) { - for (var j = rnd.Next(0, maxOccurrences + 1); j > 0; --j) - { current = current.Insert(rnd.Next(0, current.Length + 1), insert()); } + for (var j = this.rnd.Next(0, maxOccurrences + 1); j > 0; --j) + { + current = current.Insert(this.rnd.Next(0, current.Length + 1), insert()); + } } private string GetRandomLine() { var current = Path.GetRandomFileName().Replace(".", ""); - InsertRandom(ref current, () => ";"); - InsertRandom(ref current, () => "{"); - InsertRandom(ref current, () => "}"); - InsertRandom(ref current, () => "."); - InsertRandom(ref current, () => " ", 6); + this.InsertRandom(ref current, () => ";"); + this.InsertRandom(ref current, () => "{"); + this.InsertRandom(ref current, () => "}"); + this.InsertRandom(ref current, () => "."); + this.InsertRandom(ref current, () => " ", 6); return current; } - private static string[] AllKeywords = Keywords.ReservedKeywords.ToArray(); + private static string[] allKeywords = Keywords.ReservedKeywords.ToArray(); + internal string[] GetRandomLines(int nrLines, bool withLanguageKeywords = true) { var lines = new string[nrLines]; - string GetKeyword() => $" {AllKeywords[rnd.Next(0, AllKeywords.Length)]} "; + string GetKeyword() => $" {allKeywords[this.rnd.Next(0, allKeywords.Length)]} "; for (var nr = 0; nr < nrLines; ++nr) { - lines[nr] = GetRandomLine(); - if (withLanguageKeywords) this.InsertRandom(ref lines[nr], GetKeyword, 6); + lines[nr] = this.GetRandomLine(); + if (withLanguageKeywords) + { + this.InsertRandom(ref lines[nr], GetKeyword, 6); + } } return lines; } internal string GenerateRandomFile(int nrLines, bool? emptyLastLine, bool withLanguageKeywords = true) { - var filename = Path.Combine(testInputDirectory, Path.GetRandomFileName()); + var filename = Path.Combine(TestInputDirectory, Path.GetRandomFileName()); var content = this.GetRandomLines(nrLines, withLanguageKeywords); using (StreamWriter sw = new StreamWriter(filename)) { foreach (var line in content) { - if (rnd.Next(0, 3) == 0) sw.WriteLine(); // inserting a couple of empty lines as well + if (this.rnd.Next(0, 3) == 0) + { + sw.WriteLine(); // inserting a couple of empty lines as well + } sw.WriteLine(line); } - sw.Write(emptyLastLine ?? rnd.Next(0, 2) == 0 ? String.Empty : this.GetRandomLine()); + sw.Write(emptyLastLine ?? this.rnd.Next(0, 2) == 0 ? string.Empty : this.GetRandomLine()); } return filename; } private VisualStudio.LanguageServer.Protocol.Range GetRandomRange(IReadOnlyList content) { - var (startLine, endLine) = (rnd.Next(0, content.Count), rnd.Next(0, content.Count)); - var (startChar, endChar) = (rnd.Next(0, content[startLine].Length + 1), rnd.Next(0, content[endLine].Length + 1)); + var (startLine, endLine) = (this.rnd.Next(0, content.Count), this.rnd.Next(0, content.Count)); + var (startChar, endChar) = (this.rnd.Next(0, content[startLine].Length + 1), this.rnd.Next(0, content[endLine].Length + 1)); ((startLine, startChar), (endLine, endChar)) = startLine <= endLine ? ((startLine, startChar), (endLine, endChar)) : ((endLine, endChar), (startLine, startChar)); - if (startLine == endLine && startChar > endChar) (startChar, endChar) = (endChar, startChar); + if (startLine == endLine && startChar > endChar) + { + (startChar, endChar) = (endChar, startChar); + } - var range = new VisualStudio.LanguageServer.Protocol.Range - { - Start = new Position(startLine, startChar), - End = new Position(endLine, endChar) + var range = new VisualStudio.LanguageServer.Protocol.Range + { + Start = new Position(startLine, startChar), + End = new Position(endLine, endChar) }; Assert.IsTrue(Builder.IsValidRange(range)); return range; @@ -94,11 +109,11 @@ private VisualStudio.LanguageServer.Protocol.Range GetRandomRange(IReadOnlyList< private TextDocumentContentChangeEvent GetRandomEdit(IReadOnlyList content, int expectedNrLines, bool withLanguageKeywords) { - var changeRange = GetRandomRange(content); + var changeRange = this.GetRandomRange(content); var nrLinesRemoved = changeRange.End.Line - changeRange.Start.Line + 1; - var nrLinesInserted = rnd.Next(1, expectedNrLines); + var nrLinesInserted = this.rnd.Next(1, expectedNrLines); var changeLength = TestUtils.GetRangeLength(changeRange, content); - var changeText = String.Join(Environment.NewLine, GetRandomLines(nrLinesInserted, withLanguageKeywords)); + var changeText = string.Join(Environment.NewLine, this.GetRandomLines(nrLinesInserted, withLanguageKeywords)); return new TextDocumentContentChangeEvent { @@ -112,31 +127,34 @@ private TextDocumentContentChangeEvent DeleteAll(IReadOnlyList content) { var changeRange = new VisualStudio.LanguageServer.Protocol.Range { - Start = new Position(0,0), - End = content.Any() ? new Position(content.Count - 1, content.Last().Length) : new Position(0,0) + Start = new Position(0, 0), + End = content.Any() ? new Position(content.Count - 1, content.Last().Length) : new Position(0, 0) }; var changeLength = TestUtils.GetRangeLength(changeRange, content); return new TextDocumentContentChangeEvent { Range = changeRange, RangeLength = changeLength, - Text = String.Empty + Text = string.Empty }; } - /// the last edit will always be a delete all + // the last edit will always be a delete all internal TextDocumentContentChangeEvent[] MakeRandomEdits(int nrEdits, ref List content, int expectedNrLines, bool withLanguageKeywords) { var edits = new TextDocumentContentChangeEvent[nrEdits]; - if (nrEdits == 0) return edits; - for (var i = 0; i < edits.Length-1; ++i) + if (nrEdits == 0) + { + return edits; + } + for (var i = 0; i < edits.Length - 1; ++i) { edits[i] = this.GetRandomEdit(content, expectedNrLines, withLanguageKeywords); TestUtils.ApplyEdit(edits[i], ref content); } - edits[edits.Length - 1] = DeleteAll(content); + edits[edits.Length - 1] = this.DeleteAll(content); TestUtils.ApplyEdit(edits.Last(), ref content); return edits; } } -} \ No newline at end of file +} diff --git a/src/QsCompiler/Tests.LanguageServer/TestSetup.cs b/src/QsCompiler/Tests.LanguageServer/TestSetup.cs index 99c6342bde..82fb6912d6 100644 --- a/src/QsCompiler/Tests.LanguageServer/TestSetup.cs +++ b/src/QsCompiler/Tests.LanguageServer/TestSetup.cs @@ -12,12 +12,11 @@ using Newtonsoft.Json.Linq; using StreamJsonRpc; - namespace Microsoft.Quantum.QsLanguageServer.Testing { public sealed partial class BasicFunctionality : IDisposable { - // basic setup + // basic setup private Connection connection; private JsonRpc rpc; @@ -25,49 +24,52 @@ public sealed partial class BasicFunctionality : IDisposable private readonly Stack receivedDiagnostics = new Stack(); public Task GetFileContentInMemoryAsync(string filename) => - rpc.InvokeWithParameterObjectAsync( + this.rpc.InvokeWithParameterObjectAsync( Methods.WorkspaceExecuteCommand.Name, TestUtils.ServerCommand(CommandIds.FileContentInMemory, TestUtils.GetTextDocumentIdentifier(filename))); public Task GetFileDiagnosticsAsync(string filename = null) => - rpc.InvokeWithParameterObjectAsync( + this.rpc.InvokeWithParameterObjectAsync( Methods.WorkspaceExecuteCommand.Name, TestUtils.ServerCommand(CommandIds.FileDiagnostics, filename == null ? new TextDocumentIdentifier { Uri = null } : TestUtils.GetTextDocumentIdentifier(filename))); - public Task SetupAsync() { var initParams = TestUtils.GetInitializeParams(); // Notify, because we should not ever have to wait for completion, except when verifying Initialize itself // IMPORTANT: if Initialize throws an exception, this exception will get lost when using Notify, and not result in a test failure! - return rpc.NotifyWithParameterObjectAsync(Methods.Initialize.Name, initParams); + return this.rpc.NotifyWithParameterObjectAsync(Methods.Initialize.Name, initParams); } + /// public void Dispose() { - rpc?.Dispose(); + this.rpc?.Dispose(); this.connection?.Dispose(); } [TestInitialize] public async Task SetupServerConnectionAsync() { - Directory.CreateDirectory(RandomInput.testInputDirectory); - var outputDir = new DirectoryInfo(RandomInput.testInputDirectory); - foreach (var file in outputDir.GetFiles()) file.Delete(); // deletes the files from previous test runs but not subfolders - - var id = inputGenerator.GetRandom(); - string ServerReaderPipe = $"QsLanguageServerReaderPipe{id}"; - string ServerWriterPipe = $"QsLanguageServerWriterPipe{id}"; - var readerPipe = new NamedPipeServerStream(ServerWriterPipe, PipeDirection.InOut, 4, PipeTransmissionMode.Message, PipeOptions.Asynchronous, 256, 256); - var writerPipe = new NamedPipeServerStream(ServerReaderPipe, PipeDirection.InOut, 4, PipeTransmissionMode.Message, PipeOptions.Asynchronous, 256, 256); - - var server = Server.ConnectViaNamedPipe(ServerWriterPipe, ServerReaderPipe); + Directory.CreateDirectory(RandomInput.TestInputDirectory); + var outputDir = new DirectoryInfo(RandomInput.TestInputDirectory); + foreach (var file in outputDir.GetFiles()) + { + file.Delete(); // deletes the files from previous test runs but not subfolders + } + + var id = this.inputGenerator.GetRandom(); + string serverReaderPipe = $"QsLanguageServerReaderPipe{id}"; + string serverWriterPipe = $"QsLanguageServerWriterPipe{id}"; + var readerPipe = new NamedPipeServerStream(serverWriterPipe, PipeDirection.InOut, 4, PipeTransmissionMode.Message, PipeOptions.Asynchronous, 256, 256); + var writerPipe = new NamedPipeServerStream(serverReaderPipe, PipeDirection.InOut, 4, PipeTransmissionMode.Message, PipeOptions.Asynchronous, 256, 256); + + var server = Server.ConnectViaNamedPipe(serverWriterPipe, serverReaderPipe); await readerPipe.WaitForConnectionAsync().ConfigureAwait(true); await writerPipe.WaitForConnectionAsync().ConfigureAwait(true); this.connection = new Connection(readerPipe, writerPipe); - this.rpc = new JsonRpc(connection.Writer, connection.Reader, this) + this.rpc = new JsonRpc(this.connection.Writer, this.connection.Reader, this) { SynchronizationContext = new QsSynchronizationContext() }; this.rpc.StartListening(); } @@ -80,14 +82,13 @@ public async Task TerminateServerConnectionAsync() this.Dispose(); } - // Methods to listen to server replies [JsonRpcMethod(Methods.TextDocumentPublishDiagnosticsName)] public void CaptureDiagnostics(JToken arg) { var param = arg.ToObject(); - receivedDiagnostics.Push(param); + this.receivedDiagnostics.Push(param); } } -} \ No newline at end of file +} diff --git a/src/QsCompiler/Tests.LanguageServer/TestUtils.cs b/src/QsCompiler/Tests.LanguageServer/TestUtils.cs index b8e1942ece..7c131f9a90 100644 --- a/src/QsCompiler/Tests.LanguageServer/TestUtils.cs +++ b/src/QsCompiler/Tests.LanguageServer/TestUtils.cs @@ -9,24 +9,33 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Builder = Microsoft.Quantum.QsCompiler.CompilationBuilder.Utils; - namespace Microsoft.Quantum.QsLanguageServer.Testing { internal static class TestUtils { internal static Uri GetUri(string filename) - { return (new Uri(Path.GetFullPath(filename))); } + { + return new Uri(Path.GetFullPath(filename)); + } internal static List GetContent(string filename) { var content = File.ReadAllLines(Path.GetFullPath(filename)).ToList(); - if (content.Any() && File.ReadAllText(filename).EndsWith(Environment.NewLine)) content.Add(String.Empty); // ReadAllLines will ignore the last line if it is empty - for (var lineNr = 0; lineNr < content.Count - 1; ++lineNr) content[lineNr] += Environment.NewLine; + if (content.Any() && File.ReadAllText(filename).EndsWith(Environment.NewLine)) + { + content.Add(string.Empty); // ReadAllLines will ignore the last line if it is empty + } + for (var lineNr = 0; lineNr < content.Count - 1; ++lineNr) + { + content[lineNr] += Environment.NewLine; + } return content; } internal static TextDocumentIdentifier GetTextDocumentIdentifier(string filename) - { return new TextDocumentIdentifier { Uri = GetUri(filename) }; } + { + return new TextDocumentIdentifier { Uri = GetUri(filename) }; + } internal static InitializeParams GetInitializeParams() { @@ -53,10 +62,14 @@ internal static DidOpenTextDocumentParams GetOpenFileParams(string filename) } internal static DidCloseTextDocumentParams GetCloseFileParams(string filename) - { return new DidCloseTextDocumentParams { TextDocument = GetTextDocumentIdentifier(filename) }; } + { + return new DidCloseTextDocumentParams { TextDocument = GetTextDocumentIdentifier(filename) }; + } internal static DidSaveTextDocumentParams GetSaveFileParams(string filename, string content) - { return new DidSaveTextDocumentParams { TextDocument = new TextDocumentIdentifier { Uri = GetUri(filename) }, Text = content }; } + { + return new DidSaveTextDocumentParams { TextDocument = new TextDocumentIdentifier { Uri = GetUri(filename) }, Text = content }; + } internal static DidChangeTextDocumentParams GetChangedFileParams(string filename, TextDocumentContentChangeEvent[] changes) { @@ -74,23 +87,33 @@ internal static TextDocumentPositionParams GetTextDocumentPositionParams(string internal static ExecuteCommandParams ServerCommand(string command, params object[] args) => new ExecuteCommandParams { Command = command, Arguments = args }; - - /// does not modify range + // does not modify range internal static int GetRangeLength(VisualStudio.LanguageServer.Protocol.Range range, IReadOnlyList content) { Assert.IsTrue(Builder.IsValidRange(range)); - if (range.Start.Line == range.End.Line) return range.End.Character - range.Start.Character; + if (range.Start.Line == range.End.Line) + { + return range.End.Character - range.Start.Character; + } var changeLength = content[range.Start.Line].Length - range.Start.Character; for (var line = range.Start.Line + 1; line < range.End.Line; ++line) - { changeLength += content[line].Length; } + { + changeLength += content[line].Length; + } return changeLength + range.End.Character; } internal static void ApplyEdit(TextDocumentContentChangeEvent change, ref List content) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (!content.Any()) throw new ArgumentException("the given content has to have at least on line"); + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + if (!content.Any()) + { + throw new ArgumentException("the given content has to have at least on line"); + } Assert.IsTrue(Builder.IsValidRange(change?.Range) && change.Text != null); Assert.IsTrue(change.Range.End.Line < content.Count()); @@ -100,15 +123,23 @@ internal static void ApplyEdit(TextDocumentContentChangeEvent change, ref List 0) { newText = content[--startLine] + newText; } - if (endLine + 1 < content.Count) { newText = newText + content[++endLine]; } + var newText = string.Concat(content[startLine].Substring(0, startChar), change.Text, content[endLine].Substring(endChar)); + if (startLine > 0) + { + newText = content[--startLine] + newText; + } + if (endLine + 1 < content.Count) + { + newText = newText + content[++endLine]; + } var lineChanges = Builder.SplitLines(newText); if (lineChanges.Length == 0 || (endLine + 1 == content.Count() && Builder.EndOfLine.Match(lineChanges.Last()).Success)) - { lineChanges = lineChanges.Concat(new string[] { String.Empty }).ToArray(); } + { + lineChanges = lineChanges.Concat(new string[] { string.Empty }).ToArray(); + } content.RemoveRange(startLine, endLine - startLine + 1); content.InsertRange(startLine, lineChanges); } } -} \ No newline at end of file +} diff --git a/src/QsCompiler/Tests.LanguageServer/Tests.LanguageServer.csproj b/src/QsCompiler/Tests.LanguageServer/Tests.LanguageServer.csproj index ba88b76c19..b0c6a44f06 100644 --- a/src/QsCompiler/Tests.LanguageServer/Tests.LanguageServer.csproj +++ b/src/QsCompiler/Tests.LanguageServer/Tests.LanguageServer.csproj @@ -19,6 +19,7 @@ + @@ -42,4 +43,8 @@ + + + + diff --git a/src/QsCompiler/Tests.LanguageServer/Tests.cs b/src/QsCompiler/Tests.LanguageServer/Tests.cs index 92aa3179f2..25414058e2 100644 --- a/src/QsCompiler/Tests.LanguageServer/Tests.cs +++ b/src/QsCompiler/Tests.LanguageServer/Tests.cs @@ -10,10 +10,8 @@ using Newtonsoft.Json.Linq; using Builder = Microsoft.Quantum.QsCompiler.CompilationBuilder.Utils; - namespace Microsoft.Quantum.QsLanguageServer.Testing { - [TestClass] public partial class BasicFunctionality { @@ -21,14 +19,16 @@ public partial class BasicFunctionality [TestMethod] public void Connection() - { Assert.IsNotNull(connection); } + { + Assert.IsNotNull(this.connection); + } [TestMethod] public async Task ShutdownAsync() { // the shutdown request should *not* result in the server exiting, and calling multiple times is fine - var response = await rpc.InvokeAsync(Methods.Shutdown.Name); - response = await rpc.InvokeAsync(Methods.Shutdown.Name); + var response = await this.rpc.InvokeAsync(Methods.Shutdown.Name); + response = await this.rpc.InvokeAsync(Methods.Shutdown.Name); Assert.IsNull(response); Assert.IsNotNull(this.rpc); } @@ -38,12 +38,12 @@ public async Task InitializationAsync() { // any message sent before the initialization request needs to result in an error - async Task AssertNotInitializedErrorUponInvokeAsync(string method) // argument here should not matter + async Task AssertNotInitializedErrorUponInvokeAsync(string method) // argument here should not matter { - var reply = await rpc.InvokeWithParameterObjectAsync(method, new object()); + var reply = await this.rpc.InvokeWithParameterObjectAsync(method, new object()); Assert.AreEqual(ProtocolError.Codes.AwaitingInitialization, Utils.TryJTokenAs(reply).Code); } - + await AssertNotInitializedErrorUponInvokeAsync(Methods.TextDocumentHover.Name); await AssertNotInitializedErrorUponInvokeAsync(Methods.TextDocumentSignatureHelp.Name); await AssertNotInitializedErrorUponInvokeAsync(Methods.TextDocumentDefinition.Name); @@ -52,14 +52,14 @@ async Task AssertNotInitializedErrorUponInvokeAsync(string method) // argument await AssertNotInitializedErrorUponInvokeAsync(Methods.TextDocumentRename.Name); await AssertNotInitializedErrorUponInvokeAsync(Methods.TextDocumentCodeAction.Name); - // the initialization request may only be sent once according to speccs + // the initialization request may only be sent once according to speccs // -> the content of initReply is verified in the ServerCapabilities test var initParams = TestUtils.GetInitializeParams(); - var initReply = await rpc.InvokeWithParameterObjectAsync(Methods.Initialize.Name, initParams); + var initReply = await this.rpc.InvokeWithParameterObjectAsync(Methods.Initialize.Name, initParams); Assert.IsNotNull(initReply); - var init = await rpc.InvokeWithParameterObjectAsync(Methods.Initialize.Name, initParams); + var init = await this.rpc.InvokeWithParameterObjectAsync(Methods.Initialize.Name, initParams); Assert.IsTrue(Utils.TryJTokenAs(init).Retry); } @@ -69,7 +69,7 @@ public async Task ServerCapabilitiesAsync() // NOTE: these assertions need to be adapted when the server capabilities are changed var initParams = TestUtils.GetInitializeParams(); initParams.Capabilities.Workspace.ApplyEdit = true; - var initReply = await rpc.InvokeWithParameterObjectAsync(Methods.Initialize.Name, initParams); + var initReply = await this.rpc.InvokeWithParameterObjectAsync(Methods.Initialize.Name, initParams); Assert.IsNotNull(initReply); Assert.IsNotNull(initReply.Capabilities); @@ -102,42 +102,42 @@ public async Task ServerCapabilitiesAsync() [TestMethod] public async Task OpenFileAsync() { - var filename = inputGenerator.GenerateRandomFile(10, null); - await SetupAsync(); - await rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, TestUtils.GetOpenFileParams(filename)); + var filename = this.inputGenerator.GenerateRandomFile(10, null); + await this.SetupAsync(); + await this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, TestUtils.GetOpenFileParams(filename)); } [TestMethod] public async Task CloseFileAsync() { - var filename = inputGenerator.GenerateRandomFile(10, null); - await SetupAsync(); + var filename = this.inputGenerator.GenerateRandomFile(10, null); + await this.SetupAsync(); - await rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, TestUtils.GetOpenFileParams(filename)); - await rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidClose.Name, TestUtils.GetCloseFileParams(filename)); + await this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, TestUtils.GetOpenFileParams(filename)); + await this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidClose.Name, TestUtils.GetCloseFileParams(filename)); // verify that a file can be closed immediately after it was opened - var openTask = rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, TestUtils.GetOpenFileParams(filename)); - await rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidClose.Name, TestUtils.GetCloseFileParams(filename)); + var openTask = this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, TestUtils.GetOpenFileParams(filename)); + await this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidClose.Name, TestUtils.GetCloseFileParams(filename)); } [TestMethod] public async Task SaveFileAsync() { var fileSize = 10; - var filename = inputGenerator.GenerateRandomFile(fileSize, null); + var filename = this.inputGenerator.GenerateRandomFile(fileSize, null); var content = File.ReadAllText(Path.GetFullPath(filename)); - await SetupAsync(); + await this.SetupAsync(); // verify that safe notification can be sent immediately after sending the open notification (even if the latter has not yet finished processing) - var openFileTask = rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, TestUtils.GetOpenFileParams(filename)); - await rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidSave.Name, TestUtils.GetSaveFileParams(filename, content)); + var openFileTask = this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, TestUtils.GetOpenFileParams(filename)); + await this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidSave.Name, TestUtils.GetSaveFileParams(filename, content)); - // check that the file content is indeed updated on save, according to the passed parameter + // check that the file content is indeed updated on save, according to the passed parameter - var newContent = String.Join(Environment.NewLine, inputGenerator.GetRandomLines(10)); - await rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidSave.Name, TestUtils.GetSaveFileParams(filename, newContent)); + var newContent = string.Join(Environment.NewLine, this.inputGenerator.GetRandomLines(10)); + await this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidSave.Name, TestUtils.GetSaveFileParams(filename, newContent)); var trackedContent = await this.GetFileContentInMemoryAsync(filename); Assert.AreEqual(newContent, Builder.JoinLines(trackedContent)); } @@ -148,19 +148,21 @@ public async Task EditFileAsync() async Task RunTest(bool emptyLastLine) { var fileSize = 10; - var filename = inputGenerator.GenerateRandomFile(fileSize, emptyLastLine); + var filename = this.inputGenerator.GenerateRandomFile(fileSize, emptyLastLine); var content = TestUtils.GetContent(filename); - await SetupAsync(); + await this.SetupAsync(); // check that edits can be pushed immediately, even if the processing of the initial open command has not yet completed // and the file can be closed and no diagnostics are left even if some changes are still queued for processing - var edits = inputGenerator.MakeRandomEdits(50, ref content, fileSize, false); + var edits = this.inputGenerator.MakeRandomEdits(50, ref content, fileSize, false); Task[] processing = new Task[edits.Length]; - var openFileTask = rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, TestUtils.GetOpenFileParams(filename)); + var openFileTask = this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, TestUtils.GetOpenFileParams(filename)); for (var i = 0; i < edits.Length; ++i) - { processing[i] = rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidChange.Name, TestUtils.GetChangedFileParams(filename, new[] { edits[i] })); } - var closeFileTask = rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidClose.Name, TestUtils.GetCloseFileParams(filename)); + { + processing[i] = this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidChange.Name, TestUtils.GetChangedFileParams(filename, new[] { edits[i] })); + } + var closeFileTask = this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidClose.Name, TestUtils.GetCloseFileParams(filename)); var finalDiagnostics = await this.GetFileDiagnosticsAsync(filename); // check that the file is no longer present in the default manager after closing (final diagnostics are null), and @@ -184,13 +186,13 @@ public async Task TextContentTrackingAsync() async Task RunTest(bool emptyLastLine) { var fileSize = 10; - var filename = inputGenerator.GenerateRandomFile(fileSize, emptyLastLine, false); + var filename = this.inputGenerator.GenerateRandomFile(fileSize, emptyLastLine, false); var expectedContent = TestUtils.GetContent(filename); var openParams = TestUtils.GetOpenFileParams(filename); - await SetupAsync(); - await rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, openParams); + await this.SetupAsync(); + await this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, openParams); - // check that the file content is accurately reflected upon opening + // check that the file content is accurately reflected upon opening var trackedContent = await this.GetFileContentInMemoryAsync(filename); var expected = Builder.JoinLines(expectedContent.ToArray()); @@ -201,8 +203,8 @@ async Task RunTest(bool emptyLastLine) // check whether a single array of changes is processed correctly - var edits = inputGenerator.MakeRandomEdits(50, ref expectedContent, fileSize, false); - await rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidChange.Name, TestUtils.GetChangedFileParams(filename, edits)); + var edits = this.inputGenerator.MakeRandomEdits(50, ref expectedContent, fileSize, false); + await this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidChange.Name, TestUtils.GetChangedFileParams(filename, edits)); trackedContent = await this.GetFileContentInMemoryAsync(filename); Assert.AreEqual(expectedContent.Count(), trackedContent.Count()); Assert.AreEqual(Builder.JoinLines(expectedContent.ToArray()), Builder.JoinLines(trackedContent)); @@ -211,13 +213,18 @@ async Task RunTest(bool emptyLastLine) for (var testRep = 0; testRep < 20; ++testRep) { - edits = inputGenerator.MakeRandomEdits(50, ref expectedContent, fileSize, false); + edits = this.inputGenerator.MakeRandomEdits(50, ref expectedContent, fileSize, false); Task[] processing = new Task[edits.Length]; for (var i = 0; i < edits.Length; ++i) - { processing[i] = rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidChange.Name, TestUtils.GetChangedFileParams(filename, new[] { edits[i] })); } - - for (var i = edits.Length - 1; i >= 0; --i) await processing[i]; + { + processing[i] = this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidChange.Name, TestUtils.GetChangedFileParams(filename, new[] { edits[i] })); + } + + for (var i = edits.Length - 1; i >= 0; --i) + { + await processing[i]; + } trackedContent = await this.GetFileContentInMemoryAsync(filename); expected = Builder.JoinLines(expectedContent.ToArray()); @@ -234,21 +241,26 @@ async Task RunTest(bool emptyLastLine) public async Task CodeContentTrackingAsync() { var fileSize = 10; - var filename = inputGenerator.GenerateRandomFile(fileSize, null, true); + var filename = this.inputGenerator.GenerateRandomFile(fileSize, null, true); var expectedContent = TestUtils.GetContent(filename); var openParams = TestUtils.GetOpenFileParams(filename); - await SetupAsync(); - await rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, openParams); + await this.SetupAsync(); + await this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidOpen.Name, openParams); for (var testRep = 0; testRep < 20; ++testRep) { - var edits = inputGenerator.MakeRandomEdits(50, ref expectedContent, fileSize, true); + var edits = this.inputGenerator.MakeRandomEdits(50, ref expectedContent, fileSize, true); Task[] processing = new Task[edits.Length]; for (var i = 0; i < edits.Length; ++i) - { processing[i] = rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidChange.Name, TestUtils.GetChangedFileParams(filename, new[] { edits[i] })); } + { + processing[i] = this.rpc.InvokeWithParameterObjectAsync(Methods.TextDocumentDidChange.Name, TestUtils.GetChangedFileParams(filename, new[] { edits[i] })); + } - for (var i = edits.Length - 1; i >= 0; --i) await processing[i]; + for (var i = edits.Length - 1; i >= 0; --i) + { + await processing[i]; + } var trackedContent = await this.GetFileContentInMemoryAsync(filename); var expected = Builder.JoinLines(expectedContent.ToArray()); diff --git a/src/QsCompiler/TextProcessor/QsFragmentParsing.fs b/src/QsCompiler/TextProcessor/QsFragmentParsing.fs index 05de2fe1eb..80aa2b276e 100644 --- a/src/QsCompiler/TextProcessor/QsFragmentParsing.fs +++ b/src/QsCompiler/TextProcessor/QsFragmentParsing.fs @@ -347,8 +347,12 @@ let private setStatement = ] let identifierExpr continuation = - let asIdentifier (sym : QsSymbol) = (Identifier (sym, Null), sym.Range) |> QsExpression.New - let validItem = (missingExpr <|> (localIdentifier |>> asIdentifier)) .>> followedBy continuation // missingExpr needs to be first + let asIdentifier (sym : QsSymbol) = + match sym.Symbol with + | InvalidSymbol -> preturn unknownExpr + | Symbol _ -> (Identifier (sym, Null), sym.Range) |> QsExpression.New |> preturn + | _ -> fail "symbol is not a local identifier" + let validItem = (missingExpr <|> (localIdentifier >>= asIdentifier)) .>> followedBy continuation // missingExpr needs to be first let exprError (ex : QsExpression) = ex.Range |> function | Value range -> range |> QsCompilerDiagnostic.Error (ErrorCode.InvalidIdentifierExprInUpdate, []) |> preturn >>= pushDiagnostic | Null -> fail "expression without range info" diff --git a/src/QsCompiler/TextProcessor/TextProcessor.fsproj b/src/QsCompiler/TextProcessor/TextProcessor.fsproj index 894da758a3..5878b51d1a 100644 --- a/src/QsCompiler/TextProcessor/TextProcessor.fsproj +++ b/src/QsCompiler/TextProcessor/TextProcessor.fsproj @@ -30,7 +30,7 @@ - + diff --git a/src/QsCompiler/Transformations/Attributes.cs b/src/QsCompiler/Transformations/Attributes.cs index f670602b4a..8a79abeec9 100644 --- a/src/QsCompiler/Transformations/Attributes.cs +++ b/src/QsCompiler/Transformations/Attributes.cs @@ -5,16 +5,14 @@ using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.SyntaxTree; - namespace Microsoft.Quantum.QsCompiler.Transformations { - using CallablePredicate = Func; using AttributeId = QsNullable; + using CallablePredicate = Func; using QsRangeInfo = QsNullable>; - /// - /// Contains tools for building and adding attributes to an existing Q# compilation. + /// Contains tools for building and adding attributes to an existing Q# compilation. /// public static class AttributeUtils { @@ -26,8 +24,8 @@ private static AttributeId BuildId(QsQualifiedName name) => // public static methods /// - /// Returns a Q# attribute with the given name and argument that can be attached to a declaration. - /// The attribute id is set to Null if the given name is null. + /// Returns a Q# attribute with the given name and argument that can be attached to a declaration. + /// The attribute id is set to Null if the given name is null. /// The attribute argument is set to an invalid expression if the given argument is null. /// public static QsDeclarationAttribute BuildAttribute(QsQualifiedName name, TypedExpression arg) => @@ -41,17 +39,17 @@ public static TypedExpression StringArgument(string content) => SyntaxGenerator.StringLiteral(NonNullable.New(content ?? ""), ImmutableArray.Empty); /// - /// Builds an attribute argument with the given string valued tuple items. - /// If a given string is null, the value of the corresponding item is set to the empty string. - /// If no items are given, a suitable argument of type unit is returned. + /// Builds an attribute argument with the given string valued tuple items. + /// If a given string is null, the value of the corresponding item is set to the empty string. + /// If no items are given, a suitable argument of type unit is returned. /// public static TypedExpression StringArguments(params string[] items) => items == null || items.Length == 0 ? SyntaxGenerator.UnitValue : - items.Length == 1 ? StringArgument(items.Single()) : + items.Length == 1 ? StringArgument(items.Single()) : SyntaxGenerator.TupleLiteral(items.Select(StringArgument)); /// - /// Adds the given attribute to all callables in the given compilation that satisfy the given predicate + /// Adds the given attribute to all callables in the given compilation that satisfy the given predicate /// - if the predicate is specified and not null. /// Throws an ArgumentNullException if the given attribute or compilation is null. /// @@ -59,7 +57,7 @@ public static QsCompilation AddToCallables(QsCompilation compilation, QsDeclarat new AddAttributes(new[] { (attribute, predicate) }).OnCompilation(compilation); /// - /// Adds the given attribute(s) to all callables in the given compilation that satisfy the given predicate + /// Adds the given attribute(s) to all callables in the given compilation that satisfy the given predicate /// - if the predicate is specified and not null. /// Throws an ArgumentNullException if one of the given attributes or the compilation is null. /// @@ -74,7 +72,7 @@ public static QsCompilation AddToCallables(QsCompilation compilation, params QsD new AddAttributes(attributes.Select(att => (att, (CallablePredicate)null))).OnCompilation(compilation); /// - /// Adds the given attribute to all callables in the given namespace that satisfy the given predicate + /// Adds the given attribute to all callables in the given namespace that satisfy the given predicate /// - if the predicate is specified and not null. /// Throws an ArgumentNullException if the given attribute or namespace is null. /// @@ -82,7 +80,7 @@ public static QsNamespace AddToCallables(QsNamespace ns, QsDeclarationAttribute new AddAttributes(new[] { (attribute, predicate) }).Namespaces.OnNamespace(ns); /// - /// Adds the given attribute(s) to all callables in the given namespace that satisfy the given predicate + /// Adds the given attribute(s) to all callables in the given namespace that satisfy the given predicate /// - if the predicate is specified and not null. /// Throws an ArgumentNullException if one of the given attributes or the namespace is null. /// @@ -96,7 +94,6 @@ public static QsNamespace AddToCallables(QsNamespace ns, params (QsDeclarationAt public static QsNamespace AddToCallables(QsNamespace ns, params QsDeclarationAttribute[] attributes) => new AddAttributes(attributes.Select(att => (att, (CallablePredicate)null))).Namespaces.OnNamespace(ns); - // private transformation class(es) /// @@ -117,7 +114,10 @@ internal TransformationState(IEnumerable<(QsDeclarationAttribute, Func attributes) : base(new TransformationState(attributes?.Select(entry => (entry.Item1, entry.Item2 ?? (_ => true))))) { - if (attributes == null || attributes.Any(entry => entry.Item1 == null)) throw new ArgumentNullException(nameof(attributes)); + if (attributes == null || attributes.Any(entry => entry.Item1 == null)) + { + throw new ArgumentNullException(nameof(attributes)); + } this.Namespaces = new NamespaceTransformation(this); this.Statements = new Core.StatementTransformation(this, Core.TransformationOptions.Disabled); this.StatementKinds = new Core.StatementKindTransformation(this, Core.TransformationOptions.Disabled); @@ -126,7 +126,6 @@ internal AddAttributes(IEnumerable<(QsDeclarationAttribute, CallablePredicate)> this.Types = new Core.TypeTransformation(this, Core.TransformationOptions.Disabled); } - // helper classes private class NamespaceTransformation @@ -134,10 +133,11 @@ private class NamespaceTransformation { public NamespaceTransformation(AddAttributes parent) : base(parent) - { } + { + } public override QsCallable OnCallableDeclaration(QsCallable c) => - c.AddAttributes(SharedState.AttributeSelection + c.AddAttributes(this.SharedState.AttributeSelection .Where(entry => entry.Item2(c)) .Select(entry => entry.Item1)); } diff --git a/src/QsCompiler/Transformations/BasicTransformations.cs b/src/QsCompiler/Transformations/BasicTransformations.cs index 9c21286a0b..0ed2be3eec 100644 --- a/src/QsCompiler/Transformations/BasicTransformations.cs +++ b/src/QsCompiler/Transformations/BasicTransformations.cs @@ -9,10 +9,9 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.Transformations.Core; - namespace Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations { - public class GetSourceFiles + public class GetSourceFiles : SyntaxTreeTransformation { public class TransformationState @@ -21,7 +20,6 @@ public class TransformationState new HashSet>(); } - private GetSourceFiles() : base(new TransformationState(), TransformationOptions.NoRebuild) { @@ -39,9 +37,15 @@ private GetSourceFiles() /// public static ImmutableHashSet> Apply(IEnumerable namespaces) { - if (namespaces == null || namespaces.Contains(null)) throw new ArgumentNullException(nameof(namespaces)); + if (namespaces == null || namespaces.Contains(null)) + { + throw new ArgumentNullException(nameof(namespaces)); + } var filter = new GetSourceFiles(); - foreach (var ns in namespaces) filter.Namespaces.OnNamespace(ns); + foreach (var ns in namespaces) + { + filter.Namespaces.OnNamespace(ns); + } return filter.SharedState.SourceFiles.ToImmutableHashSet(); } @@ -52,15 +56,15 @@ public static ImmutableHashSet> Apply(IEnumerable> Apply(params QsNamespace[] namespaces) => Apply((IEnumerable)namespaces); - // helper classes - private class NamespaceTransformation + private class NamespaceTransformation : NamespaceTransformation { - public NamespaceTransformation(SyntaxTreeTransformation parent) - : base(parent, TransformationOptions.NoRebuild) { } + : base(parent, TransformationOptions.NoRebuild) + { + } public override QsSpecialization OnSpecializationDeclaration(QsSpecialization spec) // short cut to avoid further evaluation { @@ -76,14 +80,13 @@ public override NonNullable OnSourceFile(NonNullable f) } } - /// /// Calling Transform on a syntax tree returns a new tree that only contains the type and callable declarations /// that are defined in the source file with the identifier given upon initialization. /// The transformation also ensures that the elements in each namespace are ordered according to /// the location at which they are defined in the file. Auto-generated declarations will be ordered alphabetically. /// - public class FilterBySourceFile + public class FilterBySourceFile : SyntaxTreeTransformation { public class TransformationState @@ -96,8 +99,7 @@ public TransformationState(Func, bool> predicate) => this.Predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); } - - public FilterBySourceFile(Func, bool> predicate) + public FilterBySourceFile(Func, bool> predicate) : base(new TransformationState(predicate)) { this.Namespaces = new NamespaceTransformation(this); @@ -110,7 +112,10 @@ public FilterBySourceFile(Func, bool> predicate) public static QsNamespace Apply(QsNamespace ns, Func, bool> predicate) { - if (ns == null) throw new ArgumentNullException(nameof(ns)); + if (ns == null) + { + throw new ArgumentNullException(nameof(ns)); + } var filter = new FilterBySourceFile(predicate); return filter.Namespaces.OnNamespace(ns); } @@ -121,37 +126,51 @@ public static QsNamespace Apply(QsNamespace ns, params NonNullable[] fil return Apply(ns, s => sourcesToKeep.Contains(s.Value)); } - // helper classes - public class NamespaceTransformation + public class NamespaceTransformation : NamespaceTransformation { public NamespaceTransformation(SyntaxTreeTransformation parent) - : base(parent) { } + : base(parent) + { + } // TODO: these overrides needs to be adapted once we support external specializations + /// public override QsCustomType OnTypeDeclaration(QsCustomType t) { if (this.SharedState.Predicate(t.SourceFile)) - { this.SharedState.Elements.Add((t.Location.IsValue ? t.Location.Item.Offset.Item1 : (int?)null, QsNamespaceElement.NewQsCustomType(t))); } + { + this.SharedState.Elements.Add((t.Location.IsValue ? t.Location.Item.Offset.Item1 : (int?)null, QsNamespaceElement.NewQsCustomType(t))); + } return t; } + /// public override QsCallable OnCallableDeclaration(QsCallable c) { if (this.SharedState.Predicate(c.SourceFile)) - { this.SharedState.Elements.Add((c.Location.IsValue ? c.Location.Item.Offset.Item1 : (int?)null, QsNamespaceElement.NewQsCallable(c))); } + { + this.SharedState.Elements.Add((c.Location.IsValue ? c.Location.Item.Offset.Item1 : (int?)null, QsNamespaceElement.NewQsCallable(c))); + } return c; } + /// public override QsNamespace OnNamespace(QsNamespace ns) { static int SortComparison((int?, QsNamespaceElement) x, (int?, QsNamespaceElement) y) { - if (x.Item1.HasValue && y.Item1.HasValue) return Comparer.Default.Compare(x.Item1.Value, y.Item1.Value); - if (!x.Item1.HasValue && !y.Item1.HasValue) return Comparer.Default.Compare(x.Item2.GetFullName().ToString(), y.Item2.GetFullName().ToString()); + if (x.Item1.HasValue && y.Item1.HasValue) + { + return Comparer.Default.Compare(x.Item1.Value, y.Item1.Value); + } + if (!x.Item1.HasValue && !y.Item1.HasValue) + { + return Comparer.Default.Compare(x.Item2.GetFullName().ToString(), y.Item2.GetFullName().ToString()); + } return x.Item1.HasValue ? -1 : 1; } this.SharedState.Elements.Clear(); @@ -162,29 +181,33 @@ static int SortComparison((int?, QsNamespaceElement) x, (int?, QsNamespaceElemen } } - /// /// Class that allows to transform scopes by keeping only statements whose expressions satisfy a certain criterion. /// Calling Transform will build a new Scope that contains only the statements for which the fold of a given condition /// over all contained expressions evaluates to true. /// If evaluateOnSubexpressions is set to true, the fold is evaluated on all subexpressions as well. /// - public class SelectByFoldingOverExpressions + public class SelectByFoldingOverExpressions : SyntaxTreeTransformation { - public class TransformationState + public class TransformationState : FoldOverExpressions.IFoldingState { + /// public bool Recur { get; } + public readonly bool Seed; internal readonly Func Condition; internal readonly Func ConstructFold; + /// public bool Fold(TypedExpression ex, bool current) => this.ConstructFold(this.Condition(ex), current); + /// public bool FoldResult { get; set; } + public bool SatisfiesCondition => this.FoldResult; public TransformationState(Func condition, Func fold, bool seed, bool recur = true) @@ -197,7 +220,6 @@ public TransformationState(Func condition, Func condition, Func fold, bool seed, bool evaluateOnSubexpressions = true) : base(new TransformationState(condition, fold, seed, evaluateOnSubexpressions)) { @@ -208,23 +230,23 @@ public SelectByFoldingOverExpressions(Func condition, Fun this); } - // helper classes - public class StatementTransformation

- : Core.StatementTransformation where P : SelectByFoldingOverExpressions + public class StatementTransformation + : Core.StatementTransformation where TSelector : SelectByFoldingOverExpressions { - protected P SubSelector; - protected readonly Func CreateSelector; + protected TSelector SubSelector; + protected readonly Func CreateSelector; ///

/// The given function for creating a new subselector is expected to initialize a new internal state with the same configurations as the one given upon construction. /// Upon initialization, the FoldResult of the internal state should be set to the specified seed rather than the FoldResult of the given constructor argument. /// - public StatementTransformation(Func createSelector, SyntaxTreeTransformation parent) + public StatementTransformation(Func createSelector, SyntaxTreeTransformation parent) : base(parent) => this.CreateSelector = createSelector ?? throw new ArgumentNullException(nameof(createSelector)); + /// public override QsStatement OnStatement(QsStatement stm) { this.SubSelector = this.CreateSelector(this.SharedState); @@ -236,6 +258,7 @@ public override QsStatement OnStatement(QsStatement stm) return new QsStatement(stmKind, varDecl, loc, stm.Comments); } + /// public override QsScope OnScope(QsScope scope) { var statements = new List(); @@ -244,25 +267,29 @@ public override QsScope OnScope(QsScope scope) // StatementKind.Transform sets a new Subselector that walks all expressions contained in statement, // and sets its satisfiesCondition to true if one of them satisfies the condition given on initialization var transformed = this.OnStatement(statement); - if (this.SubSelector.SharedState.SatisfiesCondition) statements.Add(transformed); + if (this.SubSelector.SharedState.SatisfiesCondition) + { + statements.Add(transformed); + } } return new QsScope(statements.ToImmutableArray(), scope.KnownSymbols); } } } - /// /// Class that allows to transform scopes by keeping only statements that contain certain expressions. /// Calling Transform will build a new Scope that contains only the statements /// which contain an expression or subexpression (only if evaluateOnSubexpressions is set to true) /// that satisfies the condition given on initialization. /// - public class SelectByAnyContainedExpression + public class SelectByAnyContainedExpression : SelectByFoldingOverExpressions { public SelectByAnyContainedExpression(Func condition, bool evaluateOnSubexpressions = true) - : base(condition, (a, b) => a || b, false, evaluateOnSubexpressions) { } + : base(condition, (a, b) => a || b, false, evaluateOnSubexpressions) + { + } } /// @@ -271,14 +298,15 @@ public SelectByAnyContainedExpression(Func condition, boo /// for which all contained expressions or subexpressions satisfy the condition given on initialization. /// Note that subexpressions will only be verified if evaluateOnSubexpressions is set to true (default value). /// - public class SelectByAllContainedExpressions + public class SelectByAllContainedExpressions : SelectByFoldingOverExpressions { public SelectByAllContainedExpressions(Func condition, bool evaluateOnSubexpressions = true) - : base(condition, (a, b) => a && b, true, evaluateOnSubexpressions) { } + : base(condition, (a, b) => a && b, true, evaluateOnSubexpressions) + { + } } - /// /// Class that evaluates a fold on upon transforming an expression. /// If recur is set to true in the internal state of the transformation, @@ -286,26 +314,31 @@ public SelectByAllContainedExpressions(Func condition, bo /// i.e. the fold it take starting on inner expressions (from the inside out). /// Otherwise the specified folder is only applied to the expression itself. /// The result of the fold is accessible via the FoldResult property in the internal state of the transformation. - /// The transformation itself merely walks expressions and rebuilding is disabled. + /// The transformation itself merely walks expressions and rebuilding is disabled. /// - public class FoldOverExpressions - : ExpressionTransformation where T : FoldOverExpressions.IFoldingState + public class FoldOverExpressions + : ExpressionTransformation where TState : FoldOverExpressions.IFoldingState { public interface IFoldingState { public bool Recur { get; } - public S Fold(TypedExpression ex, S current); - public S FoldResult { get; set; } - } + public TResult Fold(TypedExpression ex, TResult current); - public FoldOverExpressions(SyntaxTreeTransformation parent) - : base(parent, TransformationOptions.NoRebuild) { } + public TResult FoldResult { get; set; } + } - public FoldOverExpressions(T state) - : base(state) { } + public FoldOverExpressions(SyntaxTreeTransformation parent) + : base(parent, TransformationOptions.NoRebuild) + { + } + public FoldOverExpressions(TState state) + : base(state) + { + } + /// public override TypedExpression OnTypedExpression(TypedExpression ex) { ex = this.SharedState.Recur ? base.OnTypedExpression(ex) : ex; @@ -314,11 +347,10 @@ public override TypedExpression OnTypedExpression(TypedExpression ex) } } - /// /// Upon transformation, applies the specified action to each expression and subexpression. /// The action to apply is specified upon construction, and will be applied before recurring into subexpressions. - /// The transformation merely walks expressions and rebuilding is disabled. + /// The transformation merely walks expressions and rebuilding is disabled. /// public class TypedExpressionWalker : ExpressionTransformation @@ -332,12 +364,12 @@ public TypedExpressionWalker(Action onExpression, T internalSta this.OnExpression = onExpression ?? throw new ArgumentNullException(nameof(onExpression)); public readonly Action OnExpression; + + /// public override TypedExpression OnTypedExpression(TypedExpression ex) { this.OnExpression(ex); return base.OnTypedExpression(ex); } } - } - diff --git a/src/QsCompiler/Transformations/ClassicallyControlled.cs b/src/QsCompiler/Transformations/ClassicallyControlled.cs index 444e6cd265..7009a2f3d8 100644 --- a/src/QsCompiler/Transformations/ClassicallyControlled.cs +++ b/src/QsCompiler/Transformations/ClassicallyControlled.cs @@ -6,12 +6,11 @@ using System.Collections.Immutable; using System.Linq; using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.DependencyAnalysis; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; -using Microsoft.Quantum.QsCompiler.DependencyAnalysis; using Microsoft.Quantum.QsCompiler.Transformations.Core; - namespace Microsoft.Quantum.QsCompiler.Transformations.ClassicallyControlled { using ExpressionKind = QsExpressionKind; @@ -50,14 +49,18 @@ private RestructureConditions() : base() private class NamespaceTransformation : Core.NamespaceTransformation { - public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } public override QsCallable OnFunction(QsCallable c) => c; // Prevent anything in functions from being considered } private class StatementTransformation : Core.StatementTransformation { - public StatementTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public StatementTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } #region Condition Reshaping Logic @@ -66,7 +69,10 @@ public StatementTransformation(SyntaxTreeTransformation parent) : base(parent) { /// private (bool, QsConditionalStatement) ProcessElif(QsConditionalStatement conditionStatment) { - if (conditionStatment.ConditionalBlocks.Length < 2) return (false, conditionStatment); + if (conditionStatment.ConditionalBlocks.Length < 2) + { + return (false, conditionStatment); + } var subCondition = new QsConditionalStatement(conditionStatment.ConditionalBlocks.RemoveAt(0), conditionStatment.Default); var secondConditionBlock = conditionStatment.ConditionalBlocks[1].Item2; @@ -90,7 +96,10 @@ public StatementTransformation(SyntaxTreeTransformation parent) : base(parent) { private (bool, QsConditionalStatement) ProcessOR(QsConditionalStatement conditionStatment) { // This method expects elif blocks to have been abstracted out - if (conditionStatment.ConditionalBlocks.Length != 1) return (false, conditionStatment); + if (conditionStatment.ConditionalBlocks.Length != 1) + { + return (false, conditionStatment); + } var (condition, block) = conditionStatment.ConditionalBlocks[0]; @@ -122,7 +131,10 @@ public StatementTransformation(SyntaxTreeTransformation parent) : base(parent) { private (bool, QsConditionalStatement) ProcessAND(QsConditionalStatement conditionStatment) { // This method expects elif blocks to have been abstracted out - if (conditionStatment.ConditionalBlocks.Length != 1) return (false, conditionStatment); + if (conditionStatment.ConditionalBlocks.Length != 1) + { + return (false, conditionStatment); + } var (condition, block) = conditionStatment.ConditionalBlocks[0]; @@ -156,13 +168,14 @@ private QsStatement ReshapeConditional(QsStatement statement) if (statement.Statement is QsStatementKind.QsConditionalStatement condition) { var stm = condition.Item; - (_, stm) = ProcessElif(stm); + (_, stm) = this.ProcessElif(stm); bool wasOrProcessed, wasAndProcessed; do { - (wasOrProcessed, stm) = ProcessOR(stm); - (wasAndProcessed, stm) = ProcessAND(stm); - } while (wasOrProcessed || wasAndProcessed); + (wasOrProcessed, stm) = this.ProcessOR(stm); + (wasAndProcessed, stm) = this.ProcessAND(stm); + } + while (wasOrProcessed || wasAndProcessed); return new QsStatement( QsStatementKind.NewQsConditionalStatement(stm), @@ -184,7 +197,7 @@ public override QsScope OnScope(QsScope scope) { if (statement.Statement is QsStatementKind.QsConditionalStatement) { - var stm = ReshapeConditional(statement); + var stm = this.ReshapeConditional(statement); stm = this.OnStatement(stm); statements.Add(stm); } @@ -210,7 +223,7 @@ public class TransformationState public TransformationState(QsCompilation compilation) { - Compilation = compilation; + this.Compilation = compilation; } } @@ -224,14 +237,18 @@ private ConvertConditions(QsCompilation compilation) : base(new TransformationSt private class NamespaceTransformation : NamespaceTransformation { - public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } public override QsCallable OnFunction(QsCallable c) => c; // Prevent anything in functions from being considered } private class StatementTransformation : StatementTransformation { - public StatementTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public StatementTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } /// /// Get the combined type resolutions for a pair of nested resolutions, @@ -283,12 +300,14 @@ private TypeArgsResolution GetCombinedTypeResolution(TypeArgsResolution outer, T { // We are dissolving the application of arguments here, so the call's type argument // resolutions have to be moved to the 'identifier' sub expression. - var combined = TypeParamUtils.TryCombineTypeResolutionsForTarget(global.Item, + var combined = TypeParamUtils.TryCombineTypeResolutionsForTarget( + global.Item, out var combinedTypeArguments, - newCallIdentifier.TypeParameterResolutions, callTypeArguments); + newCallIdentifier.TypeParameterResolutions, + callTypeArguments); QsCompilerError.Verify(combined, "failed to combine type parameter resolution"); - var globalCallable = SharedState.Compilation.Namespaces + var globalCallable = this.SharedState.Compilation.Namespaces .Where(ns => ns.Name.Equals(global.Item.Namespace)) .Callables() .FirstOrDefault(c => c.FullName.Name.Equals(global.Item.Name)); @@ -431,8 +450,8 @@ private TypedExpression CreateApplyConditionallyExpression(TypedExpression condi { QsCompilerError.Verify(equalityScope != null || inequalityScope != null, $"Cannot have null for both equality and inequality scopes when creating ApplyConditionally expressions."); - var (isEqualityValid, equalityId, equalityArgs) = IsValidScope(equalityScope); - var (isInequaltiyValid, inequalityId, inequalityArgs) = IsValidScope(inequalityScope); + var (isEqualityValid, equalityId, equalityArgs) = this.IsValidScope(equalityScope); + var (isInequaltiyValid, inequalityId, inequalityArgs) = this.IsValidScope(inequalityScope); if (!isEqualityValid && equalityScope != null) { @@ -446,11 +465,11 @@ private TypedExpression CreateApplyConditionallyExpression(TypedExpression condi if (equalityScope == null) { - (equalityId, equalityArgs) = GetNoOp(); + (equalityId, equalityArgs) = this.GetNoOp(); } else if (inequalityScope == null) { - (inequalityId, inequalityArgs) = GetNoOp(); + (inequalityId, inequalityArgs) = this.GetNoOp(); } // Get characteristic properties from global id @@ -493,16 +512,16 @@ static TypedExpression BoxResultInArray(TypedExpression expression) => new InferredExpressionInformation(false, expression.InferredInformation.HasLocalQuantumDependency), QsNullable>.Null); - var equality = CreateValueTupleExpression(equalityId, equalityArgs); - var inequality = CreateValueTupleExpression(inequalityId, inequalityArgs); - var controlArgs = CreateValueTupleExpression( + var equality = this.CreateValueTupleExpression(equalityId, equalityArgs); + var inequality = this.CreateValueTupleExpression(inequalityId, inequalityArgs); + var controlArgs = this.CreateValueTupleExpression( BoxResultInArray(conditionExpr1), BoxResultInArray(conditionExpr2), equality, inequality); var targetArgsTypes = ImmutableArray.Create(equalityArgs.ResolvedType, inequalityArgs.ResolvedType); - return CreateControlCall(controlOpInfo, props, controlArgs, targetArgsTypes); + return this.CreateControlCall(controlOpInfo, props, controlArgs, targetArgsTypes); } /// @@ -510,8 +529,8 @@ static TypedExpression BoxResultInArray(TypedExpression expression) => /// private TypedExpression CreateApplyIfExpression(QsResult result, TypedExpression conditionExpression, QsScope conditionScope, QsScope defaultScope) { - var (isConditionValid, conditionId, conditionArgs) = IsValidScope(conditionScope); - var (isDefaultValid, defaultId, defaultArgs) = IsValidScope(defaultScope); + var (isConditionValid, conditionId, conditionArgs) = this.IsValidScope(conditionScope); + var (isDefaultValid, defaultId, defaultArgs) = this.IsValidScope(defaultScope); BuiltIn controlOpInfo; TypedExpression controlArgs; @@ -553,14 +572,12 @@ private TypedExpression CreateApplyIfExpression(QsResult result, TypedExpression } (TypedExpression, ImmutableArray) GetArgs(TypedExpression zeroId, TypedExpression zeroArgs, TypedExpression oneId, TypedExpression oneArgs) => - ( - CreateValueTupleExpression( + (this.CreateValueTupleExpression( conditionExpression, - CreateValueTupleExpression(zeroId, zeroArgs), - CreateValueTupleExpression(oneId, oneArgs)), + this.CreateValueTupleExpression(zeroId, zeroArgs), + this.CreateValueTupleExpression(oneId, oneArgs)), - ImmutableArray.Create(zeroArgs.ResolvedType, oneArgs.ResolvedType) - ); + ImmutableArray.Create(zeroArgs.ResolvedType, oneArgs.ResolvedType)); (controlArgs, targetArgsTypes) = (result == QsResult.Zero) ? GetArgs(conditionId, conditionArgs, defaultId, defaultArgs) @@ -593,9 +610,9 @@ private TypedExpression CreateApplyIfExpression(QsResult result, TypedExpression : BuiltIn.ApplyIfOne; } - controlArgs = CreateValueTupleExpression( + controlArgs = this.CreateValueTupleExpression( conditionExpression, - CreateValueTupleExpression(conditionId, conditionArgs)); + this.CreateValueTupleExpression(conditionId, conditionArgs)); targetArgsTypes = ImmutableArray.Create(conditionArgs.ResolvedType); } @@ -603,14 +620,13 @@ private TypedExpression CreateApplyIfExpression(QsResult result, TypedExpression { return null; // ToDo: Diagnostic message - default block exists, but is not valid } - } else { return null; // ToDo: Diagnostic message - condition block not valid } - return CreateControlCall(controlOpInfo, props, controlArgs, targetArgsTypes); + return this.CreateControlCall(controlOpInfo, props, controlArgs, targetArgsTypes); } /// @@ -639,22 +655,22 @@ private QsStatement CreateControlStatement(QsStatement statement, TypedExpressio /// private QsStatement ConvertConditionalToControlCall(QsStatement statement) { - var (isCondition, condition, conditionScope, defaultScope) = IsConditionWithSingleBlock(statement); + var (isCondition, condition, conditionScope, defaultScope) = this.IsConditionWithSingleBlock(statement); if (isCondition) { - if (IsConditionedOnResultLiteralExpression(condition, out var literal, out var conditionExpression)) + if (this.IsConditionedOnResultLiteralExpression(condition, out var literal, out var conditionExpression)) { - return CreateControlStatement(statement, CreateApplyIfExpression(literal, conditionExpression, conditionScope, defaultScope)); + return this.CreateControlStatement(statement, this.CreateApplyIfExpression(literal, conditionExpression, conditionScope, defaultScope)); } - else if (IsConditionedOnResultEqualityExpression(condition, out var lhsConditionExpression, out var rhsConditionExpression)) + else if (this.IsConditionedOnResultEqualityExpression(condition, out var lhsConditionExpression, out var rhsConditionExpression)) { - return CreateControlStatement(statement, CreateApplyConditionallyExpression(lhsConditionExpression, rhsConditionExpression, conditionScope, defaultScope)); + return this.CreateControlStatement(statement, this.CreateApplyConditionallyExpression(lhsConditionExpression, rhsConditionExpression, conditionScope, defaultScope)); } - else if (IsConditionedOnResultInequalityExpression(condition, out lhsConditionExpression, out rhsConditionExpression)) + else if (this.IsConditionedOnResultInequalityExpression(condition, out lhsConditionExpression, out rhsConditionExpression)) { // The scope arguments are reversed to account for the negation of the NEQ - return CreateControlStatement(statement, CreateApplyConditionallyExpression(lhsConditionExpression, rhsConditionExpression, defaultScope, conditionScope)); + return this.CreateControlStatement(statement, this.CreateApplyConditionallyExpression(lhsConditionExpression, rhsConditionExpression, defaultScope, conditionScope)); } // ToDo: Diagnostic message @@ -787,7 +803,7 @@ public override QsScope OnScope(QsScope scope) if (statement.Statement is QsStatementKind.QsConditionalStatement) { var stm = this.OnStatement(statement); - stm = ConvertConditionalToControlCall(stm); + stm = this.ConvertConditionalToControlCall(stm); statements.Add(stm); } else @@ -821,11 +837,16 @@ public LiftContent() : base(new TransformationState()) private new class StatementKindTransformation : ContentLifting.LiftContent.StatementKindTransformation { - public StatementKindTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public StatementKindTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } private bool IsScopeSingleCall(QsScope contents) { - if (contents.Statements.Length != 1) return false; + if (contents.Statements.Length != 1) + { + return false; + } return contents.Statements[0].Statement is QsStatementKind.QsExpressionStatement expr && expr.Item.Expression is ExpressionKind.CallLikeExpression call @@ -836,34 +857,35 @@ private bool IsScopeSingleCall(QsScope contents) public override QsStatementKind OnConditionalStatement(QsConditionalStatement stm) { - var contextIsConditionLiftable = SharedState.IsConditionLiftable; - SharedState.IsConditionLiftable = true; + var contextIsConditionLiftable = this.SharedState.IsConditionLiftable; + this.SharedState.IsConditionLiftable = true; var newConditionBlocks = new List>(); var generatedOperations = new List(); foreach (var conditionBlock in stm.ConditionalBlocks) { - var contextValidScope = SharedState.IsValidScope; - var contextParams = SharedState.GeneratedOpParams; + var contextValidScope = this.SharedState.IsValidScope; + var contextParams = this.SharedState.GeneratedOpParams; - SharedState.IsValidScope = true; - SharedState.GeneratedOpParams = conditionBlock.Item2.Body.KnownSymbols.Variables; + this.SharedState.IsValidScope = true; + this.SharedState.GeneratedOpParams = conditionBlock.Item2.Body.KnownSymbols.Variables; var (expr, block) = this.OnPositionedBlock(QsNullable.NewValue(conditionBlock.Item1), conditionBlock.Item2); // ToDo: Reduce the number of unnecessary generated operations by generalizing // the condition logic for the conversion and using that condition here - //var (isExprCondition, _, _) = IsConditionedOnResultLiteralExpression(expr.Item); + // var (isExprCondition, _, _) = IsConditionedOnResultLiteralExpression(expr.Item); - if (IsScopeSingleCall(block.Body)) + if (this.IsScopeSingleCall(block.Body)) { newConditionBlocks.Add(Tuple.Create(expr.Item, block)); } // ToDo: We may want to prevent empty blocks from getting lifted - else //if(block.Body.Statements.Length > 0) + // else if (block.Body.Statements.Length > 0) + else { // Lift the scope to its own operation - if (SharedState.LiftBody(block.Body, out var callable, out var call)) + if (this.SharedState.LiftBody(block.Body, out var callable, out var call)) { block = new QsPositionedBlock( new QsScope(ImmutableArray.Create(call), block.Body.KnownSymbols), @@ -874,36 +896,40 @@ public override QsStatementKind OnConditionalStatement(QsConditionalStatement st } else { - SharedState.IsConditionLiftable = false; + this.SharedState.IsConditionLiftable = false; } } - SharedState.GeneratedOpParams = contextParams; - SharedState.IsValidScope = contextValidScope; + this.SharedState.GeneratedOpParams = contextParams; + this.SharedState.IsValidScope = contextValidScope; - if (!SharedState.IsConditionLiftable) break; + if (!this.SharedState.IsConditionLiftable) + { + break; + } } var newDefault = QsNullable.Null; - if (SharedState.IsConditionLiftable && stm.Default.IsValue) + if (this.SharedState.IsConditionLiftable && stm.Default.IsValue) { - var contextValidScope = SharedState.IsValidScope; - var contextParams = SharedState.GeneratedOpParams; + var contextValidScope = this.SharedState.IsValidScope; + var contextParams = this.SharedState.GeneratedOpParams; - SharedState.IsValidScope = true; - SharedState.GeneratedOpParams = stm.Default.Item.Body.KnownSymbols.Variables; + this.SharedState.IsValidScope = true; + this.SharedState.GeneratedOpParams = stm.Default.Item.Body.KnownSymbols.Variables; var (_, block) = this.OnPositionedBlock(QsNullable.Null, stm.Default.Item); - if (IsScopeSingleCall(block.Body)) + if (this.IsScopeSingleCall(block.Body)) { newDefault = QsNullable.NewValue(block); } // ToDo: We may want to prevent empty blocks from getting lifted - else //if(block.Body.Statements.Length > 0) + // else if (block.Body.Statements.Length > 0) + else { // Lift the scope to its own operation - if (SharedState.LiftBody(block.Body, out var callable, out var call)) + if (this.SharedState.LiftBody(block.Body, out var callable, out var call)) { block = new QsPositionedBlock( new QsScope(ImmutableArray.Create(call), block.Body.KnownSymbols), @@ -914,26 +940,26 @@ public override QsStatementKind OnConditionalStatement(QsConditionalStatement st } else { - SharedState.IsConditionLiftable = false; + this.SharedState.IsConditionLiftable = false; } } - SharedState.GeneratedOpParams = contextParams; - SharedState.IsValidScope = contextValidScope; + this.SharedState.GeneratedOpParams = contextParams; + this.SharedState.IsValidScope = contextValidScope; } - if (SharedState.IsConditionLiftable) + if (this.SharedState.IsConditionLiftable) { - SharedState.GeneratedOperations.AddRange(generatedOperations); + this.SharedState.GeneratedOperations.AddRange(generatedOperations); } - var rtrn = SharedState.IsConditionLiftable + var rtrn = this.SharedState.IsConditionLiftable ? QsStatementKind.NewQsConditionalStatement( new QsConditionalStatement(newConditionBlocks.ToImmutableArray(), newDefault)) : QsStatementKind.NewQsConditionalStatement( new QsConditionalStatement(stm.ConditionalBlocks, stm.Default)); - SharedState.IsConditionLiftable = contextIsConditionLiftable; + this.SharedState.IsConditionLiftable = contextIsConditionLiftable; return rtrn; } diff --git a/src/QsCompiler/Transformations/CodeTransformations.cs b/src/QsCompiler/Transformations/CodeTransformations.cs index b9d64e0fe7..dce8c45cc7 100644 --- a/src/QsCompiler/Transformations/CodeTransformations.cs +++ b/src/QsCompiler/Transformations/CodeTransformations.cs @@ -10,7 +10,6 @@ using Microsoft.Quantum.QsCompiler.Transformations.FunctorGeneration; using Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace; - namespace Microsoft.Quantum.QsCompiler.Transformations { /// @@ -56,7 +55,10 @@ public static QsScope GenerateControlled(this QsScope scope) /// public static bool InlineConjugations(this QsCompilation compilation, out QsCompilation inlined, Action onException = null) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } var inline = new InlineConjugations(onException); var namespaces = compilation.Namespaces.Select(inline.Namespaces.OnNamespace).ToImmutableArray(); inlined = new QsCompilation(namespaces, compilation.EntryPoints); @@ -71,8 +73,14 @@ public static bool InlineConjugations(this QsCompilation compilation, out QsComp /// public static bool PreEvaluateAll(this QsCompilation compilation, out QsCompilation evaluated, Action onException = null) { - if (compilation == null) throw new ArgumentNullException(nameof(compilation)); - try { evaluated = PreEvaluation.All(compilation); } + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } + try + { + evaluated = PreEvaluation.All(compilation); + } catch (Exception ex) { onException?.Invoke(ex); diff --git a/src/QsCompiler/Transformations/Conjugations.cs b/src/QsCompiler/Transformations/Conjugations.cs index d91064ae63..fe6684781a 100644 --- a/src/QsCompiler/Transformations/Conjugations.cs +++ b/src/QsCompiler/Transformations/Conjugations.cs @@ -8,7 +8,6 @@ using Microsoft.Quantum.QsCompiler.Transformations.Core; using Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace; - namespace Microsoft.Quantum.QsCompiler.Transformations.Conjugations { /// @@ -25,6 +24,7 @@ public class InlineConjugations public class TransformationState { public bool Success { get; internal set; } + internal readonly Action OnException; internal Func ResolveNames = @@ -40,7 +40,6 @@ public TransformationState(Action onException = null) } } - public InlineConjugations(Action onException = null) : base(new TransformationState(onException)) { @@ -50,15 +49,15 @@ public InlineConjugations(Action onException = null) this.Types = new TypeTransformation(this, TransformationOptions.Disabled); } - // helper classes private class StatementTransformation : StatementTransformation { public StatementTransformation(SyntaxTreeTransformation parent) - : base(parent) { } - + : base(parent) + { + } public override QsScope OnScope(QsScope scope) { @@ -77,25 +76,31 @@ public override QsScope OnScope(QsScope scope) statements.AddRange(inner.Statements); statements.AddRange(adjOuter.Statements); } - else statements.Add(this.OnStatement(statement)); + else + { + statements.Add(this.OnStatement(statement)); + } } return new QsScope(statements.ToImmutableArray(), scope.KnownSymbols); } } - private class NamespaceTransformation : NamespaceTransformation { public NamespaceTransformation(SyntaxTreeTransformation parent) - : base(parent) { } - + : base(parent) + { + } - public override Tuple>, QsScope> OnProvidedImplementation - (QsTuple> argTuple, QsScope body) + public override Tuple>, QsScope> OnProvidedImplementation( + QsTuple> argTuple, QsScope body) { this.SharedState.Reset(); - try { body = this.Transformation.Statements.OnScope(body); } + try + { + body = this.Transformation.Statements.OnScope(body); + } catch (Exception ex) { this.SharedState.OnException?.Invoke(ex); @@ -106,5 +111,3 @@ public override Tuple>, QsScope> } } } - - diff --git a/src/QsCompiler/Transformations/ContentLifting.cs b/src/QsCompiler/Transformations/ContentLifting.cs index c44f3cde4a..07c7a208f3 100644 --- a/src/QsCompiler/Transformations/ContentLifting.cs +++ b/src/QsCompiler/Transformations/ContentLifting.cs @@ -11,7 +11,6 @@ using Microsoft.Quantum.QsCompiler.Transformations.Core; using Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace; - namespace Microsoft.Quantum.QsCompiler.Transformations.ContentLifting { using ExpressionKind = QsExpressionKind; @@ -33,13 +32,13 @@ internal class CallableDetails internal CallableDetails(QsCallable callable) { - Callable = callable; + this.Callable = callable; // ToDo: this may need to be adapted once we support type specializations - Adjoint = callable.Specializations.FirstOrDefault(spec => spec.Kind == QsSpecializationKind.QsAdjoint); - Controlled = callable.Specializations.FirstOrDefault(spec => spec.Kind == QsSpecializationKind.QsControlled); - ControlledAdjoint = callable.Specializations.FirstOrDefault(spec => spec.Kind == QsSpecializationKind.QsControlledAdjoint); + this.Adjoint = callable.Specializations.FirstOrDefault(spec => spec.Kind == QsSpecializationKind.QsAdjoint); + this.Controlled = callable.Specializations.FirstOrDefault(spec => spec.Kind == QsSpecializationKind.QsControlled); + this.ControlledAdjoint = callable.Specializations.FirstOrDefault(spec => spec.Kind == QsSpecializationKind.QsControlledAdjoint); // ToDo: this may need to be per-specialization - TypeParameters = callable.Signature.TypeParameters.Any(param => param.IsValidName) + this.TypeParameters = callable.Signature.TypeParameters.Any(param => param.IsValidName) ? QsNullable>.NewValue(callable.Signature.TypeParameters .Where(param => param.IsValidName) .Select(param => @@ -79,7 +78,7 @@ QsSpecialization MakeSpec(QsSpecializationKind kind, ResolvedSignature signature kind, callableName, ImmutableArray.Empty, - CurrentCallable.Callable.SourceFile, + this.CurrentCallable.Callable.SourceFile, QsNullable.Null, QsNullable>.Null, signature, @@ -87,46 +86,55 @@ QsSpecialization MakeSpec(QsSpecializationKind kind, ResolvedSignature signature ImmutableArray.Empty, QsComments.Empty); - var adj = CurrentCallable.Adjoint; - var ctl = CurrentCallable.Controlled; - var ctlAdj = CurrentCallable.ControlledAdjoint; + var adj = this.CurrentCallable.Adjoint; + var ctl = this.CurrentCallable.Controlled; + var ctlAdj = this.CurrentCallable.ControlledAdjoint; bool addAdjoint = false; bool addControlled = false; bool isSelfAdjoint = false; - if (InWithinBlock) + if (this.InWithinBlock) { addAdjoint = true; addControlled = false; } - else if (InBody) + else if (this.InBody) { if (adj != null && adj.Implementation is SpecializationImplementation.Generated adjGen) { addAdjoint = adjGen.Item.IsInvert; isSelfAdjoint = adjGen.Item.IsSelfInverse; } - if (ctl != null && ctl.Implementation is SpecializationImplementation.Generated ctlGen) addControlled = ctlGen.Item.IsDistribute; + if (ctl != null && ctl.Implementation is SpecializationImplementation.Generated ctlGen) + { + addControlled = ctlGen.Item.IsDistribute; + } if (ctlAdj != null && ctlAdj.Implementation is SpecializationImplementation.Generated ctlAdjGen) { - addAdjoint = addAdjoint || ctlAdjGen.Item.IsInvert && ctl.Implementation.IsGenerated; - addControlled = addControlled || ctlAdjGen.Item.IsDistribute && adj.Implementation.IsGenerated; + addAdjoint = addAdjoint || (ctlAdjGen.Item.IsInvert && ctl.Implementation.IsGenerated); + addControlled = addControlled || (ctlAdjGen.Item.IsDistribute && adj.Implementation.IsGenerated); isSelfAdjoint = isSelfAdjoint || ctlAdjGen.Item.IsSelfInverse; } } else if (ctlAdj != null && ctlAdj.Implementation is SpecializationImplementation.Generated gen) { - addControlled = InAdjoint && gen.Item.IsDistribute; - addAdjoint = InControlled && gen.Item.IsInvert; + addControlled = this.InAdjoint && gen.Item.IsDistribute; + addAdjoint = this.InControlled && gen.Item.IsInvert; isSelfAdjoint = gen.Item.IsSelfInverse; } var props = new List(); - if (addAdjoint) props.Add(OpProperty.Adjointable); - if (addControlled) props.Add(OpProperty.Controllable); + if (addAdjoint) + { + props.Add(OpProperty.Adjointable); + } + if (addControlled) + { + props.Add(OpProperty.Controllable); + } var newSig = new ResolvedSignature( - CurrentCallable.Callable.Signature.TypeParameters, + this.CurrentCallable.Callable.Signature.TypeParameters, paramsType, ResolvedType.New(ResolvedTypeKind.UnitType), new CallableInformation(ResolvedCharacteristics.FromProperties(props), new InferredCallableInformation(isSelfAdjoint, false))); @@ -170,7 +178,7 @@ QsSpecialization MakeSpec(QsSpecializationKind kind, ResolvedSignature signature private (QsCallable, ResolvedType) GenerateOperation(QsScope contents) { - var newName = UniqueVariableNames.PrependGuid(CurrentCallable.Callable.FullName); + var newName = UniqueVariableNames.PrependGuid(this.CurrentCallable.Callable.FullName); var knownVariables = contents.KnownSymbols.Variables; @@ -195,14 +203,14 @@ QsSpecialization MakeSpec(QsSpecializationKind kind, ResolvedSignature signature .ToImmutableArray())); } - var (signature, specializations) = MakeSpecializations(newName, paramTypes, SpecializationImplementation.NewProvided(parameters, contents)); + var (signature, specializations) = this.MakeSpecializations(newName, paramTypes, SpecializationImplementation.NewProvided(parameters, contents)); var generatedCallable = new QsCallable( QsCallableKind.Operation, newName, ImmutableArray.Empty, new Modifiers(AccessModifier.Internal), - CurrentCallable.Callable.SourceFile, + this.CurrentCallable.Callable.SourceFile, QsNullable.Null, signature, parameters, @@ -211,7 +219,7 @@ QsSpecialization MakeSpec(QsSpecializationKind kind, ResolvedSignature signature QsComments.Empty); // Change the origin of all type parameter references to use the new name and make all variables immutable - generatedCallable = UpdateGeneratedOp.Apply(generatedCallable, knownVariables, CurrentCallable.Callable.FullName, newName); + generatedCallable = UpdateGeneratedOp.Apply(generatedCallable, knownVariables, this.CurrentCallable.Callable.FullName, newName); return (generatedCallable, signature.ArgumentType); } @@ -228,14 +236,14 @@ QsSpecialization MakeSpec(QsSpecializationKind kind, ResolvedSignature signature /// public bool LiftBody(QsScope body, out QsCallable callable, out QsStatement callStatement) { - if (!IsValidScope) + if (!this.IsValidScope) { callable = null; callStatement = null; return false; } - var (generatedOp, originalArgumentType) = GenerateOperation(body); + var (generatedOp, originalArgumentType) = this.GenerateOperation(body); var generatedOpType = ResolvedType.New(ResolvedTypeKind.NewOperation( Tuple.Create( originalArgumentType, @@ -243,7 +251,7 @@ public bool LiftBody(QsScope body, out QsCallable callable, out QsStatement call generatedOp.Signature.Information)); // Forward the type parameters of the parent callable to the type arguments of the call to the generated operation. - var typeArguments = CurrentCallable.TypeParameters; + var typeArguments = this.CurrentCallable.TypeParameters; var generatedOpId = new TypedExpression( ExpressionKind.NewIdentifier( Identifier.NewGlobalCallable(generatedOp.FullName), @@ -266,10 +274,10 @@ public bool LiftBody(QsScope body, out QsCallable callable, out QsStatement call ExpressionKind.NewIdentifier( Identifier.NewLocalVariable(var.VariableName), QsNullable>.Null), - TypeArgsResolution.Empty, - var.Type, - var.InferredInformation, - QsNullable>.Null)) + TypeArgsResolution.Empty, + var.Type, + var.InferredInformation, + QsNullable>.Null)) .ToImmutableArray(); arguments = new TypedExpression( @@ -294,7 +302,7 @@ public bool LiftBody(QsScope body, out QsCallable callable, out QsStatement call typeArguments.IsNull ? TypeArgsResolution.Empty : typeArguments.Item - .Select(type => Tuple.Create(CurrentCallable.Callable.FullName, ((ResolvedTypeKind.TypeParameter)type.Resolution).Item.TypeName, type)) + .Select(type => Tuple.Create(this.CurrentCallable.Callable.FullName, ((ResolvedTypeKind.TypeParameter)type.Resolution).Item.TypeName, type)) .ToImmutableArray(), ResolvedType.New(ResolvedTypeKind.UnitType), new InferredExpressionInformation(false, true), @@ -335,9 +343,9 @@ public class TransformationState public TransformationState(ImmutableArray>> parameters, QsQualifiedName oldName, QsQualifiedName newName) { - Parameters = parameters; - OldName = oldName; - NewName = newName; + this.Parameters = parameters; + this.OldName = oldName; + this.NewName = newName; } } @@ -351,7 +359,9 @@ private UpdateGeneratedOp(ImmutableArray { - public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } public override ImmutableDictionary>, ResolvedType> OnTypeParamResolutions(ImmutableDictionary>, ResolvedType> typeParams) { @@ -365,7 +375,7 @@ public override TypedExpression OnTypedExpression(TypedExpression ex) if ((ex.InferredInformation.IsMutable || ex.InferredInformation.HasLocalQuantumDependency) && ex.Expression is ExpressionKind.Identifier id && id.Item1 is Identifier.LocalVariable variable - && SharedState.Parameters.Any(x => x.VariableName.Equals(variable))) + && this.SharedState.Parameters.Any(x => x.VariableName.Equals(variable))) { // Set the mutability to false ex = new TypedExpression( @@ -377,16 +387,18 @@ public override TypedExpression OnTypedExpression(TypedExpression ex) } // Prevent IsRecursiveIdentifier from propagating beyond the typed expression it is referring to - var isRecursiveIdentifier = SharedState.IsRecursiveIdentifier; + var isRecursiveIdentifier = this.SharedState.IsRecursiveIdentifier; var rtrn = base.OnTypedExpression(ex); - SharedState.IsRecursiveIdentifier = isRecursiveIdentifier; + this.SharedState.IsRecursiveIdentifier = isRecursiveIdentifier; return rtrn; } } private class ExpressionKindTransformation : ExpressionKindTransformation { - public ExpressionKindTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public ExpressionKindTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } public override ExpressionKind OnIdentifier(Identifier sym, QsNullable> tArgs) { @@ -394,12 +406,12 @@ public override ExpressionKind OnIdentifier(Identifier sym, QsNullable { - public TypeTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public TypeTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } public override ResolvedTypeKind OnTypeParameter(QsTypeParameter tp) { // Reroute a type parameter's origin to the newly generated operation - if (!SharedState.IsRecursiveIdentifier && SharedState.OldName.Equals(tp.Origin)) + if (!this.SharedState.IsRecursiveIdentifier && this.SharedState.OldName.Equals(tp.Origin)) { - tp = new QsTypeParameter(SharedState.NewName, tp.TypeName, tp.Range); + tp = new QsTypeParameter(this.SharedState.NewName, tp.TypeName, tp.Range); } return base.OnTypeParameter(tp); @@ -452,114 +466,132 @@ protected LiftContent(T state) : base(state) protected class NamespaceTransformation : NamespaceTransformation { - public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } + /// public override QsCallable OnCallableDeclaration(QsCallable c) { - SharedState.CurrentCallable = new LiftContent.CallableDetails(c); + this.SharedState.CurrentCallable = new LiftContent.CallableDetails(c); return base.OnCallableDeclaration(c); } + /// public override QsSpecialization OnBodySpecialization(QsSpecialization spec) { - SharedState.InBody = true; + this.SharedState.InBody = true; var rtrn = base.OnBodySpecialization(spec); - SharedState.InBody = false; + this.SharedState.InBody = false; return rtrn; } + /// public override QsSpecialization OnAdjointSpecialization(QsSpecialization spec) { - SharedState.InAdjoint = true; + this.SharedState.InAdjoint = true; var rtrn = base.OnAdjointSpecialization(spec); - SharedState.InAdjoint = false; + this.SharedState.InAdjoint = false; return rtrn; } + /// public override QsSpecialization OnControlledSpecialization(QsSpecialization spec) { - SharedState.InControlled = true; + this.SharedState.InControlled = true; var rtrn = base.OnControlledSpecialization(spec); - SharedState.InControlled = false; + this.SharedState.InControlled = false; return rtrn; } + /// public override QsSpecialization OnControlledAdjointSpecialization(QsSpecialization spec) { - SharedState.InControlledAdjoint = true; + this.SharedState.InControlledAdjoint = true; var rtrn = base.OnControlledAdjointSpecialization(spec); - SharedState.InControlledAdjoint = false; + this.SharedState.InControlledAdjoint = false; return rtrn; } + /// // ToDo: We will want to support lifting of functions in the future public override QsCallable OnFunction(QsCallable c) => c; // Prevent anything in functions from being lifted + /// public override QsNamespace OnNamespace(QsNamespace ns) { // Generated operations list will be populated in the transform - SharedState.GeneratedOperations = new List(); + this.SharedState.GeneratedOperations = new List(); return base.OnNamespace(ns) - .WithElements(elems => elems.AddRange(SharedState.GeneratedOperations.Select(op => QsNamespaceElement.NewQsCallable(op)))); + .WithElements(elems => elems.AddRange(this.SharedState.GeneratedOperations.Select(op => QsNamespaceElement.NewQsCallable(op)))); } } protected class StatementKindTransformation : StatementKindTransformation { - public StatementKindTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public StatementKindTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } + /// public override QsStatementKind OnConjugation(QsConjugation stm) { - var superInWithinBlock = SharedState.InWithinBlock; - SharedState.InWithinBlock = true; + var superInWithinBlock = this.SharedState.InWithinBlock; + this.SharedState.InWithinBlock = true; var (_, outer) = this.OnPositionedBlock(QsNullable.Null, stm.OuterTransformation); - SharedState.InWithinBlock = superInWithinBlock; + this.SharedState.InWithinBlock = superInWithinBlock; var (_, inner) = this.OnPositionedBlock(QsNullable.Null, stm.InnerTransformation); return QsStatementKind.NewQsConjugation(new QsConjugation(outer, inner)); } + /// public override QsStatementKind OnReturnStatement(TypedExpression ex) { - SharedState.IsValidScope = false; + this.SharedState.IsValidScope = false; return base.OnReturnStatement(ex); } + /// public override QsStatementKind OnValueUpdate(QsValueUpdate stm) { // If lhs contains an identifier found in the scope's known variables (variables from the super-scope), the scope is not valid var lhs = this.Expressions.OnTypedExpression(stm.Lhs); - if (SharedState.ContainsParamRef) + if (this.SharedState.ContainsParamRef) { - SharedState.IsValidScope = false; + this.SharedState.IsValidScope = false; } var rhs = this.Expressions.OnTypedExpression(stm.Rhs); return QsStatementKind.NewQsValueUpdate(new QsValueUpdate(lhs, rhs)); } + /// public override QsStatementKind OnStatementKind(QsStatementKind kind) { - SharedState.ContainsParamRef = false; // Every statement kind starts off false + this.SharedState.ContainsParamRef = false; // Every statement kind starts off false return base.OnStatementKind(kind); } } protected class ExpressionTransformation : ExpressionTransformation { - public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } + /// public override TypedExpression OnTypedExpression(TypedExpression ex) { - var contextContainsParamRef = SharedState.ContainsParamRef; - SharedState.ContainsParamRef = false; + var contextContainsParamRef = this.SharedState.ContainsParamRef; + this.SharedState.ContainsParamRef = false; var rtrn = base.OnTypedExpression(ex); // If the sub context contains a reference, then the super context contains a reference, // otherwise return the super context to its original value - SharedState.ContainsParamRef |= contextContainsParamRef; + this.SharedState.ContainsParamRef |= contextContainsParamRef; return rtrn; } @@ -567,14 +599,17 @@ public override TypedExpression OnTypedExpression(TypedExpression ex) protected class ExpressionKindTransformation : ExpressionKindTransformation { - public ExpressionKindTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public ExpressionKindTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } + /// public override ExpressionKind OnIdentifier(Identifier sym, QsNullable> tArgs) { if (sym is Identifier.LocalVariable local && - SharedState.GeneratedOpParams.Any(param => param.VariableName.Equals(local.Item))) + this.SharedState.GeneratedOpParams.Any(param => param.VariableName.Equals(local.Item))) { - SharedState.ContainsParamRef = true; + this.SharedState.ContainsParamRef = true; } return base.OnIdentifier(sym, tArgs); } diff --git a/src/QsCompiler/Transformations/FunctorGeneration.cs b/src/QsCompiler/Transformations/FunctorGeneration.cs index 14819e0977..9ee3b6c821 100644 --- a/src/QsCompiler/Transformations/FunctorGeneration.cs +++ b/src/QsCompiler/Transformations/FunctorGeneration.cs @@ -12,16 +12,15 @@ using Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations; using Microsoft.Quantum.QsCompiler.Transformations.Core; - namespace Microsoft.Quantum.QsCompiler.Transformations.FunctorGeneration { /// /// Scope transformation that replaces each operation call within a given scope - /// with a call to the operation after application of the functor given on initialization. - /// The default values used for auto-generation will be used for the additional functor arguments. - /// Additional functor arguments will be added to the list of defined variables for each scope. + /// with a call to the operation after application of the functor given on initialization. + /// The default values used for auto-generation will be used for the additional functor arguments. + /// Additional functor arguments will be added to the list of defined variables for each scope. /// - public class ApplyFunctorToOperationCalls + public class ApplyFunctorToOperationCalls : SyntaxTreeTransformation { public class TransformationsState @@ -33,15 +32,17 @@ public TransformationsState(QsFunctor functor) => } public ApplyFunctorToOperationCalls(QsFunctor functor) - : base(new TransformationsState(functor)) + : base(new TransformationsState(functor)) { - if (functor.IsControlled) this.Statements = new AddVariableDeclarations(this, ControlQubitsDeclaration); + if (functor.IsControlled) + { + this.Statements = new AddVariableDeclarations(this, ControlQubitsDeclaration); + } this.StatementKinds = new IgnoreOuterBlockInConjugations(this); this.ExpressionKinds = new ExpressionKindTransformation(this); this.Types = new TypeTransformation(this, TransformationOptions.Disabled); } - // static methods for convenience private static readonly NonNullable ControlQubitsName = @@ -64,23 +65,26 @@ public ApplyFunctorToOperationCalls(QsFunctor functor) public static readonly Func ApplyControlled = new ApplyFunctorToOperationCalls(QsFunctor.Controlled).Statements.OnScope; - // helper classes /// - /// Replaces each operation call with a call to the operation after application of the given functor. - /// The default values used for auto-generation will be used for the additional functor arguments. + /// Replaces each operation call with a call to the operation after application of the given functor. + /// The default values used for auto-generation will be used for the additional functor arguments. /// - public class ExpressionKindTransformation + public class ExpressionKindTransformation : ExpressionKindTransformation { public ExpressionKindTransformation(SyntaxTreeTransformation parent) - : base(parent) { } + : base(parent) + { + } public ExpressionKindTransformation(QsFunctor functor) - : base(new TransformationsState(functor)) { } - + : base(new TransformationsState(functor)) + { + } + /// public override QsExpressionKind OnOperationCall(TypedExpression method, TypedExpression arg) { if (this.SharedState.FunctorToApply.IsControlled) @@ -92,7 +96,10 @@ public override QsExpressionKind OnOp { method = SyntaxGenerator.AdjointOperation(method); } - else throw new NotImplementedException("unsupported functor"); + else + { + throw new NotImplementedException("unsupported functor"); + } return base.OnOperationCall(method, arg); } } @@ -104,29 +111,34 @@ public override QsExpressionKind OnOp public class AddVariableDeclarations : StatementTransformation { - private readonly IEnumerable>> AddedVariableDeclarations; + private readonly IEnumerable>> addedVariableDeclarations; public AddVariableDeclarations(SyntaxTreeTransformation parent, params LocalVariableDeclaration>[] addedVars) : base(parent) => - this.AddedVariableDeclarations = addedVars ?? throw new ArgumentNullException(nameof(addedVars)); + this.addedVariableDeclarations = addedVars ?? throw new ArgumentNullException(nameof(addedVars)); + /// public override LocalDeclarations OnLocalDeclarations(LocalDeclarations decl) => - base.OnLocalDeclarations(new LocalDeclarations(decl.Variables.AddRange(this.AddedVariableDeclarations))); + base.OnLocalDeclarations(new LocalDeclarations(decl.Variables.AddRange(this.addedVariableDeclarations))); } /// - /// Ensures that the outer block of conjugations is ignored during transformation. + /// Ensures that the outer block of conjugations is ignored during transformation. /// - public class IgnoreOuterBlockInConjugations + public class IgnoreOuterBlockInConjugations : StatementKindTransformation { public IgnoreOuterBlockInConjugations(SyntaxTreeTransformation parent, TransformationOptions options = null) - : base(parent, options ?? TransformationOptions.Default) { } + : base(parent, options ?? TransformationOptions.Default) + { + } public IgnoreOuterBlockInConjugations(T sharedInternalState, TransformationOptions options = null) - : base(sharedInternalState, options ?? TransformationOptions.Default) { } - + : base(sharedInternalState, options ?? TransformationOptions.Default) + { + } + /// public override QsStatementKind OnConjugation(QsConjugation stm) { var inner = stm.InnerTransformation; @@ -136,18 +148,17 @@ public override QsStatementKind OnConjugation(QsConjugation stm) } } - /// - /// Scope transformation that reverses the order of execution for operation calls within a given scope, + /// Scope transformation that reverses the order of execution for operation calls within a given scope, /// unless these calls occur within the outer block of a conjugation. Outer transformations of conjugations are left unchanged. - /// Note that the transformed scope is only guaranteed to be valid if operation calls only occur within expression statements! - /// Otherwise the transformation will succeed, but the generated scope is not necessarily valid. - /// Throws an InvalidOperationException if the scope to transform contains while-loops. + /// Note that the transformed scope is only guaranteed to be valid if operation calls only occur within expression statements! + /// Otherwise the transformation will succeed, but the generated scope is not necessarily valid. + /// Throws an InvalidOperationException if the scope to transform contains while-loops. /// - internal class ReverseOrderOfOperationCalls - : SelectByAllContainedExpressions + internal class ReverseOrderOfOperationCalls + : SelectByAllContainedExpressions { - public ReverseOrderOfOperationCalls() + public ReverseOrderOfOperationCalls() : base(ex => !ex.InferredInformation.HasLocalQuantumDependency, false) // no need to evaluate subexpressions { // Do *not* disable transformations; the base class takes care of that! @@ -155,14 +166,15 @@ public ReverseOrderOfOperationCalls() this.Statements = new StatementTransformation(this); } - // helper classes private class StatementTransformation : StatementTransformation { public StatementTransformation(ReverseOrderOfOperationCalls parent) - : base(state => new ReverseOrderOfOperationCalls(), parent) { } + : base(state => new ReverseOrderOfOperationCalls(), parent) + { + } public override QsScope OnScope(QsScope scope) { @@ -171,8 +183,14 @@ public override QsScope OnScope(QsScope scope) foreach (var statement in scope.Statements) { var transformed = this.OnStatement(statement); - if (this.SubSelector.SharedState.SatisfiesCondition) topStatements.Add(statement); - else bottomStatements.Add(transformed); + if (this.SubSelector.SharedState.SatisfiesCondition) + { + topStatements.Add(statement); + } + else + { + bottomStatements.Add(transformed); + } } bottomStatements.Reverse(); return new QsScope(topStatements.Concat(bottomStatements).ToImmutableArray(), scope.KnownSymbols); @@ -181,15 +199,17 @@ public override QsScope OnScope(QsScope scope) /// /// Helper class to reverse the order of all operation calls - /// unless these calls occur within the outer block of a conjugation. + /// unless these calls occur within the outer block of a conjugation. /// Outer transformations of conjugations are left unchanged. - /// Throws an InvalidOperationException upon while-loops. + /// Throws an InvalidOperationException upon while-loops. /// - private class ReverseLoops + private class ReverseLoops : IgnoreOuterBlockInConjugations { - internal ReverseLoops(ReverseOrderOfOperationCalls parent) - : base(parent) { } + internal ReverseLoops(ReverseOrderOfOperationCalls parent) + : base(parent) + { + } public override QsStatementKind OnForStatement(QsForStatement stm) { @@ -203,5 +223,3 @@ public override QsStatementKind OnWhileStatement(QsWhileStatement stm) => } } } - - diff --git a/src/QsCompiler/Transformations/IntrinsicResolution.cs b/src/QsCompiler/Transformations/IntrinsicResolution.cs index 7f7649acba..6606dfd6c1 100644 --- a/src/QsCompiler/Transformations/IntrinsicResolution.cs +++ b/src/QsCompiler/Transformations/IntrinsicResolution.cs @@ -8,7 +8,6 @@ using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.SyntaxTree; - namespace Microsoft.Quantum.QsCompiler.Transformations.IntrinsicResolution { public static class ReplaceWithTargetIntrinsics @@ -24,13 +23,14 @@ public static QsCompilation Apply(QsCompilation environment, QsCompilation targe var envNames = environment.Namespaces.ToImmutableDictionary(ns => ns.Name); var targetNames = target.Namespaces.Select(ns => ns.Name).ToImmutableHashSet(); - return new QsCompilation(environment.Namespaces - .Where(ns => !targetNames.Contains(ns.Name)) - .Concat(target.Namespaces.Select(ns => - envNames.TryGetValue(ns.Name, out var envNs) - ? MergeNamespaces(envNs, ns) - : ns)) - .ToImmutableArray(), target.EntryPoints); + return new QsCompilation( + environment.Namespaces + .Where(ns => !targetNames.Contains(ns.Name)) + .Concat(target.Namespaces.Select(ns => + envNames.TryGetValue(ns.Name, out var envNs) + ? MergeNamespaces(envNs, ns) + : ns)) + .ToImmutableArray(), target.EntryPoints); } /// @@ -88,11 +88,12 @@ private static bool CompareSignatures(ResolvedSignature first, ResolvedSignature private static bool CompareUserDefinedTypes(QsNamespaceElement.QsCustomType first, QsNamespaceElement.QsCustomType second) { var tempNs = StripPositionInfo.Apply( - new QsNamespace(NonNullable.New("tempNs"), + new QsNamespace( + NonNullable.New("tempNs"), ImmutableArray.Create(first, second), Array.Empty>().ToLookup(ns => ns, _ => ImmutableArray.Empty))); var firstUDT = ((QsNamespaceElement.QsCustomType)tempNs.Elements[0]).Item; - var secondUDT = ((QsNamespaceElement.QsCustomType)tempNs.Elements[1]).Item; + var secondUDT = ((QsNamespaceElement.QsCustomType)tempNs.Elements[1]).Item; return firstUDT.TypeItems.Equals(secondUDT.TypeItems); } } diff --git a/src/QsCompiler/Transformations/Monomorphization.cs b/src/QsCompiler/Transformations/Monomorphization.cs index dead596b89..a8cd63f959 100644 --- a/src/QsCompiler/Transformations/Monomorphization.cs +++ b/src/QsCompiler/Transformations/Monomorphization.cs @@ -12,7 +12,6 @@ using Microsoft.Quantum.QsCompiler.Transformations.Core; using Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace; - namespace Microsoft.Quantum.QsCompiler.Transformations.Monomorphization { using Concretion = Dictionary>, ResolvedType>; @@ -33,21 +32,24 @@ public static class Monomorphize { private struct Request { - public QsQualifiedName originalName; - public ImmutableConcretion typeResolutions; - public QsQualifiedName concreteName; + public QsQualifiedName OriginalName; + public ImmutableConcretion TypeResolutions; + public QsQualifiedName ConcreteName; } private struct Response { - public QsQualifiedName originalName; - public ImmutableConcretion typeResolutions; - public QsCallable concreteCallable; + public QsQualifiedName OriginalName; + public ImmutableConcretion TypeResolutions; + public QsCallable ConcreteCallable; } public static QsCompilation Apply(QsCompilation compilation) { - if (compilation == null || compilation.Namespaces.Contains(null)) throw new ArgumentNullException(nameof(compilation)); + if (compilation == null || compilation.Namespaces.Contains(null)) + { + throw new ArgumentNullException(nameof(compilation)); + } var globals = compilation.Namespaces.GlobalCallableResolutions(); @@ -59,9 +61,9 @@ public static QsCompilation Apply(QsCompilation compilation) var entryPoints = compilation.EntryPoints .Select(call => new Request { - originalName = call, - typeResolutions = ImmutableConcretion.Empty, - concreteName = call + OriginalName = call, + TypeResolutions = ImmutableConcretion.Empty, + ConcreteName = call }); var requests = new Stack(entryPoints); @@ -72,14 +74,16 @@ public static QsCompilation Apply(QsCompilation compilation) Request currentRequest = requests.Pop(); // If there is a call to an unknown callable, throw exception - if (!globals.TryGetValue(currentRequest.originalName, out QsCallable originalGlobal)) - throw new ArgumentException($"Couldn't find definition for callable: {currentRequest.originalName}"); + if (!globals.TryGetValue(currentRequest.OriginalName, out QsCallable originalGlobal)) + { + throw new ArgumentException($"Couldn't find definition for callable: {currentRequest.OriginalName}"); + } var currentResponse = new Response { - originalName = currentRequest.originalName, - typeResolutions = currentRequest.typeResolutions, - concreteCallable = originalGlobal.WithFullName(name => currentRequest.concreteName) + OriginalName = currentRequest.OriginalName, + TypeResolutions = currentRequest.TypeResolutions, + ConcreteCallable = originalGlobal.WithFullName(name => currentRequest.ConcreteName) }; GetConcreteIdentifierFunc getConcreteIdentifier = (globalCallable, types) => @@ -115,10 +119,10 @@ private static Identifier GetConcreteIdentifier( string name = null; // Check for recursive call - if (currentResponse.originalName.Equals(globalCallable.Item) && - typesHashSet.SetEquals(currentResponse.typeResolutions)) + if (currentResponse.OriginalName.Equals(globalCallable.Item) && + typesHashSet.SetEquals(currentResponse.TypeResolutions)) { - name = currentResponse.concreteCallable.FullName.Name.Value; + name = currentResponse.ConcreteCallable.FullName.Name.Value; } // Search requests for identifier @@ -126,9 +130,9 @@ private static Identifier GetConcreteIdentifier( { name = requests .Where(req => - req.originalName.Equals(globalCallable.Item) && - typesHashSet.SetEquals(req.typeResolutions)) - .Select(req => req.concreteName.Name.Value) + req.OriginalName.Equals(globalCallable.Item) && + typesHashSet.SetEquals(req.TypeResolutions)) + .Select(req => req.ConcreteName.Name.Value) .FirstOrDefault(); } @@ -137,9 +141,9 @@ private static Identifier GetConcreteIdentifier( { name = responses .Where(res => - res.originalName.Equals(globalCallable.Item) && - typesHashSet.SetEquals(res.typeResolutions)) - .Select(res => res.concreteCallable.FullName.Name.Value) + res.OriginalName.Equals(globalCallable.Item) && + typesHashSet.SetEquals(res.TypeResolutions)) + .Select(res => res.ConcreteCallable.FullName.Name.Value) .FirstOrDefault(); } @@ -155,13 +159,14 @@ private static Identifier GetConcreteIdentifier( requests.Push(new Request() { - originalName = globalCallable.Item, - typeResolutions = types, - concreteName = concreteName + OriginalName = globalCallable.Item, + TypeResolutions = types, + ConcreteName = concreteName }); } - else // If the identifier was found, update with the name + else { + // If the identifier was found, update with the name concreteName = new QsQualifiedName(globalCallable.Item.Namespace, NonNullable.New(name)); } @@ -174,7 +179,7 @@ private class ResolveGenerics : SyntaxTreeTransformation responses, ImmutableHashSet intrinsicCallableSet) { - var filter = new ResolveGenerics(responses.ToLookup(res => res.concreteCallable.FullName.Namespace, res => res.concreteCallable), intrinsicCallableSet); + var filter = new ResolveGenerics(responses.ToLookup(res => res.ConcreteCallable.FullName.Namespace, res => res.ConcreteCallable), intrinsicCallableSet); return filter.OnCompilation(compilation); } @@ -206,14 +211,15 @@ private ResolveGenerics(ILookup, QsCallable> namespaceCallab private class NamespaceTransformation : NamespaceTransformation { - public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } private bool NamespaceElementFilter(QsNamespaceElement elem) { if (elem is QsNamespaceElement.QsCallable call) { - - return BuiltIn.RewriteStepDependencies.Contains(call.Item.FullName) || SharedState.IntrinsicCallableSet.Contains(call.Item.FullName); + return BuiltIn.RewriteStepDependencies.Contains(call.Item.FullName) || this.SharedState.IntrinsicCallableSet.Contains(call.Item.FullName); } else { @@ -226,8 +232,8 @@ public override QsNamespace OnNamespace(QsNamespace ns) // Removes unused or generic callables from the namespace // Adds in the used concrete callables return ns.WithElements(elems => elems - .Where(NamespaceElementFilter) - .Concat(SharedState.NamespaceCallables[ns.Name].Select(QsNamespaceElement.NewQsCallable)) + .Where(this.NamespaceElementFilter) + .Concat(this.SharedState.NamespaceCallables[ns.Name].Select(QsNamespaceElement.NewQsCallable)) .ToImmutableArray()); } } @@ -243,16 +249,19 @@ private class ReplaceTypeParamImplementations : public static Response Apply(Response current) { // Nothing to change if the current callable is already concrete - if (current.typeResolutions == ImmutableConcretion.Empty) return current; + if (current.TypeResolutions == ImmutableConcretion.Empty) + { + return current; + } - var filter = new ReplaceTypeParamImplementations(current.typeResolutions); + var filter = new ReplaceTypeParamImplementations(current.TypeResolutions); // Create a new response with the transformed callable return new Response { - originalName = current.originalName, - typeResolutions = current.typeResolutions, - concreteCallable = filter.Namespaces.OnCallableDeclaration(current.concreteCallable) + OriginalName = current.OriginalName, + TypeResolutions = current.TypeResolutions, + ConcreteCallable = filter.Namespaces.OnCallableDeclaration(current.ConcreteCallable) }; } @@ -274,7 +283,9 @@ private ReplaceTypeParamImplementations(ImmutableConcretion typeParams) : base(n private class NamespaceTransformation : NamespaceTransformation { - public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } public override ResolvedSignature OnSignature(ResolvedSignature s) { @@ -283,19 +294,20 @@ public override ResolvedSignature OnSignature(ResolvedSignature s) ImmutableArray.Empty, s.ArgumentType, s.ReturnType, - s.Information - ); + s.Information); return base.OnSignature(s); } } private class TypeTransformation : TypeTransformation { - public TypeTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public TypeTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } public override QsTypeKind OnTypeParameter(QsTypeParameter tp) { - if (SharedState.TypeParams.TryGetValue(Tuple.Create(tp.Origin, tp.TypeName), out var typeParam)) + if (this.SharedState.TypeParams.TryGetValue(Tuple.Create(tp.Origin, tp.TypeName), out var typeParam)) { return typeParam.Resolution; } @@ -318,9 +330,9 @@ public static Response Apply(Response current, GetConcreteIdentifierFunc getConc // Create a new response with the transformed callable return new Response { - originalName = current.originalName, - typeResolutions = current.typeResolutions, - concreteCallable = filter.Namespaces.OnCallableDeclaration(current.concreteCallable) + OriginalName = current.OriginalName, + TypeResolutions = current.TypeResolutions, + ConcreteCallable = filter.Namespaces.OnCallableDeclaration(current.ConcreteCallable) }; } @@ -347,7 +359,9 @@ private ReplaceTypeParamCalls(GetConcreteIdentifierFunc getConcreteIdentifier, I private class ExpressionTransformation : ExpressionTransformation { - public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } public override TypedExpression OnTypedExpression(TypedExpression ex) { @@ -369,39 +383,41 @@ public override ImmutableConcretion OnTypeParamResolutions(ImmutableConcretion t { // Merge the type params into the current dictionary - foreach (var kvp in typeParams.Where(kv => !SharedState.IntrinsicCallableSet.Contains(kv.Key.Item1))) + foreach (var kvp in typeParams.Where(kv => !this.SharedState.IntrinsicCallableSet.Contains(kv.Key.Item1))) { - SharedState.CurrentParamTypes.Add(kvp.Key, kvp.Value); + this.SharedState.CurrentParamTypes.Add(kvp.Key, kvp.Value); } - return typeParams.Where(kv => SharedState.IntrinsicCallableSet.Contains(kv.Key.Item1)).ToImmutableDictionary(); + return typeParams.Where(kv => this.SharedState.IntrinsicCallableSet.Contains(kv.Key.Item1)).ToImmutableDictionary(); } } private class ExpressionKindTransformation : ExpressionKindTransformation { - public ExpressionKindTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public ExpressionKindTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } public override QsExpressionKind OnIdentifier(Identifier sym, QsNullable> tArgs) { if (sym is Identifier.GlobalCallable global) { - ImmutableConcretion applicableParams = SharedState.CurrentParamTypes + ImmutableConcretion applicableParams = this.SharedState.CurrentParamTypes .Where(kvp => kvp.Key.Item1.Equals(global.Item)) .ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value); // We want to skip over intrinsic callables. They will not be monomorphized. - if (!SharedState.IntrinsicCallableSet.Contains(global.Item)) + if (!this.SharedState.IntrinsicCallableSet.Contains(global.Item)) { // Create a new identifier - sym = SharedState.GetConcreteIdentifier(global, applicableParams); + sym = this.SharedState.GetConcreteIdentifier(global, applicableParams); tArgs = QsNullable>.Null; } // Remove Type Params used from the CurrentParamTypes foreach (var key in applicableParams.Keys) { - SharedState.CurrentParamTypes.Remove(key); + this.SharedState.CurrentParamTypes.Remove(key); } } else if (sym is Identifier.LocalVariable && tArgs.IsValue && tArgs.Item.Any()) @@ -415,11 +431,13 @@ public override QsExpressionKind OnId private class TypeTransformation : TypeTransformation { - public TypeTransformation(SyntaxTreeTransformation parent) : base(parent) { } + public TypeTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } public override QsTypeKind OnTypeParameter(QsTypeParameter tp) { - if (SharedState.CurrentParamTypes.TryGetValue(Tuple.Create(tp.Origin, tp.TypeName), out var typeParam)) + if (this.SharedState.CurrentParamTypes.TryGetValue(Tuple.Create(tp.Origin, tp.TypeName), out var typeParam)) { return typeParam.Resolution; } diff --git a/src/QsCompiler/Transformations/MonomorphizationValidation.cs b/src/QsCompiler/Transformations/MonomorphizationValidation.cs index a78ed0b485..6a522a86cf 100644 --- a/src/QsCompiler/Transformations/MonomorphizationValidation.cs +++ b/src/QsCompiler/Transformations/MonomorphizationValidation.cs @@ -9,7 +9,6 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.Transformations.Core; - namespace Microsoft.Quantum.QsCompiler.Transformations.Monomorphization.Validation { public class ValidateMonomorphization : SyntaxTreeTransformation @@ -46,12 +45,14 @@ internal ValidateMonomorphization(ImmutableHashSet intrinsicCal private class NamespaceTransformation : NamespaceTransformation { - public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent, TransformationOptions.NoRebuild) { } + public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent, TransformationOptions.NoRebuild) + { + } public override QsCallable OnCallableDeclaration(QsCallable c) { // Don't validate intrinsics - if (SharedState.IntrinsicCallableSet.Contains(c.FullName)) + if (this.SharedState.IntrinsicCallableSet.Contains(c.FullName)) { return c; } @@ -74,12 +75,14 @@ public override ResolvedSignature OnSignature(ResolvedSignature s) private class ExpressionTransformation : ExpressionTransformation { - public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent, TransformationOptions.NoRebuild) { } + public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent, TransformationOptions.NoRebuild) + { + } public override ImmutableDictionary>, ResolvedType> OnTypeParamResolutions(ImmutableDictionary>, ResolvedType> typeParams) { // Type resolutions for intrinsics are allowed - if (typeParams.Any(kvp => !SharedState.IntrinsicCallableSet.Contains(kvp.Key.Item1))) + if (typeParams.Any(kvp => !this.SharedState.IntrinsicCallableSet.Contains(kvp.Key.Item1))) { throw new Exception("Type Parameter Resolutions must be empty"); } @@ -90,12 +93,14 @@ public override ImmutableDictionary>, private class TypeTransformation : TypeTransformation { - public TypeTransformation(SyntaxTreeTransformation parent) : base(parent, TransformationOptions.NoRebuild) { } + public TypeTransformation(SyntaxTreeTransformation parent) : base(parent, TransformationOptions.NoRebuild) + { + } // If an intrinsic callable is generic, then its type parameters can occur within expressions; // when generic intrinsics are called, the type of that call contains type parameter types. public override QsTypeKind OnTypeParameter(QsTypeParameter tp) => - SharedState.IntrinsicCallableSet.Contains(tp.Origin) + this.SharedState.IntrinsicCallableSet.Contains(tp.Origin) ? QsTypeKind.NewTypeParameter(tp) : throw new Exception("Type Parameter types must be resolved"); } diff --git a/src/QsCompiler/Transformations/Properties/AssemblyInfo.cs b/src/QsCompiler/Transformations/Properties/AssemblyInfo.cs index 8413ca8700..091d227001 100644 --- a/src/QsCompiler/Transformations/Properties/AssemblyInfo.cs +++ b/src/QsCompiler/Transformations/Properties/AssemblyInfo.cs @@ -3,6 +3,5 @@ using System.Runtime.CompilerServices; - // Allow the test assembly to use our internal methods -[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsCompiler" + SigningConstants.PUBLIC_KEY)] +[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsCompiler" + SigningConstants.PublicKey)] diff --git a/src/QsCompiler/Transformations/QsharpCodeOutput.cs b/src/QsCompiler/Transformations/QsharpCodeOutput.cs index 7ea1e6d045..ad6d869e34 100644 --- a/src/QsCompiler/Transformations/QsharpCodeOutput.cs +++ b/src/QsCompiler/Transformations/QsharpCodeOutput.cs @@ -15,12 +15,10 @@ using Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations; using Microsoft.Quantum.QsCompiler.Transformations.Core; - namespace Microsoft.Quantum.QsCompiler.Transformations.QsCodeOutput { - using QsTypeKind = QsTypeKind; using QsExpressionKind = QsExpressionKind; - + using QsTypeKind = QsTypeKind; /// /// Class used to represent contextual information for expression transformations. @@ -43,9 +41,8 @@ public TransformationContext() } } - /// - /// Class used to generate Q# code for compiled Q# namespaces. + /// Class used to generate Q# code for compiled Q# namespaces. /// Upon calling Transform, the Output property is set to the Q# code corresponding to the given namespace. /// public class SyntaxTreeToQsharp @@ -84,7 +81,7 @@ public TransformationState(TransformationContext context = null) => this.Context = context ?? new TransformationContext(); internal static bool PrecededByCode(IEnumerable output) => - output == null ? false : output.Any() && !String.IsNullOrWhiteSpace(output.Last().Replace("{", "")); + output == null ? false : output.Any() && !string.IsNullOrWhiteSpace(output.Last().Replace("{", "")); internal static bool PrecededByBlock(IEnumerable output) => output == null ? false : output.Any() && output.Last().Trim() == "}"; @@ -101,7 +98,6 @@ internal void InvokeOnInvalid(Action action) } } - public SyntaxTreeToQsharp(TransformationContext context = null) : base(new TransformationState(context), TransformationOptions.NoRebuild) { @@ -112,7 +108,6 @@ public SyntaxTreeToQsharp(TransformationContext context = null) this.Namespaces = new NamespaceTransformation(this); } - // public methods for convenience public static SyntaxTreeToQsharp Default => @@ -137,7 +132,7 @@ public string ToCode(QsStatementKind stmKind) { var nrPreexistingLines = this.SharedState.StatementOutputHandle.Count; this.StatementKinds.OnStatementKind(stmKind); - return String.Join(Environment.NewLine, this.SharedState.StatementOutputHandle.Skip(nrPreexistingLines)); + return string.Join(Environment.NewLine, this.SharedState.StatementOutputHandle.Skip(nrPreexistingLines)); } public string ToCode(QsStatement stm) => @@ -147,14 +142,17 @@ public string ToCode(QsNamespace ns) { var nrPreexistingLines = this.SharedState.NamespaceOutputHandle.Count; this.Namespaces.OnNamespace(ns); - return String.Join(Environment.NewLine, this.SharedState.NamespaceOutputHandle.Skip(nrPreexistingLines)); + return string.Join(Environment.NewLine, this.SharedState.NamespaceOutputHandle.Skip(nrPreexistingLines)); } public static string CharacteristicsExpression(ResolvedCharacteristics characteristics) => TypeTransformation.CharacteristicsExpression(characteristics); - public static string ArgumentTuple(QsTuple> arg, - Func typeTransformation, Action onInvalidName = null, bool symbolsOnly = false) => + public static string ArgumentTuple( + QsTuple> arg, + Func typeTransformation, + Action onInvalidName = null, + bool symbolsOnly = false) => NamespaceTransformation.ArgumentTuple(arg, item => (NamespaceTransformation.SymbolName(item.VariableName, onInvalidName), item.Type), typeTransformation, symbolsOnly); public static string DeclarationSignature(QsCallable c, Func typeTransformation, Action onInvalidName = null) @@ -163,20 +161,23 @@ public static string DeclarationSignature(QsCallable c, Func - /// For each file in the given parameter array of open directives, - /// generates a dictionary that maps (the name of) each partial namespace contained in the file - /// to a string containing the formatted Q# code for the part of the namespace. - /// Qualified or unqualified names for types and identifiers are generated based on the given namespace and open directives. - /// Throws an ArgumentNullException if the given namespace is null. + /// For each file in the given parameter array of open directives, + /// generates a dictionary that maps (the name of) each partial namespace contained in the file + /// to a string containing the formatted Q# code for the part of the namespace. + /// Qualified or unqualified names for types and identifiers are generated based on the given namespace and open directives. + /// Throws an ArgumentNullException if the given namespace is null. /// -> IMPORTANT: The given namespace is expected to contain *all* elements in that namespace for the *entire* compilation unit! /// - public static bool Apply(out List, string>> generatedCode, + public static bool Apply( + out List, string>> generatedCode, IEnumerable namespaces, params (NonNullable, ImmutableDictionary, ImmutableArray<(NonNullable, string)>>)[] openDirectives) { - if (namespaces == null) throw new ArgumentNullException(nameof(namespaces)); + if (namespaces == null) + { + throw new ArgumentNullException(nameof(namespaces)); + } generatedCode = new List, string>>(); var symbolsInNS = namespaces.ToImmutableDictionary(ns => ns.Name, ns => ns.Elements @@ -190,7 +191,10 @@ public static bool Apply(out List, strin foreach (var ns in namespaces) { var tree = FilterBySourceFile.Apply(ns, sourceFile); - if (!tree.Elements.Any()) continue; + if (!tree.Elements.Any()) + { + continue; + } // determine all symbols that occur in multiple open namespaces var ambiguousSymbols = symbolsInNS.Where(entry => imports[ns.Name].Contains((entry.Key, null))) @@ -217,61 +221,62 @@ public static bool Apply(out List, strin generator.SharedState.NamespaceDocumentation = docComments.Count() == 1 ? docComments.Single() : ImmutableArray.Empty; // let's drop the doc if it is ambiguous generator.Namespaces.OnNamespace(tree); - if (totNrInvalid > 0) success = false; - nsInFile.Add(ns.Name, String.Join(Environment.NewLine, generator.SharedState.NamespaceOutputHandle)); + if (totNrInvalid > 0) + { + success = false; + } + nsInFile.Add(ns.Name, string.Join(Environment.NewLine, generator.SharedState.NamespaceOutputHandle)); } generatedCode.Add(nsInFile.ToImmutableDictionary()); } return success; } - // helper classes /// - /// Class used to generate Q# code for Q# types. - /// Adds an Output string property to ExpressionTypeTransformation, - /// that upon calling Transform on a Q# type is set to the Q# code corresponding to that type. + /// Class used to generate Q# code for Q# types. + /// Adds an Output string property to ExpressionTypeTransformation, + /// that upon calling Transform on a Q# type is set to the Q# code corresponding to that type. /// public class TypeTransformation : TypeTransformation { - private readonly Func TypeToQs; + private readonly Func typeToQs; protected string Output // the sole purpose of this is a shorter name ... { get => this.SharedState.TypeOutputHandle; - set => SharedState.TypeOutputHandle = value; + set => this.SharedState.TypeOutputHandle = value; } - public TypeTransformation(SyntaxTreeToQsharp parent) + public TypeTransformation(SyntaxTreeToQsharp parent) : base(parent, TransformationOptions.NoRebuild) => - this.TypeToQs = parent.ToCode; + this.typeToQs = parent.ToCode; - public TypeTransformation() + public TypeTransformation() : base(new TransformationState(), TransformationOptions.NoRebuild) => - this.TypeToQs = t => + this.typeToQs = t => { this.Transformation.Types.OnType(t); return this.SharedState.TypeOutputHandle; }; - // internal static methods internal static string CharacteristicsExpression(ResolvedCharacteristics characteristics, Action onInvalidSet = null) { - int CurrentPrecedence = 0; + int currentPrecedence = 0; string SetPrecedenceAndReturn(int prec, string str) { - CurrentPrecedence = prec; + currentPrecedence = prec; return str; } string Recur(int prec, ResolvedCharacteristics ex) { var output = SetAnnotation(ex); - return prec < CurrentPrecedence || CurrentPrecedence == int.MaxValue ? output : $"({output})"; + return prec < currentPrecedence || currentPrecedence == int.MaxValue ? output : $"({output})"; } string BinaryOperator(Keywords.QsOperator op, ResolvedCharacteristics lhs, ResolvedCharacteristics rhs) => @@ -282,65 +287,87 @@ string SetAnnotation(ResolvedCharacteristics charEx) if (charEx.Expression is CharacteristicsKind.SimpleSet set) { string setName = null; - if (set.Item.IsAdjointable) setName = Keywords.qsAdjSet.id; - else if (set.Item.IsControllable) setName = Keywords.qsCtlSet.id; - else throw new NotImplementedException("unknown set name"); + if (set.Item.IsAdjointable) + { + setName = Keywords.qsAdjSet.id; + } + else if (set.Item.IsControllable) + { + setName = Keywords.qsCtlSet.id; + } + else + { + throw new NotImplementedException("unknown set name"); + } return SetPrecedenceAndReturn(int.MaxValue, setName); } else if (charEx.Expression is CharacteristicsKind.Union u) - { return BinaryOperator(Keywords.qsSetUnion, u.Item1, u.Item2); } + { + return BinaryOperator(Keywords.qsSetUnion, u.Item1, u.Item2); + } else if (charEx.Expression is CharacteristicsKind.Intersection i) - { return BinaryOperator(Keywords.qsSetIntersection, i.Item1, i.Item2); } + { + return BinaryOperator(Keywords.qsSetIntersection, i.Item1, i.Item2); + } else if (charEx.Expression.IsInvalidSetExpr) { onInvalidSet?.Invoke(); return SetPrecedenceAndReturn(int.MaxValue, InvalidSet); } - else throw new NotImplementedException("unknown set expression"); + else + { + throw new NotImplementedException("unknown set expression"); + } } return characteristics.Expression.IsEmptySet ? null : SetAnnotation(characteristics); } + // overrides - // overrides - + /// public override QsTypeKind OnArrayType(ResolvedType b) { - this.Output = $"{this.TypeToQs(b)}[]"; + this.Output = $"{this.typeToQs(b)}[]"; return QsTypeKind.NewArrayType(b); } + /// public override QsTypeKind OnBool() { this.Output = Keywords.qsBool.id; return QsTypeKind.Bool; } + /// public override QsTypeKind OnDouble() { this.Output = Keywords.qsDouble.id; return QsTypeKind.Double; } + /// public override QsTypeKind OnFunction(ResolvedType it, ResolvedType ot) { - this.Output = $"({this.TypeToQs(it)} -> {this.TypeToQs(ot)})"; + this.Output = $"({this.typeToQs(it)} -> {this.typeToQs(ot)})"; return QsTypeKind.NewFunction(it, ot); } + /// public override QsTypeKind OnInt() { this.Output = Keywords.qsInt.id; return QsTypeKind.Int; } + /// public override QsTypeKind OnBigInt() { this.Output = Keywords.qsBigInt.id; return QsTypeKind.BigInt; } + /// public override QsTypeKind OnInvalidType() { this.SharedState.BeforeInvalidType?.Invoke(); @@ -348,60 +375,70 @@ public override QsTypeKind OnInvalidType() return QsTypeKind.InvalidType; } + /// public override QsTypeKind OnMissingType() { this.Output = "_"; // needs to be underscore, since this is valid as type argument specifier return QsTypeKind.MissingType; } + /// public override QsTypeKind OnPauli() { this.Output = Keywords.qsPauli.id; return QsTypeKind.Pauli; } + /// public override QsTypeKind OnQubit() { this.Output = Keywords.qsQubit.id; return QsTypeKind.Qubit; } + /// public override QsTypeKind OnRange() { this.Output = Keywords.qsRange.id; return QsTypeKind.Range; } + /// public override QsTypeKind OnResult() { this.Output = Keywords.qsResult.id; return QsTypeKind.Result; } + /// public override QsTypeKind OnString() { this.Output = Keywords.qsString.id; return QsTypeKind.String; } + /// public override QsTypeKind OnTupleType(ImmutableArray ts) { - this.Output = $"({String.Join(", ", ts.Select(this.TypeToQs))})"; + this.Output = $"({string.Join(", ", ts.Select(this.typeToQs))})"; return QsTypeKind.NewTupleType(ts); } + /// public override QsTypeKind OnTypeParameter(QsTypeParameter tp) { this.Output = $"'{tp.TypeName.Value}"; return QsTypeKind.NewTypeParameter(tp); } + /// public override QsTypeKind OnUnitType() { this.Output = Keywords.qsUnit.id; return QsTypeKind.UnitType; } + /// public override QsTypeKind OnUserDefinedType(UserDefinedType udt) { var isInCurrentNamespace = udt.Namespace.Value == this.SharedState.Context.CurrentNamespace; @@ -413,50 +450,50 @@ public override QsTypeKind OnUserDefinedType(UserDefinedType udt) return QsTypeKind.NewUserDefinedType(udt); } + /// public override ResolvedCharacteristics OnCharacteristicsExpression(ResolvedCharacteristics set) { this.Output = CharacteristicsExpression(set, onInvalidSet: this.SharedState.BeforeInvalidSet); return set; } + /// public override QsTypeKind OnOperation(Tuple sign, CallableInformation info) { info = this.OnCallableInformation(info); - var characteristics = String.IsNullOrWhiteSpace(this.Output) ? "" : $" {Keywords.qsCharacteristics.id} {this.Output}"; - this.Output = $"({this.TypeToQs(sign.Item1)} => {this.TypeToQs(sign.Item2)}{characteristics})"; + var characteristics = string.IsNullOrWhiteSpace(this.Output) ? "" : $" {Keywords.qsCharacteristics.id} {this.Output}"; + this.Output = $"({this.typeToQs(sign.Item1)} => {this.typeToQs(sign.Item2)}{characteristics})"; return QsTypeKind.NewOperation(sign, info); } } - /// - /// Class used to generate Q# code for Q# expressions. - /// Upon calling Transform, the Output property is set to the Q# code corresponding to an expression of the given kind. + /// Class used to generate Q# code for Q# expressions. + /// Upon calling Transform, the Output property is set to the Q# code corresponding to an expression of the given kind. /// public class ExpressionKindTransformation : ExpressionKindTransformation { // allows to omit unnecessary parentheses - private int CurrentPrecedence = 0; - - // used to replace interpolated pieces in string literals - private static readonly Regex InterpolationArg = + private int currentPrecedence = 0; + + // used to replace interpolated pieces in string literals + private static readonly Regex InterpolationArg = new Regex(@"(? TypeToQs; + private readonly Func typeToQs; protected string Output // the sole purpose of this is a shorter name ... { get => this.SharedState.ExpressionOutputHandle; - set => SharedState.ExpressionOutputHandle = value; + set => this.SharedState.ExpressionOutputHandle = value; } public ExpressionKindTransformation(SyntaxTreeToQsharp parent) : base(parent, TransformationOptions.NoRebuild) => - this.TypeToQs = parent.ToCode; - + this.typeToQs = parent.ToCode; - // private helper functions + // private helper functions private static string ReplaceInterpolatedArgs(string text, Func replace) { @@ -468,7 +505,7 @@ private static string ReplaceInterpolatedArgs(string text, Func rep private string Recur(int prec, TypedExpression ex) { this.Transformation.Expressions.OnTypedExpression(ex); - return prec < this.CurrentPrecedence || this.CurrentPrecedence == int.MaxValue // need to cover the case where prec = currentPrec = MaxValue + return prec < this.currentPrecedence || this.currentPrecedence == int.MaxValue // need to cover the case where prec = currentPrec = MaxValue ? this.Output : $"({this.Output})"; } @@ -478,19 +515,19 @@ private void UnaryOperator(Keywords.QsOperator op, TypedExpression ex) this.Output = Keywords.ReservedKeywords.Contains(op.op) ? $"{op.op} {this.Recur(op.prec, ex)}" : $"{op.op}{this.Recur(op.prec, ex)}"; - this.CurrentPrecedence = op.prec; + this.currentPrecedence = op.prec; } private void BinaryOperator(Keywords.QsOperator op, TypedExpression lhs, TypedExpression rhs) { this.Output = $"{this.Recur(op.prec, lhs)} {op.op} {this.Recur(op.prec, rhs)}"; - this.CurrentPrecedence = op.prec; + this.currentPrecedence = op.prec; } private void TernaryOperator(Keywords.QsOperator op, TypedExpression fst, TypedExpression snd, TypedExpression trd) { this.Output = $"{this.Recur(op.prec, fst)} {op.op} {this.Recur(op.prec, snd)} {op.cont} {this.Recur(op.prec, trd)}"; - this.CurrentPrecedence = op.prec; + this.currentPrecedence = op.prec; } private QsExpressionKind CallLike(TypedExpression method, TypedExpression arg) @@ -498,247 +535,284 @@ private QsExpressionKind CallLike(TypedExpression method, TypedExpression arg) var prec = Keywords.qsCallCombinator.prec; var argStr = arg.Expression.IsValueTuple || arg.Expression.IsUnitValue ? this.Recur(int.MinValue, arg) : $"({this.Recur(int.MinValue, arg)})"; this.Output = $"{this.Recur(prec, method)}{argStr}"; - this.CurrentPrecedence = prec; + this.currentPrecedence = prec; return QsExpressionKind.NewCallLikeExpression(method, arg); } - // overrides + /// public override QsExpressionKind OnCopyAndUpdateExpression(TypedExpression lhs, TypedExpression acc, TypedExpression rhs) { - TernaryOperator(Keywords.qsCopyAndUpdateOp, lhs, acc, rhs); + this.TernaryOperator(Keywords.qsCopyAndUpdateOp, lhs, acc, rhs); return QsExpressionKind.NewCopyAndUpdate(lhs, acc, rhs); } + /// public override QsExpressionKind OnConditionalExpression(TypedExpression cond, TypedExpression ifTrue, TypedExpression ifFalse) { - TernaryOperator(Keywords.qsConditionalOp, cond, ifTrue, ifFalse); + this.TernaryOperator(Keywords.qsConditionalOp, cond, ifTrue, ifFalse); return QsExpressionKind.NewCONDITIONAL(cond, ifTrue, ifFalse); } + /// public override QsExpressionKind OnAddition(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsADDop, lhs, rhs); + this.BinaryOperator(Keywords.qsADDop, lhs, rhs); return QsExpressionKind.NewADD(lhs, rhs); } + /// public override QsExpressionKind OnBitwiseAnd(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsBANDop, lhs, rhs); + this.BinaryOperator(Keywords.qsBANDop, lhs, rhs); return QsExpressionKind.NewBAND(lhs, rhs); } + /// public override QsExpressionKind OnBitwiseExclusiveOr(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsBXORop, lhs, rhs); + this.BinaryOperator(Keywords.qsBXORop, lhs, rhs); return QsExpressionKind.NewBXOR(lhs, rhs); } + /// public override QsExpressionKind OnBitwiseOr(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsBORop, lhs, rhs); + this.BinaryOperator(Keywords.qsBORop, lhs, rhs); return QsExpressionKind.NewBOR(lhs, rhs); } + /// public override QsExpressionKind OnDivision(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsDIVop, lhs, rhs); + this.BinaryOperator(Keywords.qsDIVop, lhs, rhs); return QsExpressionKind.NewDIV(lhs, rhs); } + /// public override QsExpressionKind OnEquality(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsEQop, lhs, rhs); + this.BinaryOperator(Keywords.qsEQop, lhs, rhs); return QsExpressionKind.NewEQ(lhs, rhs); } + /// public override QsExpressionKind OnExponentiate(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsPOWop, lhs, rhs); + this.BinaryOperator(Keywords.qsPOWop, lhs, rhs); return QsExpressionKind.NewPOW(lhs, rhs); } + /// public override QsExpressionKind OnGreaterThan(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsGTop, lhs, rhs); + this.BinaryOperator(Keywords.qsGTop, lhs, rhs); return QsExpressionKind.NewGT(lhs, rhs); } + /// public override QsExpressionKind OnGreaterThanOrEqual(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsGTEop, lhs, rhs); + this.BinaryOperator(Keywords.qsGTEop, lhs, rhs); return QsExpressionKind.NewGTE(lhs, rhs); } + /// public override QsExpressionKind OnInequality(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsNEQop, lhs, rhs); + this.BinaryOperator(Keywords.qsNEQop, lhs, rhs); return QsExpressionKind.NewNEQ(lhs, rhs); } + /// public override QsExpressionKind OnLeftShift(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsLSHIFTop, lhs, rhs); + this.BinaryOperator(Keywords.qsLSHIFTop, lhs, rhs); return QsExpressionKind.NewLSHIFT(lhs, rhs); } + /// public override QsExpressionKind OnLessThan(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsLTop, lhs, rhs); + this.BinaryOperator(Keywords.qsLTop, lhs, rhs); return QsExpressionKind.NewLT(lhs, rhs); } + /// public override QsExpressionKind OnLessThanOrEqual(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsLTEop, lhs, rhs); + this.BinaryOperator(Keywords.qsLTEop, lhs, rhs); return QsExpressionKind.NewLTE(lhs, rhs); } + /// public override QsExpressionKind OnLogicalAnd(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsANDop, lhs, rhs); + this.BinaryOperator(Keywords.qsANDop, lhs, rhs); return QsExpressionKind.NewAND(lhs, rhs); } + /// public override QsExpressionKind OnLogicalOr(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsORop, lhs, rhs); + this.BinaryOperator(Keywords.qsORop, lhs, rhs); return QsExpressionKind.NewOR(lhs, rhs); } + /// public override QsExpressionKind OnModulo(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsMODop, lhs, rhs); + this.BinaryOperator(Keywords.qsMODop, lhs, rhs); return QsExpressionKind.NewMOD(lhs, rhs); } + /// public override QsExpressionKind OnMultiplication(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsMULop, lhs, rhs); + this.BinaryOperator(Keywords.qsMULop, lhs, rhs); return QsExpressionKind.NewMUL(lhs, rhs); } + /// public override QsExpressionKind OnRightShift(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsRSHIFTop, lhs, rhs); + this.BinaryOperator(Keywords.qsRSHIFTop, lhs, rhs); return QsExpressionKind.NewRSHIFT(lhs, rhs); } + /// public override QsExpressionKind OnSubtraction(TypedExpression lhs, TypedExpression rhs) { - BinaryOperator(Keywords.qsSUBop, lhs, rhs); + this.BinaryOperator(Keywords.qsSUBop, lhs, rhs); return QsExpressionKind.NewSUB(lhs, rhs); } + /// public override QsExpressionKind OnNegative(TypedExpression ex) { - UnaryOperator(Keywords.qsNEGop, ex); + this.UnaryOperator(Keywords.qsNEGop, ex); return QsExpressionKind.NewNEG(ex); } + /// public override QsExpressionKind OnLogicalNot(TypedExpression ex) { - UnaryOperator(Keywords.qsNOTop, ex); + this.UnaryOperator(Keywords.qsNOTop, ex); return QsExpressionKind.NewNOT(ex); } + /// public override QsExpressionKind OnBitwiseNot(TypedExpression ex) { - UnaryOperator(Keywords.qsBNOTop, ex); + this.UnaryOperator(Keywords.qsBNOTop, ex); return QsExpressionKind.NewBNOT(ex); } + /// public override QsExpressionKind OnOperationCall(TypedExpression method, TypedExpression arg) { return this.CallLike(method, arg); } + /// public override QsExpressionKind OnFunctionCall(TypedExpression method, TypedExpression arg) { return this.CallLike(method, arg); } + /// public override QsExpressionKind OnPartialApplication(TypedExpression method, TypedExpression arg) { return this.CallLike(method, arg); } + /// public override QsExpressionKind OnAdjointApplication(TypedExpression ex) { var op = Keywords.qsAdjointModifier; this.Output = $"{op.op} {this.Recur(op.prec, ex)}"; - this.CurrentPrecedence = op.prec; + this.currentPrecedence = op.prec; return QsExpressionKind.NewAdjointApplication(ex); } + /// public override QsExpressionKind OnControlledApplication(TypedExpression ex) { var op = Keywords.qsControlledModifier; this.Output = $"{op.op} {this.Recur(op.prec, ex)}"; - this.CurrentPrecedence = op.prec; + this.currentPrecedence = op.prec; return QsExpressionKind.NewControlledApplication(ex); } + /// public override QsExpressionKind OnUnwrapApplication(TypedExpression ex) { var op = Keywords.qsUnwrapModifier; this.Output = $"{this.Recur(op.prec, ex)}{op.op}"; - this.CurrentPrecedence = op.prec; + this.currentPrecedence = op.prec; return QsExpressionKind.NewUnwrapApplication(ex); } + /// public override QsExpressionKind OnUnitValue() { this.Output = "()"; - this.CurrentPrecedence = int.MaxValue; + this.currentPrecedence = int.MaxValue; return QsExpressionKind.UnitValue; } + /// public override QsExpressionKind OnMissingExpression() { this.Output = "_"; - this.CurrentPrecedence = int.MaxValue; + this.currentPrecedence = int.MaxValue; return QsExpressionKind.MissingExpr; } + /// public override QsExpressionKind OnInvalidExpression() { this.SharedState.BeforeInvalidExpression?.Invoke(); this.Output = InvalidExpression; - this.CurrentPrecedence = int.MaxValue; + this.currentPrecedence = int.MaxValue; return QsExpressionKind.InvalidExpr; } + /// public override QsExpressionKind OnValueTuple(ImmutableArray vs) { - this.Output = $"({String.Join(", ", vs.Select(v => this.Recur(int.MinValue, v)))})"; - this.CurrentPrecedence = int.MaxValue; + this.Output = $"({string.Join(", ", vs.Select(v => this.Recur(int.MinValue, v)))})"; + this.currentPrecedence = int.MaxValue; return QsExpressionKind.NewValueTuple(vs); } + /// public override QsExpressionKind OnValueArray(ImmutableArray vs) { - this.Output = $"[{String.Join(", ", vs.Select(v => this.Recur(int.MinValue, v)))}]"; - this.CurrentPrecedence = int.MaxValue; + this.Output = $"[{string.Join(", ", vs.Select(v => this.Recur(int.MinValue, v)))}]"; + this.currentPrecedence = int.MaxValue; return QsExpressionKind.NewValueArray(vs); } + /// public override QsExpressionKind OnNewArray(ResolvedType bt, TypedExpression idx) { - this.Output = $"{Keywords.arrayDecl.id} {this.TypeToQs(bt)}[{this.Recur(int.MinValue, idx)}]"; - this.CurrentPrecedence = int.MaxValue; + this.Output = $"{Keywords.arrayDecl.id} {this.typeToQs(bt)}[{this.Recur(int.MinValue, idx)}]"; + this.currentPrecedence = int.MaxValue; return QsExpressionKind.NewNewArray(bt, idx); } + /// public override QsExpressionKind OnArrayItem(TypedExpression arr, TypedExpression idx) { var prec = Keywords.qsArrayAccessCombinator.prec; this.Output = $"{this.Recur(prec, arr)}[{this.Recur(int.MinValue, idx)}]"; // Todo: generate contextual open range expression when appropriate - this.CurrentPrecedence = prec; + this.currentPrecedence = prec; return QsExpressionKind.NewArrayItem(arr, idx); } + /// public override QsExpressionKind OnNamedItem(TypedExpression ex, Identifier acc) { this.OnIdentifier(acc, QsNullable>.Null); @@ -747,77 +821,121 @@ public override QsExpressionKind OnNamedItem(TypedExpression ex, Identifier acc) return QsExpressionKind.NewNamedItem(ex, acc); } + /// public override QsExpressionKind OnIntLiteral(long i) { this.Output = i.ToString(CultureInfo.InvariantCulture); - this.CurrentPrecedence = int.MaxValue; + this.currentPrecedence = int.MaxValue; return QsExpressionKind.NewIntLiteral(i); } + /// public override QsExpressionKind OnBigIntLiteral(BigInteger b) { this.Output = b.ToString("R", CultureInfo.InvariantCulture) + "L"; - this.CurrentPrecedence = int.MaxValue; + this.currentPrecedence = int.MaxValue; return QsExpressionKind.NewBigIntLiteral(b); } + /// public override QsExpressionKind OnDoubleLiteral(double d) { this.Output = d.ToString("R", CultureInfo.InvariantCulture); - if ((int)d == d) this.Output = $"{this.Output}.0"; - this.CurrentPrecedence = int.MaxValue; + if ((int)d == d) + { + this.Output = $"{this.Output}.0"; + } + this.currentPrecedence = int.MaxValue; return QsExpressionKind.NewDoubleLiteral(d); } + /// public override QsExpressionKind OnBoolLiteral(bool b) { - if (b) this.Output = Keywords.qsTrue.id; - else this.Output = Keywords.qsFalse.id; - this.CurrentPrecedence = int.MaxValue; + if (b) + { + this.Output = Keywords.qsTrue.id; + } + else + { + this.Output = Keywords.qsFalse.id; + } + this.currentPrecedence = int.MaxValue; return QsExpressionKind.NewBoolLiteral(b); } + /// public override QsExpressionKind OnStringLiteral(NonNullable s, ImmutableArray exs) { string InterpolatedArg(int index) => $"{{{this.Recur(int.MinValue, exs[index])}}}"; this.Output = exs.Length == 0 ? $"\"{s.Value}\"" : $"$\"{ReplaceInterpolatedArgs(s.Value, InterpolatedArg)}\""; - this.CurrentPrecedence = int.MaxValue; + this.currentPrecedence = int.MaxValue; return QsExpressionKind.NewStringLiteral(s, exs); } + /// public override QsExpressionKind OnRangeLiteral(TypedExpression lhs, TypedExpression rhs) { var op = Keywords.qsRangeOp; var lhsStr = lhs.Expression.IsRangeLiteral ? this.Recur(int.MinValue, lhs) : this.Recur(op.prec, lhs); this.Output = $"{lhsStr} {op.op} {this.Recur(op.prec, rhs)}"; - this.CurrentPrecedence = op.prec; + this.currentPrecedence = op.prec; return QsExpressionKind.NewRangeLiteral(lhs, rhs); } + /// public override QsExpressionKind OnResultLiteral(QsResult r) { - if (r.IsZero) this.Output = Keywords.qsZero.id; - else if (r.IsOne) this.Output = Keywords.qsOne.id; - else throw new NotImplementedException("unknown Result literal"); - this.CurrentPrecedence = int.MaxValue; + if (r.IsZero) + { + this.Output = Keywords.qsZero.id; + } + else if (r.IsOne) + { + this.Output = Keywords.qsOne.id; + } + else + { + throw new NotImplementedException("unknown Result literal"); + } + this.currentPrecedence = int.MaxValue; return QsExpressionKind.NewResultLiteral(r); } + /// public override QsExpressionKind OnPauliLiteral(QsPauli p) { - if (p.IsPauliI) this.Output = Keywords.qsPauliI.id; - else if (p.IsPauliX) this.Output = Keywords.qsPauliX.id; - else if (p.IsPauliY) this.Output = Keywords.qsPauliY.id; - else if (p.IsPauliZ) this.Output = Keywords.qsPauliZ.id; - else throw new NotImplementedException("unknown Pauli literal"); - this.CurrentPrecedence = int.MaxValue; + if (p.IsPauliI) + { + this.Output = Keywords.qsPauliI.id; + } + else if (p.IsPauliX) + { + this.Output = Keywords.qsPauliX.id; + } + else if (p.IsPauliY) + { + this.Output = Keywords.qsPauliY.id; + } + else if (p.IsPauliZ) + { + this.Output = Keywords.qsPauliZ.id; + } + else + { + throw new NotImplementedException("unknown Pauli literal"); + } + this.currentPrecedence = int.MaxValue; return QsExpressionKind.NewPauliLiteral(p); } + /// public override QsExpressionKind OnIdentifier(Identifier sym, QsNullable> tArgs) { if (sym is Identifier.LocalVariable loc) - { this.Output = loc.Item.Value; } + { + this.Output = loc.Item.Value; + } else if (sym.IsInvalidIdentifier) { this.SharedState.BeforeInvalidIdentifier?.Invoke(); @@ -832,157 +950,210 @@ public override QsExpressionKind OnIdentifier(Identifier sym, QsNullable"; + { + this.Output = $"{this.Output}<{string.Join(", ", tArgs.Item.Select(this.typeToQs))}>"; } - this.CurrentPrecedence = int.MaxValue; + this.currentPrecedence = int.MaxValue; return QsExpressionKind.NewIdentifier(sym, tArgs); } } - /// - /// Class used to generate Q# code for Q# statements. + /// Class used to generate Q# code for Q# statements. /// Upon calling Transform, the _Output property of the scope transformation given on initialization - /// is set to the Q# code corresponding to a statement of the given kind. + /// is set to the Q# code corresponding to a statement of the given kind. /// public class StatementKindTransformation : StatementKindTransformation { - private int CurrentIndendation = 0; + private int currentIndendation = 0; + + private readonly Func expressionToQs; - private readonly Func ExpressionToQs; - - private bool PrecededByCode => + private bool PrecededByCode => TransformationState.PrecededByCode(this.SharedState.StatementOutputHandle); - private bool PrecededByBlock => + private bool PrecededByBlock => TransformationState.PrecededByBlock(this.SharedState.StatementOutputHandle); public StatementKindTransformation(SyntaxTreeToQsharp parent) : base(parent, TransformationOptions.NoRebuild) => - this.ExpressionToQs = parent.ToCode; - + this.expressionToQs = parent.ToCode; // private helper functions private void AddToOutput(string line) { - for (var i = 0; i < this.CurrentIndendation; ++i) line = $" {line}"; + for (var i = 0; i < this.currentIndendation; ++i) + { + line = $" {line}"; + } this.SharedState.StatementOutputHandle.Add(line); } private void AddComments(IEnumerable comments) { foreach (var comment in comments) - { this.AddToOutput(String.IsNullOrWhiteSpace(comment) ? "" : $"//{comment}"); } + { + this.AddToOutput(string.IsNullOrWhiteSpace(comment) ? "" : $"//{comment}"); + } } private void AddStatement(string stm) { var comments = this.SharedState.StatementComments; - if (this.PrecededByBlock || (this.PrecededByCode && comments.OpeningComments.Length != 0)) this.AddToOutput(""); + if (this.PrecededByBlock || (this.PrecededByCode && comments.OpeningComments.Length != 0)) + { + this.AddToOutput(""); + } this.AddComments(comments.OpeningComments); this.AddToOutput($"{stm};"); this.AddComments(comments.ClosingComments); - if (comments.ClosingComments.Length != 0) this.AddToOutput(""); + if (comments.ClosingComments.Length != 0) + { + this.AddToOutput(""); + } } private void AddBlockStatement(string intro, QsScope statements, bool withWhiteSpace = true) { var comments = this.SharedState.StatementComments; - if (this.PrecededByCode && withWhiteSpace) this.AddToOutput(""); + if (this.PrecededByCode && withWhiteSpace) + { + this.AddToOutput(""); + } this.AddComments(comments.OpeningComments); this.AddToOutput($"{intro} {"{"}"); - ++this.CurrentIndendation; + ++this.currentIndendation; this.Transformation.Statements.OnScope(statements); this.AddComments(comments.ClosingComments); - --this.CurrentIndendation; + --this.currentIndendation; this.AddToOutput("}"); } private string SymbolTuple(SymbolTuple sym) { - if (sym.IsDiscardedItem) return "_"; - else if (sym is SymbolTuple.VariableName name) return name.Item.Value; - else if (sym is SymbolTuple.VariableNameTuple tuple) return $"({String.Join(", ", tuple.Item.Select(SymbolTuple))})"; + if (sym.IsDiscardedItem) + { + return "_"; + } + else if (sym is SymbolTuple.VariableName name) + { + return name.Item.Value; + } + else if (sym is SymbolTuple.VariableNameTuple tuple) + { + return $"({string.Join(", ", tuple.Item.Select(this.SymbolTuple))})"; + } else if (sym.IsInvalidItem) { this.SharedState.BeforeInvalidSymbol?.Invoke(); return InvalidSymbol; } - else throw new NotImplementedException("unknown item in symbol tuple"); + else + { + throw new NotImplementedException("unknown item in symbol tuple"); + } } private string InitializerTuple(ResolvedInitializer init) { - if (init.Resolution.IsSingleQubitAllocation) return $"{Keywords.qsQubit.id}()"; + if (init.Resolution.IsSingleQubitAllocation) + { + return $"{Keywords.qsQubit.id}()"; + } else if (init.Resolution is QsInitializerKind.QubitRegisterAllocation reg) - { return $"{Keywords.qsQubit.id}[{this.ExpressionToQs(reg.Item)}]"; } + { + return $"{Keywords.qsQubit.id}[{this.expressionToQs(reg.Item)}]"; + } else if (init.Resolution is QsInitializerKind.QubitTupleAllocation tuple) - { return $"({String.Join(", ", tuple.Item.Select(InitializerTuple))})"; } + { + return $"({string.Join(", ", tuple.Item.Select(this.InitializerTuple))})"; + } else if (init.Resolution.IsInvalidInitializer) { this.SharedState.BeforeInvalidInitializer?.Invoke(); return InvalidInitializer; } - else throw new NotImplementedException("unknown qubit initializer"); + else + { + throw new NotImplementedException("unknown qubit initializer"); + } } - // overrides + /// public override QsStatementKind OnQubitScope(QsQubitScope stm) { var symbols = this.SymbolTuple(stm.Binding.Lhs); var initializers = this.InitializerTuple(stm.Binding.Rhs); string header; - if (stm.Kind.IsBorrow) header = Keywords.qsBorrowing.id; - else if (stm.Kind.IsAllocate) header = Keywords.qsUsing.id; - else throw new NotImplementedException("unknown qubit scope"); + if (stm.Kind.IsBorrow) + { + header = Keywords.qsBorrowing.id; + } + else if (stm.Kind.IsAllocate) + { + header = Keywords.qsUsing.id; + } + else + { + throw new NotImplementedException("unknown qubit scope"); + } var intro = $"{header} ({symbols} = {initializers})"; this.AddBlockStatement(intro, stm.Body); return QsStatementKind.NewQsQubitScope(stm); } + /// public override QsStatementKind OnForStatement(QsForStatement stm) { var symbols = this.SymbolTuple(stm.LoopItem.Item1); - var intro = $"{Keywords.qsFor.id} ({symbols} {Keywords.qsRangeIter.id} {this.ExpressionToQs(stm.IterationValues)})"; + var intro = $"{Keywords.qsFor.id} ({symbols} {Keywords.qsRangeIter.id} {this.expressionToQs(stm.IterationValues)})"; this.AddBlockStatement(intro, stm.Body); return QsStatementKind.NewQsForStatement(stm); } + /// public override QsStatementKind OnWhileStatement(QsWhileStatement stm) { - var intro = $"{Keywords.qsWhile.id} ({this.ExpressionToQs(stm.Condition)})"; + var intro = $"{Keywords.qsWhile.id} ({this.expressionToQs(stm.Condition)})"; this.AddBlockStatement(intro, stm.Body); return QsStatementKind.NewQsWhileStatement(stm); } + /// public override QsStatementKind OnRepeatStatement(QsRepeatStatement stm) { this.SharedState.StatementComments = stm.RepeatBlock.Comments; this.AddBlockStatement(Keywords.qsRepeat.id, stm.RepeatBlock.Body); this.SharedState.StatementComments = stm.FixupBlock.Comments; - this.AddToOutput($"{Keywords.qsUntil.id} ({this.ExpressionToQs(stm.SuccessCondition)})"); + this.AddToOutput($"{Keywords.qsUntil.id} ({this.expressionToQs(stm.SuccessCondition)})"); this.AddBlockStatement(Keywords.qsRUSfixup.id, stm.FixupBlock.Body, false); return QsStatementKind.NewQsRepeatStatement(stm); } + /// public override QsStatementKind OnConditionalStatement(QsConditionalStatement stm) { var header = Keywords.qsIf.id; - if (this.PrecededByCode) this.AddToOutput(""); + if (this.PrecededByCode) + { + this.AddToOutput(""); + } foreach (var clause in stm.ConditionalBlocks) { this.SharedState.StatementComments = clause.Item2.Comments; - var intro = $"{header} ({this.ExpressionToQs(clause.Item1)})"; + var intro = $"{header} ({this.expressionToQs(clause.Item1)})"; this.AddBlockStatement(intro, clause.Item2.Body, false); header = Keywords.qsElif.id; } @@ -994,6 +1165,7 @@ public override QsStatementKind OnConditionalStatement(QsConditionalStatement st return QsStatementKind.NewQsConditionalStatement(stm); } + /// public override QsStatementKind OnConjugation(QsConjugation stm) { this.SharedState.StatementComments = stm.OuterTransformation.Comments; @@ -1003,57 +1175,71 @@ public override QsStatementKind OnConjugation(QsConjugation stm) return QsStatementKind.NewQsConjugation(stm); } - + /// public override QsStatementKind OnExpressionStatement(TypedExpression ex) { - this.AddStatement(this.ExpressionToQs(ex)); + this.AddStatement(this.expressionToQs(ex)); return QsStatementKind.NewQsExpressionStatement(ex); } + /// public override QsStatementKind OnFailStatement(TypedExpression ex) { - this.AddStatement($"{Keywords.qsFail.id} {this.ExpressionToQs(ex)}"); + this.AddStatement($"{Keywords.qsFail.id} {this.expressionToQs(ex)}"); return QsStatementKind.NewQsFailStatement(ex); } + /// public override QsStatementKind OnReturnStatement(TypedExpression ex) { - this.AddStatement($"{Keywords.qsReturn.id} {this.ExpressionToQs(ex)}"); + this.AddStatement($"{Keywords.qsReturn.id} {this.expressionToQs(ex)}"); return QsStatementKind.NewQsReturnStatement(ex); } + /// public override QsStatementKind OnVariableDeclaration(QsBinding stm) { string header; - if (stm.Kind.IsImmutableBinding) header = Keywords.qsImmutableBinding.id; - else if (stm.Kind.IsMutableBinding) header = Keywords.qsMutableBinding.id; - else throw new NotImplementedException("unknown binding kind"); + if (stm.Kind.IsImmutableBinding) + { + header = Keywords.qsImmutableBinding.id; + } + else if (stm.Kind.IsMutableBinding) + { + header = Keywords.qsMutableBinding.id; + } + else + { + throw new NotImplementedException("unknown binding kind"); + } - this.AddStatement($"{header} {this.SymbolTuple(stm.Lhs)} = {this.ExpressionToQs(stm.Rhs)}"); + this.AddStatement($"{header} {this.SymbolTuple(stm.Lhs)} = {this.expressionToQs(stm.Rhs)}"); return QsStatementKind.NewQsVariableDeclaration(stm); } + /// public override QsStatementKind OnValueUpdate(QsValueUpdate stm) { - this.AddStatement($"{Keywords.qsValueUpdate.id} {this.ExpressionToQs(stm.Lhs)} = {this.ExpressionToQs(stm.Rhs)}"); + this.AddStatement($"{Keywords.qsValueUpdate.id} {this.expressionToQs(stm.Lhs)} = {this.expressionToQs(stm.Rhs)}"); return QsStatementKind.NewQsValueUpdate(stm); } } - /// - /// Class used to generate Q# code for Q# statements. + /// Class used to generate Q# code for Q# statements. /// Upon calling Transform, the Output property is set to the Q# code corresponding to the given statement block. /// public class StatementTransformation : StatementTransformation { public StatementTransformation(SyntaxTreeTransformation parent) - : base(parent, TransformationOptions.NoRebuild) { } - + : base(parent, TransformationOptions.NoRebuild) + { + } // overrides + /// public override QsStatement OnStatement(QsStatement stm) { this.SharedState.StatementComments = stm.Comments; @@ -1061,38 +1247,41 @@ public override QsStatement OnStatement(QsStatement stm) } } - public class NamespaceTransformation : NamespaceTransformation { - private int CurrentIndendation = 0; - private string CurrentSpecialization = null; - private int NrSpecialzations = 0; + private int currentIndendation = 0; + private string currentSpecialization = null; + private int nrSpecialzations = 0; - private QsComments DeclarationComments = QsComments.Empty; + private QsComments declarationComments = QsComments.Empty; - private readonly Func TypeToQs; + private readonly Func typeToQs; private List Output => // the sole purpose of this is a shorter name ... this.SharedState.NamespaceOutputHandle; public NamespaceTransformation(SyntaxTreeToQsharp parent) : base(parent, TransformationOptions.NoRebuild) => - this.TypeToQs = parent.ToCode; - + this.typeToQs = parent.ToCode; // private helper functions private void AddToOutput(string line) { - for (var i = 0; i < this.CurrentIndendation; ++i) line = $" {line}"; + for (var i = 0; i < this.currentIndendation; ++i) + { + line = $" {line}"; + } this.Output.Add(line); } private void AddComments(IEnumerable comments) { foreach (var comment in comments) - { this.AddToOutput(String.IsNullOrWhiteSpace(comment) ? "" : $"//{comment}"); } + { + this.AddToOutput(string.IsNullOrWhiteSpace(comment) ? "" : $"//{comment}"); + } } private void AddDirective(string str) @@ -1102,21 +1291,32 @@ private void AddDirective(string str) private void AddDocumentation(IEnumerable doc) { - if (doc == null) return; + if (doc == null) + { + return; + } foreach (var line in doc) - { this.AddToOutput($"///{line}"); } + { + this.AddToOutput($"///{line}"); + } } private void AddBlock(Action processBlock) { - var comments = this.DeclarationComments; + var comments = this.declarationComments; var opening = "{"; - if (!this.Output.Any()) this.AddToOutput(opening); - else this.Output[this.Output.Count - 1] += $" {opening}"; - ++this.CurrentIndendation; + if (!this.Output.Any()) + { + this.AddToOutput(opening); + } + else + { + this.Output[this.Output.Count - 1] += $" {opening}"; + } + ++this.currentIndendation; processBlock(); this.AddComments(comments.ClosingComments); - --this.CurrentIndendation; + --this.currentIndendation; this.AddToOutput("}"); } @@ -1126,38 +1326,58 @@ private void ProcessNamespaceElements(IEnumerable elements) var callables = elements.Where(e => e.IsQsCallable); foreach (var t in types) - { this.OnNamespaceElement(t); } - if (types.Any()) this.AddToOutput(""); + { + this.OnNamespaceElement(t); + } + if (types.Any()) + { + this.AddToOutput(""); + } foreach (var c in callables) - { this.OnNamespaceElement(c); } + { + this.OnNamespaceElement(c); + } } - - // internal static methods + // internal static methods internal static string SymbolName(QsLocalSymbol sym, Action onInvalidName) { - if (sym is QsLocalSymbol.ValidName n) return n.Item.Value; + if (sym is QsLocalSymbol.ValidName n) + { + return n.Item.Value; + } else if (sym.IsInvalidName) { onInvalidName?.Invoke(); return InvalidSymbol; } - else throw new NotImplementedException("unknown case for local symbol"); + else + { + throw new NotImplementedException("unknown case for local symbol"); + } } internal static string TypeParameters(ResolvedSignature sign, Action onInvalidName) { - if (sign.TypeParameters.IsEmpty) return String.Empty; - return $"<{String.Join(", ", sign.TypeParameters.Select(tp => $"'{SymbolName(tp, onInvalidName)}"))}>"; + if (sign.TypeParameters.IsEmpty) + { + return string.Empty; + } + return $"<{string.Join(", ", sign.TypeParameters.Select(tp => $"'{SymbolName(tp, onInvalidName)}"))}>"; } - internal static string ArgumentTuple(QsTuple arg, - Func getItemNameAndType, Func typeTransformation, bool symbolsOnly = false) + internal static string ArgumentTuple( + QsTuple arg, + Func getItemNameAndType, + Func typeTransformation, + bool symbolsOnly = false) { if (arg is QsTuple.QsTuple t) - { return $"({String.Join(", ", t.Item.Select(a => ArgumentTuple(a, getItemNameAndType, typeTransformation, symbolsOnly)))})"; } + { + return $"({string.Join(", ", t.Item.Select(a => ArgumentTuple(a, getItemNameAndType, typeTransformation, symbolsOnly)))})"; + } else if (arg is QsTuple.QsTupleItem i) { var (itemName, itemType) = getItemNameAndType(i.Item); @@ -1165,118 +1385,155 @@ internal static string ArgumentTuple(QsTuple arg, ? $"{(symbolsOnly ? "_" : $"{typeTransformation(itemType)}")}" : $"{itemName}{(symbolsOnly ? "" : $" : {typeTransformation(itemType)}")}"; } - else throw new NotImplementedException("unknown case for argument tuple item"); + else + { + throw new NotImplementedException("unknown case for argument tuple item"); + } } - // overrides - public override Tuple>, QsScope> OnProvidedImplementation - (QsTuple> argTuple, QsScope body) + /// + public override Tuple>, QsScope> OnProvidedImplementation( + QsTuple> argTuple, QsScope body) { var functorArg = "(...)"; - if (this.CurrentSpecialization == Keywords.ctrlDeclHeader.id || this.CurrentSpecialization == Keywords.ctrlAdjDeclHeader.id) + if (this.currentSpecialization == Keywords.ctrlDeclHeader.id || this.currentSpecialization == Keywords.ctrlAdjDeclHeader.id) { var ctlQubitsName = SyntaxGenerator.ControlledFunctorArgument(argTuple); - if (ctlQubitsName != null) functorArg = $"({ctlQubitsName}, ...)"; + if (ctlQubitsName != null) + { + functorArg = $"({ctlQubitsName}, ...)"; + } + } + else if (this.currentSpecialization != Keywords.bodyDeclHeader.id && this.currentSpecialization != Keywords.adjDeclHeader.id) + { + throw new NotImplementedException("the current specialization could not be determined"); } - else if (this.CurrentSpecialization != Keywords.bodyDeclHeader.id && this.CurrentSpecialization != Keywords.adjDeclHeader.id) - { throw new NotImplementedException("the current specialization could not be determined"); } void ProcessContent() { this.SharedState.StatementOutputHandle.Clear(); this.Transformation.Statements.OnScope(body); foreach (var line in this.SharedState.StatementOutputHandle) - { this.AddToOutput(line); } + { + this.AddToOutput(line); + } } - if (this.NrSpecialzations != 1) // todo: needs to be adapted once we support type specializations + if (this.nrSpecialzations != 1) { - this.AddToOutput($"{this.CurrentSpecialization} {functorArg}"); + // todo: needs to be adapted once we support type specializations + this.AddToOutput($"{this.currentSpecialization} {functorArg}"); this.AddBlock(ProcessContent); } else { - var comments = this.DeclarationComments; + var comments = this.declarationComments; ProcessContent(); this.AddComments(comments.ClosingComments); } return new Tuple>, QsScope>(argTuple, body); } + /// public override void OnInvalidGeneratorDirective() { this.SharedState.BeforeInvalidFunctorGenerator?.Invoke(); - this.AddDirective($"{this.CurrentSpecialization} {InvalidFunctorGenerator}"); + this.AddDirective($"{this.currentSpecialization} {InvalidFunctorGenerator}"); } + /// public override void OnDistributeDirective() => - this.AddDirective($"{this.CurrentSpecialization} {Keywords.distributeFunctorGenDirective.id}"); + this.AddDirective($"{this.currentSpecialization} {Keywords.distributeFunctorGenDirective.id}"); + /// public override void OnInvertDirective() => - this.AddDirective($"{this.CurrentSpecialization} {Keywords.invertFunctorGenDirective.id}"); + this.AddDirective($"{this.currentSpecialization} {Keywords.invertFunctorGenDirective.id}"); + /// public override void OnSelfInverseDirective() => - this.AddDirective($"{this.CurrentSpecialization} {Keywords.selfFunctorGenDirective.id}"); + this.AddDirective($"{this.currentSpecialization} {Keywords.selfFunctorGenDirective.id}"); + /// public override void OnIntrinsicImplementation() => - this.AddDirective($"{this.CurrentSpecialization} {Keywords.intrinsicFunctorGenDirective.id}"); + this.AddDirective($"{this.currentSpecialization} {Keywords.intrinsicFunctorGenDirective.id}"); + /// public override void OnExternalImplementation() { this.SharedState.BeforeExternalImplementation?.Invoke(); - this.AddDirective($"{this.CurrentSpecialization} {ExternalImplementation}"); + this.AddDirective($"{this.currentSpecialization} {ExternalImplementation}"); } + /// public override QsSpecialization OnBodySpecialization(QsSpecialization spec) { - this.CurrentSpecialization = Keywords.bodyDeclHeader.id; + this.currentSpecialization = Keywords.bodyDeclHeader.id; return base.OnBodySpecialization(spec); } + /// public override QsSpecialization OnAdjointSpecialization(QsSpecialization spec) { - this.CurrentSpecialization = Keywords.adjDeclHeader.id; + this.currentSpecialization = Keywords.adjDeclHeader.id; return base.OnAdjointSpecialization(spec); } + /// public override QsSpecialization OnControlledSpecialization(QsSpecialization spec) { - this.CurrentSpecialization = Keywords.ctrlDeclHeader.id; + this.currentSpecialization = Keywords.ctrlDeclHeader.id; return base.OnControlledSpecialization(spec); } + /// public override QsSpecialization OnControlledAdjointSpecialization(QsSpecialization spec) { - this.CurrentSpecialization = Keywords.ctrlAdjDeclHeader.id; + this.currentSpecialization = Keywords.ctrlAdjDeclHeader.id; return base.OnControlledAdjointSpecialization(spec); } + /// public override QsSpecialization OnSpecializationDeclaration(QsSpecialization spec) { var precededByCode = TransformationState.PrecededByCode(this.Output); var precededByBlock = TransformationState.PrecededByBlock(this.Output); - if (precededByCode && (precededByBlock || spec.Implementation.IsProvided || spec.Documentation.Any())) this.AddToOutput(""); - this.DeclarationComments = spec.Comments; + if (precededByCode && (precededByBlock || spec.Implementation.IsProvided || spec.Documentation.Any())) + { + this.AddToOutput(""); + } + this.declarationComments = spec.Comments; this.AddComments(spec.Comments.OpeningComments); - if (spec.Comments.OpeningComments.Any() && spec.Documentation.Any()) this.AddToOutput(""); + if (spec.Comments.OpeningComments.Any() && spec.Documentation.Any()) + { + this.AddToOutput(""); + } this.AddDocumentation(spec.Documentation); return base.OnSpecializationDeclaration(spec); } + /// public override QsCallable OnCallableDeclaration(QsCallable c) { - if (c.Kind.IsTypeConstructor) return c; // no code for these + if (c.Kind.IsTypeConstructor) + { + return c; // no code for these + } this.AddToOutput(""); - this.DeclarationComments = c.Comments; + this.declarationComments = c.Comments; this.AddComments(c.Comments.OpeningComments); - if (c.Comments.OpeningComments.Any() && c.Documentation.Any()) this.AddToOutput(""); + if (c.Comments.OpeningComments.Any() && c.Documentation.Any()) + { + this.AddToOutput(""); + } this.AddDocumentation(c.Documentation); foreach (var attribute in c.Attributes) - { this.OnAttribute(attribute); } + { + this.OnAttribute(attribute); + } - var signature = DeclarationSignature(c, this.TypeToQs, this.SharedState.BeforeInvalidSymbol); + var signature = DeclarationSignature(c, this.typeToQs, this.SharedState.BeforeInvalidSymbol); this.Transformation.Types.OnCharacteristicsExpression(c.Signature.Information.Characteristics); var characteristics = this.SharedState.TypeOutputHandle; @@ -1284,59 +1541,96 @@ public override QsCallable OnCallableDeclaration(QsCallable c) var specBundles = SpecializationBundleProperties.Bundle(spec => spec.TypeArguments, spec => spec.Kind, userDefinedSpecs); bool NeedsToBeExplicit(QsSpecialization s) { - if (s.Kind.IsQsBody) return true; + if (s.Kind.IsQsBody) + { + return true; + } else if (s.Implementation is SpecializationImplementation.Generated gen) { - if (gen.Item.IsSelfInverse) return s.Kind.IsQsAdjoint; - if (s.Kind.IsQsControlled || s.Kind.IsQsAdjoint) return false; + if (gen.Item.IsSelfInverse) + { + return s.Kind.IsQsAdjoint; + } + if (s.Kind.IsQsControlled || s.Kind.IsQsAdjoint) + { + return false; + } var relevantUserDefinedSpecs = specBundles.TryGetValue(SpecializationBundleProperties.BundleId(s.TypeArguments), out var dict) ? dict // there may be no user defined implementations for a certain set of type arguments, in which case there is no such entry in the dictionary : ImmutableDictionary.Empty; var userDefAdj = relevantUserDefinedSpecs.ContainsKey(QsSpecializationKind.QsAdjoint); var userDefCtl = relevantUserDefinedSpecs.ContainsKey(QsSpecializationKind.QsControlled); - if (gen.Item.IsInvert) return userDefAdj || !userDefCtl; - if (gen.Item.IsDistribute) return userDefCtl && !userDefAdj; + if (gen.Item.IsInvert) + { + return userDefAdj || !userDefCtl; + } + if (gen.Item.IsDistribute) + { + return userDefCtl && !userDefAdj; + } return false; } - else return !s.Implementation.IsIntrinsic; + else + { + return !s.Implementation.IsIntrinsic; + } } c = c.WithSpecializations(specs => specs.Where(NeedsToBeExplicit).ToImmutableArray()); - this.NrSpecialzations = c.Specializations.Length; + this.nrSpecialzations = c.Specializations.Length; var declHeader = c.Kind.IsOperation ? Keywords.opDeclHeader.id : c.Kind.IsFunction ? Keywords.fctDeclHeader.id : throw new NotImplementedException("unknown callable kind"); - + this.AddToOutput($"{declHeader} {signature}"); - if (!String.IsNullOrWhiteSpace(characteristics)) this.AddToOutput($"{Keywords.qsCharacteristics.id} {characteristics}"); + if (!string.IsNullOrWhiteSpace(characteristics)) + { + this.AddToOutput($"{Keywords.qsCharacteristics.id} {characteristics}"); + } this.AddBlock(() => c.Specializations.Select(this.OnSpecializationDeclaration).ToImmutableArray()); this.AddToOutput(""); return c; } + /// public override QsCustomType OnTypeDeclaration(QsCustomType t) { this.AddToOutput(""); - this.DeclarationComments = t.Comments; // no need to deal with closing comments (can't exist), but need to make sure DeclarationComments is up to date + this.declarationComments = t.Comments; // no need to deal with closing comments (can't exist), but need to make sure DeclarationComments is up to date this.AddComments(t.Comments.OpeningComments); - if (t.Comments.OpeningComments.Any() && t.Documentation.Any()) this.AddToOutput(""); + if (t.Comments.OpeningComments.Any() && t.Documentation.Any()) + { + this.AddToOutput(""); + } this.AddDocumentation(t.Documentation); foreach (var attribute in t.Attributes) - { this.OnAttribute(attribute); } + { + this.OnAttribute(attribute); + } (string, ResolvedType) GetItemNameAndType(QsTypeItem item) { - if (item is QsTypeItem.Named named) return (named.Item.VariableName.Value, named.Item.Type); - else if (item is QsTypeItem.Anonymous type) return (null, type.Item); - else throw new NotImplementedException("unknown case for type item"); + if (item is QsTypeItem.Named named) + { + return (named.Item.VariableName.Value, named.Item.Type); + } + else if (item is QsTypeItem.Anonymous type) + { + return (null, type.Item); + } + else + { + throw new NotImplementedException("unknown case for type item"); + } } - var udtTuple = ArgumentTuple(t.TypeItems, GetItemNameAndType, this.TypeToQs); + var udtTuple = ArgumentTuple(t.TypeItems, GetItemNameAndType, this.typeToQs); this.AddDirective($"{Keywords.typeDeclHeader.id} {t.FullName.Name.Value} = {udtTuple}"); return t; } + /// public override QsDeclarationAttribute OnAttribute(QsDeclarationAttribute att) { // do *not* set DeclarationComments! @@ -1352,6 +1646,7 @@ public override QsDeclarationAttribute OnAttribute(QsDeclarationAttribute att) return att; } + /// public override QsNamespace OnNamespace(QsNamespace ns) { if (this.SharedState.Context.CurrentNamespace != ns.Name.Value) @@ -1367,12 +1662,22 @@ public override QsNamespace OnNamespace(QsNamespace ns) { var context = this.SharedState.Context; var explicitImports = context.OpenedNamespaces.Where(opened => !BuiltIn.NamespacesToAutoOpen.Contains(opened)); - if (explicitImports.Any() || context.NamespaceShortNames.Any()) this.AddToOutput(""); + if (explicitImports.Any() || context.NamespaceShortNames.Any()) + { + this.AddToOutput(""); + } foreach (var nsName in explicitImports.OrderBy(name => name)) - { this.AddDirective($"{Keywords.importDirectiveHeader.id} {nsName.Value}"); } + { + this.AddDirective($"{Keywords.importDirectiveHeader.id} {nsName.Value}"); + } foreach (var kv in context.NamespaceShortNames.OrderBy(pair => pair.Key)) - { this.AddDirective($"{Keywords.importDirectiveHeader.id} {kv.Key.Value} {Keywords.importedAs.id} {kv.Value.Value}"); } - if (explicitImports.Any() || context.NamespaceShortNames.Any()) this.AddToOutput(""); + { + this.AddDirective($"{Keywords.importDirectiveHeader.id} {kv.Key.Value} {Keywords.importedAs.id} {kv.Value.Value}"); + } + if (explicitImports.Any() || context.NamespaceShortNames.Any()) + { + this.AddToOutput(""); + } this.ProcessNamespaceElements(ns.Elements); }); @@ -1381,4 +1686,3 @@ public override QsNamespace OnNamespace(QsNamespace ns) } } } - diff --git a/src/QsCompiler/Transformations/SearchAndReplace.cs b/src/QsCompiler/Transformations/SearchAndReplace.cs index f1a66f5b7f..a43bda8934 100644 --- a/src/QsCompiler/Transformations/SearchAndReplace.cs +++ b/src/QsCompiler/Transformations/SearchAndReplace.cs @@ -13,13 +13,11 @@ using Microsoft.Quantum.QsCompiler.Transformations.Core; using Microsoft.Quantum.QsCompiler.Transformations.QsCodeOutput; - namespace Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace { - using QsTypeKind = QsTypeKind; using QsExpressionKind = QsExpressionKind; using QsRangeInfo = QsNullable>; - + using QsTypeKind = QsTypeKind; // routines for finding occurrences of symbols/identifiers @@ -33,14 +31,17 @@ public class IdentifierReferences public class Location : IEquatable { public readonly NonNullable SourceFile; + /// /// contains the offset of the root node relative to which the statement location is given /// public readonly Tuple DeclarationOffset; + /// /// contains the location of the statement containing the symbol relative to the root node /// public readonly QsLocation RelativeStatementLocation; + /// /// contains the range of the symbol relative to the statement position /// @@ -54,6 +55,7 @@ public Location(NonNullable source, Tuple declOffset, QsLocati this.SymbolRange = range ?? throw new ArgumentNullException(nameof(range)); } + /// public bool Equals(Location other) => this.SourceFile.Value == other?.SourceFile.Value && this.DeclarationOffset.Equals(other?.DeclarationOffset) @@ -62,21 +64,34 @@ public bool Equals(Location other) => && this.SymbolRange.Item1.Equals(other?.SymbolRange?.Item1) && this.SymbolRange.Item2.Equals(other?.SymbolRange?.Item2); + /// public override bool Equals(object obj) => this.Equals(obj as Location); + /// public override int GetHashCode() { var (hash, multiplier) = (0x51ed270b, -1521134295); - if (this.DeclarationOffset != null) hash = (hash * multiplier) + this.DeclarationOffset.GetHashCode(); - if (this.RelativeStatementLocation.Offset != null) hash = (hash * multiplier) + this.RelativeStatementLocation.Offset.GetHashCode(); - if (this.RelativeStatementLocation.Range != null) hash = (hash * multiplier) + this.RelativeStatementLocation.Range.GetHashCode(); - if (this.SymbolRange != null) hash = (hash * multiplier) + this.SymbolRange.GetHashCode(); + if (this.DeclarationOffset != null) + { + hash = (hash * multiplier) + this.DeclarationOffset.GetHashCode(); + } + if (this.RelativeStatementLocation.Offset != null) + { + hash = (hash * multiplier) + this.RelativeStatementLocation.Offset.GetHashCode(); + } + if (this.RelativeStatementLocation.Range != null) + { + hash = (hash * multiplier) + this.RelativeStatementLocation.Range.GetHashCode(); + } + if (this.SymbolRange != null) + { + hash = (hash * multiplier) + this.SymbolRange.GetHashCode(); + } return this.SourceFile.Value == null ? hash : (hash * multiplier) + this.SourceFile.Value.GetHashCode(); } } - /// /// Class used to track the internal state for a transformation that finds all locations where a certain identifier occurs. /// If no source file is specified prior to transformation, its name is set to the empty string. @@ -86,49 +101,51 @@ public override int GetHashCode() public class TransformationState { public Tuple, QsLocation> DeclarationLocation { get; internal set; } + public ImmutableHashSet Locations { get; private set; } /// /// Whenever DeclarationOffset is set, the current statement offset is set to this default value. /// - private readonly QsLocation DefaultOffset = null; - private readonly IImmutableSet> RelevantSourseFiles = null; + private readonly QsLocation defaultOffset = null; + private readonly IImmutableSet> relevantSourseFiles = null; internal bool IsRelevant(NonNullable source) => - this.RelevantSourseFiles?.Contains(source) ?? true; - + this.relevantSourseFiles?.Contains(source) ?? true; - internal TransformationState(Func trackId, - QsLocation defaultOffset = null, IImmutableSet> limitToSourceFiles = null) + internal TransformationState( + Func trackId, + QsLocation defaultOffset = null, + IImmutableSet> limitToSourceFiles = null) { this.TrackIdentifier = trackId ?? throw new ArgumentNullException(nameof(trackId)); - this.RelevantSourseFiles = limitToSourceFiles; + this.relevantSourseFiles = limitToSourceFiles; this.Locations = ImmutableHashSet.Empty; - this.DefaultOffset = defaultOffset; + this.defaultOffset = defaultOffset; } - private NonNullable CurrentSourceFile = NonNullable.New(""); - private Tuple RootOffset = null; + private NonNullable currentSourceFile = NonNullable.New(""); + private Tuple rootOffset = null; internal QsLocation CurrentLocation = null; internal readonly Func TrackIdentifier; public Tuple DeclarationOffset { - internal get => this.RootOffset; + internal get => this.rootOffset; set { - this.RootOffset = value ?? throw new ArgumentNullException(nameof(value), "declaration offset cannot be null"); - this.CurrentLocation = this.DefaultOffset; + this.rootOffset = value ?? throw new ArgumentNullException(nameof(value), "declaration offset cannot be null"); + this.CurrentLocation = this.defaultOffset; } } public NonNullable Source { - internal get => this.CurrentSourceFile; + internal get => this.currentSourceFile; set { - this.CurrentSourceFile = value; - this.RootOffset = null; + this.currentSourceFile = value; + this.rootOffset = null; this.CurrentLocation = null; } } @@ -137,7 +154,7 @@ internal void LogIdentifierLocation(Identifier id, QsRangeInfo range) { if (this.TrackIdentifier(id) && this.CurrentLocation?.Offset != null && range.IsValue) { - var idLoc = new Location(this.Source, this.RootOffset, this.CurrentLocation, range.Item); + var idLoc = new Location(this.Source, this.rootOffset, this.CurrentLocation, range.Item); this.Locations = this.Locations.Add(idLoc); } } @@ -145,33 +162,42 @@ internal void LogIdentifierLocation(Identifier id, QsRangeInfo range) internal void LogIdentifierLocation(TypedExpression ex) { if (ex.Expression is QsExpressionKind.Identifier id) - { this.LogIdentifierLocation(id.Item1, ex.Range); } + { + this.LogIdentifierLocation(id.Item1, ex.Range); + } } } - public IdentifierReferences(TransformationState state) + public IdentifierReferences(TransformationState state) : base(state, TransformationOptions.NoRebuild) - { + { this.Types = new TypeTransformation(this); this.Expressions = new TypedExpressionWalker(this.SharedState.LogIdentifierLocation, this); this.Statements = new StatementTransformation(this); this.Namespaces = new NamespaceTransformation(this); } - public IdentifierReferences(NonNullable idName, QsLocation defaultOffset, IImmutableSet> limitToSourceFiles = null) - : this(new TransformationState(id => id is Identifier.LocalVariable varName && varName.Item.Value == idName.Value, defaultOffset, limitToSourceFiles)) { } + public IdentifierReferences(NonNullable idName, QsLocation defaultOffset, IImmutableSet> limitToSourceFiles = null) + : this(new TransformationState(id => id is Identifier.LocalVariable varName && varName.Item.Value == idName.Value, defaultOffset, limitToSourceFiles)) + { + } - public IdentifierReferences(QsQualifiedName idName, QsLocation defaultOffset, IImmutableSet> limitToSourceFiles = null) + public IdentifierReferences(QsQualifiedName idName, QsLocation defaultOffset, IImmutableSet> limitToSourceFiles = null) : this(new TransformationState(id => id is Identifier.GlobalCallable cName && cName.Item.Equals(idName), defaultOffset, limitToSourceFiles)) { - if (idName == null) throw new ArgumentNullException(nameof(idName)); + if (idName == null) + { + throw new ArgumentNullException(nameof(idName)); + } } - // static methods for convenience - public static ImmutableHashSet Find(NonNullable idName, QsScope scope, - NonNullable sourceFile, Tuple rootLoc) + public static ImmutableHashSet Find( + NonNullable idName, + QsScope scope, + NonNullable sourceFile, + Tuple rootLoc) { var finder = new IdentifierReferences(idName, null, ImmutableHashSet.Create(sourceFile)); finder.SharedState.Source = sourceFile; @@ -180,8 +206,12 @@ public static ImmutableHashSet Find(NonNullable idName, QsScop return finder.SharedState.Locations; } - public static ImmutableHashSet Find(QsQualifiedName idName, QsNamespace ns, QsLocation defaultOffset, - out Tuple, QsLocation> declarationLocation, IImmutableSet> limitToSourceFiles = null) + public static ImmutableHashSet Find( + QsQualifiedName idName, + QsNamespace ns, + QsLocation defaultOffset, + out Tuple, QsLocation> declarationLocation, + IImmutableSet> limitToSourceFiles = null) { var finder = new IdentifierReferences(idName, defaultOffset, limitToSourceFiles); finder.Namespaces.OnNamespace(ns ?? throw new ArgumentNullException(nameof(ns))); @@ -189,14 +219,15 @@ public static ImmutableHashSet Find(QsQualifiedName idName, QsNamespac return finder.SharedState.Locations; } - // helper classes - private class TypeTransformation + private class TypeTransformation : TypeTransformation { - public TypeTransformation(SyntaxTreeTransformation parent) - : base(parent, TransformationOptions.NoRebuild) { } + public TypeTransformation(SyntaxTreeTransformation parent) + : base(parent, TransformationOptions.NoRebuild) + { + } public override QsTypeKind OnUserDefinedType(UserDefinedType udt) { @@ -214,11 +245,13 @@ public override QsTypeKind OnTypeParameter(QsTypeParameter tp) } } - private class StatementTransformation + private class StatementTransformation : StatementTransformation { public StatementTransformation(SyntaxTreeTransformation parent) - : base(parent, TransformationOptions.NoRebuild) { } + : base(parent, TransformationOptions.NoRebuild) + { + } public override QsNullable OnLocation(QsNullable loc) { @@ -227,26 +260,37 @@ public override QsNullable OnLocation(QsNullable loc) } } - private class NamespaceTransformation + private class NamespaceTransformation : NamespaceTransformation { - public NamespaceTransformation(SyntaxTreeTransformation parent) - : base(parent, TransformationOptions.NoRebuild) { } + : base(parent, TransformationOptions.NoRebuild) + { + } public override QsCustomType OnTypeDeclaration(QsCustomType t) { - if (!this.SharedState.IsRelevant(t.SourceFile) || t.Location.IsNull) return t; + if (!this.SharedState.IsRelevant(t.SourceFile) || t.Location.IsNull) + { + return t; + } if (this.SharedState.TrackIdentifier(Identifier.NewGlobalCallable(t.FullName))) - { this.SharedState.DeclarationLocation = new Tuple, QsLocation>(t.SourceFile, t.Location.Item); } + { + this.SharedState.DeclarationLocation = new Tuple, QsLocation>(t.SourceFile, t.Location.Item); + } return base.OnTypeDeclaration(t); } public override QsCallable OnCallableDeclaration(QsCallable c) { - if (!this.SharedState.IsRelevant(c.SourceFile) || c.Location.IsNull) return c; + if (!this.SharedState.IsRelevant(c.SourceFile) || c.Location.IsNull) + { + return c; + } if (this.SharedState.TrackIdentifier(Identifier.NewGlobalCallable(c.FullName))) - { this.SharedState.DeclarationLocation = new Tuple, QsLocation>(c.SourceFile, c.Location.Item); } + { + this.SharedState.DeclarationLocation = new Tuple, QsLocation>(c.SourceFile, c.Location.Item); + } return base.OnCallableDeclaration(c); } @@ -254,7 +298,10 @@ public override QsDeclarationAttribute OnAttribute(QsDeclarationAttribute att) { var declRoot = this.SharedState.DeclarationOffset; this.SharedState.DeclarationOffset = att.Offset; - if (att.TypeId.IsValue) this.Transformation.Types.OnUserDefinedType(att.TypeId.Item); + if (att.TypeId.IsValue) + { + this.Transformation.Types.OnUserDefinedType(att.TypeId.Item); + } this.Transformation.Expressions.OnTypedExpression(att.Argument); this.SharedState.DeclarationOffset = declRoot; return att; @@ -277,7 +324,6 @@ public override NonNullable OnSourceFile(NonNullable source) } } - // routines for finding all symbols/identifiers /// @@ -293,18 +339,17 @@ public class TransformationState internal QsLocation StatementLocation = null; internal Func UpdatedExpression; - private readonly List<(NonNullable, QsLocation)> UpdatedLocals = new List<(NonNullable, QsLocation)>(); - private readonly List<(NonNullable, QsLocation)> UsedLocals = new List<(NonNullable, QsLocation)>(); + private readonly List<(NonNullable, QsLocation)> updatedLocals = new List<(NonNullable, QsLocation)>(); + private readonly List<(NonNullable, QsLocation)> usedLocals = new List<(NonNullable, QsLocation)>(); internal TransformationState() => this.UpdatedExpression = new TypedExpressionWalker(this.UpdatedLocal, this).OnTypedExpression; public ILookup, QsLocation> ReassignedVariables => - this.UpdatedLocals.ToLookup(var => var.Item1, var => var.Item2); + this.updatedLocals.ToLookup(var => var.Item1, var => var.Item2); public ILookup, QsLocation> UsedLocalVariables => - this.UsedLocals.ToLookup(var => var.Item1, var => var.Item2); - + this.usedLocals.ToLookup(var => var.Item1, var => var.Item2); private Action Add(List<(NonNullable, QsLocation)> accumulate) => (TypedExpression ex) => { @@ -316,12 +361,12 @@ private Action Add(List<(NonNullable, QsLocation)> accu } }; - internal Action UsedLocal => Add(this.UsedLocals); - internal Action UpdatedLocal => Add(this.UpdatedLocals); - } + internal Action UsedLocal => this.Add(this.usedLocals); + internal Action UpdatedLocal => this.Add(this.updatedLocals); + } - public AccumulateIdentifiers() + public AccumulateIdentifiers() : base(new TransformationState(), TransformationOptions.NoRebuild) { this.Statements = new StatementTransformation(this); @@ -330,14 +375,15 @@ public AccumulateIdentifiers() this.Types = new TypeTransformation(this, TransformationOptions.Disabled); } - // helper classes - private class StatementTransformation + private class StatementTransformation : StatementTransformation { public StatementTransformation(SyntaxTreeTransformation parent) - : base(parent, TransformationOptions.NoRebuild) { } + : base(parent, TransformationOptions.NoRebuild) + { + } public override QsStatement OnStatement(QsStatement stm) { @@ -347,11 +393,13 @@ public override QsStatement OnStatement(QsStatement stm) } } - private class StatementKindTransformation + private class StatementKindTransformation : StatementKindTransformation { public StatementKindTransformation(SyntaxTreeTransformation parent) - : base(parent, TransformationOptions.NoRebuild) { } + : base(parent, TransformationOptions.NoRebuild) + { + } public override QsStatementKind OnValueUpdate(QsValueUpdate stm) { @@ -362,7 +410,6 @@ public override QsStatementKind OnValueUpdate(QsValueUpdate stm) } } - // routines for replacing symbols/identifiers /// @@ -370,7 +417,7 @@ public override QsStatementKind OnValueUpdate(QsValueUpdate stm) /// public class NameDecorator { - private const string original = "original"; + private const string Original = "original"; private readonly string label; @@ -383,7 +430,7 @@ public class NameDecorator public NameDecorator(string label) { this.label = label; - pattern = new Regex($"^__{Regex.Escape(label)}_?[0-9]*__(?<{original}>.*)__$"); + this.pattern = new Regex($"^__{Regex.Escape(label)}_?[0-9]*__(?<{Original}>.*)__$"); } /// @@ -392,8 +439,8 @@ public NameDecorator(string label) /// The name to decorate. /// The number to use along with the label to decorate the name. /// The decorated name. - public string Decorate(string name, int number) => - $"__{label}{(number < -0 ? "_" : "")}{Math.Abs(number)}__{name}__"; + public string Decorate(string name, int number) => + $"__{this.label}{(number < -0 ? "_" : "")}{Math.Abs(number)}__{name}__"; /// /// Decorates the name of the qualified name with the label of this name decorator and the given number. @@ -402,7 +449,7 @@ public string Decorate(string name, int number) => /// The number to use along with the label to decorate the qualified name. /// The decorated qualified name. public QsQualifiedName Decorate(QsQualifiedName name, int number) => - new QsQualifiedName(name.Namespace, NonNullable.New(Decorate(name.Name.Value, number))); + new QsQualifiedName(name.Namespace, NonNullable.New(this.Decorate(name.Name.Value, number))); /// /// Reverses decoration previously done to the name using the same label as this name decorator. @@ -414,7 +461,7 @@ public QsQualifiedName Decorate(QsQualifiedName name, int number) => /// public string Undecorate(string name) { - var match = pattern.Match(name).Groups[original]; + var match = this.pattern.Match(name).Groups[Original]; return match.Success ? match.Value : null; } } @@ -427,19 +474,19 @@ public string Undecorate(string name) public class UniqueVariableNames : SyntaxTreeTransformation { - private static readonly NameDecorator decorator = new NameDecorator("qsVar"); + private static readonly NameDecorator Decorator = new NameDecorator("qsVar"); public class TransformationState { - private int VariableNr = 0; - private readonly Dictionary, NonNullable> UniqueNames = + private int variableNr = 0; + private readonly Dictionary, NonNullable> uniqueNames = new Dictionary, NonNullable>(); internal bool TryGetUniqueName(NonNullable name, out NonNullable unique) => - this.UniqueNames.TryGetValue(name, out unique); + this.uniqueNames.TryGetValue(name, out unique); internal QsExpressionKind AdaptIdentifier(Identifier sym, QsNullable> tArgs) => - sym is Identifier.LocalVariable varName && this.UniqueNames.TryGetValue(varName.Item, out var unique) + sym is Identifier.LocalVariable varName && this.uniqueNames.TryGetValue(varName.Item, out var unique) ? QsExpressionKind.NewIdentifier(Identifier.NewLocalVariable(unique), tArgs) : QsExpressionKind.NewIdentifier(sym, tArgs); @@ -448,13 +495,12 @@ internal QsExpressionKind AdaptIdentifier(Identifier sym, QsNullable internal NonNullable GenerateUniqueName(NonNullable varName) { - var unique = NonNullable.New(decorator.Decorate(varName.Value, VariableNr++)); - this.UniqueNames[varName] = unique; + var unique = NonNullable.New(Decorator.Decorate(varName.Value, this.variableNr++)); + this.uniqueNames[varName] = unique; return unique; } } - public UniqueVariableNames() : base(new TransformationState()) { @@ -464,7 +510,6 @@ public UniqueVariableNames() this.Types = new TypeTransformation(this, TransformationOptions.Disabled); } - // static methods for convenience internal static QsQualifiedName PrependGuid(QsQualifiedName original) => @@ -473,8 +518,7 @@ internal static QsQualifiedName PrependGuid(QsQualifiedName original) => NonNullable.New("_" + Guid.NewGuid().ToString("N") + "_" + original.Name.Value)); public static NonNullable StripUniqueName(NonNullable uniqueName) => - NonNullable.New(decorator.Undecorate(uniqueName.Value) ?? uniqueName.Value); - + NonNullable.New(Decorator.Undecorate(uniqueName.Value) ?? uniqueName.Value); // helper classes @@ -482,7 +526,9 @@ private class StatementTransformation : StatementTransformation { public StatementTransformation(SyntaxTreeTransformation parent) - : base(parent) { } + : base(parent) + { + } public override NonNullable OnVariableName(NonNullable name) => this.SharedState.TryGetUniqueName(name, out var unique) ? unique : name; @@ -492,7 +538,9 @@ private class StatementKindTransformation : StatementKindTransformation { public StatementKindTransformation(SyntaxTreeTransformation parent) - : base(parent) { } + : base(parent) + { + } public override SymbolTuple OnSymbolTuple(SymbolTuple syms) => syms is SymbolTuple.VariableNameTuple tuple @@ -506,7 +554,9 @@ private class ExpressionKindTransformation : ExpressionKindTransformation { public ExpressionKindTransformation(SyntaxTreeTransformation parent) - : base(parent) { } + : base(parent) + { + } public override QsExpressionKind OnIdentifier(Identifier sym, QsNullable> tArgs) => this.SharedState.AdaptIdentifier(sym, tArgs); @@ -532,7 +582,7 @@ internal TransformationState(IImmutableDictionary /// The renamed version of the qualified name if one exists; otherwise, returns the original name. /// - internal QsQualifiedName GetNewName(QsQualifiedName name) => names.GetValueOrDefault(name) ?? name; + internal QsQualifiedName GetNewName(QsQualifiedName name) => this.names.GetValueOrDefault(name) ?? name; /// /// Gets the renamed version of the user-defined type if one exists; otherwise, returns the original one. @@ -542,13 +592,12 @@ internal TransformationState(IImmutableDictionary internal UserDefinedType RenameUdt(UserDefinedType udt) { - var newName = GetNewName(new QsQualifiedName(udt.Namespace, udt.Name)); + var newName = this.GetNewName(new QsQualifiedName(udt.Namespace, udt.Name)); return new UserDefinedType(newName.Namespace, newName.Name, udt.Range); } } - - private readonly TransformationState State; + private readonly TransformationState state; /// /// Creates a new rename references transformation. @@ -556,13 +605,12 @@ internal UserDefinedType RenameUdt(UserDefinedType udt) /// The mapping from existing names to new names. public RenameReferences(IImmutableDictionary names) { - State = new TransformationState(names); - Types = new TypeTransformation(this); - ExpressionKinds = new ExpressionKindTransformation(this); - Namespaces = new NamespaceTransformation(this); + this.state = new TransformationState(names); + this.Types = new TypeTransformation(this); + this.ExpressionKinds = new ExpressionKindTransformation(this); + this.Namespaces = new NamespaceTransformation(this); } - // methods for transformations on headers /// @@ -573,15 +621,15 @@ public RenameReferences(IImmutableDictionary n public CallableDeclarationHeader OnCallableDeclarationHeader(CallableDeclarationHeader callable) => new CallableDeclarationHeader( kind: callable.Kind, - qualifiedName: State.GetNewName(callable.QualifiedName), - attributes: callable.Attributes.Select(Namespaces.OnAttribute).ToImmutableArray(), + qualifiedName: this.state.GetNewName(callable.QualifiedName), + attributes: callable.Attributes.Select(this.Namespaces.OnAttribute).ToImmutableArray(), modifiers: callable.Modifiers, sourceFile: callable.SourceFile, position: callable.Position, symbolRange: callable.SymbolRange, - argumentTuple: Namespaces.OnArgumentTuple(callable.ArgumentTuple), - signature: Namespaces.OnSignature(callable.Signature), - documentation: Namespaces.OnDocumentation(callable.Documentation)); + argumentTuple: this.Namespaces.OnArgumentTuple(callable.ArgumentTuple), + signature: this.Namespaces.OnSignature(callable.Signature), + documentation: this.Namespaces.OnDocumentation(callable.Documentation)); /// /// Renames references in the specialization declaration header, including the name of the specialization @@ -595,18 +643,18 @@ public SpecializationDeclarationHeader OnSpecializationDeclarationHeader( var typeArguments = specialization.TypeArguments.IsValue ? QsNullable>.NewValue( - specialization.TypeArguments.Item.Select(Types.OnType).ToImmutableArray()) + specialization.TypeArguments.Item.Select(this.Types.OnType).ToImmutableArray()) : QsNullable>.Null; return new SpecializationDeclarationHeader( kind: specialization.Kind, typeArguments: typeArguments, information: specialization.Information, - parent: State.GetNewName(specialization.Parent), - attributes: specialization.Attributes.Select(Namespaces.OnAttribute).ToImmutableArray(), + parent: this.state.GetNewName(specialization.Parent), + attributes: specialization.Attributes.Select(this.Namespaces.OnAttribute).ToImmutableArray(), sourceFile: specialization.SourceFile, position: specialization.Position, headerRange: specialization.HeaderRange, - documentation: Namespaces.OnDocumentation(specialization.Documentation)); + documentation: this.Namespaces.OnDocumentation(specialization.Documentation)); } /// @@ -617,46 +665,45 @@ public SpecializationDeclarationHeader OnSpecializationDeclarationHeader( public TypeDeclarationHeader OnTypeDeclarationHeader(TypeDeclarationHeader type) { return new TypeDeclarationHeader( - qualifiedName: State.GetNewName(type.QualifiedName), - attributes: type.Attributes.Select(Namespaces.OnAttribute).ToImmutableArray(), + qualifiedName: this.state.GetNewName(type.QualifiedName), + attributes: type.Attributes.Select(this.Namespaces.OnAttribute).ToImmutableArray(), modifiers: type.Modifiers, sourceFile: type.SourceFile, position: type.Position, symbolRange: type.SymbolRange, - type: Types.OnType(type.Type), - typeItems: Namespaces.OnTypeItems(type.TypeItems), - documentation: Namespaces.OnDocumentation(type.Documentation)); + type: this.Types.OnType(type.Type), + typeItems: this.Namespaces.OnTypeItems(type.TypeItems), + documentation: this.Namespaces.OnDocumentation(type.Documentation)); } - // private helper classes private class TypeTransformation : Core.TypeTransformation { - private readonly TransformationState State; + private readonly TransformationState state; public TypeTransformation(RenameReferences parent) : base(parent) => - this.State = parent.State; + this.state = parent.state; public override QsTypeKind OnUserDefinedType(UserDefinedType udt) => - QsTypeKind.NewUserDefinedType(State.RenameUdt(udt)); + QsTypeKind.NewUserDefinedType(this.state.RenameUdt(udt)); public override QsTypeKind OnTypeParameter(QsTypeParameter tp) => - QsTypeKind.NewTypeParameter(new QsTypeParameter(State.GetNewName(tp.Origin), tp.TypeName, tp.Range)); + QsTypeKind.NewTypeParameter(new QsTypeParameter(this.state.GetNewName(tp.Origin), tp.TypeName, tp.Range)); } private class ExpressionKindTransformation : Core.ExpressionKindTransformation { - private readonly TransformationState State; + private readonly TransformationState state; public ExpressionKindTransformation(RenameReferences parent) : base(parent) => - this.State = parent.State; + this.state = parent.state; public override QsExpressionKind OnIdentifier(Identifier id, QsNullable> typeArgs) { if (id is Identifier.GlobalCallable global) { - id = Identifier.NewGlobalCallable(State.GetNewName(global.Item)); + id = Identifier.NewGlobalCallable(this.state.GetNewName(global.Item)); } return base.OnIdentifier(id, typeArgs); } @@ -664,28 +711,28 @@ public override QsExpressionKind OnIdentifier(Identifier id, QsNullable - this.State = parent.State; + this.state = parent.state; public override QsDeclarationAttribute OnAttribute(QsDeclarationAttribute attribute) { - var argument = Transformation.Expressions.OnTypedExpression(attribute.Argument); + var argument = this.Transformation.Expressions.OnTypedExpression(attribute.Argument); var typeId = attribute.TypeId.IsValue - ? QsNullable.NewValue(State.RenameUdt(attribute.TypeId.Item)) + ? QsNullable.NewValue(this.state.RenameUdt(attribute.TypeId.Item)) : attribute.TypeId; return new QsDeclarationAttribute(typeId, argument, attribute.Offset, attribute.Comments); } public override QsCallable OnCallableDeclaration(QsCallable callable) => - base.OnCallableDeclaration(callable.WithFullName(State.GetNewName)); + base.OnCallableDeclaration(callable.WithFullName(this.state.GetNewName)); public override QsCustomType OnTypeDeclaration(QsCustomType type) => - base.OnTypeDeclaration(type.WithFullName(State.GetNewName)); + base.OnTypeDeclaration(type.WithFullName(this.state.GetNewName)); public override QsSpecialization OnSpecializationDeclaration(QsSpecialization spec) => - base.OnSpecializationDeclaration(spec.WithParent(State.GetNewName)); + base.OnSpecializationDeclaration(spec.WithParent(this.state.GetNewName)); } } } diff --git a/src/QsCompiler/Transformations/Transformations.csproj b/src/QsCompiler/Transformations/Transformations.csproj index 092230b79f..1ade4fb78e 100644 --- a/src/QsCompiler/Transformations/Transformations.csproj +++ b/src/QsCompiler/Transformations/Transformations.csproj @@ -14,8 +14,16 @@ + + + + + + + + diff --git a/src/QuantumSdk/DefaultItems/DefaultItems.targets b/src/QuantumSdk/DefaultItems/DefaultItems.targets index a9e001f517..09d09a1468 100644 --- a/src/QuantumSdk/DefaultItems/DefaultItems.targets +++ b/src/QuantumSdk/DefaultItems/DefaultItems.targets @@ -28,31 +28,31 @@ Possible values are 'Exe', or 'Library'. - + - HoneywellProcessor - IonQProcessor - QCIProcessor - Unspecified - + HoneywellProcessor + IonQProcessor + QCIProcessor + Unspecified + Possible values must match 'ionq.*', 'honeywell.*', 'qci.*', or 'Any'. The execution target for a Q# library needs to be 'Any'. - OpenQASM - ExtendedQASM - OpenQASM - Default + OpenQASM + ExtendedQASM + OpenQASM + Default - QPRGen1 - QPRGen0 - QPRGen1 - Unknown + QPRGen1 + QPRGen0 + QPRGen1 + Unknown diff --git a/src/QuantumSdk/QuantumSdk.nuspec b/src/QuantumSdk/QuantumSdk.nuspec index b3ffa5ec23..13e221edd5 100644 --- a/src/QuantumSdk/QuantumSdk.nuspec +++ b/src/QuantumSdk/QuantumSdk.nuspec @@ -1,5 +1,5 @@ - + Microsoft.Quantum.Sdk $version$ @@ -11,7 +11,7 @@ The Microsoft Quantum Sdk for developing in Q#. See: https://docs.microsoft.com/en-us/quantum/relnotes/ https://github.com/microsoft/qsharp-compiler - https://secure.gravatar.com/avatar/bd1f02955b2853ba0a3b1cdc2434e8ec.png + images\qdk-nuget-icon.png © Microsoft Corporation. All rights reserved. Quantum Q# Qsharp @@ -24,5 +24,6 @@ + diff --git a/src/QuantumSdk/Sdk/Sdk.props b/src/QuantumSdk/Sdk/Sdk.props index faf7be0d8a..5b96ae2dd5 100644 --- a/src/QuantumSdk/Sdk/Sdk.props +++ b/src/QuantumSdk/Sdk/Sdk.props @@ -26,9 +26,9 @@ - - - + + + diff --git a/src/QuantumSdk/Sdk/Sdk.targets b/src/QuantumSdk/Sdk/Sdk.targets index 3ad2f317f0..436cd9a4ea 100644 --- a/src/QuantumSdk/Sdk/Sdk.targets +++ b/src/QuantumSdk/Sdk/Sdk.targets @@ -27,7 +27,7 @@ - + @@ -77,8 +77,9 @@ <_QscCommandRuntimeFlag>--runtime $(ResolvedRuntimeCapabilities) <_QscCommandTargetDecompositionsFlag Condition="@(ResolvedTargetSpecificDecompositions->Count()) > 0">--target-specific-decompositions "@(ResolvedTargetSpecificDecompositions,'" "')" <_QscCommandTestNamesFlag Condition="$(ExposeReferencesViaTestNames)">--load-test-names - <_QscCommandPredefinedAssemblyProperties>ResolvedExecutionTarget:$(ResolvedQsharpExecutionTarget) ResolvedQsharpOutputType:$(ResolvedQsharpOutputType) + <_QscCommandPredefinedAssemblyProperties>ProcessorArchitecture:$(ResolvedProcessorArchitecture) QsharpOutputType:$(ResolvedQsharpOutputType) <_QscCommandPredefinedAssemblyProperties Condition="$(DefaultSimulator) != ''">$(_QscCommandPredefinedAssemblyProperties) DefaultSimulator:$(DefaultSimulator) + <_QscCommandPredefinedAssemblyProperties Condition="$(ExecutionTarget) != ''">$(_QscCommandPredefinedAssemblyProperties) ExecutionTarget:$(ExecutionTarget) <_QscCommandAssemblyPropertiesFlag>--assembly-properties $(_QscCommandPredefinedAssemblyProperties) $(QscCommandAssemblyProperties) <_QscPackageLoadFallbackFoldersFlag Condition="@(ResolvedPackageLoadFallbackFolders->Count()) > 0">--package-load-fallback-folders "@(ResolvedPackageLoadFallbackFolders,'" "')" <_QscCommandArgs>--proj "$(PathCompatibleAssemblyName)" $(_QscCommandIsExecutableFlag) $(_QscCommandDocsFlag) $(_QscCommandInputFlag) $(_QscCommandOutputFlag) $(_QscCommandReferencesFlag) $(_QscCommandLoadFlag) $(_QscCommandRuntimeFlag) $(_QscCommandTargetDecompositionsFlag) $(_QscPackageLoadFallbackFoldersFlag) $(_QscCommandTestNamesFlag) $(_QscCommandAssemblyPropertiesFlag) $(AdditionalQscArguments) diff --git a/src/VSCodeExtension/.gitignore b/src/VSCodeExtension/.gitignore index 7d51638447..7c58afd891 100644 --- a/src/VSCodeExtension/.gitignore +++ b/src/VSCodeExtension/.gitignore @@ -8,4 +8,3 @@ node_modules NOTICE.txt package.json package-lock.json -syntaxes/qsharp.tmLanguage.json \ No newline at end of file diff --git a/src/VSCodeExtension/.vscodeignore b/src/VSCodeExtension/.vscodeignore index 5231325bff..ffb0eb5ec9 100644 --- a/src/VSCodeExtension/.vscodeignore +++ b/src/VSCodeExtension/.vscodeignore @@ -3,10 +3,10 @@ out/test/** out/**/*.map src/** -.gitignore +**/.gitignore tsconfig.json vsc-extension-quickstart.md tslint.json Build-Dependencies.ps1 BUILDING.md -*.v.template +**/*.v.template diff --git a/src/VSCodeExtension/MS_Quantum_Spot_Dev-200.png b/src/VSCodeExtension/MS_Quantum_Spot_Dev-200.png new file mode 100644 index 0000000000..267980dfcb Binary files /dev/null and b/src/VSCodeExtension/MS_Quantum_Spot_Dev-200.png differ diff --git a/src/VSCodeExtension/mobius_strip_preview.png b/src/VSCodeExtension/mobius_strip_preview.png deleted file mode 100644 index 9c8069d141..0000000000 Binary files a/src/VSCodeExtension/mobius_strip_preview.png and /dev/null differ diff --git a/src/VSCodeExtension/package.json.v.template b/src/VSCodeExtension/package.json.v.template index 5cd437b440..ddcd4518ab 100644 --- a/src/VSCodeExtension/package.json.v.template +++ b/src/VSCodeExtension/package.json.v.template @@ -6,7 +6,7 @@ "publisher": "quantum", "author": "Microsoft DevLabs", "homepage": "http://docs.microsoft.com/quantum/", - "icon": "mobius_strip_preview.png", + "icon": "MS_Quantum_Spot_Dev-200.png", "repository": { "type": "git", "url": "https://github.com/microsoft/qsharp-compiler.git" @@ -36,7 +36,7 @@ "commands": [ { "command": "quantum.installTemplates", - "title": "Q#: Install project templates" + "title": "Q#: Install command line project templates" }, { @@ -114,6 +114,9 @@ "vscode-extension-telemetry": "0.0.18", "vscode-languageclient": "5.1.0", "which": "1.3.1", + "yeoman-environment": "^2.10.3", + "yeoman-generator": "^2.0.1", + "yosay": "^2.0.1", "@types/fs-extra": "^8.0.0" }, "devDependencies": { @@ -121,6 +124,11 @@ "typescript": "^2.6.1", "tslint": "^5.8.0", "mocha": "^5.2.0", + "@types/yeoman-generator": "^3.1.4", + "@types/yosay": "^0.0.29", + "yeoman-test": "^1.7.0", + "yeoman-assert": "^3.1.0", + "@types/yeoman-environment": "^2.3.3", "@types/node": "^9.6.5", "@types/mocha": "^5.2.0", "@types/which": "1.3.1", @@ -130,15 +138,15 @@ }, "blobs": { "win32": { - "url": "https://msquantumpublic.blob.core.windows.net/qsharp-compiler/LanguageServer-win10-x64-#ASSEMBLY_VERSION#.zip", + "url": "https://msquantumpublic.blob.core.windows.net/qsharp-compiler/LanguageServer-win10-x64-#SEMVER_VERSION#.zip", "sha256": "" }, "darwin": { - "url": "https://msquantumpublic.blob.core.windows.net/qsharp-compiler/LanguageServer-osx-x64-#ASSEMBLY_VERSION#.zip", + "url": "https://msquantumpublic.blob.core.windows.net/qsharp-compiler/LanguageServer-osx-x64-#SEMVER_VERSION#.zip", "sha256": "" }, "linux": { - "url": "https://msquantumpublic.blob.core.windows.net/qsharp-compiler/LanguageServer-linux-x64-#ASSEMBLY_VERSION#.zip", + "url": "https://msquantumpublic.blob.core.windows.net/qsharp-compiler/LanguageServer-linux-x64-#SEMVER_VERSION#.zip", "sha256": "" } }, diff --git a/src/VSCodeExtension/src/commands.ts b/src/VSCodeExtension/src/commands.ts index 37c6778afb..f45b11f6a2 100644 --- a/src/VSCodeExtension/src/commands.ts +++ b/src/VSCodeExtension/src/commands.ts @@ -10,6 +10,9 @@ import { IPackageInfo } from './packageInfo'; import * as semver from 'semver'; import { promisify } from 'util'; import { oc } from 'ts-optchain'; +import { QSharpGenerator } from './yeoman-generator'; + +import * as yeoman from 'yeoman-environment'; export function registerCommand(context: vscode.ExtensionContext, name: string, action: () => void) { context.subscriptions.push( @@ -22,107 +25,17 @@ export function registerCommand(context: vscode.ExtensionContext, name: string, ) } -function createNewProjectAtUri(dotNetSdk: DotnetInfo, projectType: string, uri: vscode.Uri) { - let proc = cp.spawn( - dotNetSdk.path, - ["new", projectType, "-lang", "Q#", "-o", uri.fsPath] - ); - - let errorMessage = ""; - proc.stderr.on('data', data => { - errorMessage = errorMessage + data; - }); - - proc.on( - 'exit', (code, signal) => { - if (code === 0) { - const openItem = "Open new project..."; - vscode.window.showInformationMessage( - `Successfully created new project at ${uri.fsPath}.`, - openItem - ).then( - (item) => { - if (item === openItem) { - vscode.commands.executeCommand( - "vscode.openFolder", - uri - ).then( - (value) => {}, - (value) => { - vscode.window.showErrorMessage("Could not open new project"); - } - ); - } - } - ); - } else { - // Check if the problem was that the project templates are missing. - // If so, we can give a more helpful error message and offer the - // user to install templates. - if (errorMessage.includes("Q#") && errorMessage.includes("-lang")) { - const installTemplatesItem = "Install project templates and retry"; - vscode.window.showErrorMessage( - "Project creation failed. The Q# project templates may not be installed.", - installTemplatesItem - ) - .then( - item => { - if (item === installTemplatesItem) { - vscode.commands.executeCommand( - "quantum.installTemplates" - ).then( - () => createNewProjectAtUri(dotNetSdk, projectType, uri) - ); - } - } - ); - } else { - vscode.window.showErrorMessage( - `.NET Core SDK exited with code ${code} when creating a new project:\n${errorMessage}` - ); - } - } +export function createNewProject(context: vscode.ExtensionContext) { + let env = yeoman.createEnv(); + env.options.extensionPath = context.extensionPath; + env.registerStub(QSharpGenerator, 'qsharp:app'); + env.run('qsharp:app', (err: null | Error) => { + if (err) { + let errorMessage = err.name + ": " + err.message; + console.log(errorMessage); + vscode.window.showErrorMessage(errorMessage); } - ); -} - -export function createNewProject(dotNetSdk: DotnetInfo) { - const projectTypes: {[key: string]: string} = { - "Standalone console application": "console", - "Quantum library": "classlib", - "Unit testing project": "xunit" - }; - vscode.window.showQuickPick( - Object.keys(projectTypes) - ).then( - projectTypeSelection => { - if (projectTypeSelection === undefined) { - throw undefined; - } - let projectType = projectTypes[projectTypeSelection]; - - vscode.window.showSaveDialog({ - saveLabel: "Create Project" - }).then( - (uri) => { - if (uri !== undefined) { - if (uri.scheme !== "file") { - vscode.window.showErrorMessage( - "New projects must be saved to the filesystem." - ); - throw new Error("URI scheme was not file."); - } - else { - return uri; - } - } else { - throw undefined; - } - } - ) - .then(uri => createNewProjectAtUri(dotNetSdk, projectType, uri)); - } - ); + }); } export function installTemplates(dotNetSdk: DotnetInfo, packageInfo?: IPackageInfo) { diff --git a/src/VSCodeExtension/src/extension.ts b/src/VSCodeExtension/src/extension.ts index f887d417b1..7debb216c8 100644 --- a/src/VSCodeExtension/src/extension.ts +++ b/src/VSCodeExtension/src/extension.ts @@ -63,7 +63,7 @@ export async function activate(context: vscode.ExtensionContext) { context, "quantum.newProject", () => { - requireDotNetSdk(dotNetSdkVersion).then(createNewProject); + createNewProject(context); } ); diff --git a/src/VSCodeExtension/src/languageServer.ts b/src/VSCodeExtension/src/languageServer.ts index 292556aa58..657af2ece7 100644 --- a/src/VSCodeExtension/src/languageServer.ts +++ b/src/VSCodeExtension/src/languageServer.ts @@ -213,8 +213,8 @@ export class LanguageServer { if (info === undefined || info === null) { throw new Error("Package info was undefined."); } - if (versionCheck && info.assemblyVersion !== version) { - console.log(`[qsharp-lsp] Found version ${version}, expected version ${info.assemblyVersion}. Clearing cached version.`); + if (versionCheck && info.version !== version) { + console.log(`[qsharp-lsp] Found version ${version}, expected version ${info.version}. Clearing cached version.`); await this.clearCache(); return false; } diff --git a/src/VSCodeExtension/src/yeoman-generator.ts b/src/VSCodeExtension/src/yeoman-generator.ts new file mode 100644 index 0000000000..189610a73c --- /dev/null +++ b/src/VSCodeExtension/src/yeoman-generator.ts @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as vscode from 'vscode'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import yo = require("yeoman-generator"); +import yosay = require("yosay"); + +export class QSharpGenerator extends yo { + + constructor(args : any, opts : any) { + super(args, opts); + console.log( + yosay("Welcome to the Q# generator!") + ); + + this.sourceRoot(path.join(this.options.extensionPath, "templates")); + } + + prompting() { + let done = this.async(); + + // This dictionary maps the public description of the project type to the name + // of the folder with the corresponding template files. + const projectTypes: {[key: string]: string} = { + "Standalone console application": "application", + "Quantum library": "library", + "Unit testing project": "unittest" + }; + + vscode.window.showQuickPick( + Object.keys(projectTypes) + ).then( + projectTypeSelection => { + if (projectTypeSelection === undefined) { + throw undefined; + } + + vscode.window.showSaveDialog({ + saveLabel: "Create Project" + }).then( + (uri) => { + if (uri !== undefined) { + if (uri.scheme !== "file") { + vscode.window.showErrorMessage( + "New projects must be saved to the filesystem." + ); + throw new Error("URI scheme was not file."); + } + else { + return uri; + } + } else { + throw undefined; + } + } + ) + .then(uri => { + this.options.projectType = projectTypes[projectTypeSelection]; + this.options.outputUri = uri; + done(); + }); + } + ); + } + + writing() { + console.log( + yosay("Creating Q# project.") + ); + + let sourceDir = path.join(this.templatePath(), this.options.projectType); + let targetDir = this.options.outputUri.fsPath; + fs.mkdir(targetDir); + + // Namespace is the directory name itself. + let dirs = targetDir.split(path.sep); + + // In case there is a trailing separator. + let namespaceName = dirs.pop() || dirs.pop(); + + fs.readdir(sourceDir, (err, files) => { + if (err){ + throw err; + } + files.forEach( (filename) => { + let destinationName = filename; + let fileExtension = filename.split(".").pop(); + + if (fileExtension && fileExtension.toLowerCase() === "csproj") { + destinationName = namespaceName + ".csproj"; + } + + this.fs.copyTpl( + path.join(sourceDir, filename), + path.join(targetDir, destinationName), + { name: namespaceName } + ); + }); + }); + + const openItem = "Open new project..."; + vscode.window.showInformationMessage( + `Successfully created new project at ${targetDir}.`, + openItem + ).then( + (item) => { + if (item === openItem) { + vscode.commands.executeCommand( + "vscode.openFolder", + this.options.outputUri + ).then( + (value) => { }, + (value) => { + vscode.window.showErrorMessage("Could not open new project"); + } + ); + } + } + ); + } +} diff --git a/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json.v.template b/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json similarity index 96% rename from src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json.v.template rename to src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json index 4dbc1671fd..b088feca46 100644 --- a/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json.v.template +++ b/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json @@ -1,8 +1,6 @@ { - "copyright": "Copyright ⓒ 2017-2019 Microsoft Corporation. All Rights Reserved.", "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", "name": "qsharp", - "version": "#VSIX_VERSION#", "fileTypes": [ "qs" ], diff --git a/src/VSCodeExtension/templates/application/.gitignore b/src/VSCodeExtension/templates/application/.gitignore new file mode 100644 index 0000000000..2b04328876 --- /dev/null +++ b/src/VSCodeExtension/templates/application/.gitignore @@ -0,0 +1,2 @@ +# Generated or copied files +*.csproj diff --git a/src/VSCodeExtension/templates/application/Application.csproj.v.template b/src/VSCodeExtension/templates/application/Application.csproj.v.template new file mode 100644 index 0000000000..2061e46bde --- /dev/null +++ b/src/VSCodeExtension/templates/application/Application.csproj.v.template @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/src/VSCodeExtension/templates/application/Program.qs b/src/VSCodeExtension/templates/application/Program.qs new file mode 100644 index 0000000000..969e2dcce0 --- /dev/null +++ b/src/VSCodeExtension/templates/application/Program.qs @@ -0,0 +1,10 @@ +namespace <%= name %> { + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + + + @EntryPoint() + operation SayHello() : Unit { + Message("Hello quantum world!"); + } +} diff --git a/src/VSCodeExtension/templates/library/.gitignore b/src/VSCodeExtension/templates/library/.gitignore new file mode 100644 index 0000000000..2b04328876 --- /dev/null +++ b/src/VSCodeExtension/templates/library/.gitignore @@ -0,0 +1,2 @@ +# Generated or copied files +*.csproj diff --git a/src/VSCodeExtension/templates/library/Library.csproj.v.template b/src/VSCodeExtension/templates/library/Library.csproj.v.template new file mode 100644 index 0000000000..b5159e47ee --- /dev/null +++ b/src/VSCodeExtension/templates/library/Library.csproj.v.template @@ -0,0 +1,7 @@ + + + + netstandard2.1 + + + diff --git a/src/VSCodeExtension/templates/library/Library.qs b/src/VSCodeExtension/templates/library/Library.qs new file mode 100644 index 0000000000..54a38c7587 --- /dev/null +++ b/src/VSCodeExtension/templates/library/Library.qs @@ -0,0 +1,9 @@ +namespace <%= name %> { + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + + + operation SayHello() : Unit { + Message("Hello quantum world!"); + } +} diff --git a/src/VSCodeExtension/templates/unittest/.gitignore b/src/VSCodeExtension/templates/unittest/.gitignore new file mode 100644 index 0000000000..2b04328876 --- /dev/null +++ b/src/VSCodeExtension/templates/unittest/.gitignore @@ -0,0 +1,2 @@ +# Generated or copied files +*.csproj diff --git a/src/VSCodeExtension/templates/unittest/Test.csproj.v.template b/src/VSCodeExtension/templates/unittest/Test.csproj.v.template new file mode 100644 index 0000000000..40bccfea26 --- /dev/null +++ b/src/VSCodeExtension/templates/unittest/Test.csproj.v.template @@ -0,0 +1,16 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + diff --git a/src/VSCodeExtension/templates/unittest/Tests.qs b/src/VSCodeExtension/templates/unittest/Tests.qs new file mode 100644 index 0000000000..901bb6b366 --- /dev/null +++ b/src/VSCodeExtension/templates/unittest/Tests.qs @@ -0,0 +1,16 @@ +namespace <%= name %> { + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Intrinsic; + + + @Test("QuantumSimulator") + operation AllocateQubit() : Unit { + + using (q = Qubit()) { + Assert([PauliZ], [q], Zero, "Newly allocated qubit must be in |0> state."); + } + + Message("Test passed."); + } +} diff --git a/src/VisualStudioExtension/QsharpAppTemplate/MS_Quantum_Spot_Dev-32.png b/src/VisualStudioExtension/QsharpAppTemplate/MS_Quantum_Spot_Dev-32.png new file mode 100644 index 0000000000..3033a5d8a5 Binary files /dev/null and b/src/VisualStudioExtension/QsharpAppTemplate/MS_Quantum_Spot_Dev-32.png differ diff --git a/src/VisualStudioExtension/QsharpAppTemplate/QsharpAppTemplate.csproj b/src/VisualStudioExtension/QsharpAppTemplate/QsharpAppTemplate.csproj index ce4d64e473..fafc0be155 100644 --- a/src/VisualStudioExtension/QsharpAppTemplate/QsharpAppTemplate.csproj +++ b/src/VisualStudioExtension/QsharpAppTemplate/QsharpAppTemplate.csproj @@ -72,7 +72,7 @@ - + diff --git a/src/VisualStudioExtension/QsharpAppTemplate/QsharpAppTemplate.vstemplate b/src/VisualStudioExtension/QsharpAppTemplate/QsharpAppTemplate.vstemplate index 5448e1a570..1a9303c8ed 100644 --- a/src/VisualStudioExtension/QsharpAppTemplate/QsharpAppTemplate.vstemplate +++ b/src/VisualStudioExtension/QsharpAppTemplate/QsharpAppTemplate.vstemplate @@ -3,7 +3,7 @@ Q# Application A project for creating a Q# command-line application. - mobius_strip_icon.png + MS_Quantum_Spot_Dev-32.png CSharp 1000 Microsoft.Common.Console.QSharp diff --git a/src/VisualStudioExtension/QsharpAppTemplate/mobius_strip_icon.png b/src/VisualStudioExtension/QsharpAppTemplate/mobius_strip_icon.png deleted file mode 100644 index e59603b681..0000000000 Binary files a/src/VisualStudioExtension/QsharpAppTemplate/mobius_strip_icon.png and /dev/null differ diff --git a/src/VisualStudioExtension/QsharpFileTemplate/MS_Quantum_Spot_Dev-32.png b/src/VisualStudioExtension/QsharpFileTemplate/MS_Quantum_Spot_Dev-32.png new file mode 100644 index 0000000000..3033a5d8a5 Binary files /dev/null and b/src/VisualStudioExtension/QsharpFileTemplate/MS_Quantum_Spot_Dev-32.png differ diff --git a/src/VisualStudioExtension/QsharpFileTemplate/QsharpFileTemplate.csproj b/src/VisualStudioExtension/QsharpFileTemplate/QsharpFileTemplate.csproj index 2738c23998..ec3c48951a 100644 --- a/src/VisualStudioExtension/QsharpFileTemplate/QsharpFileTemplate.csproj +++ b/src/VisualStudioExtension/QsharpFileTemplate/QsharpFileTemplate.csproj @@ -68,7 +68,7 @@ - + Always diff --git a/src/VisualStudioExtension/QsharpFileTemplate/QsharpFileTemplate.vstemplate b/src/VisualStudioExtension/QsharpFileTemplate/QsharpFileTemplate.vstemplate index d9a5c3ef5a..32cf8832ec 100644 --- a/src/VisualStudioExtension/QsharpFileTemplate/QsharpFileTemplate.vstemplate +++ b/src/VisualStudioExtension/QsharpFileTemplate/QsharpFileTemplate.vstemplate @@ -3,7 +3,7 @@ Q# File An empty Q# operation. - mobius_strip_icon.png + MS_Quantum_Spot_Dev-32.png e8636bf0-e3cd-4689-b27c-6a8c954f4dc4 CSharp 2 diff --git a/src/VisualStudioExtension/QsharpFileTemplate/mobius_strip_icon.png b/src/VisualStudioExtension/QsharpFileTemplate/mobius_strip_icon.png deleted file mode 100644 index e59603b681..0000000000 Binary files a/src/VisualStudioExtension/QsharpFileTemplate/mobius_strip_icon.png and /dev/null differ diff --git a/src/VisualStudioExtension/QsharpLibTemplate/MS_Quantum_Spot_Dev-32.png b/src/VisualStudioExtension/QsharpLibTemplate/MS_Quantum_Spot_Dev-32.png new file mode 100644 index 0000000000..3033a5d8a5 Binary files /dev/null and b/src/VisualStudioExtension/QsharpLibTemplate/MS_Quantum_Spot_Dev-32.png differ diff --git a/src/VisualStudioExtension/QsharpLibTemplate/QsharpLibTemplate.csproj b/src/VisualStudioExtension/QsharpLibTemplate/QsharpLibTemplate.csproj index bdd0824948..fea92bc330 100644 --- a/src/VisualStudioExtension/QsharpLibTemplate/QsharpLibTemplate.csproj +++ b/src/VisualStudioExtension/QsharpLibTemplate/QsharpLibTemplate.csproj @@ -72,7 +72,7 @@ - + diff --git a/src/VisualStudioExtension/QsharpLibTemplate/QsharpLibTemplate.vstemplate b/src/VisualStudioExtension/QsharpLibTemplate/QsharpLibTemplate.vstemplate index 015fc37776..234a87fab4 100644 --- a/src/VisualStudioExtension/QsharpLibTemplate/QsharpLibTemplate.vstemplate +++ b/src/VisualStudioExtension/QsharpLibTemplate/QsharpLibTemplate.vstemplate @@ -3,7 +3,7 @@ Q# Library A project for creating a Q# library. - mobius_strip_icon.png + MS_Quantum_Spot_Dev-32.png CSharp 1000 Microsoft.Common.Classlib.QSharp diff --git a/src/VisualStudioExtension/QsharpLibTemplate/mobius_strip_icon.png b/src/VisualStudioExtension/QsharpLibTemplate/mobius_strip_icon.png deleted file mode 100644 index e59603b681..0000000000 Binary files a/src/VisualStudioExtension/QsharpLibTemplate/mobius_strip_icon.png and /dev/null differ diff --git a/src/VisualStudioExtension/QsharpTestTemplate/MS_Quantum_Spot_Dev-32.png b/src/VisualStudioExtension/QsharpTestTemplate/MS_Quantum_Spot_Dev-32.png new file mode 100644 index 0000000000..3033a5d8a5 Binary files /dev/null and b/src/VisualStudioExtension/QsharpTestTemplate/MS_Quantum_Spot_Dev-32.png differ diff --git a/src/VisualStudioExtension/QsharpTestTemplate/QsharpTestTemplate.csproj b/src/VisualStudioExtension/QsharpTestTemplate/QsharpTestTemplate.csproj index 77b216baac..888e5fdc93 100644 --- a/src/VisualStudioExtension/QsharpTestTemplate/QsharpTestTemplate.csproj +++ b/src/VisualStudioExtension/QsharpTestTemplate/QsharpTestTemplate.csproj @@ -72,7 +72,7 @@ - + diff --git a/src/VisualStudioExtension/QsharpTestTemplate/QsharpTestTemplate.vstemplate b/src/VisualStudioExtension/QsharpTestTemplate/QsharpTestTemplate.vstemplate index 69182d202d..6f4a0d2283 100644 --- a/src/VisualStudioExtension/QsharpTestTemplate/QsharpTestTemplate.vstemplate +++ b/src/VisualStudioExtension/QsharpTestTemplate/QsharpTestTemplate.vstemplate @@ -3,7 +3,7 @@ Q# Test Project A project for creating unit tests in Q#. - mobius_strip_icon.png + MS_Quantum_Spot_Dev-32.png CSharp 1000 Microsoft.Test.QSharp diff --git a/src/VisualStudioExtension/QsharpTestTemplate/mobius_strip_icon.png b/src/VisualStudioExtension/QsharpTestTemplate/mobius_strip_icon.png deleted file mode 100644 index e59603b681..0000000000 Binary files a/src/VisualStudioExtension/QsharpTestTemplate/mobius_strip_icon.png and /dev/null differ diff --git a/src/VisualStudioExtension/QsharpVSIX/MS_Quantum_Spot_Dev-1024.png b/src/VisualStudioExtension/QsharpVSIX/MS_Quantum_Spot_Dev-1024.png new file mode 100644 index 0000000000..0191e7a10b Binary files /dev/null and b/src/VisualStudioExtension/QsharpVSIX/MS_Quantum_Spot_Dev-1024.png differ diff --git a/src/VisualStudioExtension/QsharpVSIX/MS_Quantum_Spot_Dev-200.png b/src/VisualStudioExtension/QsharpVSIX/MS_Quantum_Spot_Dev-200.png new file mode 100644 index 0000000000..267980dfcb Binary files /dev/null and b/src/VisualStudioExtension/QsharpVSIX/MS_Quantum_Spot_Dev-200.png differ diff --git a/src/VisualStudioExtension/QsharpVSIX/MS_Quantum_Spot_Dev-32.png b/src/VisualStudioExtension/QsharpVSIX/MS_Quantum_Spot_Dev-32.png new file mode 100644 index 0000000000..3033a5d8a5 Binary files /dev/null and b/src/VisualStudioExtension/QsharpVSIX/MS_Quantum_Spot_Dev-32.png differ diff --git a/src/VisualStudioExtension/QsharpVSIX/QsharpVSIX.csproj b/src/VisualStudioExtension/QsharpVSIX/QsharpVSIX.csproj index 56ec60b3f3..2a25f6598b 100644 --- a/src/VisualStudioExtension/QsharpVSIX/QsharpVSIX.csproj +++ b/src/VisualStudioExtension/QsharpVSIX/QsharpVSIX.csproj @@ -15,7 +15,7 @@ Properties Microsoft.Quantum.VisualStudio Microsoft.Quantum.VisualStudio.Extension - Microsoft.Quantum.Development.Kit-$(AssemblyVersion).vsix + Microsoft.Quantum.Development.Kit-$(VSVSIX_VERSION).vsix ..\..\QsCompiler\LanguageServer\bin\$(Configuration)\netcoreapp3.1\publish\ v4.7.2 false @@ -127,11 +127,11 @@ - + Always true - + Always true diff --git a/src/VisualStudioExtension/QsharpVSIX/mobius_strip_icon.png b/src/VisualStudioExtension/QsharpVSIX/mobius_strip_icon.png deleted file mode 100644 index e59603b681..0000000000 Binary files a/src/VisualStudioExtension/QsharpVSIX/mobius_strip_icon.png and /dev/null differ diff --git a/src/VisualStudioExtension/QsharpVSIX/mobius_strip_large.png b/src/VisualStudioExtension/QsharpVSIX/mobius_strip_large.png deleted file mode 100644 index 6fa6b123bd..0000000000 Binary files a/src/VisualStudioExtension/QsharpVSIX/mobius_strip_large.png and /dev/null differ diff --git a/src/VisualStudioExtension/QsharpVSIX/mobius_strip_preview.png b/src/VisualStudioExtension/QsharpVSIX/mobius_strip_preview.png deleted file mode 100644 index 9c8069d141..0000000000 Binary files a/src/VisualStudioExtension/QsharpVSIX/mobius_strip_preview.png and /dev/null differ diff --git a/src/VisualStudioExtension/QsharpVSIX/source.extension.vsixmanifest.v.template b/src/VisualStudioExtension/QsharpVSIX/source.extension.vsixmanifest.v.template index 821e13b320..f8ada28ff5 100644 --- a/src/VisualStudioExtension/QsharpVSIX/source.extension.vsixmanifest.v.template +++ b/src/VisualStudioExtension/QsharpVSIX/source.extension.vsixmanifest.v.template @@ -1,14 +1,14 @@  - + Microsoft Quantum Development Kit Microsoft Quantum Development Kit provides support for developing quantum algorithms in the Q# programming language. http://docs.microsoft.com/en-us/quantum/ LICENSE.txt http://docs.microsoft.com/en-us/quantum/quantum-relnotes - mobius_strip_preview.png - mobius_strip_preview.png + MS_Quantum_Spot_Dev-200.png + MS_Quantum_Spot_Dev-200.png quantum, Q#