diff --git a/src/QsCompiler/CommandLineTool/Commands/Build.cs b/src/QsCompiler/CommandLineTool/Commands/Build.cs index 32a0e4878d..ba1a7965fd 100644 --- a/src/QsCompiler/CommandLineTool/Commands/Build.cs +++ b/src/QsCompiler/CommandLineTool/Commands/Build.cs @@ -131,7 +131,8 @@ public static int Run(BuildOptions options, ConsoleLogger logger) ProjectName = options.ProjectName, GenerateFunctorSupport = true, SkipSyntaxTreeTrimming = options.TrimLevel == 0, - AttemptFullPreEvaluation = options.TrimLevel > 1, + ConvertClassicalControl = options.TrimLevel >= 2, + AttemptFullPreEvaluation = options.TrimLevel > 2, DocumentationOutputFolder = options.DocFolder, BuildOutputFolder = options.OutputFolder ?? (usesPlugins ? "." : null), DllOutputPath = options.EmitDll ? " " : null, // set to e.g. an empty space to generate the dll in the same location as the .bson file diff --git a/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs b/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs index 3d718ff0ac..6fc6433f27 100644 --- a/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs +++ b/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs @@ -221,7 +221,8 @@ public static int Run(DiagnoseOptions options, ConsoleLogger logger) { GenerateFunctorSupport = true, SkipSyntaxTreeTrimming = options.TrimLevel == 0, - AttemptFullPreEvaluation = options.TrimLevel > 1, + ConvertClassicalControl = options.TrimLevel >= 2, + AttemptFullPreEvaluation = options.TrimLevel > 2, RewriteSteps = options.Plugins?.Select(step => (step, (string)null)) ?? ImmutableArray<(string, string)>.Empty, EnableAdditionalChecks = true }; diff --git a/src/QsCompiler/Compiler/CompilationLoader.cs b/src/QsCompiler/Compiler/CompilationLoader.cs index eaf79450e5..200969bb40 100644 --- a/src/QsCompiler/Compiler/CompilationLoader.cs +++ b/src/QsCompiler/Compiler/CompilationLoader.cs @@ -1,905 +1,924 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; -using Microsoft.Quantum.QsCompiler.BuiltInRewriteSteps; -using Microsoft.Quantum.QsCompiler.CompilationBuilder; -using Microsoft.Quantum.QsCompiler.DataTypes; -using Microsoft.Quantum.QsCompiler.Diagnostics; -using Microsoft.Quantum.QsCompiler.Documentation; -using Microsoft.Quantum.QsCompiler.ReservedKeywords; -using Microsoft.Quantum.QsCompiler.Serialization; -using Microsoft.Quantum.QsCompiler.SyntaxTree; -using Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations; -using Microsoft.VisualStudio.LanguageServer.Protocol; -using Newtonsoft.Json.Bson; -using MetadataReference = Microsoft.CodeAnalysis.MetadataReference; - - -namespace Microsoft.Quantum.QsCompiler -{ - public class CompilationLoader - { - /// - /// Represents the type of a task event. - /// - public enum CompilationTaskEventType - { - Start, - End - } - - /// - /// Represents the arguments associated to a task event. - /// - public class CompilationTaskEventArgs : EventArgs - { - public CompilationTaskEventType Type; - public string ParentTaskName; - public string TaskName; - - public CompilationTaskEventArgs(CompilationTaskEventType type, string parentTaskName, string taskName) - { - ParentTaskName = parentTaskName; - TaskName = taskName; - Type = type; - } - } - - /// - /// 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; } - - - /// - /// may be specified via configuration (or project) file in the future - /// - public struct Configuration - { - /// - /// The name of the project. Used as assembly name in the generated dll. - /// 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; - /// - /// 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; - /// - /// 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. - /// However, the compiler may overwrite the assembly constants defined for the Q# compilation unit in the dictionary of the loaded step. - /// The given dictionary in this configuration is left unchanged in any case. - /// - public IReadOnlyDictionary AssemblyConstants; - - /// - /// Indicates whether a serialization of the syntax tree needs to be generated. - /// 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; - - /// - /// If the ProjectName does not have an ending "proj", appends a .qsproj ending to the project name. - /// Returns null if the project name is null. - /// - internal string ProjectNameWithExtension => - this.ProjectName == null ? null : - this.ProjectName.EndsWith("proj") ? this.ProjectName : - $"{this.ProjectName}.qsproj"; - - /// - /// If the ProjectName does have an extension ending with "proj", returns the project name without that extension. - /// Returns null if the project name is null. - /// - internal string ProjectNameWithoutExtension => - this.ProjectName == null ? null : - Path.GetExtension(this.ProjectName).EndsWith("proj") ? Path.GetFileNameWithoutExtension(this.ProjectName) : - this.ProjectName; - } - - /// - /// used to indicate the status of individual compilation steps - /// - public enum Status { NotRun = -1, Succeeded = 0, Failed = 1 } - - private class ExecutionStatus - { - internal Status SourceFileLoading = Status.NotRun; - internal Status ReferenceLoading = Status.NotRun; - internal Status PluginLoading = Status.NotRun; - internal Status Validation = Status.NotRun; - internal Status FunctorSupport = Status.NotRun; - internal Status PreEvaluation = Status.NotRun; - internal Status TreeTrimming = Status.NotRun; - internal Status Monomorphization = Status.NotRun; - internal Status Documentation = Status.NotRun; - internal Status Serialization = Status.NotRun; - internal Status BinaryFormat = Status.NotRun; - internal Status DllGeneration = Status.NotRun; - internal Status[] LoadedRewriteSteps; - - internal ExecutionStatus(IEnumerable externalRewriteSteps) => - this.LoadedRewriteSteps = externalRewriteSteps.Select(_ => Status.NotRun).ToArray(); - - private bool WasSuccessful(bool run, Status code) => - (run && code == Status.Succeeded) || (!run && code == Status.NotRun); - - internal bool Success(Configuration options, bool isExe) => - this.SourceFileLoading <= 0 && - this.ReferenceLoading <= 0 && - WasSuccessful(true, this.Validation) && - WasSuccessful(true, this.PluginLoading) && - WasSuccessful(options.GenerateFunctorSupport, this.FunctorSupport) && - WasSuccessful(options.AttemptFullPreEvaluation, this.PreEvaluation) && - WasSuccessful(!options.SkipSyntaxTreeTrimming, this.TreeTrimming) && - WasSuccessful(isExe && !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)); - } - - /// - /// 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; - /// - /// 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; - /// - /// 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; - /// - /// 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; - /// - /// 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; - /// - /// 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; - /// - /// 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; - /// - /// 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; - /// - /// 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; - /// - /// 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; - /// - /// 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; - - /// - /// Indicates whether all rewrite steps with the given name and loaded from the given source executed successfully. - /// The source, if specified, is the path to the dll in which the step is specified. - /// Returns a status NotRun if no such step was found or executed. - /// Execution is considered successful if the precondition and transformation (if any) returned true. - /// - 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(); - 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; - /// - /// 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, this.CompilationOutput?.EntryPoints.Length != 0); - - - /// - /// Logger used to log all diagnostic events during compilation. - /// - private readonly ILogger Logger; - /// - /// Configuration specifying the compilation steps to execute. - /// - private readonly Configuration Config; - /// - /// Used to track the status of individual compilation steps. - /// - 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; - - /// - /// 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. - /// - public readonly string DllOutputPath; - - /// - /// Contains the full Q# syntax tree after executing all configured rewrite steps, including the content of loaded references. - /// - public IEnumerable GeneratedSyntaxTree => - this.CompilationOutput?.Namespaces; - - /// - /// Contains the Uri and names of all rewrite steps loaded from the specified dlls - /// in the order in which they are executed. - /// - public ImmutableArray<(Uri, string)> LoadedRewriteSteps => - this.ExternalRewriteSteps.Select(step => (step.Origin, step.Name)).ToImmutableArray(); - - - /// - /// Builds the compilation for the source files and references loaded by the given loaders, - /// executing the compilation steps specified by the given options. - /// Uses the specified logger to log all diagnostic events. - /// Throws an ArgumentNullException if either one of the given loaders is null or returns null. - /// - public CompilationLoader(SourceLoader loadSources, ReferenceLoader loadReferences, Configuration? options = null, ILogger logger = null) - { - RaiseCompilationTaskStart(null, "OverallCompilation"); - - // loading the content to compiler - - this.Logger = logger; - this.LoadDiagnostics = ImmutableArray.Empty; - this.Config = options ?? new Configuration(); - - Status rewriteStepLoading = Status.Succeeded; - 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; - - RaiseCompilationTaskStart("OverallCompilation", "SourcesLoading"); - var sourceFiles = loadSources?.Invoke(this.LoadSourceFiles) - ?? throw new ArgumentNullException("unable to load source files"); - RaiseCompilationTaskEnd("OverallCompilation", "SourcesLoading"); - RaiseCompilationTaskStart("OverallCompilation", "ReferenceLoading"); - var references = loadReferences?.Invoke(refs => this.LoadAssemblies(refs, this.Config.LoadReferencesBasedOnGeneratedCsharp)) - ?? throw new ArgumentNullException("unable to load referenced binary files"); - RaiseCompilationTaskEnd("OverallCompilation", "ReferenceLoading"); - - // building the compilation - - 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 compilationManager = new CompilationUnitManager(this.OnCompilerException); - compilationManager.UpdateReferencesAsync(references); - compilationManager.AddOrUpdateSourceFilesAsync(files); - this.VerifiedCompilation = compilationManager.Build(); - this.CompilationOutput = this.VerifiedCompilation?.BuiltCompilation; - compilationManager.Dispose(); - - foreach (var diag in this.VerifiedCompilation?.Diagnostics() ?? Enumerable.Empty()) - { this.LogAndUpdate(ref this.CompilationStatus.Validation, diag); } - - // executing the specified rewrite steps - - if (!this.Config.SkipMonomorphization && this.CompilationOutput?.EntryPoints.Length != 0) - { - if (!Uri.TryCreate(Assembly.GetExecutingAssembly().CodeBase, UriKind.Absolute, out Uri thisDllUri)) - { thisDllUri = new Uri(Path.GetFullPath(".", "CompilationLoader.cs")); } - var rewriteStep = new RewriteSteps.LoadedStep(new Monomorphization(), typeof(IRewriteStep), thisDllUri); - this.CompilationStatus.Monomorphization = this.ExecuteRewriteStep(rewriteStep, this.CompilationOutput, out this.CompilationOutput); - } - - if (this.Config.GenerateFunctorSupport) - { - this.CompilationStatus.FunctorSupport = Status.Succeeded; - void onException(Exception ex) => this.LogAndUpdate(ref this.CompilationStatus.FunctorSupport, ex); - var generated = this.CompilationOutput != null && CodeGeneration.GenerateFunctorSpecializations(this.CompilationOutput, out this.CompilationOutput, onException); - if (!generated) this.LogAndUpdate(ref this.CompilationStatus.FunctorSupport, ErrorCode.FunctorGenerationFailed, Enumerable.Empty()); - } - - if (!this.Config.SkipSyntaxTreeTrimming) - { - this.CompilationStatus.TreeTrimming = Status.Succeeded; - void onException(Exception ex) => this.LogAndUpdate(ref this.CompilationStatus.TreeTrimming, ex); - var trimmed = this.CompilationOutput != null && this.CompilationOutput.InlineConjugations(out this.CompilationOutput, onException); - if (!trimmed) this.LogAndUpdate(ref this.CompilationStatus.TreeTrimming, ErrorCode.TreeTrimmingFailed, Enumerable.Empty()); - } - - if (this.Config.AttemptFullPreEvaluation) - { - this.CompilationStatus.PreEvaluation = Status.Succeeded; - void onException(Exception ex) => this.LogAndUpdate(ref this.CompilationStatus.PreEvaluation, ex); - var evaluated = this.CompilationOutput != null && this.CompilationOutput.PreEvaluateAll(out this.CompilationOutput, onException); - if (!evaluated) this.LogAndUpdate(ref this.CompilationStatus.PreEvaluation, ErrorCode.PreEvaluationFailed, Enumerable.Empty()); - } - - RaiseCompilationTaskEnd("OverallCompilation", "Build"); - - // generating the compiled binary and dll - - 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) - { - RaiseCompilationTaskStart("OutputGeneration", "BinaryGeneration"); - this.PathToCompiledBinary = this.GenerateBinary(ms); - RaiseCompilationTaskEnd("OutputGeneration", "BinaryGeneration"); - } - if (serialized && this.Config.DllOutputPath != null) - { - RaiseCompilationTaskStart("OutputGeneration", "DllGeneration"); - this.DllOutputPath = this.GenerateDll(ms); - RaiseCompilationTaskEnd("OutputGeneration", "DllGeneration"); - } - } - - // executing the specified generation steps - - 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"); - } - - RaiseCompilationTaskEnd("OverallCompilation", "OutputGeneration"); - - // invoking rewrite steps in external dlls - - RaiseCompilationTaskStart("OverallCompilation", "RewriteSteps"); - for (int i = 0; i < this.ExternalRewriteSteps.Length; i++) - { - if (this.CompilationOutput == null) continue; - var executed = this.ExecuteRewriteStep(this.ExternalRewriteSteps[i], this.CompilationOutput, out var transformed); - if (executed == Status.Succeeded) this.CompilationOutput = transformed; - this.CompilationStatus.LoadedRewriteSteps[i] = executed; - } - - RaiseCompilationTaskEnd("OverallCompilation", "RewriteSteps"); - RaiseCompilationTaskEnd(null, "OverallCompilation"); - } - - /// - /// Executes the given rewrite step on the given compilation, returning a transformed compilation as an out parameter. - /// Catches and logs any thrown exception. Returns the status of the rewrite step. - /// Throws an ArgumentNullException if the rewrite step to execute or the given compilation is null. - /// - 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)); - - string GetDiagnosticsCode(DiagnosticSeverity severity) => - rewriteStep.Name == "CsharpGeneration" && severity == DiagnosticSeverity.Error ? Errors.Code(ErrorCode.CsharpGenerationGeneratedError) : - rewriteStep.Name == "CsharpGeneration" && severity == DiagnosticSeverity.Warning ? Warnings.Code(WarningCode.CsharpGenerationGeneratedWarning) : - rewriteStep.Name == "CsharpGeneration" && severity == DiagnosticSeverity.Information ? Informations.Code(InformationCode.CsharpGenerationGeneratedInfo) : - null; - - Status LogDiagnostics(Status status = Status.Succeeded) - { - try - { - foreach (var diagnostic in rewriteStep.GeneratedDiagnostics ?? ImmutableArray.Empty) - { this.LogAndUpdate(ref status, RewriteSteps.LoadedStep.ConvertDiagnostic(diagnostic, GetDiagnosticsCode)); } - } - catch { this.LogAndUpdate(ref status, Warning(WarningCode.RewriteStepDiagnosticsGenerationFailed, new[] { rewriteStep.Name })); } - return status; - } - - var status = Status.Succeeded; - var messageSource = ProjectManager.MessageSource(rewriteStep.Origin); - Diagnostic Warning(WarningCode code, params string[] args) => Warnings.LoadWarning(code, args, messageSource); - try - { - transformed = compilation; - var preconditionFailed = rewriteStep.ImplementsPreconditionVerification && !rewriteStep.PreconditionVerification(compilation); - if (preconditionFailed) - { - LogDiagnostics(); - this.LogAndUpdate(ref status, Warning(WarningCode.PreconditionVerificationFailed, new[] { rewriteStep.Name, messageSource })); - return status; - } - - var transformationFailed = rewriteStep.ImplementsTransformation && !rewriteStep.Transformation(compilation, out transformed); - var postconditionFailed = this.Config.EnableAdditionalChecks && rewriteStep.ImplementsPostconditionVerification && !rewriteStep.PostconditionVerification(transformed); - LogDiagnostics(); - - if (transformationFailed) this.LogAndUpdate(ref status, ErrorCode.RewriteStepExecutionFailed, new[] { rewriteStep.Name, messageSource }); - if (postconditionFailed) this.LogAndUpdate(ref status, ErrorCode.PostconditionVerificationFailed, new[] { rewriteStep.Name, messageSource }); - return status; - } - 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 }); - transformed = null; - } - return status; - } - - /// - /// Builds the compilation of the specified source files and references, - /// executing the compilation steps specified by the given options. - /// 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) { } - - /// - /// Builds the compilation of the specified source files and the loaded references returned by the given loader, - /// executing the compilation steps specified by the given options. - /// Uses the specified logger to log all diagnostic events. - /// 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) { } - - /// - /// Builds the compilation of the content returned by the given loader and the specified references, - /// executing the compilation steps specified by the given options. - /// Uses the specified logger to log all diagnostic events. - /// 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) { } - - - // private routines used for logging and status updates - - /// - /// Logs the given diagnostic and updates the status passed as reference accordingly. - /// Throws an ArgumentNullException if the given diagnostic is null. - /// - private void LogAndUpdate(ref Status current, Diagnostic d) - { - this.Logger?.Log(d); - if (d.IsError()) current = Status.Failed; - } - - /// - /// Logs the given exception and updates the status passed as reference accordingly. - /// - private void LogAndUpdate(ref Status current, Exception ex) - { - this.Logger?.Log(ex); - current = Status.Failed; - } - - /// - /// Logs an error with the given error code and message parameters, and updates the status passed as reference accordingly. - /// - private void LogAndUpdate(ref Status current, ErrorCode code, IEnumerable args) - { - this.Logger?.Log(code, args); - current = Status.Failed; - } - - /// - /// Logs the given diagnostic and updates the status passed as reference accordingly. - /// Adds the given diagnostic to the tracked load diagnostics. - /// Throws an ArgumentNullException if the given diagnostic is null. - /// - private void LogAndUpdateLoadDiagnostics(ref Status current, Diagnostic d) - { - this.LoadDiagnostics = this.LoadDiagnostics.Add(d); - this.LogAndUpdate(ref current, d); - } - - /// - /// Logs an UnexpectedCompilerException error as well as the given exception, and updates the validation status accordingly. - /// - private void OnCompilerException(Exception ex) - { - this.LogAndUpdate(ref this.CompilationStatus.Validation, ErrorCode.UnexpectedCompilerException, Enumerable.Empty()); - this.LogAndUpdate(ref this.CompilationStatus.Validation, ex); - } - - /// - /// Logs the names of the given source files as Information. - /// Does nothing if the given argument is null. - /// - private void PrintResolvedFiles(IEnumerable sourceFiles) - { - 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()); - } - - /// - /// Logs the names of the given assemblies as Information. - /// Does nothing if the given argument is null. - /// - private void PrintResolvedAssemblies(IEnumerable> assemblies) - { - 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()); - } - - /// - /// Logs the names and origins of the given rewrite steps as Information. - /// Does nothing if the given argument is null. - /// - private void PrintLoadedRewriteSteps(IEnumerable rewriteSteps) - { - 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()); - } - - - // routines for loading from and dumping to files - - /// - /// Used to load the content of the specified source files from disk. - /// Returns a dictionary mapping the file uri to its content. - /// Logs suitable diagnostics in the process and modifies the compilation status accordingly. - /// Prints all loaded files using PrintResolvedFiles. - /// - 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.PrintResolvedFiles(sourceFiles.Keys); - return sourceFiles; - } - - /// - /// Used to load the content of the specified assembly references from disk. - /// 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 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, (code, args) => onDiagnostic(Errors.LoadError(code, args, projId))); - this.PrintResolvedAssemblies(references.Declarations.Keys); - return references; - } - - /// - /// Writes a binary representation of the built Q# compilation output to the given memory stream. - /// Logs suitable diagnostics in the process and modifies the compilation status accordingly. - /// Does *not* close the given memory stream, and - /// returns true if the serialization has been successfully generated. - /// Throws an ArgumentNullException if the given memory stream is null. - /// - private bool SerializeSyntaxTree(MemoryStream ms) - { - if (ms == null) throw new ArgumentNullException(nameof(ms)); - bool ErrorAndReturn() - { - this.LogAndUpdate(ref this.CompilationStatus.Serialization, ErrorCode.SerializationFailed, Enumerable.Empty()); - 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); } - catch (Exception ex) - { - this.LogAndUpdate(ref this.CompilationStatus.Serialization, ex); - ErrorAndReturn(); - } - return true; - } - - /// - /// Backtracks to the beginning of the given memory stream and writes its content to disk, - /// generating a suitable bson file in the specified build output folder using the project name as file name. - /// Generates a file name at random if no project name is specified. - /// Logs suitable diagnostics in the process and modifies the compilation status accordingly. - /// Returns the absolute path of the file where the binary representation has been generated. - /// Returns null if the binary file could not be generated. - /// Does *not* close the given memory stream. - /// Throws an ArgumentNullException if the given memory stream is null. - /// - private string GenerateBinary(MemoryStream serialization) - { - 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 target = GeneratedFile(projId, outFolder, ".bson", ""); - - try - { - serialization.Seek(0, SeekOrigin.Begin); - using (var file = new FileStream(target, FileMode.Create, FileAccess.Write)) - { 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()); - return null; - } - } - - /// - /// Backtracks to the beginning of the given memory stream and, - /// assuming the given memory stream contains a serialization of the compiled syntax tree, - /// generates a dll containing the compiled binary at the specified dll output path. - /// Logs suitable diagnostics in the process and modifies the compilation status accordingly. - /// Returns the absolute path of the file where the dll has been generated. - /// Returns null if the dll could not be generated. - /// Does *not* close the given memory stream. - /// Throws an ArgumentNullException if the given memory stream is null. - /// - private string GenerateDll(MemoryStream serialization) - { - 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); - outputPath = Path.ChangeExtension(outputPath, "dll"); - - MetadataReference CreateReference(string file, int id) => - MetadataReference.CreateFromFile(file) - .WithAliases(new string[] { $"{DotnetCoreDll.ReferenceAlias}{id}" }); // referenced Q# dlls are recognized based on this alias - - // We need to force the inclusion of references despite that we do not include C# code that depends on them. - // This is done via generating a certain handle in all dlls built via this compilation loader. - // 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 - { - using var stream = File.OpenRead(dll.Value); - using var assemblyFile = new PEReader(stream); - var metadataReader = assemblyFile.GetMetadataReader(); - return metadataReader.TypeDefinitions - .Select(metadataReader.GetTypeDefinition) - .Any(t => metadataReader.GetString(t.Namespace) == DotnetCoreDll.MetadataNamespace); - } - catch { return false; } - } - - try - { - var referencePaths = GetSourceFiles.Apply(this.CompilationOutput.Namespaces) // we choose to keep only Q# references that have been used - .Where(file => file.Value.EndsWith(".dll")); - var references = referencePaths.Select((dll, id) => (dll, CreateReference(dll.Value, id), CanBeIncluded(dll))).ToImmutableArray(); - var csharpTree = MetadataGeneration.GenerateAssemblyMetadata(references.Where(r => r.Item3).Select(r => r.Item2)); - 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); - } - - var compilation = CodeAnalysis.CSharp.CSharpCompilation.Create( - 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) - ); - - 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, - options: new CodeAnalysis.Emit.EmitOptions(), - 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()))}"); - return outputPath; - } - catch (Exception ex) - { - this.LogAndUpdate(ref this.CompilationStatus.DllGeneration, ex); - this.LogAndUpdate(ref this.CompilationStatus.DllGeneration, ErrorCode.GeneratingDllFailed, Enumerable.Empty()); - return null; - } - } - - /// - /// Given the path to a Q# binary file, reads the content of that file and returns the corresponding compilation as out parameter. - /// Throws the corresponding exception if the given path does not correspond to a suitable binary file. - /// - public static bool ReadBinary(string file, out QsCompilation syntaxTree) => - ReadBinary(new MemoryStream(File.ReadAllBytes(Path.GetFullPath(file))), out syntaxTree); - - /// - /// Given a stream with the content of a Q# binary file, returns the corresponding compilation as out parameter. - /// Throws an ArgumentNullException if the given stream is null. - /// - public static bool ReadBinary(Stream stream, out QsCompilation syntaxTree) => - AssemblyLoader.LoadSyntaxTree(stream, out syntaxTree); - - /// - /// Given a file id assigned by the Q# compiler, computes the corresponding path in the specified output folder. - /// Returns the computed absolute path for a file with the specified ending. - /// If the content for that file is specified, writes that content to disk. - /// Throws an ArgumentException if the given file id is incompatible with and id assigned by the Q# compiler. - /// Throws the corresponding exception any of the path operations fails or if the writing fails. - /// - 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"); } - string FullDirectoryName(string dir) => - Path.GetFullPath(dir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar); - - outputFolder = String.IsNullOrWhiteSpace(outputFolder) ? "." : outputFolder; - var outputUri = new Uri(FullDirectoryName(outputFolder)); - var currentDir = new Uri(FullDirectoryName(".")); - var relFilePath = currentDir.MakeRelativeUri(file); - var filePath = Uri.UnescapeDataString(new Uri(outputUri, relFilePath).LocalPath); - var fileDir = filePath.StartsWith(outputUri.LocalPath) - ? Path.GetDirectoryName(filePath) - : 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); - File.WriteAllText(targetFile, content); - return targetFile; - } - - /// - /// Raises a compilation task start event. - /// - private void RaiseCompilationTaskStart (string parentTaskName, string taskName) => - CompilationTaskEvent?.Invoke(this, new CompilationTaskEventArgs(CompilationTaskEventType.Start, parentTaskName, taskName)); - - /// - /// Raises a compilation task end event. - /// - private void RaiseCompilationTaskEnd(string parentTaskName, string taskName) => - CompilationTaskEvent?.Invoke(this, new CompilationTaskEventArgs(CompilationTaskEventType.End, parentTaskName, taskName)); - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using Microsoft.Quantum.QsCompiler.BuiltInRewriteSteps; +using Microsoft.Quantum.QsCompiler.CompilationBuilder; +using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.Diagnostics; +using Microsoft.Quantum.QsCompiler.Documentation; +using Microsoft.Quantum.QsCompiler.ReservedKeywords; +using Microsoft.Quantum.QsCompiler.Serialization; +using Microsoft.Quantum.QsCompiler.SyntaxTree; +using Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Newtonsoft.Json.Bson; +using MetadataReference = Microsoft.CodeAnalysis.MetadataReference; + + +namespace Microsoft.Quantum.QsCompiler +{ + public class CompilationLoader + { + /// + /// Represents the type of a task event. + /// + public enum CompilationTaskEventType + { + Start, + End + } + + /// + /// Represents the arguments associated to a task event. + /// + public class CompilationTaskEventArgs : EventArgs + { + public CompilationTaskEventType Type; + public string ParentTaskName; + public string TaskName; + + public CompilationTaskEventArgs(CompilationTaskEventType type, string parentTaskName, string taskName) + { + ParentTaskName = parentTaskName; + TaskName = taskName; + Type = type; + } + } + + /// + /// 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; } + + + /// + /// may be specified via configuration (or project) file in the future + /// + public struct Configuration + { + /// + /// The name of the project. Used as assembly name in the generated dll. + /// 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; + /// + /// If set to true, the compiler will remove if-statements and replace them with calls to appropriate + /// intrinsic operations. + /// + public bool ConvertClassicalControl; + /// + /// 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; + /// + /// 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. + /// However, the compiler may overwrite the assembly constants defined for the Q# compilation unit in the dictionary of the loaded step. + /// The given dictionary in this configuration is left unchanged in any case. + /// + public IReadOnlyDictionary AssemblyConstants; + + /// + /// Indicates whether a serialization of the syntax tree needs to be generated. + /// 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; + + /// + /// If the ProjectName does not have an ending "proj", appends a .qsproj ending to the project name. + /// Returns null if the project name is null. + /// + internal string ProjectNameWithExtension => + this.ProjectName == null ? null : + this.ProjectName.EndsWith("proj") ? this.ProjectName : + $"{this.ProjectName}.qsproj"; + + /// + /// If the ProjectName does have an extension ending with "proj", returns the project name without that extension. + /// Returns null if the project name is null. + /// + internal string ProjectNameWithoutExtension => + this.ProjectName == null ? null : + Path.GetExtension(this.ProjectName).EndsWith("proj") ? Path.GetFileNameWithoutExtension(this.ProjectName) : + this.ProjectName; + } + + /// + /// used to indicate the status of individual compilation steps + /// + public enum Status { NotRun = -1, Succeeded = 0, Failed = 1 } + + private class ExecutionStatus + { + internal Status SourceFileLoading = Status.NotRun; + internal Status ReferenceLoading = Status.NotRun; + internal Status PluginLoading = Status.NotRun; + internal Status Validation = Status.NotRun; + internal Status FunctorSupport = Status.NotRun; + internal Status PreEvaluation = Status.NotRun; + internal Status TreeTrimming = Status.NotRun; + internal Status ConvertClassicalControl = Status.NotRun; + internal Status Monomorphization = Status.NotRun; + internal Status Documentation = Status.NotRun; + internal Status Serialization = Status.NotRun; + internal Status BinaryFormat = Status.NotRun; + internal Status DllGeneration = Status.NotRun; + internal Status[] LoadedRewriteSteps; + + internal ExecutionStatus(IEnumerable externalRewriteSteps) => + this.LoadedRewriteSteps = externalRewriteSteps.Select(_ => Status.NotRun).ToArray(); + + private bool WasSuccessful(bool run, Status code) => + (run && code == Status.Succeeded) || (!run && code == Status.NotRun); + + internal bool Success(Configuration options, bool isExe) => + this.SourceFileLoading <= 0 && + this.ReferenceLoading <= 0 && + WasSuccessful(true, this.Validation) && + WasSuccessful(true, this.PluginLoading) && + WasSuccessful(options.GenerateFunctorSupport, this.FunctorSupport) && + WasSuccessful(options.AttemptFullPreEvaluation, this.PreEvaluation) && + WasSuccessful(!options.SkipSyntaxTreeTrimming, this.TreeTrimming) && + WasSuccessful(options.ConvertClassicalControl, this.ConvertClassicalControl) && + + WasSuccessful(isExe && !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)); + } + + /// + /// 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; + /// + /// 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; + /// + /// 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; + /// + /// 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; + /// + /// 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; + /// + /// 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; + /// + /// 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; + /// + /// 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; + /// + /// 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; + /// + /// 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; + /// + /// 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; + + /// + /// Indicates whether all rewrite steps with the given name and loaded from the given source executed successfully. + /// The source, if specified, is the path to the dll in which the step is specified. + /// Returns a status NotRun if no such step was found or executed. + /// Execution is considered successful if the precondition and transformation (if any) returned true. + /// + 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(); + 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; + /// + /// 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, this.CompilationOutput?.EntryPoints.Length != 0); + + + /// + /// Logger used to log all diagnostic events during compilation. + /// + private readonly ILogger Logger; + /// + /// Configuration specifying the compilation steps to execute. + /// + private readonly Configuration Config; + /// + /// Used to track the status of individual compilation steps. + /// + 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; + + /// + /// 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. + /// + public readonly string DllOutputPath; + + /// + /// Contains the full Q# syntax tree after executing all configured rewrite steps, including the content of loaded references. + /// + public IEnumerable GeneratedSyntaxTree => + this.CompilationOutput?.Namespaces; + + /// + /// Contains the Uri and names of all rewrite steps loaded from the specified dlls + /// in the order in which they are executed. + /// + public ImmutableArray<(Uri, string)> LoadedRewriteSteps => + this.ExternalRewriteSteps.Select(step => (step.Origin, step.Name)).ToImmutableArray(); + + + /// + /// Builds the compilation for the source files and references loaded by the given loaders, + /// executing the compilation steps specified by the given options. + /// Uses the specified logger to log all diagnostic events. + /// Throws an ArgumentNullException if either one of the given loaders is null or returns null. + /// + public CompilationLoader(SourceLoader loadSources, ReferenceLoader loadReferences, Configuration? options = null, ILogger logger = null) + { + RaiseCompilationTaskStart(null, "OverallCompilation"); + + // loading the content to compiler + + this.Logger = logger; + this.LoadDiagnostics = ImmutableArray.Empty; + this.Config = options ?? new Configuration(); + + Status rewriteStepLoading = Status.Succeeded; + 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; + + RaiseCompilationTaskStart("OverallCompilation", "SourcesLoading"); + var sourceFiles = loadSources?.Invoke(this.LoadSourceFiles) + ?? throw new ArgumentNullException("unable to load source files"); + RaiseCompilationTaskEnd("OverallCompilation", "SourcesLoading"); + RaiseCompilationTaskStart("OverallCompilation", "ReferenceLoading"); + var references = loadReferences?.Invoke(refs => this.LoadAssemblies(refs, this.Config.LoadReferencesBasedOnGeneratedCsharp)) + ?? throw new ArgumentNullException("unable to load referenced binary files"); + RaiseCompilationTaskEnd("OverallCompilation", "ReferenceLoading"); + + // building the compilation + + 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 compilationManager = new CompilationUnitManager(this.OnCompilerException); + compilationManager.UpdateReferencesAsync(references); + compilationManager.AddOrUpdateSourceFilesAsync(files); + this.VerifiedCompilation = compilationManager.Build(); + this.CompilationOutput = this.VerifiedCompilation?.BuiltCompilation; + compilationManager.Dispose(); + + foreach (var diag in this.VerifiedCompilation?.Diagnostics() ?? Enumerable.Empty()) + { this.LogAndUpdate(ref this.CompilationStatus.Validation, diag); } + + // executing the specified rewrite steps + + if (!Uri.TryCreate(Assembly.GetExecutingAssembly().CodeBase, UriKind.Absolute, out Uri thisDllUri)) + { thisDllUri = new Uri(Path.GetFullPath(".", "CompilationLoader.cs")); } + + QsCompilation ExecuteAsAtomicTransformation(RewriteSteps.LoadedStep rewriteStep, ref Status status) + { + status = this.ExecuteRewriteStep(rewriteStep, this.CompilationOutput, out var transformed); + return status == Status.Succeeded ? transformed : this.CompilationOutput; + } + + if (this.Config.ConvertClassicalControl) + { + var rewriteStep = new RewriteSteps.LoadedStep(new ClassicallyControlled(), typeof(IRewriteStep), thisDllUri); + this.CompilationOutput = ExecuteAsAtomicTransformation(rewriteStep, ref this.CompilationStatus.ConvertClassicalControl); + } + + if (!this.Config.SkipMonomorphization && this.CompilationOutput?.EntryPoints.Length != 0) + { + var rewriteStep = new RewriteSteps.LoadedStep(new Monomorphization(), typeof(IRewriteStep), thisDllUri); + this.CompilationOutput = ExecuteAsAtomicTransformation(rewriteStep, ref this.CompilationStatus.Monomorphization); + } + + if (this.Config.GenerateFunctorSupport) + { + this.CompilationStatus.FunctorSupport = Status.Succeeded; + void onException(Exception ex) => this.LogAndUpdate(ref this.CompilationStatus.FunctorSupport, ex); + var generated = this.CompilationOutput != null && CodeGeneration.GenerateFunctorSpecializations(this.CompilationOutput, out this.CompilationOutput, onException); + if (!generated) this.LogAndUpdate(ref this.CompilationStatus.FunctorSupport, ErrorCode.FunctorGenerationFailed, Enumerable.Empty()); + } + + if (!this.Config.SkipSyntaxTreeTrimming) + { + this.CompilationStatus.TreeTrimming = Status.Succeeded; + void onException(Exception ex) => this.LogAndUpdate(ref this.CompilationStatus.TreeTrimming, ex); + var trimmed = this.CompilationOutput != null && this.CompilationOutput.InlineConjugations(out this.CompilationOutput, onException); + if (!trimmed) this.LogAndUpdate(ref this.CompilationStatus.TreeTrimming, ErrorCode.TreeTrimmingFailed, Enumerable.Empty()); + } + + if (this.Config.AttemptFullPreEvaluation) + { + this.CompilationStatus.PreEvaluation = Status.Succeeded; + void onException(Exception ex) => this.LogAndUpdate(ref this.CompilationStatus.PreEvaluation, ex); + var evaluated = this.CompilationOutput != null && this.CompilationOutput.PreEvaluateAll(out this.CompilationOutput, onException); + if (!evaluated) this.LogAndUpdate(ref this.CompilationStatus.PreEvaluation, ErrorCode.PreEvaluationFailed, Enumerable.Empty()); + } + + RaiseCompilationTaskEnd("OverallCompilation", "Build"); + + // generating the compiled binary and dll + + 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) + { + RaiseCompilationTaskStart("OutputGeneration", "BinaryGeneration"); + this.PathToCompiledBinary = this.GenerateBinary(ms); + RaiseCompilationTaskEnd("OutputGeneration", "BinaryGeneration"); + } + if (serialized && this.Config.DllOutputPath != null) + { + RaiseCompilationTaskStart("OutputGeneration", "DllGeneration"); + this.DllOutputPath = this.GenerateDll(ms); + RaiseCompilationTaskEnd("OutputGeneration", "DllGeneration"); + } + } + + // executing the specified generation steps + + 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"); + } + + RaiseCompilationTaskEnd("OverallCompilation", "OutputGeneration"); + + // invoking rewrite steps in external dlls + + RaiseCompilationTaskStart("OverallCompilation", "RewriteSteps"); + for (int i = 0; i < this.ExternalRewriteSteps.Length; i++) + { + if (this.CompilationOutput == null) continue; + this.CompilationOutput = ExecuteAsAtomicTransformation(this.ExternalRewriteSteps[i], ref this.CompilationStatus.LoadedRewriteSteps[i]); + } + + RaiseCompilationTaskEnd("OverallCompilation", "RewriteSteps"); + RaiseCompilationTaskEnd(null, "OverallCompilation"); + } + + /// + /// Executes the given rewrite step on the given compilation, returning a transformed compilation as an out parameter. + /// Catches and logs any thrown exception. Returns the status of the rewrite step. + /// Throws an ArgumentNullException if the rewrite step to execute or the given compilation is null. + /// + 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)); + + string GetDiagnosticsCode(DiagnosticSeverity severity) => + rewriteStep.Name == "CsharpGeneration" && severity == DiagnosticSeverity.Error ? Errors.Code(ErrorCode.CsharpGenerationGeneratedError) : + rewriteStep.Name == "CsharpGeneration" && severity == DiagnosticSeverity.Warning ? Warnings.Code(WarningCode.CsharpGenerationGeneratedWarning) : + rewriteStep.Name == "CsharpGeneration" && severity == DiagnosticSeverity.Information ? Informations.Code(InformationCode.CsharpGenerationGeneratedInfo) : + null; + + Status LogDiagnostics(Status status = Status.Succeeded) + { + try + { + foreach (var diagnostic in rewriteStep.GeneratedDiagnostics ?? ImmutableArray.Empty) + { this.LogAndUpdate(ref status, RewriteSteps.LoadedStep.ConvertDiagnostic(diagnostic, GetDiagnosticsCode)); } + } + catch { this.LogAndUpdate(ref status, Warning(WarningCode.RewriteStepDiagnosticsGenerationFailed, new[] { rewriteStep.Name })); } + return status; + } + + var status = Status.Succeeded; + var messageSource = ProjectManager.MessageSource(rewriteStep.Origin); + Diagnostic Warning(WarningCode code, params string[] args) => Warnings.LoadWarning(code, args, messageSource); + try + { + transformed = compilation; + var preconditionFailed = rewriteStep.ImplementsPreconditionVerification && !rewriteStep.PreconditionVerification(compilation); + if (preconditionFailed) + { + LogDiagnostics(); + this.LogAndUpdate(ref status, Warning(WarningCode.PreconditionVerificationFailed, new[] { rewriteStep.Name, messageSource })); + return status; + } + + var transformationFailed = rewriteStep.ImplementsTransformation && !rewriteStep.Transformation(compilation, out transformed); + var postconditionFailed = this.Config.EnableAdditionalChecks && rewriteStep.ImplementsPostconditionVerification && !rewriteStep.PostconditionVerification(transformed); + LogDiagnostics(); + + if (transformationFailed) this.LogAndUpdate(ref status, ErrorCode.RewriteStepExecutionFailed, new[] { rewriteStep.Name, messageSource }); + if (postconditionFailed) this.LogAndUpdate(ref status, ErrorCode.PostconditionVerificationFailed, new[] { rewriteStep.Name, messageSource }); + return status; + } + 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 }); + transformed = null; + } + return status; + } + + /// + /// Builds the compilation of the specified source files and references, + /// executing the compilation steps specified by the given options. + /// 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) { } + + /// + /// Builds the compilation of the specified source files and the loaded references returned by the given loader, + /// executing the compilation steps specified by the given options. + /// Uses the specified logger to log all diagnostic events. + /// 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) { } + + /// + /// Builds the compilation of the content returned by the given loader and the specified references, + /// executing the compilation steps specified by the given options. + /// Uses the specified logger to log all diagnostic events. + /// 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) { } + + + // private routines used for logging and status updates + + /// + /// Logs the given diagnostic and updates the status passed as reference accordingly. + /// Throws an ArgumentNullException if the given diagnostic is null. + /// + private void LogAndUpdate(ref Status current, Diagnostic d) + { + this.Logger?.Log(d); + if (d.IsError()) current = Status.Failed; + } + + /// + /// Logs the given exception and updates the status passed as reference accordingly. + /// + private void LogAndUpdate(ref Status current, Exception ex) + { + this.Logger?.Log(ex); + current = Status.Failed; + } + + /// + /// Logs an error with the given error code and message parameters, and updates the status passed as reference accordingly. + /// + private void LogAndUpdate(ref Status current, ErrorCode code, IEnumerable args) + { + this.Logger?.Log(code, args); + current = Status.Failed; + } + + /// + /// Logs the given diagnostic and updates the status passed as reference accordingly. + /// Adds the given diagnostic to the tracked load diagnostics. + /// Throws an ArgumentNullException if the given diagnostic is null. + /// + private void LogAndUpdateLoadDiagnostics(ref Status current, Diagnostic d) + { + this.LoadDiagnostics = this.LoadDiagnostics.Add(d); + this.LogAndUpdate(ref current, d); + } + + /// + /// Logs an UnexpectedCompilerException error as well as the given exception, and updates the validation status accordingly. + /// + private void OnCompilerException(Exception ex) + { + this.LogAndUpdate(ref this.CompilationStatus.Validation, ErrorCode.UnexpectedCompilerException, Enumerable.Empty()); + this.LogAndUpdate(ref this.CompilationStatus.Validation, ex); + } + + /// + /// Logs the names of the given source files as Information. + /// Does nothing if the given argument is null. + /// + private void PrintResolvedFiles(IEnumerable sourceFiles) + { + 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()); + } + + /// + /// Logs the names of the given assemblies as Information. + /// Does nothing if the given argument is null. + /// + private void PrintResolvedAssemblies(IEnumerable> assemblies) + { + 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()); + } + + /// + /// Logs the names and origins of the given rewrite steps as Information. + /// Does nothing if the given argument is null. + /// + private void PrintLoadedRewriteSteps(IEnumerable rewriteSteps) + { + 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()); + } + + + // routines for loading from and dumping to files + + /// + /// Used to load the content of the specified source files from disk. + /// Returns a dictionary mapping the file uri to its content. + /// Logs suitable diagnostics in the process and modifies the compilation status accordingly. + /// Prints all loaded files using PrintResolvedFiles. + /// + 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.PrintResolvedFiles(sourceFiles.Keys); + return sourceFiles; + } + + /// + /// Used to load the content of the specified assembly references from disk. + /// 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 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, (code, args) => onDiagnostic(Errors.LoadError(code, args, projId))); + this.PrintResolvedAssemblies(references.Declarations.Keys); + return references; + } + + /// + /// Writes a binary representation of the built Q# compilation output to the given memory stream. + /// Logs suitable diagnostics in the process and modifies the compilation status accordingly. + /// Does *not* close the given memory stream, and + /// returns true if the serialization has been successfully generated. + /// Throws an ArgumentNullException if the given memory stream is null. + /// + private bool SerializeSyntaxTree(MemoryStream ms) + { + if (ms == null) throw new ArgumentNullException(nameof(ms)); + bool ErrorAndReturn() + { + this.LogAndUpdate(ref this.CompilationStatus.Serialization, ErrorCode.SerializationFailed, Enumerable.Empty()); + 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); } + catch (Exception ex) + { + this.LogAndUpdate(ref this.CompilationStatus.Serialization, ex); + ErrorAndReturn(); + } + return true; + } + + /// + /// Backtracks to the beginning of the given memory stream and writes its content to disk, + /// generating a suitable bson file in the specified build output folder using the project name as file name. + /// Generates a file name at random if no project name is specified. + /// Logs suitable diagnostics in the process and modifies the compilation status accordingly. + /// Returns the absolute path of the file where the binary representation has been generated. + /// Returns null if the binary file could not be generated. + /// Does *not* close the given memory stream. + /// Throws an ArgumentNullException if the given memory stream is null. + /// + private string GenerateBinary(MemoryStream serialization) + { + 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 target = GeneratedFile(projId, outFolder, ".bson", ""); + + try + { + serialization.Seek(0, SeekOrigin.Begin); + using (var file = new FileStream(target, FileMode.Create, FileAccess.Write)) + { 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()); + return null; + } + } + + /// + /// Backtracks to the beginning of the given memory stream and, + /// assuming the given memory stream contains a serialization of the compiled syntax tree, + /// generates a dll containing the compiled binary at the specified dll output path. + /// Logs suitable diagnostics in the process and modifies the compilation status accordingly. + /// Returns the absolute path of the file where the dll has been generated. + /// Returns null if the dll could not be generated. + /// Does *not* close the given memory stream. + /// Throws an ArgumentNullException if the given memory stream is null. + /// + private string GenerateDll(MemoryStream serialization) + { + 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); + outputPath = Path.ChangeExtension(outputPath, "dll"); + + MetadataReference CreateReference(string file, int id) => + MetadataReference.CreateFromFile(file) + .WithAliases(new string[] { $"{DotnetCoreDll.ReferenceAlias}{id}" }); // referenced Q# dlls are recognized based on this alias + + // We need to force the inclusion of references despite that we do not include C# code that depends on them. + // This is done via generating a certain handle in all dlls built via this compilation loader. + // 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 + { + using var stream = File.OpenRead(dll.Value); + using var assemblyFile = new PEReader(stream); + var metadataReader = assemblyFile.GetMetadataReader(); + return metadataReader.TypeDefinitions + .Select(metadataReader.GetTypeDefinition) + .Any(t => metadataReader.GetString(t.Namespace) == DotnetCoreDll.MetadataNamespace); + } + catch { return false; } + } + + try + { + var referencePaths = GetSourceFiles.Apply(this.CompilationOutput.Namespaces) // we choose to keep only Q# references that have been used + .Where(file => file.Value.EndsWith(".dll")); + var references = referencePaths.Select((dll, id) => (dll, CreateReference(dll.Value, id), CanBeIncluded(dll))).ToImmutableArray(); + var csharpTree = MetadataGeneration.GenerateAssemblyMetadata(references.Where(r => r.Item3).Select(r => r.Item2)); + 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); + } + + var compilation = CodeAnalysis.CSharp.CSharpCompilation.Create( + 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) + ); + + 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, + options: new CodeAnalysis.Emit.EmitOptions(), + 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()))}"); + return outputPath; + } + catch (Exception ex) + { + this.LogAndUpdate(ref this.CompilationStatus.DllGeneration, ex); + this.LogAndUpdate(ref this.CompilationStatus.DllGeneration, ErrorCode.GeneratingDllFailed, Enumerable.Empty()); + return null; + } + } + + /// + /// Given the path to a Q# binary file, reads the content of that file and returns the corresponding compilation as out parameter. + /// Throws the corresponding exception if the given path does not correspond to a suitable binary file. + /// + public static bool ReadBinary(string file, out QsCompilation syntaxTree) => + ReadBinary(new MemoryStream(File.ReadAllBytes(Path.GetFullPath(file))), out syntaxTree); + + /// + /// Given a stream with the content of a Q# binary file, returns the corresponding compilation as out parameter. + /// Throws an ArgumentNullException if the given stream is null. + /// + public static bool ReadBinary(Stream stream, out QsCompilation syntaxTree) => + AssemblyLoader.LoadSyntaxTree(stream, out syntaxTree); + + /// + /// Given a file id assigned by the Q# compiler, computes the corresponding path in the specified output folder. + /// Returns the computed absolute path for a file with the specified ending. + /// If the content for that file is specified, writes that content to disk. + /// Throws an ArgumentException if the given file id is incompatible with and id assigned by the Q# compiler. + /// Throws the corresponding exception any of the path operations fails or if the writing fails. + /// + 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"); } + string FullDirectoryName(string dir) => + Path.GetFullPath(dir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar); + + outputFolder = String.IsNullOrWhiteSpace(outputFolder) ? "." : outputFolder; + var outputUri = new Uri(FullDirectoryName(outputFolder)); + var currentDir = new Uri(FullDirectoryName(".")); + var relFilePath = currentDir.MakeRelativeUri(file); + var filePath = Uri.UnescapeDataString(new Uri(outputUri, relFilePath).LocalPath); + var fileDir = filePath.StartsWith(outputUri.LocalPath) + ? Path.GetDirectoryName(filePath) + : 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); + File.WriteAllText(targetFile, content); + return targetFile; + } + + /// + /// Raises a compilation task start event. + /// + private void RaiseCompilationTaskStart (string parentTaskName, string taskName) => + CompilationTaskEvent?.Invoke(this, new CompilationTaskEventArgs(CompilationTaskEventType.Start, parentTaskName, taskName)); + + /// + /// Raises a compilation task end event. + /// + private void RaiseCompilationTaskEnd(string parentTaskName, string taskName) => + CompilationTaskEvent?.Invoke(this, new CompilationTaskEventArgs(CompilationTaskEventType.End, parentTaskName, taskName)); + } +} diff --git a/src/QsCompiler/Compiler/FunctorGeneration.cs b/src/QsCompiler/Compiler/FunctorGeneration.cs index 71ba0b8c1b..123da37375 100644 --- a/src/QsCompiler/Compiler/FunctorGeneration.cs +++ b/src/QsCompiler/Compiler/FunctorGeneration.cs @@ -28,7 +28,7 @@ private static QsTuple> ControlledArg(Qs : null; /// - /// Given a sequence of specialziations, returns the implementation of the given kind, or null if no such specialization exists. + /// Given a sequence of specializations, returns the implementation of the given kind, or null if no such specialization exists. /// Throws an ArgumentException if more than one specialization of that kind exist. /// Throws an ArgumentNullException if the sequence of specializations is null, or contains any entries that are null. /// @@ -67,7 +67,7 @@ private static (QsTuple>, QsScope)? Body } /// - /// Given a Q# callable, evaluates any functor generator directive given for its adjoint specializiation. + /// Given a Q# callable, evaluates any functor generator directive given for its adjoint specialization. /// If the body specialization is either intrinsic or external, return the given callable unchanged. /// Otherwise returns a new QsCallable with the adjoint specialization set to the generated implementation if it was generated, /// or set to the original specialization if the specialization was not requested to be auto-generated. @@ -96,7 +96,7 @@ private static QsCallable BuildAdjoint(this QsCallable callable) } /// - /// Given a Q# callable, evaluates any functor generator directive given for its controlled specializiation. + /// Given a Q# callable, evaluates any functor generator directive given for its controlled specialization. /// If the body specialization is either intrinsic or external, return the given callable unchanged. /// Otherwise returns a new QsCallable with the controlled specialization set to the generated implementation if it was generated, /// or set to the original specialization if the specialization was not requested to be auto-generated. @@ -122,7 +122,7 @@ private static QsCallable BuildControlled(this QsCallable callable) } /// - /// Given a Q# callable, evaluates any functor generator directive given for its controlled adjoint specializiation. + /// Given a Q# callable, evaluates any functor generator directive given for its controlled adjoint specialization. /// If the body specialization is either intrinsic or external, return the given callable unchanged. /// Otherwise returns a new QsCallable with the controlled adjoint specialization set to the generated implementation if it was generated, /// or set to the original specialization if the specialization was not requested to be auto-generated. @@ -153,10 +153,10 @@ private static QsCallable BuildControlledAdjoint(this QsCallable callable) { var ctl = callable.Specializations.GetSpecialization(QsSpecializationKind.QsControlled); var (ctlArg, ctlImpl) = GetContent(ctl?.Implementation) ?? throw new ArgumentException("no implementation provided for controlled specialization"); - void SetImplementation(QsScope impl) => ctlAdj = ctlAdj.WithImplementation(SpecializationImplementation.NewProvided(ctlArg, impl)); - - //if (gen.Item.IsSelfInverse) SetImplementation(ctlImpl); -> nothing to do here, we want to keep this information - if (gen.Item.IsInvert) SetImplementation(ctlImpl.GenerateAdjoint()); + if (gen.Item.IsInvert) + { + ctlAdj = ctlAdj.WithImplementation(SpecializationImplementation.NewProvided(ctlArg, ctlImpl.GenerateAdjoint())); + } } } return callable.WithSpecializations(specs => specs.Select(s => s.Kind == QsSpecializationKind.QsControlledAdjoint ? ctlAdj : s).ToImmutableArray()); diff --git a/src/QsCompiler/Compiler/PluginInterface.cs b/src/QsCompiler/Compiler/PluginInterface.cs index 6454c01abc..50dc8bcd1a 100644 --- a/src/QsCompiler/Compiler/PluginInterface.cs +++ b/src/QsCompiler/Compiler/PluginInterface.cs @@ -93,7 +93,7 @@ public struct Diagnostic /// /// Verifies whether a given compilation satisfies the precondition for executing this rewrite step. /// indicates whether or not this method is implemented. - /// If the precondition verfication succeeds, then the invocation of an implemented transformation (if any) + /// 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. diff --git a/src/QsCompiler/Compiler/RewriteSteps/ClassicallyControlled.cs b/src/QsCompiler/Compiler/RewriteSteps/ClassicallyControlled.cs new file mode 100644 index 0000000000..dd7088f28f --- /dev/null +++ b/src/QsCompiler/Compiler/RewriteSteps/ClassicallyControlled.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SyntaxTree; +using Microsoft.Quantum.QsCompiler.Transformations.ClassicallyControlledTransformation; + + +namespace Microsoft.Quantum.QsCompiler.BuiltInRewriteSteps +{ + internal class ClassicallyControlled : IRewriteStep + { + public string Name => "ClassicallyControlled"; + public int Priority => 10; // Not used for built-in transformations like this + public IDictionary AssemblyConstants { get; } + public IEnumerable GeneratedDiagnostics => null; + + public bool ImplementsTransformation => true; + public bool ImplementsPreconditionVerification => true; + public bool ImplementsPostconditionVerification => false; + + public ClassicallyControlled() + { + AssemblyConstants = new Dictionary(); + } + + public bool Transformation(QsCompilation compilation, out QsCompilation transformed) + { + transformed = ClassicallyControlledTransformation.Apply(compilation); + return true; + } + + 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.Name); + var requiredBuiltIns = new List>() + { + BuiltIn.ApplyIfZero.Name, + BuiltIn.ApplyIfZeroA.Name, + BuiltIn.ApplyIfZeroC.Name, + BuiltIn.ApplyIfZeroCA.Name, + + BuiltIn.ApplyIfOne.Name, + BuiltIn.ApplyIfOneA.Name, + BuiltIn.ApplyIfOneC.Name, + BuiltIn.ApplyIfOneCA.Name, + + BuiltIn.ApplyIfElseR.Name, + BuiltIn.ApplyIfElseRA.Name, + BuiltIn.ApplyIfElseRC.Name, + BuiltIn.ApplyIfElseRCA.Name + }; + + return requiredBuiltIns.All(builtIn => providedOperations.Any(provided => provided.Equals(builtIn))); + } + + public bool PostconditionVerification(QsCompilation compilation) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/src/QsCompiler/Core/Dependencies.fs b/src/QsCompiler/Core/Dependencies.fs index 7cdefcd69d..000e21a2ca 100644 --- a/src/QsCompiler/Core/Dependencies.fs +++ b/src/QsCompiler/Core/Dependencies.fs @@ -6,6 +6,7 @@ namespace Microsoft.Quantum.QsCompiler open System.Collections.Immutable open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.SyntaxTree +open Microsoft.Quantum.QsCompiler.SyntaxTokens type BuiltIn = { @@ -21,6 +22,7 @@ type BuiltIn = { static member IntrinsicNamespace = NonNullable.New "Microsoft.Quantum.Intrinsic" static member StandardArrayNamespace = NonNullable.New "Microsoft.Quantum.Arrays" static member DiagnosticsNamespace = NonNullable.New "Microsoft.Quantum.Diagnostics" + static member ClassicallyControlledNamespace = NonNullable.New "Microsoft.Quantum.Simulation.QuantumProcessor.Extensions" /// Returns the set of namespaces that is automatically opened for each compilation. static member NamespacesToAutoOpen = ImmutableHashSet.Create (BuiltIn.CoreNamespace) @@ -85,6 +87,91 @@ type BuiltIn = { TypeParameters = ImmutableArray.Empty } + // hard dependencies in Microsoft.Quantum.Simulation.QuantumProcessor.Extensions + + // This is expected to have type <'T>((Result, (('T => Unit), 'T)) => Unit) + static member ApplyIfZero = { + Name = "ApplyIfZero" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New) + } + + // This is expected to have type <'T>((Result, (('T => Unit is Adj), 'T)) => Unit is Adj) + static member ApplyIfZeroA = { + Name = "ApplyIfZeroA" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New) + } + + // This is expected to have type <'T>((Result, (('T => Unit is Ctl), 'T)) => Unit is Ctl) + static member ApplyIfZeroC = { + Name = "ApplyIfZeroC" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New) + } + + // This is expected to have type <'T>((Result, (('T => Unit is Adj + Ctl), 'T)) => Unit is Adj + Ctl) + static member ApplyIfZeroCA = { + Name = "ApplyIfZeroCA" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New) + } + + // This is expected to have type <'T>((Result, (('T => Unit), 'T)) => Unit) + static member ApplyIfOne = { + Name = "ApplyIfOne" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New) + } + + // This is expected to have type <'T>((Result, (('T => Unit is Adj), 'T)) => Unit is Adj) + static member ApplyIfOneA = { + Name = "ApplyIfOneA" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New) + } + + // This is expected to have type <'T>((Result, (('T => Unit is Ctl), 'T)) => Unit is Ctl) + static member ApplyIfOneC = { + Name = "ApplyIfOneC" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New) + } + + // This is expected to have type <'T>((Result, (('T => Unit is Adj + Ctl), 'T)) => Unit is Adj + Ctl) + static member ApplyIfOneCA = { + Name = "ApplyIfOneCA" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New) + } + + // This is expected to have type <'T, 'U>((Result, (('T => Unit), 'T), (('U => Unit), 'U)) => Unit) + static member ApplyIfElseR = { + Name = "ApplyIfElseR" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New) + } + + // This is expected to have type <'T, 'U>((Result, (('T => Unit is Adj), 'T), (('U => Unit is Adj), 'U)) => Unit is Adj) + static member ApplyIfElseRA = { + Name = "ApplyIfElseRA" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New) + } + + // This is expected to have type <'T, 'U>((Result, (('T => Unit is Ctl), 'T), (('U => Unit is Ctl), 'U)) => Unit is Ctl) + static member ApplyIfElseRC = { + Name = "ApplyIfElseRC" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New) + } + + // This is expected to have type <'T, 'U>((Result, (('T => Unit is Adj + Ctl), 'T), (('U => Unit is Adj + Ctl), 'U)) => Unit is Adj + Ctl) + static member ApplyIfElseRCA = { + Name = "ApplyIfElseRCA" |> NonNullable.New + Namespace = BuiltIn.ClassicallyControlledNamespace + TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New) + } // "weak dependencies" in other namespaces (e.g. things used for code actions) diff --git a/src/QsCompiler/DataStructures/SyntaxExtensions.fs b/src/QsCompiler/DataStructures/SyntaxExtensions.fs index 15d8e0ba17..4c71dc94c7 100644 --- a/src/QsCompiler/DataStructures/SyntaxExtensions.fs +++ b/src/QsCompiler/DataStructures/SyntaxExtensions.fs @@ -171,21 +171,6 @@ type TypedExpression with // utils for walking the data structure - /// Returns true if the expression contains missing expressions. - /// Returns false otherwise. - static member public ContainsMissing (ex : TypedExpression) = - match ex.TupleItems with - | Some items when items.Length > 1 -> items |> List.exists TypedExpression.ContainsMissing - | Some [] -> true - | _ -> false - - /// Returns true if the expression is a call-like expression, and the arguments contain a missing expression. - /// Returns false otherwise. - static member public IsPartialApplication kind = - match kind with - | CallLikeExpression (_, args) -> args |> TypedExpression.ContainsMissing - | _ -> false - /// Returns true if the expression kind does not contain any inner expressions. static member private IsAtomic (kind : QsExpressionKind<'E, _, _>) = match kind with diff --git a/src/QsCompiler/DataStructures/SyntaxTree.fs b/src/QsCompiler/DataStructures/SyntaxTree.fs index 7f05775360..47fa010d67 100644 --- a/src/QsCompiler/DataStructures/SyntaxTree.fs +++ b/src/QsCompiler/DataStructures/SyntaxTree.fs @@ -364,6 +364,17 @@ type TypedExpression = { member this.TypeParameterResolutions = this.TypeArguments.ToImmutableDictionary((fun (origin, name, _) -> origin, name), (fun (_,_,t) -> t)) + /// Returns true if the expression is a call-like expression, and the arguments contain a missing expression. + /// Returns false otherwise. + static member public IsPartialApplication kind = + let rec containsMissing ex = + match ex.Expression with + | MissingExpr -> true + | ValueTuple items -> items |> Seq.exists containsMissing + | _ -> false + match kind with + | CallLikeExpression (_, args) -> args |> containsMissing + | _ -> false /// Fully resolved Q# initializer expression. diff --git a/src/QsCompiler/Optimizations/OptimizingTransformations/ConstantPropagation.fs b/src/QsCompiler/Optimizations/OptimizingTransformations/ConstantPropagation.fs index e4c70ef545..7fd900a6b8 100644 --- a/src/QsCompiler/Optimizations/OptimizingTransformations/ConstantPropagation.fs +++ b/src/QsCompiler/Optimizations/OptimizingTransformations/ConstantPropagation.fs @@ -22,7 +22,7 @@ type ConstantPropagation(callables) = /// Returns whether the given expression should be propagated as a constant. /// For a statement of the form "let x = [expr];", if shouldPropagate(expr) is true, - /// then we should substitute [expr] for x wherever x occurs in future code. + /// then we should substitute x with [expr] wherever x occurs in future code. let rec shouldPropagate callables (expr : TypedExpression) = let folder ex sub = isLiteral callables ex || diff --git a/src/QsCompiler/Optimizations/Utils/Evaluation.fs b/src/QsCompiler/Optimizations/Utils/Evaluation.fs index 8447ebeb5f..e052c745e0 100644 --- a/src/QsCompiler/Optimizations/Utils/Evaluation.fs +++ b/src/QsCompiler/Optimizations/Utils/Evaluation.fs @@ -219,7 +219,7 @@ and [] private ExpressionKindEvaluator(callables: ImmutableDictio let fe = FunctionEvaluator (callables) return! fe.evaluateFunction qualName arg types stmtsLeft |> Option.map (fun x -> x.Expression) | CallLikeExpression (baseMethod, partialArg) -> - do! check (TypedExpression.ContainsMissing partialArg) + do! check (TypedExpression.IsPartialApplication method.Expression) return this.Transform (CallLikeExpression (baseMethod, fillPartialArg (partialArg, arg))) | _ -> return! None } |? CallLikeExpression (method, arg) @@ -229,7 +229,7 @@ and [] private ExpressionKindEvaluator(callables: ImmutableDictio maybe { match method.Expression with | CallLikeExpression (baseMethod, partialArg) -> - do! check (TypedExpression.ContainsMissing partialArg) + do! check (TypedExpression.IsPartialApplication method.Expression) return this.Transform (CallLikeExpression (baseMethod, fillPartialArg (partialArg, arg))) | _ -> return! None } |? CallLikeExpression (method, arg) @@ -239,7 +239,7 @@ and [] private ExpressionKindEvaluator(callables: ImmutableDictio maybe { match method.Expression with | CallLikeExpression (baseMethod, partialArg) -> - do! check (TypedExpression.ContainsMissing partialArg) + do! check (TypedExpression.IsPartialApplication method.Expression) return this.Transform (CallLikeExpression (baseMethod, fillPartialArg (partialArg, arg))) | _ -> return! None } |? CallLikeExpression (method, arg) diff --git a/src/QsCompiler/Optimizations/Utils/HelperFunctions.fs b/src/QsCompiler/Optimizations/Utils/HelperFunctions.fs index 0d38fb6277..44d295f2e1 100644 --- a/src/QsCompiler/Optimizations/Utils/HelperFunctions.fs +++ b/src/QsCompiler/Optimizations/Utils/HelperFunctions.fs @@ -190,18 +190,26 @@ and internal defaultValue (bt: TypeKind): ExprKind option = | _ -> None +/// Returns true if the expression contains missing expressions. +/// Returns false otherwise. +let rec private containsMissing (ex : TypedExpression) = + match ex.Expression with + | MissingExpr -> true + | ValueTuple items -> items |> Seq.exists containsMissing + | _ -> false + /// Fills a partial argument by replacing MissingExprs with the corresponding values of a tuple let rec internal fillPartialArg (partialArg: TypedExpression, arg: TypedExpression): TypedExpression = match partialArg with | Missing -> arg | Tuple items -> let argsList = - match List.filter TypedExpression.ContainsMissing items, arg with + match List.filter containsMissing items, arg with | [_], _ -> [arg] | _, Tuple args -> args | _ -> failwithf "args must be a tuple" items |> List.mapFold (fun args t1 -> - if TypedExpression.ContainsMissing t1 then + if containsMissing t1 then match args with | [] -> failwithf "ran out of args" | head :: tail -> fillPartialArg (t1, head), tail diff --git a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs index 4855f39847..5282c6deb3 100644 --- a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs @@ -381,11 +381,11 @@ let private VerifyIdentifier addDiagnostic (symbols : SymbolTracker<_>) (sym, tA /// Verifies whether an expression of the given argument type can be used as argument to a method (function, operation, or setter) /// that expects an argument of the given target type. The given target type may contain a missing type (valid for a setter). -/// Accumulates and returns an array with error codes for the cases where this is not the case, and returns an empyt array otherwise. +/// Accumulates and returns an array with error codes for the cases where this is not the case, and returns an empty array otherwise. /// Note that MissingTypes in the argument type should not occur aside from possibly as array base type of the expression. /// A missing type in the given argument type will cause a verification failure in QsCompilerError. /// For each type parameter in the target type, calls addTypeParameterResolution with a tuple of the type parameter and the type that is substituted for it. -/// IMPORTANT: The consistent (i.e. non-ambiguous and non-contraining) resolution of type parameters is *not* verified by this routine +/// IMPORTANT: The consistent (i.e. non-ambiguous and non-constraining) resolution of type parameters is *not* verified by this routine /// and needs to be verified in a separate step! let internal TypeMatchArgument addTypeParameterResolution targetType argType = let givenAndExpectedType = [argType |> toString; targetType |> toString] diff --git a/src/QsCompiler/TestTargets/Libraries/Library1/Library1.csproj b/src/QsCompiler/TestTargets/Libraries/Library1/Library1.csproj index 2ac4b52561..40476e0288 100644 --- a/src/QsCompiler/TestTargets/Libraries/Library1/Library1.csproj +++ b/src/QsCompiler/TestTargets/Libraries/Library1/Library1.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/QsCompiler/TestTargets/Simulation/Example/Example.csproj b/src/QsCompiler/TestTargets/Simulation/Example/Example.csproj index a2bbd3b3a8..b7d89acc9c 100644 --- a/src/QsCompiler/TestTargets/Simulation/Example/Example.csproj +++ b/src/QsCompiler/TestTargets/Simulation/Example/Example.csproj @@ -1,9 +1,10 @@ - + Detailed Exe netcoreapp3.0 + false false diff --git a/src/QsCompiler/TestTargets/Simulation/Target/Simulation.csproj b/src/QsCompiler/TestTargets/Simulation/Target/Simulation.csproj index a87b1f1752..daee5a684b 100644 --- a/src/QsCompiler/TestTargets/Simulation/Target/Simulation.csproj +++ b/src/QsCompiler/TestTargets/Simulation/Target/Simulation.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/QsCompiler/Tests.Compiler/ClassicalControlTests.fs b/src/QsCompiler/Tests.Compiler/ClassicalControlTests.fs new file mode 100644 index 0000000000..a8ebac129f --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/ClassicalControlTests.fs @@ -0,0 +1,1240 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.QsCompiler.Testing + +open System +open System.IO +open Microsoft.Quantum.QsCompiler +open Microsoft.Quantum.QsCompiler.CompilationBuilder +open Microsoft.Quantum.QsCompiler.DataTypes +open Microsoft.Quantum.QsCompiler.SyntaxTree +open Microsoft.Quantum.QsCompiler.Transformations.ClassicallyControlledTransformation +open Xunit +open Microsoft.Quantum.QsCompiler.Transformations.QsCodeOutput +open System.Text.RegularExpressions +open Microsoft.Quantum.QsCompiler.SyntaxTokens + + +type ClassicalControlTests () = + + let compilationManager = new CompilationUnitManager(new Action (fun ex -> failwith ex.Message)) + + let getTempFile () = new Uri(Path.GetFullPath(Path.GetRandomFileName())) + let getManager uri content = CompilationUnitManager.InitializeFileManager(uri, content, compilationManager.PublishDiagnostics, compilationManager.LogException) + + do let addOrUpdateSourceFile filePath = getManager (new Uri(filePath)) (File.ReadAllText filePath) |> compilationManager.AddOrUpdateSourceFileAsync |> ignore + Path.Combine ("TestCases", "LinkingTests", "Core.qs") |> Path.GetFullPath |> addOrUpdateSourceFile + Path.Combine ("TestCases", "LinkingTests", "QuantumProcessorExtensions.qs") |> Path.GetFullPath |> addOrUpdateSourceFile + + let ReadAndChunkSourceFile fileName = + let sourceInput = Path.Combine ("TestCases", "LinkingTests", fileName) |> File.ReadAllText + sourceInput.Split ([|"==="|], StringSplitOptions.RemoveEmptyEntries) + + let BuildContent content = + + let fileId = getTempFile() + let file = getManager fileId content + + compilationManager.AddOrUpdateSourceFileAsync(file) |> ignore + let compilationDataStructures = compilationManager.Build() + compilationManager.TryRemoveSourceFileAsync(fileId, false) |> ignore + + compilationDataStructures.Diagnostics() |> Seq.exists (fun d -> d.IsError()) |> Assert.False + Assert.NotNull compilationDataStructures.BuiltCompilation + + compilationDataStructures + + let CompileClassicalControlTest testNumber = + let srcChunks = ReadAndChunkSourceFile "ClassicalControl.qs" + srcChunks.Length >= testNumber + 1 |> Assert.True + let shared = srcChunks.[0] + let compilationDataStructures = BuildContent <| shared + srcChunks.[testNumber] + let processedCompilation = ClassicallyControlledTransformation.Apply compilationDataStructures.BuiltCompilation + Assert.NotNull processedCompilation + Signatures.SignatureCheck [Signatures.ClassicalControlNs] Signatures.ClassicalControlSignatures.[testNumber-1] processedCompilation + processedCompilation + + let GetBodyFromCallable call = call.Specializations |> Seq.find (fun x -> x.Kind = QsSpecializationKind.QsBody) + let GetAdjFromCallable call = call.Specializations |> Seq.find (fun x -> x.Kind = QsSpecializationKind.QsAdjoint) + let GetCtlFromCallable call = call.Specializations |> Seq.find (fun x -> x.Kind = QsSpecializationKind.QsControlled) + let GetCtlAdjFromCallable call = call.Specializations |> Seq.find (fun x -> x.Kind = QsSpecializationKind.QsControlledAdjoint) + + let GetLinesFromSpecialization specialization = + let writer = new SyntaxTreeToQs() + + specialization + |> fun x -> match x.Implementation with | Provided (_, body) -> Some body | _ -> None + |> Option.get + |> writer.Scope.Transform + |> ignore + + (writer.Scope :?> ScopeToQs).Output.Split(Environment.NewLine) + |> Array.filter (fun str -> str <> String.Empty) + + let CheckIfLineIsCall ``namespace`` name input = + let call = sprintf @"(%s\.)?%s" <| Regex.Escape ``namespace`` <| Regex.Escape name + let typeArgs = @"(<\s*([^<]*[^<\s])\s*>)?" // Does not support nested type args + let args = @"\(\s*(.*[^\s])?\s*\)" + let regex = sprintf @"^\s*%s\s*%s\s*%s;$" call typeArgs args + + let regexMatch = Regex.Match(input, regex) + if regexMatch.Success then + (true, regexMatch.Groups.[3].Value, regexMatch.Groups.[4].Value) + else + (false, "", "") + + let MakeApplicationRegex (opName : QsQualifiedName) = + let call = sprintf @"(%s\.)?%s" <| Regex.Escape opName.Namespace.Value <| Regex.Escape opName.Name.Value + let typeArgs = @"(<\s*([^<]*[^<\s])\s*>)?" // Does not support nested type args + let args = @"\(\s*(.*[^\s])?\s*\)" + + sprintf @"\(%s\s*%s,\s*%s\)" <| call <| typeArgs <| args + + let IsApplyIfArgMatch input resultVar (opName : QsQualifiedName) = + let regexMatch = Regex.Match(input, sprintf @"^\s*%s,\s*%s$" <| Regex.Escape resultVar <| MakeApplicationRegex opName) + + if regexMatch.Success then + (true, regexMatch.Groups.[3].Value, regexMatch.Groups.[4].Value) + else + (false, "", "") + + let IsApplyIfElseArgsMatch input resultVar (opName1 : QsQualifiedName) (opName2 : QsQualifiedName) = + let ApplyIfElseRegex = sprintf @"^%s,\s*%s,\s*%s$" + <| Regex.Escape resultVar + <| MakeApplicationRegex opName1 + <| MakeApplicationRegex opName2 + + let regexMatch = Regex.Match(input, ApplyIfElseRegex) + if regexMatch.Success then + (true, regexMatch.Groups.[3].Value, regexMatch.Groups.[4].Value, regexMatch.Groups.[7].Value, regexMatch.Groups.[8].Value) + else + (false, "", "", "", "") + + let CheckIfSpecializationHasCalls specialization (calls : seq) = + let lines = GetLinesFromSpecialization specialization + Seq.forall (fun (i, ns, name) -> CheckIfLineIsCall ns name lines.[i] |> (fun (x, _, _) -> x)) calls + + let AssertSpecializationHasCalls specialization calls = + Assert.True(CheckIfSpecializationHasCalls specialization calls, sprintf "Callable %O(%A) did not have expected content" specialization.Parent specialization.Kind) + + let ExpandBuiltInQualifiedSymbol (i, (builtin : BuiltIn)) = (i, builtin.Namespace.Value, builtin.Name.Value) + + let IdentifyGeneratedByCalls generatedCallables calls = + let mutable callables = generatedCallables |> Seq.map (fun x -> x, x |> (GetBodyFromCallable >> GetLinesFromSpecialization)) + let hasCall callable (call : seq) = + let (_, lines : string[]) = callable + Seq.forall (fun (i, ns, name) -> CheckIfLineIsCall ns name lines.[i] |> (fun (x, _, _) -> x)) call + + Assert.True(Seq.length callables = Seq.length calls) // This should be true if this method is called correctly + + let mutable rtrn = Seq.empty + + let removeAt i lst = + Seq.append + <| Seq.take i lst + <| Seq.skip (i+1) lst + + for call in calls do + callables + |> Seq.tryFindIndex (fun callSig -> hasCall callSig call) + |> (fun x -> + Assert.True (x <> None, sprintf "Did not find expected generated content") + rtrn <- Seq.append rtrn [Seq.item x.Value callables] + callables <- removeAt x.Value callables + ) + rtrn |> Seq.map (fun (x,y) -> x) + + let GetCallablesWithSuffix compilation ns (suffix : string) = + compilation.Namespaces + |> Seq.filter (fun x -> x.Name.Value = ns) + |> GlobalCallableResolutions + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith suffix) + |> Seq.map (fun x -> x.Value) + + let GetCallableWithName compilation ns name = + compilation.Namespaces + |> Seq.filter (fun x -> x.Name.Value = ns) + |> GlobalCallableResolutions + |> Seq.find (fun x -> x.Key.Name.Value = name) + |> (fun x -> x.Value) + + let ApplyIfElseTest compilation = + let generated = GetCallablesWithSuffix compilation Signatures.ClassicalControlNs "_Foo" + + Assert.True(2 = Seq.length generated) // Should already be asserted by the signature check + + let ifContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let elseContent = + [ + (0, "SubOps", "SubOp2"); + (1, "SubOps", "SubOp3"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [ifContent; elseContent] + let ifOp, elseOp = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens) + + let original = GetCallableWithName compilation Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable + let lines = original |> GetLinesFromSpecialization + + Assert.True(2 = Seq.length lines, sprintf "Callable %O(%A) did not have the expected number of statements" original.Parent original.Kind) + + let (success, _, args) = CheckIfLineIsCall BuiltIn.ApplyIfElseR.Namespace.Value BuiltIn.ApplyIfElseR.Name.Value lines.[1] + Assert.True(success, sprintf "Callable %O(%A) did not have expected content" original.Parent original.Kind) + + args, ifOp.FullName, elseOp.FullName + + let DoesCallSupportFunctors expectedFunctors call = + let hasAdjoint = expectedFunctors |> Seq.contains QsFunctor.Adjoint + let hasControlled = expectedFunctors |> Seq.contains QsFunctor.Controlled + + // Checks the characteristics match + let charMatch = lazy match call.Signature.Information.Characteristics.SupportedFunctors with + | Value x -> x.SetEquals(expectedFunctors) + | Null -> 0 = Seq.length expectedFunctors + + // Checks that the target specializations are present + let adjMatch = lazy if hasAdjoint then + call.Specializations + |> Seq.tryFind (fun x -> x.Kind = QsSpecializationKind.QsAdjoint) + |> function + | None -> false + | Some x -> match x.Implementation with + | SpecializationImplementation.Generated gen -> gen = QsGeneratorDirective.Invert || gen = QsGeneratorDirective.SelfInverse + | SpecializationImplementation.Provided _ -> true + | _ -> false + else true + + let ctlMatch = lazy if hasControlled then + call.Specializations + |> Seq.tryFind (fun x -> x.Kind = QsSpecializationKind.QsControlled) + |> function + | None -> false + | Some x -> match x.Implementation with + | SpecializationImplementation.Generated gen -> gen = QsGeneratorDirective.Distribute + | SpecializationImplementation.Provided _ -> true + | _ -> false + else true + + charMatch.Value && adjMatch.Value && ctlMatch.Value + + let AssertCallSupportsFunctors expectedFunctors call = + Assert.True(DoesCallSupportFunctors expectedFunctors call, sprintf "Callable %O did not support the expected functors" call.FullName) + + [] + [] + member this.``Basic Hoist`` () = + let result = CompileClassicalControlTest 1 + + let generated = GetCallablesWithSuffix result Signatures.ClassicalControlNs "_Foo" + |> (fun x -> Assert.True(1 = Seq.length x); Seq.item 0 x |> GetBodyFromCallable) + + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + (2, "SubOps", "SubOp3"); + ] + |> AssertSpecializationHasCalls generated + + [] + [] + member this.``Hoist Loops`` () = + CompileClassicalControlTest 2 |> ignore + + [] + [] + member this.``Don't Hoist Single Call`` () = + // Single calls should not be hoisted into their own operation + CompileClassicalControlTest 3 |> ignore + + [] + [] + member this.``Hoist Single Non-Call`` () = + // Single expressions that are not calls should be hoisted into their own operation + CompileClassicalControlTest 4 |> ignore + + [] + [] + member this.``Don't Hoist Return Statements`` () = + CompileClassicalControlTest 5 |> ignore + + [] + [] + member this.``All-Or-None Hoisting`` () = + CompileClassicalControlTest 6 |> ignore + + [] + [] + member this.``ApplyIfZero And ApplyIfOne`` () = + let result = CompileClassicalControlTest 7 + + let originalOp = GetCallableWithName result Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable + + [ + (1, BuiltIn.ApplyIfZero); + (3, BuiltIn.ApplyIfOne); + ] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls originalOp + + [] + [] + member this.``Apply If Zero Else One`` () = + let (args, ifOp, elseOp) = CompileClassicalControlTest 8 |> ApplyIfElseTest + IsApplyIfElseArgsMatch args "r" ifOp elseOp + |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyIfElse did not have the correct arguments")) + + [] + [] + member this.``Apply If One Else Zero`` () = + let (args, ifOp, elseOp) = CompileClassicalControlTest 9 |> ApplyIfElseTest + // The operation arguments should be swapped from the previous test + IsApplyIfElseArgsMatch args "r" elseOp ifOp + |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyIfElse did not have the correct arguments")) + + [] + [] + member this.``If Elif`` () = + let result = CompileClassicalControlTest 10 + + let generated = GetCallablesWithSuffix result Signatures.ClassicalControlNs "_Foo" + + Assert.True(3 = Seq.length generated) // Should already be asserted by the signature check + + let ifContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let elifContent = + [ + (0, "SubOps", "SubOp3"); + (1, "SubOps", "SubOp1"); + ] + let elseContent = + [ + (0, "SubOps", "SubOp2"); + (1, "SubOps", "SubOp3"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [ifContent; elifContent; elseContent] + let ifOp, elifOp, elseOp = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens), (Seq.item 2 orderedGens) + + let original = GetCallableWithName result Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable + let lines = original |> GetLinesFromSpecialization + + Assert.True(2 = Seq.length lines, sprintf "Callable %O(%A) did not have the expected number of statements" original.Parent original.Kind) + + let (success, _, args) = CheckIfLineIsCall BuiltIn.ApplyIfElseR.Namespace.Value BuiltIn.ApplyIfElseR.Name.Value lines.[1] + Assert.True(success, sprintf "Callable %O(%A) did not have expected content" original.Parent original.Kind) + + let errorMsg = "ApplyIfElse did not have the correct arguments" + let (success, _, _, _, subArgs) = IsApplyIfElseArgsMatch args "r" ifOp.FullName { Namespace = BuiltIn.ApplyIfElseR.Namespace; Name = BuiltIn.ApplyIfElseR.Name } + Assert.True(success, errorMsg) + IsApplyIfElseArgsMatch subArgs "r" elseOp.FullName elifOp.FullName // elif and else are swapped because second condition is against One + |> (fun (x, _, _, _, _) -> Assert.True(x, errorMsg)) + + [] + [] + member this.``And Condition`` () = + let (args, ifOp, elseOp) = CompileClassicalControlTest 11 |> ApplyIfElseTest + + let errorMsg = "ApplyIfElse did not have the correct arguments" + let (success, _, subArgs, _, _) = IsApplyIfElseArgsMatch args "r" { Namespace = BuiltIn.ApplyIfElseR.Namespace; Name = BuiltIn.ApplyIfElseR.Name } elseOp + Assert.True(success, errorMsg) + IsApplyIfElseArgsMatch subArgs "r" elseOp ifOp // if and else are swapped because second condition is against One + |> (fun (x, _, _, _, _) -> Assert.True(x, errorMsg)) + + [] + [] + member this.``Or Condition`` () = + let (args, ifOp, elseOp) = CompileClassicalControlTest 12 |> ApplyIfElseTest + + let errorMsg = "ApplyIfElse did not have the correct arguments" + let (success, _, _, _, subArgs) = IsApplyIfElseArgsMatch args "r" ifOp { Namespace = BuiltIn.ApplyIfElseR.Namespace; Name = BuiltIn.ApplyIfElseR.Name } + Assert.True(success, errorMsg) + IsApplyIfElseArgsMatch subArgs "r" elseOp ifOp // if and else are swapped because second condition is against One + |> (fun (x, _, _, _, _) -> Assert.True(x, errorMsg)) + + [] + [] + member this.``Don't Hoist Functions`` () = + CompileClassicalControlTest 13 |> ignore + + [] + [] + member this.``Hoist Self-Contained Mutable`` () = + CompileClassicalControlTest 14 |> ignore + + [] + [] + member this.``Don't Hoist General Mutable`` () = + CompileClassicalControlTest 15 |> ignore + + [] + [] + member this.``Generics Support`` () = + let result = CompileClassicalControlTest 16 + + let callables = result.Namespaces + |> Seq.filter (fun x -> x.Name.Value = Signatures.ClassicalControlNs) + |> GlobalCallableResolutions + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "Foo") + |> fun x -> x.Value + let generated = callables + |> Seq.find (fun x -> x.Key.Name.Value.EndsWith "_Foo") + |> fun x -> x.Value + + let GetTypeParams call = + call.Signature.TypeParameters + |> Seq.choose (function | ValidName str -> Some str.Value | InvalidName -> None) + + let AssertTypeArgsMatch typeArgs1 typeArgs2 = + let errorMsg = "The type parameters for the original and generated operations do not match" + Assert.True(Seq.length typeArgs1 = Seq.length typeArgs2, errorMsg) + + for pair in Seq.zip typeArgs1 typeArgs2 do + Assert.True(fst pair = snd pair, errorMsg) + + // Assert that the generated operation has the same type parameters as the original operation + let originalTypeParams = GetTypeParams original + let generatedTypeParams = GetTypeParams generated + AssertTypeArgsMatch originalTypeParams generatedTypeParams + + // Assert that the original operation calls the generated operation with the appropriate type arguments + let lines = GetBodyFromCallable original |> GetLinesFromSpecialization + let (success, _, args) = CheckIfLineIsCall BuiltIn.ApplyIfZero.Namespace.Value BuiltIn.ApplyIfZero.Name.Value lines.[1] + Assert.True(success, sprintf "Callable %O(%A) did not have expected content" original.FullName QsSpecializationKind.QsBody) + + let (success, typeArgs, _) = IsApplyIfArgMatch args "r" generated.FullName + Assert.True(success, sprintf "ApplyIfZero did not have the correct arguments") + + AssertTypeArgsMatch originalTypeParams <| typeArgs.Replace("'", "").Replace(" ", "").Split(",") + + [] + [] + member this.``Adjoint Support`` () = + let result = CompileClassicalControlTest 17 + + let callables = result.Namespaces + |> Seq.filter (fun x -> x.Name.Value = Signatures.ClassicalControlNs) + |> GlobalCallableResolutions + + let selfOp = callables + |> Seq.find (fun x -> x.Key.Name.Value = "Self") + |> fun x -> x.Value + let invertOp = callables + |> Seq.find (fun x -> x.Key.Name.Value = "Invert") + |> fun x -> x.Value + let providedOp = callables + |> Seq.find (fun x -> x.Key.Name.Value = "Provided") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZero)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable selfOp) + + [(1, BuiltIn.ApplyIfZeroA)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable invertOp) + + [(1, BuiltIn.ApplyIfZero)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable providedOp) + + let _selfOp = callables + |> Seq.find (fun x -> x.Key.Name.Value.EndsWith "_Self") + |> fun x -> x.Value + let _invertOp = callables + |> Seq.find (fun x -> x.Key.Name.Value.EndsWith "_Invert") + |> fun x -> x.Value + let _providedOps = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_Provided") + |> Seq.map (fun x -> x.Value) + + Assert.True(2 = Seq.length _providedOps) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let adjointContent = + [ + (0, "SubOps", "SubOp2"); + (1, "SubOps", "SubOp3"); + ] + + let orderedGens = IdentifyGeneratedByCalls _providedOps [bodyContent; adjointContent] + let bodyGen, adjGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens) + + AssertCallSupportsFunctors [] _selfOp + AssertCallSupportsFunctors [QsFunctor.Adjoint] _invertOp + AssertCallSupportsFunctors [] bodyGen + AssertCallSupportsFunctors [] adjGen + + [] + [] + member this.``Controlled Support`` () = + let result = CompileClassicalControlTest 18 + + let callables = result.Namespaces + |> Seq.filter (fun x -> x.Name.Value = Signatures.ClassicalControlNs) + |> GlobalCallableResolutions + + let distributeOp = callables + |> Seq.find (fun x -> x.Key.Name.Value = "Distribute") + |> fun x -> x.Value + let providedOp = callables + |> Seq.find (fun x -> x.Key.Name.Value = "Provided") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZeroC)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable distributeOp) + + [(1, BuiltIn.ApplyIfZero)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable providedOp) + + let _distributeOp = callables + |> Seq.find (fun x -> x.Key.Name.Value.EndsWith "_Distribute") + |> fun x -> x.Value + let _providedOps = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_Provided") + |> Seq.map (fun x -> x.Value) + + Assert.True(2 = Seq.length _providedOps) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let controlledContent = + [ + (0, "SubOps", "SubOp2"); + (1, "SubOps", "SubOp3"); + ] + + let orderedGens = IdentifyGeneratedByCalls _providedOps [bodyContent; controlledContent] + let bodyGen, ctlGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled] _distributeOp + AssertCallSupportsFunctors [] bodyGen + AssertCallSupportsFunctors [] ctlGen + + [] + [] + member this.``Controlled Adjoint Support - Provided`` () = + let result = CompileClassicalControlTest 19 + + let callables = result.Namespaces + |> Seq.filter (fun x -> x.Name.Value = Signatures.ClassicalControlNs) + |> GlobalCallableResolutions + + (*-----------------------------------------*) + + let bodyCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "ProvidedBody") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZeroCA)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetCtlAdjFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_ProvidedBody") + |> Seq.map (fun x -> x.Value) + + Assert.True(2 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let ctlAdjContent = + [ + (0, "SubOps", "SubOp2"); + (1, "SubOps", "SubOp3"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [bodyContent; ctlAdjContent] + let bodyGen, ctlAdjGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] bodyGen + AssertCallSupportsFunctors [] ctlAdjGen + + bodyCheck () + + (*-----------------------------------------*) + + let controlledCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "ProvidedControlled") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZeroA)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetCtlFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetCtlAdjFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_ProvidedControlled") + |> Seq.map (fun x -> x.Value) + + Assert.True(3 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let ctlContent = + [ + (0, "SubOps", "SubOp3"); + (1, "SubOps", "SubOp1"); + ] + let ctlAdjContent = + [ + (0, "SubOps", "SubOp2"); + (1, "SubOps", "SubOp3"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [bodyContent; ctlContent; ctlAdjContent] + let bodyGen, ctlGen, ctlAdjGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens), (Seq.item 2 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [QsFunctor.Adjoint] bodyGen + AssertCallSupportsFunctors [] ctlGen + AssertCallSupportsFunctors [] ctlAdjGen + + controlledCheck () + + (*-----------------------------------------*) + + let adjointCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "ProvidedAdjoint") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZeroC)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetAdjFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetCtlAdjFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_ProvidedAdjoint") + |> Seq.map (fun x -> x.Value) + + Assert.True(3 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let adjContent = + [ + (0, "SubOps", "SubOp3"); + (1, "SubOps", "SubOp1"); + ] + let ctlAdjContent = + [ + (0, "SubOps", "SubOp2"); + (1, "SubOps", "SubOp3"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [bodyContent; adjContent; ctlAdjContent] + let bodyGen, adjGen, ctlAdjGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens), (Seq.item 2 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [QsFunctor.Controlled] bodyGen + AssertCallSupportsFunctors [] adjGen + AssertCallSupportsFunctors [] ctlAdjGen + + adjointCheck () + + (*-----------------------------------------*) + + let allCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "ProvidedAll") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZero)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetCtlFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetAdjFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetCtlAdjFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_ProvidedAll") + |> Seq.map (fun x -> x.Value) + + Assert.True(4 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let ctlContent = + [ + (0, "SubOps", "SubOp3"); + (1, "SubOps", "SubOp1"); + ] + let adjContent = + [ + (0, "SubOps", "SubOp2"); + (1, "SubOps", "SubOp3"); + ] + let ctlAdjContent = + [ + (2, "SubOps", "SubOp3"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [bodyContent; ctlContent; adjContent; ctlAdjContent] + let bodyGen, ctlGen, adjGen, ctlAdjGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens), (Seq.item 2 orderedGens), (Seq.item 3 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [] bodyGen + AssertCallSupportsFunctors [] ctlGen + AssertCallSupportsFunctors [] adjGen + AssertCallSupportsFunctors [] ctlAdjGen + + allCheck () + + [] + [] + member this.``Controlled Adjoint Support - Distribute`` ()= + let result = CompileClassicalControlTest 20 + + let callables = result.Namespaces + |> Seq.filter (fun x -> x.Name.Value = Signatures.ClassicalControlNs) + |> GlobalCallableResolutions + + (*-----------------------------------------*) + + let bodyCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "DistributeBody") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZeroCA)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_DistributeBody") + |> Seq.map (fun x -> x.Value) + + Assert.True(1 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + + let bodyGen = (Seq.item 0 generated) + AssertSpecializationHasCalls (GetBodyFromCallable bodyGen) bodyContent + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] bodyGen + + bodyCheck () + + (*-----------------------------------------*) + + let controlledCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "DistributeControlled") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZeroCA)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetCtlFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_DistributeControlled") + |> Seq.map (fun x -> x.Value) + + Assert.True(2 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let ctlContent = + [ + (0, "SubOps", "SubOp3"); + (1, "SubOps", "SubOp1"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [bodyContent; ctlContent] + let bodyGen, ctlGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] bodyGen + AssertCallSupportsFunctors [] ctlGen + + controlledCheck () + + (*-----------------------------------------*) + + let adjointCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "DistributeAdjoint") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZeroC)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + [(1, BuiltIn.ApplyIfOneC)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetAdjFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_DistributeAdjoint") + |> Seq.map (fun x -> x.Value) + + Assert.True(2 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let adjContent = + [ + (0, "SubOps", "SubOp3"); + (1, "SubOps", "SubOp1"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [bodyContent; adjContent] + let bodyGen, adjGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [QsFunctor.Controlled] bodyGen + AssertCallSupportsFunctors [QsFunctor.Controlled] adjGen + + adjointCheck () + + (*-----------------------------------------*) + + let allCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "DistributeAll") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZero)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetCtlFromCallable original) + + [(1, BuiltIn.ApplyIfOneC)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetAdjFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_DistributeAll") + |> Seq.map (fun x -> x.Value) + + Assert.True(3 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let ctlContent = + [ + (0, "SubOps", "SubOp3"); + (1, "SubOps", "SubOp1"); + ] + let adjContent = + [ + (0, "SubOps", "SubOp2"); + (1, "SubOps", "SubOp3"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [bodyContent; ctlContent; adjContent] + let bodyGen, ctlGen, adjGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens), (Seq.item 2 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [] bodyGen + AssertCallSupportsFunctors [] ctlGen + AssertCallSupportsFunctors [QsFunctor.Controlled] adjGen + + allCheck () + + [] + [] + member this.``Controlled Adjoint Support - Invert`` ()= + let result = CompileClassicalControlTest 21 + + let callables = result.Namespaces + |> Seq.filter (fun x -> x.Name.Value = Signatures.ClassicalControlNs) + |> GlobalCallableResolutions + + (*-----------------------------------------*) + + let bodyCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "InvertBody") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZeroCA)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_InvertBody") + |> Seq.map (fun x -> x.Value) + + Assert.True(1 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + + let bodyGen = (Seq.item 0 generated) + AssertSpecializationHasCalls (GetBodyFromCallable bodyGen) bodyContent + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] bodyGen + + bodyCheck () + + (*-----------------------------------------*) + + let controlledCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "InvertControlled") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZeroA)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + [(1, BuiltIn.ApplyIfOneA)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetCtlFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_InvertControlled") + |> Seq.map (fun x -> x.Value) + + Assert.True(2 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let ctlContent = + [ + (0, "SubOps", "SubOp3"); + (1, "SubOps", "SubOp1"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [bodyContent; ctlContent] + let bodyGen, ctlGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [QsFunctor.Adjoint] bodyGen + AssertCallSupportsFunctors [QsFunctor.Adjoint] ctlGen + + controlledCheck () + + (*-----------------------------------------*) + + let adjointCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "InvertAdjoint") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZeroCA)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetAdjFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_InvertAdjoint") + |> Seq.map (fun x -> x.Value) + + Assert.True(2 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let adjContent = + [ + (0, "SubOps", "SubOp3"); + (1, "SubOps", "SubOp1"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [bodyContent; adjContent] + let bodyGen, adjGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] bodyGen + AssertCallSupportsFunctors [] adjGen + + adjointCheck () + + (*-----------------------------------------*) + + let allCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "InvertAll") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZero)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + [(1, BuiltIn.ApplyIfOneA)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetCtlFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetAdjFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_InvertAll") + |> Seq.map (fun x -> x.Value) + + Assert.True(3 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let ctlContent = + [ + (0, "SubOps", "SubOp3"); + (1, "SubOps", "SubOp1"); + ] + let adjContent = + [ + (0, "SubOps", "SubOp2"); + (1, "SubOps", "SubOp3"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [bodyContent; ctlContent; adjContent] + let bodyGen, ctlGen, adjGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens), (Seq.item 2 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [] bodyGen + AssertCallSupportsFunctors [QsFunctor.Adjoint] ctlGen + AssertCallSupportsFunctors [] adjGen + + allCheck () + + [] + [] + member this.``Controlled Adjoint Support - Self`` ()= + let result = CompileClassicalControlTest 22 + + let callables = result.Namespaces + |> Seq.filter (fun x -> x.Name.Value = Signatures.ClassicalControlNs) + |> GlobalCallableResolutions + + (*-----------------------------------------*) + + let bodyCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "SelfBody") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZeroC)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_SelfBody") + |> Seq.map (fun x -> x.Value) + + Assert.True(1 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + + let bodyGen = (Seq.item 0 generated) + AssertSpecializationHasCalls (GetBodyFromCallable bodyGen) bodyContent + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [QsFunctor.Controlled] bodyGen + + bodyCheck () + + (*-----------------------------------------*) + + let controlledCheck () = + let original = callables + |> Seq.find (fun x -> x.Key.Name.Value = "SelfControlled") + |> fun x -> x.Value + + [(1, BuiltIn.ApplyIfZero)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetBodyFromCallable original) + + [(1, BuiltIn.ApplyIfOne)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls (GetCtlFromCallable original) + + let generated = callables + |> Seq.filter (fun x -> x.Key.Name.Value.EndsWith "_SelfControlled") + |> Seq.map (fun x -> x.Value) + + Assert.True(2 = Seq.length generated) // Should already be asserted by the signature check + + let bodyContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let ctlContent = + [ + (0, "SubOps", "SubOp3"); + (1, "SubOps", "SubOp1"); + ] + + let orderedGens = IdentifyGeneratedByCalls generated [bodyContent; ctlContent] + let bodyGen, ctlGen = (Seq.item 0 orderedGens), (Seq.item 1 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Controlled;QsFunctor.Adjoint] original + AssertCallSupportsFunctors [] bodyGen + AssertCallSupportsFunctors [] ctlGen + + controlledCheck () + + [] + [] + member this.``Within Block Support`` () = + let result = CompileClassicalControlTest 23 + + let original = GetCallableWithName result Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable + let generated = GetCallablesWithSuffix result Signatures.ClassicalControlNs "_Foo" + + Assert.True(2 = Seq.length generated) // Should already be asserted by the signature check + + let originalContent = + [ + (2, BuiltIn.ApplyIfZeroA); + (5, BuiltIn.ApplyIfOne); + ] |> Seq.map ExpandBuiltInQualifiedSymbol + let outerContent = + [ + (0, "SubOps", "SubOp1"); + (1, "SubOps", "SubOp2"); + ] + let innerContent = + [ + (0, "SubOps", "SubOp2"); + (1, "SubOps", "SubOp3"); + ] + + AssertSpecializationHasCalls original originalContent + + let orderedGens = IdentifyGeneratedByCalls generated [outerContent; innerContent] + let outerOp = (Seq.item 0 orderedGens) + + AssertCallSupportsFunctors [QsFunctor.Adjoint] outerOp + + let lines = GetLinesFromSpecialization original + let (success, _, args) = CheckIfLineIsCall BuiltIn.ApplyIfZeroA.Namespace.Value BuiltIn.ApplyIfZeroA.Name.Value lines.[2] + Assert.True(success, sprintf "Callable %O(%A) did not have expected content" original.Parent QsSpecializationKind.QsBody) + + let (success, _, _) = IsApplyIfArgMatch args "r" outerOp.FullName + Assert.True(success, "ApplyIfZeroA did not have the correct arguments") + + [] + [] + member this.``Arguments Partially Resolve Type Parameters`` () = + let result = CompileClassicalControlTest 24 + + let original = GetCallableWithName result Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable + + let lines = GetLinesFromSpecialization original + let (success, _, args) = CheckIfLineIsCall BuiltIn.ApplyIfZero.Namespace.Value BuiltIn.ApplyIfZero.Name.Value lines.[1] + Assert.True(success, sprintf "Callable %O(%A) did not have expected content" original.Parent QsSpecializationKind.QsBody) + + let (success, typeArgs, _) = IsApplyIfArgMatch args "r" {Namespace = NonNullable<_>.New Signatures.ClassicalControlNs; Name = NonNullable<_>.New "Bar"} + Assert.True(success, "ApplyIfZero did not have the correct arguments") + + Assert.True((typeArgs = "Int, Double"), "Bar did not have the correct type arguments") + + [] + [] + member this.``Hoist Functor Application`` () = + CompileClassicalControlTest 25 |> ignore + + [] + [] + member this.``Hoist Partial Application`` () = + CompileClassicalControlTest 26 |> ignore + + [] + [] + member this.``Hoist Array Item Call`` () = + CompileClassicalControlTest 27 |> ignore \ No newline at end of file diff --git a/src/QsCompiler/Tests.Compiler/LinkingTests.fs b/src/QsCompiler/Tests.Compiler/LinkingTests.fs index 39206b7e61..7956a74edf 100644 --- a/src/QsCompiler/Tests.Compiler/LinkingTests.fs +++ b/src/QsCompiler/Tests.Compiler/LinkingTests.fs @@ -54,7 +54,7 @@ type LinkingTests (output:ITestOutputHelper) = tests.Verify (callable.FullName, diag) member private this.BuildContent content = - + let fileId = getTempFile() let file = getManager fileId content @@ -79,16 +79,16 @@ type LinkingTests (output:ITestOutputHelper) = monomorphicCompilation member private this.CompileIntrinsicResolution source environment = - + let envDS = this.BuildContent environment let sourceDS = this.BuildContent source IntrinsicResolutionTransformation.Apply(envDS.BuiltCompilation, sourceDS.BuiltCompilation) member private this.RunIntrinsicResolutionTest testNumber = - + let srcChunks = LinkingTests.ReadAndChunkSourceFile "IntrinsicResolution.qs" - srcChunks.Length >= 2*testNumber |> Assert.True + srcChunks.Length >= 2 * testNumber |> Assert.True let chunckNumber = 2 * (testNumber - 1) let result = this.CompileIntrinsicResolution srcChunks.[chunckNumber] srcChunks.[chunckNumber+1] Signatures.SignatureCheck [Signatures.IntrinsicResolutionNs] Signatures.IntrinsicResolutionSignatures.[testNumber-1] result @@ -113,7 +113,7 @@ type LinkingTests (output:ITestOutputHelper) = [] member this.``Monomorphization`` () = - + let filePath = Path.Combine ("TestCases", "LinkingTests", "Generics.qs") |> Path.GetFullPath let fileId = (new Uri(filePath)) getManager fileId (File.ReadAllText filePath) @@ -136,7 +136,7 @@ type LinkingTests (output:ITestOutputHelper) = member this.``Intrinsic Resolution Returns UDT`` () = this.RunIntrinsicResolutionTest 2 - + [] [] member this.``Intrinsic Resolution Type Mismatch Error`` () = diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/ClassicalControl.qs b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/ClassicalControl.qs new file mode 100644 index 0000000000..cbe56be9c4 --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/ClassicalControl.qs @@ -0,0 +1,885 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +namespace SubOps { + operation SubOp1() : Unit is Adj + Ctl { } + operation SubOp2() : Unit is Adj + Ctl { } + operation SubOp3() : Unit is Adj + Ctl { } +} + +// ================================= + +// Basic Hoist +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + SubOp3(); + let temp = 4; + using (q = Qubit()) { + let temp2 = q; + } + } + } + +} + +// ================================= + +// Hoist Loops +namespace Microsoft.Quantum.Testing.ClassicalControl { + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero) { + for (index in 0 .. 3) { + let temp = index; + } + + repeat { + let success = true; + } until (success) + fixup { + let temp2 = 0; + } + } + } + +} + +// ================================= + +// Don't Hoist Single Call +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + if (r == Zero) { + SubOp1(); + } + } + +} + +// ================================= + +// Hoist Single Non-Call +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero) { + let temp = 2; + } + } + +} + +// ================================= + +// Don't Hoist Return Statements +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + if (r == Zero) { + SubOp1(); + return (); + } + } + +} + +// ================================= + +// All-Or-None Hoisting +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation IfInvalid() : Unit { + let r = Zero; + if (r == Zero) { + SubOp1(); + SubOp2(); + return (); + } else { + SubOp2(); + SubOp3(); + } + } + + operation ElseInvalid() : Unit { + let r = Zero; + if (r == Zero) { + SubOp1(); + SubOp2(); + } else { + SubOp2(); + SubOp3(); + return (); + } + } + + operation BothInvalid() : Unit { + let r = Zero; + if (r == Zero) { + SubOp1(); + SubOp2(); + return (); + } else { + SubOp2(); + SubOp3(); + return (); + } + } + +} + +// ================================= + +// ApplyIfZero And ApplyIfOne +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + SubOp3(); + } + + let temp = 0; + + if (r == One) { + SubOp2(); + SubOp3(); + SubOp1(); + } + } + +} + +// ================================= + +// Apply If Zero Else One +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } else { + SubOp2(); + SubOp3(); + } + } + +} + +// ================================= + +// Apply If One Else Zero +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = One; + + if (r == One) { + SubOp1(); + SubOp2(); + } else { + SubOp2(); + SubOp3(); + } + } + +} + +// ================================= + +// If Elif +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } elif (r == One) { + SubOp3(); + SubOp1(); + } else { + SubOp2(); + SubOp3(); + } + } + +} + +// ================================= + +// And Condition +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero and r == One) { + SubOp1(); + SubOp2(); + } else { + SubOp2(); + SubOp3(); + } + } + +} + +// ================================= + +// Or Condition +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero or r == One) { + SubOp1(); + SubOp2(); + } else { + SubOp2(); + SubOp3(); + } + } + +} + +// ================================= + +// Don't Hoist Functions +namespace Microsoft.Quantum.Testing.ClassicalControl { + + function Foo() : Unit { + let r = Zero; + + if (r == Zero) { + SubFunc1(); + SubFunc2(); + SubFunc3(); + } + } + + function SubFunc1() : Unit { } + function SubFunc2() : Unit { } + function SubFunc3() : Unit { } + +} + +// ================================= + +// Hoist Self-Contained Mutable +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero) { + mutable temp = 3; + set temp = 4; + } + } + +} + +// ================================= + +// Don't Hoist General Mutable +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + mutable temp = 3; + if (r == Zero) { + set temp = 4; + } + } + +} + +// ================================= + +// Generics Support +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo<'A, 'B, 'C>() : Unit { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + +} + +// ================================= + +// Adjoint Support +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Provided() : Unit is Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + adjoint (...) { + let w = One; + + if (w == One) { + SubOp2(); + SubOp3(); + } + } + } + + operation Self() : Unit is Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + adjoint self; + } + + operation Invert() : Unit is Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + adjoint invert; + } + +} + +// ================================= + +// Controlled Support +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Provided() : Unit is Ctl { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOp2(); + SubOp3(); + } + } + } + + operation Distribute() : Unit is Ctl { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled distribute; + } + +} + +// ================================= + +// Controlled Adjoint Support - Provided +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation ProvidedBody() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled adjoint (ctl, ...) { + let y = One; + + if (y == One) { + SubOp2(); + SubOp3(); + } + } + } + + operation ProvidedAdjoint() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + adjoint (...) { + let w = One; + + if (w == One) { + SubOp3(); + SubOp1(); + } + } + + controlled adjoint (ctl, ...) { + let y = One; + + if (y == One) { + SubOp2(); + SubOp3(); + } + } + } + + operation ProvidedControlled() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOp3(); + SubOp1(); + } + } + + controlled adjoint (ctl, ...) { + let y = One; + + if (y == One) { + SubOp2(); + SubOp3(); + } + } + } + + operation ProvidedAll() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOp3(); + SubOp1(); + } + } + + adjoint (...) { + let y = One; + + if (y == One) { + SubOp2(); + SubOp3(); + } + } + + controlled adjoint (ctl, ...) { + let b = One; + + if (b == One) { + let temp1 = 0; + let temp2 = 0; + SubOp3(); + } + } + } + +} + +// ================================= + +// Controlled Adjoint Support - Distribute +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation DistributeBody() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled adjoint distribute; + } + + operation DistributeAdjoint() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + adjoint (...) { + let w = One; + + if (w == One) { + SubOp3(); + SubOp1(); + } + } + + controlled adjoint distribute; + } + + operation DistributeControlled() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOp3(); + SubOp1(); + } + } + + controlled adjoint distribute; + } + + operation DistributeAll() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOp3(); + SubOp1(); + } + } + + adjoint (...) { + let y = One; + + if (y == One) { + SubOp2(); + SubOp3(); + } + } + + controlled adjoint distribute; + } + +} + +// ================================= + +// Controlled Adjoint Support - Invert +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation InvertBody() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled adjoint invert; + } + + operation InvertAdjoint() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + adjoint (...) { + let w = One; + + if (w == One) { + SubOp3(); + SubOp1(); + } + } + + controlled adjoint invert; + } + + operation InvertControlled() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOp3(); + SubOp1(); + } + } + + controlled adjoint invert; + } + + operation InvertAll() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOp3(); + SubOp1(); + } + } + + adjoint (...) { + let y = One; + + if (y == One) { + SubOp2(); + SubOp3(); + } + } + + controlled adjoint invert; + } + +} + +// ================================= + +// Controlled Adjoint Support - Self +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation SelfBody() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled adjoint self; + } + + operation SelfControlled() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOp3(); + SubOp1(); + } + } + + controlled adjoint self; + } + +} + +// ================================= + +// Within Block Support +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = One; + within { + if (r == Zero) { + SubOp1(); + SubOp2(); + } + } apply { + if (r == One) { + SubOp2(); + SubOp3(); + } + } + } + +} + +// ================================= + +// Arguments Partially Resolve Type Parameters +namespace Microsoft.Quantum.Testing.ClassicalControl { + + operation Bar<'Q, 'W> (q : 'Q, w : 'W) : Unit { } + + operation Foo() : Unit { + let r = Zero; + if (r == Zero) { + Bar(1, 1.0); + } + } +} + +// ================================= + +// Hoist Functor Application +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + if (r == Zero) { + Adjoint SubOp1(); + } + } +} + +// ================================= + +// Hoist Partial Application +namespace Microsoft.Quantum.Testing.ClassicalControl { + + operation Bar (q : Int, w : Double) : Unit { } + + operation Foo() : Unit { + let r = Zero; + if (r == Zero) { + (Bar(1, _))(1.0); + } + } +} + +// ================================= + +// Hoist Array Item Call +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let f = [SubOp1]; + let r = Zero; + if (r == Zero) { + f[0](); + } + } +} \ No newline at end of file diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/QuantumProcessorExtensions.qs b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/QuantumProcessorExtensions.qs new file mode 100644 index 0000000000..f719f2b5f5 --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/QuantumProcessorExtensions.qs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +namespace Microsoft.Quantum.Simulation.QuantumProcessor.Extensions { + operation ApplyIfZero<'T>(measurementResult : Result, (onResultZeroOp : ('T => Unit), zeroArg : 'T)) : Unit { } + operation ApplyIfZeroA<'T>(measurementResult : Result, (onResultZeroOp : ('T => Unit is Adj), zeroArg : 'T)) : Unit is Adj { } + operation ApplyIfZeroC<'T>(measurementResult : Result, (onResultZeroOp : ('T => Unit is Ctl), zeroArg : 'T)) : Unit is Ctl { } + operation ApplyIfZeroCA<'T>(measurementResult : Result, (onResultZeroOp : ('T => Unit is Ctl + Adj), zeroArg : 'T)) : Unit is Ctl + Adj { } + + operation ApplyIfOne<'T>(measurementResult : Result, (onResultOneOp : ('T => Unit), oneArg : 'T)) : Unit { } + operation ApplyIfOneA<'T>(measurementResult : Result, (onResultOneOp : ('T => Unit is Adj), oneArg : 'T)) : Unit is Adj { } + operation ApplyIfOneC<'T>(measurementResult : Result, (onResultOneOp : ('T => Unit is Ctl), oneArg : 'T)) : Unit is Ctl { } + operation ApplyIfOneCA<'T>(measurementResult : Result, (onResultOneOp : ('T => Unit is Ctl + Adj), oneArg : 'T)) : Unit is Ctl + Adj { } + + operation ApplyIfElseR<'T,'U>(measurementResult : Result, (onResultZeroOp : ('T => Unit), zeroArg : 'T) , (onResultOneOp : ('U => Unit), oneArg : 'U)) : Unit { } + operation ApplyIfElseRA<'T,'U>(measurementResult : Result, (onResultZeroOp : ('T => Unit is Adj), zeroArg : 'T) , (onResultOneOp : ('U => Unit is Adj), oneArg : 'U)) : Unit is Adj { } + operation ApplyIfElseRC<'T,'U>(measurementResult : Result, (onResultZeroOp : ('T => Unit is Ctl), zeroArg : 'T) , (onResultOneOp : ('U => Unit is Ctl), oneArg : 'U)) : Unit is Ctl { } + operation ApplyIfElseRCA<'T,'U>(measurementResult : Result, (onResultZeroOp : ('T => Unit is Adj + Ctl), zeroArg : 'T) , (onResultOneOp : ('U => Unit is Adj + Ctl), oneArg : 'U)) : Unit is Ctl + Adj { } +} \ No newline at end of file diff --git a/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs b/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs index 034e421186..7a183d5d5f 100644 --- a/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs +++ b/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs @@ -14,12 +14,13 @@ open Xunit let private _BaseTypes = [| - "Unit", UnitType; - "Int", Int; - "Double", Double; - "String", String; - "Qubit", Qubit; - "Qubit[]", ResolvedType.New Qubit |> ArrayType; + "Unit", UnitType + "Int", Int + "Double", Double + "String", String + "Result", Result + "Qubit", Qubit + "Qubit[]", ResolvedType.New Qubit |> ArrayType |] let private _MakeTypeMap udts = @@ -48,6 +49,16 @@ let private _MakeSignatures sigs = |> Seq.map (fun (types, case) -> Seq.map (fun _sig -> _MakeSig _sig types) case) |> Seq.toArray +let _MakeTypeParam originNs originName paramName = + originName + "." + paramName, { + Origin = { + Namespace = NonNullable<_>.New originNs; + Name = NonNullable<_>.New originName + } + TypeName = NonNullable<_>.New paramName + Range = Null + } |> TypeParameter + /// For all given namespaces in checkedNamespaces, checks that there are exactly /// the callables specified with targetSignatures in the given compilation. let public SignatureCheck checkedNamespaces targetSignatures compilation = @@ -57,7 +68,7 @@ let public SignatureCheck checkedNamespaces targetSignatures compilation = | Some ns -> ns | None -> sprintf "Expected but did not find namespace: %s" targetNs |> failwith - let callableSigs = + let mutable callableSigs = checkedNamespaces |> Seq.map (fun checkedNs -> getNs checkedNs) |> SyntaxExtensions.Callables @@ -77,69 +88,76 @@ let public SignatureCheck checkedNamespaces targetSignatures compilation = | QsTypeKind.UnitType -> "()" | _ -> args |> (ExpressionToQs () |> ExpressionTypeToQs).Apply + let removeAt i lst = + Seq.append + <| Seq.take i lst + <| Seq.skip (i+1) lst + (*Tests that all target signatures are present*) for targetSig in targetSignatures do let sig_fullName, sig_argType, sig_rtrnType = targetSig callableSigs - |> Seq.exists (fun callSig -> doesCallMatchSig callSig targetSig) - |> (fun x -> Assert.True (x, sprintf "Expected but did not find: %s.%s %s : %A" sig_fullName.Namespace.Value sig_fullName.Name.Value (makeArgsString sig_argType) sig_rtrnType.Resolution)) + |> Seq.tryFindIndex (fun callSig -> doesCallMatchSig callSig targetSig) + |> (fun x -> + Assert.True (x <> None, sprintf "Expected but did not find: %s.*%s %s : %A" sig_fullName.Namespace.Value sig_fullName.Name.Value (makeArgsString sig_argType) sig_rtrnType.Resolution) + callableSigs <- removeAt x.Value callableSigs + ) (*Tests that *only* targeted signatures are present*) for callSig in callableSigs do let sig_fullName, sig_argType, sig_rtrnType = callSig - targetSignatures - |> Seq.exists (fun targetSig -> doesCallMatchSig callSig targetSig) - |> (fun x -> Assert.True (x, sprintf "Found unexpected callable: %s.%s %s : %A" sig_fullName.Namespace.Value sig_fullName.Name.Value (makeArgsString sig_argType) sig_rtrnType.Resolution)) + failwith (sprintf "Found unexpected callable: %O %s : %A" sig_fullName (makeArgsString sig_argType) sig_rtrnType.Resolution) /// Names of several testing namespaces let public MonomorphizationNs = "Microsoft.Quantum.Testing.Monomorphization" let public GenericsNs = "Microsoft.Quantum.Testing.Generics" let public IntrinsicResolutionNs = "Microsoft.Quantum.Testing.IntrinsicResolution" +let public ClassicalControlNs = "Microsoft.Quantum.Testing.ClassicalControl" /// Expected callable signatures to be found when running Monomorphization tests let public MonomorphizationSignatures = [| (_DefaultTypes, [| (*Test Case 1*) - MonomorphizationNs, "Test1", [||], "Unit"; - GenericsNs, "Test1Main", [||], "Unit"; - - GenericsNs, "BasicGeneric", [|"Double"; "Int"|], "Unit"; - GenericsNs, "BasicGeneric", [|"String"; "String"|], "Unit"; - GenericsNs, "BasicGeneric", [|"Unit"; "Unit"|], "Unit"; - GenericsNs, "BasicGeneric", [|"String"; "Double"|], "Unit"; - GenericsNs, "BasicGeneric", [|"Int"; "Double"|], "Unit"; - GenericsNs, "NoArgsGeneric", [||], "Double"; - GenericsNs, "ReturnGeneric", [|"Double"; "String"; "Int"|], "Int"; - GenericsNs, "ReturnGeneric", [|"String"; "Int"; "String"|], "String"; - |]); + MonomorphizationNs, "Test1", [||], "Unit" + GenericsNs, "Test1Main", [||], "Unit" + + GenericsNs, "BasicGeneric", [|"Double"; "Int"|], "Unit" + GenericsNs, "BasicGeneric", [|"String"; "String"|], "Unit" + GenericsNs, "BasicGeneric", [|"Unit"; "Unit"|], "Unit" + GenericsNs, "BasicGeneric", [|"String"; "Double"|], "Unit" + GenericsNs, "BasicGeneric", [|"Int"; "Double"|], "Unit" + GenericsNs, "NoArgsGeneric", [||], "Double" + GenericsNs, "ReturnGeneric", [|"Double"; "String"; "Int"|], "Int" + GenericsNs, "ReturnGeneric", [|"String"; "Int"; "String"|], "String" + |]) (_DefaultTypes, [| (*Test Case 2*) - MonomorphizationNs, "Test2", [||], "Unit"; - GenericsNs, "Test2Main", [||], "Unit"; + MonomorphizationNs, "Test2", [||], "Unit" + GenericsNs, "Test2Main", [||], "Unit" - GenericsNs, "ArrayGeneric", [|"Qubit"; "String"|], "Int"; - GenericsNs, "ArrayGeneric", [|"Qubit"; "Int"|], "Int"; - GenericsNs, "GenericCallsGeneric", [|"Qubit"; "Int"|], "Unit"; - |]); + GenericsNs, "ArrayGeneric", [|"Qubit"; "String"|], "Int" + GenericsNs, "ArrayGeneric", [|"Qubit"; "Int"|], "Int" + GenericsNs, "GenericCallsGeneric", [|"Qubit"; "Int"|], "Unit" + |]) (_DefaultTypes, [| (*Test Case 3*) - MonomorphizationNs, "Test3", [||], "Unit"; - GenericsNs, "Test3Main", [||], "Unit"; - - GenericsNs, "GenericCallsSpecializations", [|"Double"; "String"; "Qubit[]"|], "Unit"; - GenericsNs, "GenericCallsSpecializations", [|"Double"; "String"; "Double"|], "Unit"; - GenericsNs, "GenericCallsSpecializations", [|"String"; "Int"; "Unit"|], "Unit"; - - GenericsNs, "BasicGeneric", [|"Qubit[]"; "Qubit[]"|], "Unit"; - GenericsNs, "BasicGeneric", [|"String"; "Qubit[]"|], "Unit"; - GenericsNs, "BasicGeneric", [|"Double"; "String"|], "Unit"; - GenericsNs, "BasicGeneric", [|"Qubit[]"; "Double"|], "Unit"; - GenericsNs, "BasicGeneric", [|"String"; "Double"|], "Unit"; - GenericsNs, "BasicGeneric", [|"Qubit[]"; "Unit"|], "Unit"; - GenericsNs, "BasicGeneric", [|"Int"; "Unit"|], "Unit"; - GenericsNs, "BasicGeneric", [|"String"; "Int"|], "Unit"; - - GenericsNs, "ArrayGeneric", [|"Qubit"; "Qubit[]"|], "Int"; - GenericsNs, "ArrayGeneric", [|"Qubit"; "Double"|], "Int"; - GenericsNs, "ArrayGeneric", [|"Qubit"; "Unit"|], "Int"; + MonomorphizationNs, "Test3", [||], "Unit" + GenericsNs, "Test3Main", [||], "Unit" + + GenericsNs, "GenericCallsSpecializations", [|"Double"; "String"; "Qubit[]"|], "Unit" + GenericsNs, "GenericCallsSpecializations", [|"Double"; "String"; "Double"|], "Unit" + GenericsNs, "GenericCallsSpecializations", [|"String"; "Int"; "Unit"|], "Unit" + + GenericsNs, "BasicGeneric", [|"Qubit[]"; "Qubit[]"|], "Unit" + GenericsNs, "BasicGeneric", [|"String"; "Qubit[]"|], "Unit" + GenericsNs, "BasicGeneric", [|"Double"; "String"|], "Unit" + GenericsNs, "BasicGeneric", [|"Qubit[]"; "Double"|], "Unit" + GenericsNs, "BasicGeneric", [|"String"; "Double"|], "Unit" + GenericsNs, "BasicGeneric", [|"Qubit[]"; "Unit"|], "Unit" + GenericsNs, "BasicGeneric", [|"Int"; "Unit"|], "Unit" + GenericsNs, "BasicGeneric", [|"String"; "Int"|], "Unit" + + GenericsNs, "ArrayGeneric", [|"Qubit"; "Qubit[]"|], "Int" + GenericsNs, "ArrayGeneric", [|"Qubit"; "Double"|], "Int" + GenericsNs, "ArrayGeneric", [|"Qubit"; "Unit"|], "Int" |]) |] |> _MakeSignatures @@ -152,33 +170,227 @@ let private _IntrinsicResolutionTypes = _MakeTypeMap [| let public IntrinsicResolutionSignatures = [| (_DefaultTypes, [| - IntrinsicResolutionNs, "IntrinsicResolutionTest1", [||], "Unit"; - IntrinsicResolutionNs, "LocalIntrinsic", [||], "Unit"; - IntrinsicResolutionNs, "Override", [||], "Unit"; - IntrinsicResolutionNs, "EnvironmentIntrinsic", [||], "Unit"; - |]); + IntrinsicResolutionNs, "IntrinsicResolutionTest1", [||], "Unit" + IntrinsicResolutionNs, "LocalIntrinsic", [||], "Unit" + IntrinsicResolutionNs, "Override", [||], "Unit" + IntrinsicResolutionNs, "EnvironmentIntrinsic", [||], "Unit" + |]) (_IntrinsicResolutionTypes, [| - IntrinsicResolutionNs, "IntrinsicResolutionTest2", [||], "Unit"; - IntrinsicResolutionNs, "Override", [||], "TestType"; - IntrinsicResolutionNs, "TestType", [||], "TestType"; - |]); + IntrinsicResolutionNs, "IntrinsicResolutionTest2", [||], "Unit" + IntrinsicResolutionNs, "Override", [||], "TestType" + IntrinsicResolutionNs, "TestType", [||], "TestType" + |]) (_IntrinsicResolutionTypes, [| - IntrinsicResolutionNs, "IntrinsicResolutionTest3", [||], "Unit"; - IntrinsicResolutionNs, "Override", [||], "TestType"; - IntrinsicResolutionNs, "TestType", [||], "TestType"; - |]); + IntrinsicResolutionNs, "IntrinsicResolutionTest3", [||], "Unit" + IntrinsicResolutionNs, "Override", [||], "TestType" + IntrinsicResolutionNs, "TestType", [||], "TestType" + |]) (_IntrinsicResolutionTypes, [| - IntrinsicResolutionNs, "IntrinsicResolutionTest4", [||], "Unit"; - IntrinsicResolutionNs, "Override", [|"TestType"|], "Unit"; - IntrinsicResolutionNs, "TestType", [||], "TestType"; - |]); + IntrinsicResolutionNs, "IntrinsicResolutionTest4", [||], "Unit" + IntrinsicResolutionNs, "Override", [|"TestType"|], "Unit" + IntrinsicResolutionNs, "TestType", [||], "TestType" + |]) (_DefaultTypes, [| - IntrinsicResolutionNs, "IntrinsicResolutionTest5", [||], "Unit"; - IntrinsicResolutionNs, "Override", [||], "Unit"; - |]); + IntrinsicResolutionNs, "IntrinsicResolutionTest5", [||], "Unit" + IntrinsicResolutionNs, "Override", [||], "Unit" + |]) (_DefaultTypes, [| - IntrinsicResolutionNs, "IntrinsicResolutionTest6", [||], "Unit"; - IntrinsicResolutionNs, "Override", [||], "Unit"; - |]); + IntrinsicResolutionNs, "IntrinsicResolutionTest6", [||], "Unit" + IntrinsicResolutionNs, "Override", [||], "Unit" + |]) + |] + |> _MakeSignatures + +let private _TypeParameterTypes = _MakeTypeMap [| + _MakeTypeParam ClassicalControlNs "Bar" "Q" + _MakeTypeParam ClassicalControlNs "Bar" "W" +|] + +let private _DefaultWithOperation = _MakeTypeMap [| + "SubOp1Type[]", ((ResolvedType.New UnitType, ResolvedType.New UnitType), { + Characteristics = (ResolvedCharacteristics.FromProperties [OpProperty.Adjointable; OpProperty.Controllable]) + InferredInformation = InferredCallableInformation.NoInformation + }) |> QsTypeKind.Operation |> ResolvedType.New |> ArrayType +|] + +/// Expected callable signatures to be found when running Classical Control tests +let public ClassicalControlSignatures = + [| + (_DefaultTypes, [| // Basic Hoist + ClassicalControlNs, "Foo", [||], "Unit"; // The original operation + ClassicalControlNs, "_Foo", [|"Result"|], "Unit"; // The generated operation + |]) + (_DefaultTypes, [| // Hoist Loops + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // Don't Hoist Single Call + ClassicalControlNs, "Foo", [||], "Unit" + |]) + (_DefaultTypes, [| // Hoist Single Non-Call + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // Don't Hoist Return Statements + ClassicalControlNs, "Foo", [||], "Unit" + |]) + (_DefaultTypes, [| // All-Or-None Hoisting + ClassicalControlNs, "IfInvalid", [||], "Unit" + ClassicalControlNs, "ElseInvalid", [||], "Unit" + ClassicalControlNs, "BothInvalid", [||], "Unit" + |]) + (_DefaultTypes, [| // ApplyIfZero And ApplyIfOne + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + ClassicalControlNs, "_Foo", [|"Result"; "Int"|], "Unit" + |]) + (_DefaultTypes, [| // Apply If Zero Else One + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // Apply If One Else Zero + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // If Elif + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // And Condition + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // Or Condition + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // Don't Hoist Functions + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "SubFunc1", [||], "Unit" + ClassicalControlNs, "SubFunc2", [||], "Unit" + ClassicalControlNs, "SubFunc3", [||], "Unit" + |]) + (_DefaultTypes, [| // Hoist Self-Contained Mutable + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // Don't Hoist General Mutable + ClassicalControlNs, "Foo", [||], "Unit" + |]) + (_DefaultTypes, [| // Generics Support + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // Adjoint Support + ClassicalControlNs, "Provided", [||], "Unit" + ClassicalControlNs, "Self", [||], "Unit" + ClassicalControlNs, "Invert", [||], "Unit" + ClassicalControlNs, "_Provided", [|"Result"|], "Unit" + ClassicalControlNs, "_Provided", [|"Result"|], "Unit" + ClassicalControlNs, "_Self", [|"Result"|], "Unit" + ClassicalControlNs, "_Invert", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // Controlled Support + ClassicalControlNs, "Provided", [||], "Unit" + ClassicalControlNs, "Distribute", [||], "Unit" + ClassicalControlNs, "_Provided", [|"Result"|], "Unit" + ClassicalControlNs, "_Provided", [|"Result";"Qubit[]";"Unit"|], "Unit" + ClassicalControlNs, "_Distribute", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // Controlled Adjoint Support - Provided + ClassicalControlNs, "ProvidedBody", [||], "Unit" + ClassicalControlNs, "ProvidedAdjoint", [||], "Unit" + ClassicalControlNs, "ProvidedControlled", [||], "Unit" + ClassicalControlNs, "ProvidedAll", [||], "Unit" + + ClassicalControlNs, "_ProvidedBody", [|"Result"|], "Unit" + ClassicalControlNs, "_ProvidedBody", [|"Result";"Qubit[]";"Unit"|], "Unit" + + ClassicalControlNs, "_ProvidedAdjoint", [|"Result"|], "Unit" + ClassicalControlNs, "_ProvidedAdjoint", [|"Result"|], "Unit" + ClassicalControlNs, "_ProvidedAdjoint", [|"Result";"Qubit[]";"Unit"|], "Unit" + + ClassicalControlNs, "_ProvidedControlled", [|"Result"|], "Unit" + ClassicalControlNs, "_ProvidedControlled", [|"Result";"Qubit[]";"Unit"|], "Unit" + ClassicalControlNs, "_ProvidedControlled", [|"Result";"Qubit[]";"Unit"|], "Unit" + + ClassicalControlNs, "_ProvidedAll", [|"Result"|], "Unit" + ClassicalControlNs, "_ProvidedAll", [|"Result"|], "Unit" + ClassicalControlNs, "_ProvidedAll", [|"Result";"Qubit[]";"Unit"|], "Unit" + ClassicalControlNs, "_ProvidedAll", [|"Result";"Qubit[]";"Unit"|], "Unit" + |]) + (_DefaultTypes, [| // Controlled Adjoint Support - Distribute + ClassicalControlNs, "DistributeBody", [||], "Unit" + ClassicalControlNs, "DistributeAdjoint", [||], "Unit" + ClassicalControlNs, "DistributeControlled", [||], "Unit" + ClassicalControlNs, "DistributeAll", [||], "Unit" + + ClassicalControlNs, "_DistributeBody", [|"Result"|], "Unit" + + ClassicalControlNs, "_DistributeAdjoint", [|"Result"|], "Unit" + ClassicalControlNs, "_DistributeAdjoint", [|"Result"|], "Unit" + + ClassicalControlNs, "_DistributeControlled", [|"Result"|], "Unit" + ClassicalControlNs, "_DistributeControlled", [|"Result";"Qubit[]";"Unit"|], "Unit" + + ClassicalControlNs, "_DistributeAll", [|"Result"|], "Unit" + ClassicalControlNs, "_DistributeAll", [|"Result"|], "Unit" + ClassicalControlNs, "_DistributeAll", [|"Result";"Qubit[]";"Unit"|], "Unit" + |]) + (_DefaultTypes, [| // Controlled Adjoint Support - Invert + ClassicalControlNs, "InvertBody", [||], "Unit" + ClassicalControlNs, "InvertAdjoint", [||], "Unit" + ClassicalControlNs, "InvertControlled", [||], "Unit" + ClassicalControlNs, "InvertAll", [||], "Unit" + + ClassicalControlNs, "_InvertBody", [|"Result"|], "Unit" + + ClassicalControlNs, "_InvertAdjoint", [|"Result"|], "Unit" + ClassicalControlNs, "_InvertAdjoint", [|"Result"|], "Unit" + + ClassicalControlNs, "_InvertControlled", [|"Result"|], "Unit" + ClassicalControlNs, "_InvertControlled", [|"Result";"Qubit[]";"Unit"|], "Unit" + + ClassicalControlNs, "_InvertAll", [|"Result"|], "Unit" + ClassicalControlNs, "_InvertAll", [|"Result"|], "Unit" + ClassicalControlNs, "_InvertAll", [|"Result";"Qubit[]";"Unit"|], "Unit" + |]) + (_DefaultTypes, [| // Controlled Adjoint Support - Self + ClassicalControlNs, "SelfBody", [||], "Unit" + ClassicalControlNs, "SelfControlled", [||], "Unit" + + ClassicalControlNs, "_SelfBody", [|"Result"|], "Unit" + + ClassicalControlNs, "_SelfControlled", [|"Result"|], "Unit" + ClassicalControlNs, "_SelfControlled", [|"Result";"Qubit[]";"Unit"|], "Unit" + |]) + (_DefaultTypes, [| // Within Block Support + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_TypeParameterTypes, [| // Arguments Partially Resolve Type Parameters + ClassicalControlNs, "Bar", [|"Bar.Q";"Bar.W"|], "Unit" + ClassicalControlNs, "Foo", [||], "Unit" + |]) + (_DefaultTypes, [| // Hoist Functor Application + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_DefaultTypes, [| // Hoist Partial Application + ClassicalControlNs, "Bar", [|"Int";"Double"|], "Unit" + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"Result"|], "Unit" + |]) + (_DefaultWithOperation, [| // Hoist Array Item Call + ClassicalControlNs, "Foo", [||], "Unit" + ClassicalControlNs, "_Foo", [|"SubOp1Type[]";"Result"|], "Unit" + |]) |] |> _MakeSignatures \ No newline at end of file diff --git a/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj b/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj index 2f3634b115..b7f2118e59 100644 --- a/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj +++ b/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj @@ -23,6 +23,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -41,6 +44,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -131,6 +137,7 @@ + diff --git a/src/QsCompiler/Transformations/ClassicallyControlledTransformation.cs b/src/QsCompiler/Transformations/ClassicallyControlledTransformation.cs new file mode 100644 index 0000000000..3068bdd70e --- /dev/null +++ b/src/QsCompiler/Transformations/ClassicallyControlledTransformation.cs @@ -0,0 +1,1094 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; +using Microsoft.Quantum.QsCompiler.SyntaxTree; + + +namespace Microsoft.Quantum.QsCompiler.Transformations.ClassicallyControlledTransformation +{ + using ExpressionKind = QsExpressionKind; + using ResolvedTypeKind = QsTypeKind; + using TypeArgsResolution = ImmutableArray, ResolvedType>>; + + // This transformation works in two passes. + // 1st Pass: Hoist the contents of conditional statements into separate operations, where possible. + // 2nd Pass: On the way down the tree, reshape conditional statements to replace Elif's and + // top level OR and AND conditions with equivalent nested if-else statements. One the way back up + // the tree, convert conditional statements into ApplyIf calls, where possible. + // This relies on anything having type parameters must be a global callable. + public class ClassicallyControlledTransformation + { + public static QsCompilation Apply(QsCompilation compilation) + { + compilation = HoistTransformation.Apply(compilation); + + var filter = new ClassicallyControlledSyntax(compilation); + return new QsCompilation(compilation.Namespaces.Select(ns => filter.Transform(ns)).ToImmutableArray(), compilation.EntryPoints); + } + + private static TypedExpression CreateIdentifierExpression(Identifier id, + TypeArgsResolution typeArgsMapping, ResolvedType resolvedType) => + new TypedExpression + ( + ExpressionKind.NewIdentifier( + id, + typeArgsMapping.Any() + ? QsNullable>.NewValue(typeArgsMapping + .Select(argMapping => argMapping.Item3) // This should preserve the order of the type args + .ToImmutableArray()) + : QsNullable>.Null), + typeArgsMapping, + resolvedType, + new InferredExpressionInformation(false, false), + QsNullable>.Null + ); + + private static TypedExpression CreateValueTupleExpression(params TypedExpression[] expressions) => + new TypedExpression + ( + ExpressionKind.NewValueTuple(expressions.ToImmutableArray()), + TypeArgsResolution.Empty, + ResolvedType.New(ResolvedTypeKind.NewTupleType(expressions.Select(expr => expr.ResolvedType).ToImmutableArray())), + new InferredExpressionInformation(false, false), + QsNullable>.Null + ); + + private static (bool, QsResult, TypedExpression) IsConditionedOnResultLiteralExpression(TypedExpression expression) + { + if (expression.Expression is ExpressionKind.EQ eq) + { + if (eq.Item1.Expression is ExpressionKind.ResultLiteral exp1) + { + return (true, exp1.Item, eq.Item2); + } + else if (eq.Item2.Expression is ExpressionKind.ResultLiteral exp2) + { + return (true, exp2.Item, eq.Item1); + } + } + + return (false, null, null); + } + + private static (bool, QsResult, TypedExpression, QsScope, QsScope) IsConditionedOnResultLiteralStatement(QsStatement statement) + { + if (statement.Statement is QsStatementKind.QsConditionalStatement cond) + { + if (cond.Item.ConditionalBlocks.Length == 1 && (cond.Item.ConditionalBlocks[0].Item1.Expression is ExpressionKind.EQ expression)) + { + var scope = cond.Item.ConditionalBlocks[0].Item2.Body; + var defaultScope = cond.Item.Default.ValueOr(null)?.Body; + + var (success, literal, expr) = IsConditionedOnResultLiteralExpression(cond.Item.ConditionalBlocks[0].Item1); + + if (success) + { + return (true, literal, expr, scope, defaultScope); + } + } + } + + return (false, null, null, null, null); + } + + private ClassicallyControlledTransformation() { } + + private class ClassicallyControlledSyntax : SyntaxTreeTransformation + { + public ClassicallyControlledSyntax(QsCompilation compilation, ClassicallyControlledScope scope = null) : base(scope ?? new ClassicallyControlledScope(compilation)) { } + + public override QsCallable onFunction(QsCallable c) => c; // Prevent anything in functions from being considered + } + + private class ClassicallyControlledScope : ScopeTransformation + { + private QsCompilation _Compilation; + + public ClassicallyControlledScope(QsCompilation compilation, NoExpressionTransformations expr = null) : base(expr ?? new NoExpressionTransformations()) + { + _Compilation = compilation; + } + + private TypeArgsResolution GetCombinedType(TypeArgsResolution outer, TypeArgsResolution inner) + { + var outerDict = outer.ToDictionary(x => (x.Item1, x.Item2), x => x.Item3); + return inner.Select(innerRes => + { + if (innerRes.Item3.Resolution is ResolvedTypeKind.TypeParameter typeParam && + outerDict.TryGetValue((typeParam.Item.Origin, typeParam.Item.TypeName), out var outerRes)) + { + outerDict.Remove((typeParam.Item.Origin, typeParam.Item.TypeName)); + return Tuple.Create(innerRes.Item1, innerRes.Item2, outerRes); + } + else + { + return innerRes; + } + }).Concat(outerDict.Select(x => Tuple.Create(x.Key.Item1, x.Key.Item2, x.Value))).ToImmutableArray(); + } + + private (bool, TypedExpression, TypedExpression) IsValidScope(QsScope scope) + { + // if the scope has exactly one statement in it and that statement is a call like expression statement + if (scope != null + && scope.Statements.Length == 1 + && scope.Statements[0].Statement is QsStatementKind.QsExpressionStatement expr + && expr.Item.ResolvedType.Resolution.IsUnitType + && expr.Item.Expression is ExpressionKind.CallLikeExpression call + && !TypedExpression.IsPartialApplication(expr.Item.Expression) + && call.Item1.Expression is ExpressionKind.Identifier) + { + // 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 callTypeArguments = expr.Item.TypeArguments; + var idTypeArguments = call.Item1.TypeArguments; + var combinedTypeArguments = GetCombinedType(callTypeArguments, idTypeArguments); + + // This relies on anything having type parameters must be a global callable. + var newExpr1 = call.Item1; + if (combinedTypeArguments.Any() + && newExpr1.Expression is ExpressionKind.Identifier id + && id.Item1 is Identifier.GlobalCallable global) + { + var globalCallable = _Compilation.Namespaces + .Where(ns => ns.Name.Equals(global.Item.Namespace)) + .Callables() + .FirstOrDefault(c => c.FullName.Name.Equals(global.Item.Name)); + + QsCompilerError.Verify(globalCallable != null, $"Could not find the global reference {global.Item.Namespace.Value + "." + global.Item.Name.Value}"); + + var callableTypeParameters = globalCallable.Signature.TypeParameters + .Select(x => x as QsLocalSymbol.ValidName); + + QsCompilerError.Verify(callableTypeParameters.All(x => x != null), $"Invalid type parameter names."); + + newExpr1 = new TypedExpression( + ExpressionKind.NewIdentifier( + id.Item1, + QsNullable>.NewValue( + callableTypeParameters + .Select(x => combinedTypeArguments.First(y => y.Item2.Equals(x.Item)).Item3).ToImmutableArray())), + combinedTypeArguments, + call.Item1.ResolvedType, + call.Item1.InferredInformation, + call.Item1.Range); + } + + return (true, newExpr1, call.Item2); + } + + return (false, null, null); + } + + private TypedExpression CreateApplyIfCall(TypedExpression id, TypedExpression args, TypeArgsResolution typeRes) => + new TypedExpression + ( + ExpressionKind.NewCallLikeExpression(id, args), + typeRes, + ResolvedType.New(ResolvedTypeKind.UnitType), + new InferredExpressionInformation(false, true), + QsNullable>.Null + ); + + private QsStatement CreateApplyIfStatement(QsStatement statement, QsResult result, TypedExpression conditionExpression, QsScope conditionScope, QsScope defaultScope) + { + var controlCall = GetApplyIfExpression(result, conditionExpression, conditionScope, defaultScope); + + if (controlCall != null) + { + return new QsStatement( + QsStatementKind.NewQsExpressionStatement(controlCall), + statement.SymbolDeclarations, + QsNullable.Null, + statement.Comments); + } + else + { + // ToDo: add diagnostic message here + return statement; // If the blocks can't be converted, return the original + } + } + + private static ResolvedType GetApplyIfResolvedType(IEnumerable props, ResolvedType argumentType) + { + var characteristics = new CallableInformation( + ResolvedCharacteristics.FromProperties(props), + InferredCallableInformation.NoInformation); + + return ResolvedType.New(ResolvedTypeKind.NewOperation( + Tuple.Create(argumentType, ResolvedType.New(ResolvedTypeKind.UnitType)), + characteristics)); + } + + private TypedExpression GetApplyIfExpression(QsResult result, TypedExpression conditionExpression, QsScope conditionScope, QsScope defaultScope) + { + var (isCondValid, condId, condArgs) = IsValidScope(conditionScope); + var (isDefaultValid, defaultId, defaultArgs) = IsValidScope(defaultScope); + + BuiltIn controlOpInfo; + TypedExpression controlArgs; + ImmutableArray targetArgs; + + var props = ImmutableHashSet.Empty; + + if (isCondValid) + { + // Get characteristic properties from global id + if (condId.ResolvedType.Resolution is ResolvedTypeKind.Operation op) + { + props = op.Item2.Characteristics.GetProperties(); + } + + (bool adj, bool ctl) = (props.Contains(OpProperty.Adjointable), props.Contains(OpProperty.Controllable)); + + if (isDefaultValid) + { + if (adj && ctl) + { + controlOpInfo = BuiltIn.ApplyIfElseRCA; + } + else if (adj) + { + controlOpInfo = BuiltIn.ApplyIfElseRA; + } + else if (ctl) + { + controlOpInfo = BuiltIn.ApplyIfElseRC; + } + else + { + controlOpInfo = BuiltIn.ApplyIfElseR; + } + + var (zeroOpArg, oneOpArg) = (result == QsResult.Zero) + ? (CreateValueTupleExpression(condId, condArgs), CreateValueTupleExpression(defaultId, defaultArgs)) + : (CreateValueTupleExpression(defaultId, defaultArgs), CreateValueTupleExpression(condId, condArgs)); + + controlArgs = CreateValueTupleExpression(conditionExpression, zeroOpArg, oneOpArg); + + targetArgs = ImmutableArray.Create(condArgs.ResolvedType, defaultArgs.ResolvedType); + } + else if (defaultScope == null) + { + if (adj && ctl) + { + controlOpInfo = (result == QsResult.Zero) + ? BuiltIn.ApplyIfZeroCA + : BuiltIn.ApplyIfOneCA; + } + else if (adj) + { + controlOpInfo = (result == QsResult.Zero) + ? BuiltIn.ApplyIfZeroA + : BuiltIn.ApplyIfOneA; + } + else if (ctl) + { + controlOpInfo = (result == QsResult.Zero) + ? BuiltIn.ApplyIfZeroC + : BuiltIn.ApplyIfOneC; + } + else + { + controlOpInfo = (result == QsResult.Zero) + ? BuiltIn.ApplyIfZero + : BuiltIn.ApplyIfOne; + } + + controlArgs = CreateValueTupleExpression( + conditionExpression, + CreateValueTupleExpression(condId, condArgs)); + + targetArgs = ImmutableArray.Create(condArgs.ResolvedType); + } + else + { + return null; // ToDo: Diagnostic message - default body exists, but is not valid + } + + } + else + { + return null; // ToDo: Diagnostic message - cond body not valid + } + + // Build the surrounding apply-if call + var controlOpId = CreateIdentifierExpression( + Identifier.NewGlobalCallable(new QsQualifiedName(controlOpInfo.Namespace, controlOpInfo.Name)), + targetArgs + .Zip(controlOpInfo.TypeParameters, (type, param) => Tuple.Create(new QsQualifiedName(controlOpInfo.Namespace, controlOpInfo.Name), param, type)) + .ToImmutableArray(), + GetApplyIfResolvedType(props, controlArgs.ResolvedType)); + + // Creates identity resolutions for the call expression + var opTypeArgResolutions = targetArgs + .SelectMany(x => + x.Resolution is ResolvedTypeKind.TupleType tup + ? tup.Item + : ImmutableArray.Create(x)) + .Where(x => x.Resolution.IsTypeParameter) + .Select(x => (x.Resolution as ResolvedTypeKind.TypeParameter).Item) + .GroupBy(x => (x.Origin, x.TypeName)) + .Select(group => + { + var typeParam = group.First(); + return Tuple.Create(typeParam.Origin, typeParam.TypeName, ResolvedType.New(ResolvedTypeKind.NewTypeParameter(typeParam))); + }) + .ToImmutableArray(); + + return CreateApplyIfCall(controlOpId, controlArgs, opTypeArgResolutions); + } + + private (bool, QsConditionalStatement) ProcessElif(QsConditionalStatement cond) + { + if (cond.ConditionalBlocks.Length < 2) return (false, cond); + + var subCond = new QsConditionalStatement(cond.ConditionalBlocks.RemoveAt(0), cond.Default); + var secondCondBlock = cond.ConditionalBlocks[1].Item2; + + var subIfStatment = new QsStatement + ( + QsStatementKind.NewQsConditionalStatement(subCond), + LocalDeclarations.Empty, + secondCondBlock.Location, + secondCondBlock.Comments + ); + + var newDefault = QsNullable.NewValue(new QsPositionedBlock( + new QsScope(ImmutableArray.Create(subIfStatment), secondCondBlock.Body.KnownSymbols), + secondCondBlock.Location, + QsComments.Empty)); + + return (true, new QsConditionalStatement(ImmutableArray.Create(cond.ConditionalBlocks[0]), newDefault)); + } + + private (bool, QsConditionalStatement) ProcessOR(QsConditionalStatement cond) + { + // This method expects elif blocks to have been abstracted out + if (cond.ConditionalBlocks.Length != 1) return (false, cond); + + var (condition, block) = cond.ConditionalBlocks[0]; + + if (condition.Expression is ExpressionKind.OR orCond) + { + var subCond = new QsConditionalStatement(ImmutableArray.Create(Tuple.Create(orCond.Item2, block)), cond.Default); + var subIfStatment = new QsStatement + ( + QsStatementKind.NewQsConditionalStatement(subCond), + LocalDeclarations.Empty, + block.Location, + QsComments.Empty + ); + var newDefault = QsNullable.NewValue(new QsPositionedBlock( + new QsScope(ImmutableArray.Create(subIfStatment), block.Body.KnownSymbols), + block.Location, + QsComments.Empty)); + + return (true, new QsConditionalStatement(ImmutableArray.Create(Tuple.Create(orCond.Item1, block)), newDefault)); + } + else + { + return (false, cond); + } + } + + private (bool, QsConditionalStatement) ProcessAND(QsConditionalStatement cond) + { + // This method expects elif blocks to have been abstracted out + if (cond.ConditionalBlocks.Length != 1) return (false, cond); + + var (condition, block) = cond.ConditionalBlocks[0]; + + if (condition.Expression is ExpressionKind.AND andCond) + { + var subCond = new QsConditionalStatement(ImmutableArray.Create(Tuple.Create(andCond.Item2, block)), cond.Default); + var subIfStatment = new QsStatement + ( + QsStatementKind.NewQsConditionalStatement(subCond), + LocalDeclarations.Empty, + block.Location, + QsComments.Empty + ); + var newBlock = new QsPositionedBlock( + new QsScope(ImmutableArray.Create(subIfStatment), block.Body.KnownSymbols), + block.Location, + QsComments.Empty); + + return (true, new QsConditionalStatement(ImmutableArray.Create(Tuple.Create(andCond.Item1, newBlock)), cond.Default)); + } + else + { + return (false, cond); + } + } + + private QsStatement ReshapeConditional(QsStatement statement) + { + if (statement.Statement is QsStatementKind.QsConditionalStatement cond) + { + var stm = cond.Item; + (_, stm) = ProcessElif(stm); + bool wasOrProcessed, wasAndProcessed; + do + { + (wasOrProcessed, stm) = ProcessOR(stm); + (wasAndProcessed, stm) = ProcessAND(stm); + } while (wasOrProcessed || wasAndProcessed); + + return new QsStatement + ( + QsStatementKind.NewQsConditionalStatement(stm), + statement.SymbolDeclarations, + statement.Location, + statement.Comments + ); + } + return statement; + } + + public override QsScope Transform(QsScope scope) + { + var parentSymbols = this.onLocalDeclarations(scope.KnownSymbols); + var statements = new List(); + + foreach (var statement in scope.Statements) + { + if (statement.Statement is QsStatementKind.QsConditionalStatement) + { + var stm = ReshapeConditional(statement); + stm = this.onStatement(stm); + + var (isCondition, result, conditionExpression, conditionScope, defaultScope) = IsConditionedOnResultLiteralStatement(stm); + + if (isCondition) + { + statements.Add(CreateApplyIfStatement(stm, result, conditionExpression, conditionScope, defaultScope)); + } + else + { + statements.Add(stm); + } + } + else + { + statements.Add(this.onStatement(statement)); + } + } + + return new QsScope(statements.ToImmutableArray(), parentSymbols); + } + } + + // Transformation that updates the contents of newly generated operations by: + // 1. Rerouting the origins of type parameter references to the new operation + // 2. Changes the IsMutable info on variable that used to be mutable, but are now immutable params to the operation + private class UpdateGeneratedOpTransformation + { + private bool _IsRecursiveIdentifier = false; + private ImmutableArray>> _Parameters; + private QsQualifiedName _OldName; + private QsQualifiedName _NewName; + + public static QsCallable Apply(QsCallable qsCallable, ImmutableArray>> parameters, QsQualifiedName oldName, QsQualifiedName newName) + { + var filter = new SyntaxTreeTransformation>( + new ScopeTransformation( + new UpdateGeneratedOpExpression( + new UpdateGeneratedOpTransformation(parameters, oldName, newName)))); + + return filter.onCallableImplementation(qsCallable); + } + + private UpdateGeneratedOpTransformation(ImmutableArray>> parameters, QsQualifiedName oldName, QsQualifiedName newName) + { + _Parameters = parameters; + _OldName = oldName; + _NewName = newName; + } + + private class UpdateGeneratedOpExpression : ExpressionTransformation + { + private UpdateGeneratedOpTransformation _super; + + public UpdateGeneratedOpExpression(UpdateGeneratedOpTransformation super) : + base(expr => new UpdateGeneratedOpExpressionKind(super, expr as UpdateGeneratedOpExpression), + expr => new UpdateGeneratedOpExpressionType(super, expr as UpdateGeneratedOpExpression)) + { _super = super; } + + public override ImmutableDictionary>, ResolvedType> onTypeParamResolutions(ImmutableDictionary>, ResolvedType> typeParams) + { + // Prevent keys from having their names updated + return typeParams.ToImmutableDictionary(kvp => kvp.Key, kvp => this.Type.Transform(kvp.Value)); + } + + public override TypedExpression Transform(TypedExpression ex) + { + // Checks if expression is mutable identifier that is in parameter list + if (ex.InferredInformation.IsMutable && + ex.Expression is ExpressionKind.Identifier id && + id.Item1 is Identifier.LocalVariable variable && + _super._Parameters.Any(x => x.VariableName.Equals(variable))) + { + // Set the mutability to false + ex = new TypedExpression( + ex.Expression, + ex.TypeArguments, + ex.ResolvedType, + new InferredExpressionInformation(false, ex.InferredInformation.HasLocalQuantumDependency), + ex.Range); + } + + // Prevent _IsRecursiveIdentifier from propagating beyond the typed expression it is referring to + var isRecursiveIdentifier = _super._IsRecursiveIdentifier; + var rtrn = base.Transform(ex); + _super._IsRecursiveIdentifier = isRecursiveIdentifier; + return rtrn; + } + } + + private class UpdateGeneratedOpExpressionKind : ExpressionKindTransformation + { + private UpdateGeneratedOpTransformation _super; + + public UpdateGeneratedOpExpressionKind(UpdateGeneratedOpTransformation super, UpdateGeneratedOpExpression expr) : base(expr) { _super = super; } + + public override ExpressionKind onIdentifier(Identifier sym, QsNullable> tArgs) + { + var rtrn = base.onIdentifier(sym, tArgs); + + // Then check if this is a recursive identifier + // In this context, that is a call back to the original callable from the newly generated operation + if (sym is Identifier.GlobalCallable callable && _super._OldName.Equals(callable.Item)) + { + // Setting this flag will prevent the rerouting logic from processing the resolved type of the recursive identifier expression. + // This is necessary because we don't want any type parameters from the original callable from being rerouted to the new generated + // operation's type parameters in the definition of the identifier. + _super._IsRecursiveIdentifier = true; + } + return rtrn; + } + } + + private class UpdateGeneratedOpExpressionType : ExpressionTypeTransformation + { + private UpdateGeneratedOpTransformation _super; + + public UpdateGeneratedOpExpressionType(UpdateGeneratedOpTransformation super, UpdateGeneratedOpExpression expr) : base(expr) { _super = super; } + + public override ResolvedTypeKind onTypeParameter(QsTypeParameter tp) + { + // Reroute a type parameter's origin to the newly generated operation + if (!_super._IsRecursiveIdentifier && _super._OldName.Equals(tp.Origin)) + { + tp = new QsTypeParameter(_super._NewName, tp.TypeName, tp.Range); + } + + return base.onTypeParameter(tp); + } + } + } + + // Transformation handling the first pass task of hoisting of the contents of conditional statements. + // If blocks are first validated to see if they can safely be hoisted into their own operation. + // Validation requirements are that there are no return statements and that there are no set statements + // on mutables declared outside the block. Setting mutables declared inside the block is valid. + // If the block is valid, and there is more than one statement in the block, a new operation with the + // block's contents is generated, having all the same type parameters as the calling context + // and all known variables at the start of the block become parameters to the new operation. + // The contents of the conditional block are then replaced with a call to the new operation with all + // the type parameters and known variables being forwarded to the new operation as arguments. + private class HoistTransformation + { + private bool _IsValidScope = true; + private List _ControlOperations; + private ImmutableArray>> _CurrentHoistParams = + ImmutableArray>>.Empty; + private bool _ContainsHoistParamRef = false; + + private class CallableDetails + { + public QsCallable Callable; + public QsSpecialization Adjoint; + public QsSpecialization Controlled; + public QsSpecialization ControlledAdjoint; + public QsNullable> TypeParamTypes; + + public CallableDetails(QsCallable callable) + { + Callable = callable; + 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); + TypeParamTypes = callable.Signature.TypeParameters.Any(param => param.IsValidName) + ? QsNullable>.NewValue(callable.Signature.TypeParameters + .Where(param => param.IsValidName) + .Select(param => + ResolvedType.New(ResolvedTypeKind.NewTypeParameter(new QsTypeParameter( + callable.FullName, + ((QsLocalSymbol.ValidName)param).Item, + QsNullable>.Null + )))) + .ToImmutableArray()) + : QsNullable>.Null; + } + } + + private CallableDetails _CurrentCallable = null; + private bool _InBody = false; + private bool _InAdjoint = false; + private bool _InControlled = false; + + private bool _InWithinBlock = false; + + public static QsCompilation Apply(QsCompilation compilation) + { + var filter = new HoistSyntax(new HoistTransformation()); + + return new QsCompilation(compilation.Namespaces.Select(ns => filter.Transform(ns)).ToImmutableArray(), compilation.EntryPoints); + } + + private (ResolvedSignature, IEnumerable) MakeSpecializations(QsQualifiedName callableName, ResolvedType argsType, SpecializationImplementation bodyImplementation) + { + QsSpecialization MakeSpec(QsSpecializationKind kind, ResolvedSignature signature, SpecializationImplementation impl) => + new QsSpecialization( + kind, + callableName, + ImmutableArray.Empty, + _CurrentCallable.Callable.SourceFile, + QsNullable.Null, + QsNullable>.Null, + signature, + impl, + ImmutableArray.Empty, + QsComments.Empty); + + var adj = _CurrentCallable.Adjoint; + var ctl = _CurrentCallable.Controlled; + var ctlAdj = _CurrentCallable.ControlledAdjoint; + + bool addAdjoint = false; + bool addControlled = false; + + if (_InWithinBlock) + { + addAdjoint = true; + addControlled = false; + } + else if (_InBody) + { + if (adj != null && adj.Implementation is SpecializationImplementation.Generated adjGen) addAdjoint = adjGen.Item.IsInvert; + 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; + } + } + else if (ctlAdj != null && ctlAdj.Implementation is SpecializationImplementation.Generated gen) + { + addControlled = _InAdjoint && gen.Item.IsDistribute; + addAdjoint = _InControlled && gen.Item.IsInvert; + } + + var props = new List(); + if (addAdjoint) props.Add(OpProperty.Adjointable); + if (addControlled) props.Add(OpProperty.Controllable); + var newSig = new ResolvedSignature( + _CurrentCallable.Callable.Signature.TypeParameters, + argsType, + ResolvedType.New(ResolvedTypeKind.UnitType), + new CallableInformation(ResolvedCharacteristics.FromProperties(props), InferredCallableInformation.NoInformation)); + + var controlledSig = new ResolvedSignature( + newSig.TypeParameters, + ResolvedType.New(ResolvedTypeKind.NewTupleType(ImmutableArray.Create( + ResolvedType.New(ResolvedTypeKind.NewArrayType(ResolvedType.New(ResolvedTypeKind.Qubit))), + newSig.ArgumentType))), + newSig.ReturnType, + newSig.Information); + + var specializations = new List() { MakeSpec(QsSpecializationKind.QsBody, newSig, bodyImplementation) }; + + if (addAdjoint) + { + specializations.Add(MakeSpec( + QsSpecializationKind.QsAdjoint, + newSig, + SpecializationImplementation.NewGenerated(QsGeneratorDirective.Invert))); + } + + if (addControlled) + { + specializations.Add(MakeSpec( + QsSpecializationKind.QsControlled, + controlledSig, + SpecializationImplementation.NewGenerated(QsGeneratorDirective.Distribute))); + } + + if (addAdjoint && addControlled) + { + specializations.Add(MakeSpec( + QsSpecializationKind.QsControlledAdjoint, + controlledSig, + SpecializationImplementation.NewGenerated(QsGeneratorDirective.Distribute))); + } + + return (newSig, specializations); + } + + private (QsCallable, ResolvedType) GenerateOperation(QsScope contents) + { + var newName = new QsQualifiedName( + _CurrentCallable.Callable.FullName.Namespace, + NonNullable.New("_" + Guid.NewGuid().ToString("N") + "_" + _CurrentCallable.Callable.FullName.Name.Value)); + + var knownVariables = contents.KnownSymbols.IsEmpty + ? ImmutableArray>>.Empty + : contents.KnownSymbols.Variables; + + var parameters = QsTuple>.NewQsTuple(knownVariables + .Select(var => QsTuple>.NewQsTupleItem(new LocalVariableDeclaration( + QsLocalSymbol.NewValidName(var.VariableName), + var.Type, + var.InferredInformation, + var.Position, + var.Range))) + .ToImmutableArray()); + + var paramTypes = ResolvedType.New(ResolvedTypeKind.UnitType); + if (knownVariables.Length == 1) + { + paramTypes = knownVariables.First().Type; + } + else if (knownVariables.Length > 1) + { + paramTypes = ResolvedType.New(ResolvedTypeKind.NewTupleType(knownVariables + .Select(var => var.Type) + .ToImmutableArray())); + } + + var (signature, specializations) = MakeSpecializations(newName, paramTypes, SpecializationImplementation.NewProvided(parameters, contents)); + + var controlCallable = new QsCallable( + QsCallableKind.Operation, + newName, + ImmutableArray.Empty, + _CurrentCallable.Callable.SourceFile, + QsNullable.Null, + signature, + parameters, + specializations.ToImmutableArray(), + ImmutableArray.Empty, + QsComments.Empty); + + var updatedCallable = UpdateGeneratedOpTransformation.Apply(controlCallable, knownVariables, _CurrentCallable.Callable.FullName, newName); + + return (updatedCallable, signature.ArgumentType); + } + + private HoistTransformation() { } + + private class HoistSyntax : SyntaxTreeTransformation> + { + private HoistTransformation _super; + + public HoistSyntax(HoistTransformation super, ScopeTransformation scope = null) : + base(scope ?? new ScopeTransformation( + scopeTransform => new HoistStatementKind(super, scopeTransform), + new HoistExpression(super))) + { _super = super; } + + public override QsCallable onCallableImplementation(QsCallable c) + { + _super._CurrentCallable = new CallableDetails(c); + return base.onCallableImplementation(c); + } + + public override QsSpecialization onBodySpecialization(QsSpecialization spec) + { + _super._InBody = true; + var rtrn = base.onBodySpecialization(spec); + _super._InBody = false; + return rtrn; + } + + public override QsSpecialization onAdjointSpecialization(QsSpecialization spec) + { + _super._InAdjoint = true; + var rtrn = base.onAdjointSpecialization(spec); + _super._InAdjoint = false; + return rtrn; + } + + public override QsSpecialization onControlledSpecialization(QsSpecialization spec) + { + _super._InControlled = true; + var rtrn = base.onControlledSpecialization(spec); + _super._InControlled = false; + return rtrn; + } + + public override QsCallable onFunction(QsCallable c) => c; // Prevent anything in functions from being hoisted + + public override QsNamespace Transform(QsNamespace ns) + { + // Control operations list will be populated in the transform + _super._ControlOperations = new List(); + return base.Transform(ns) + .WithElements(elems => elems.AddRange(_super._ControlOperations.Select(op => QsNamespaceElement.NewQsCallable(op)))); + } + } + + private class HoistStatementKind : StatementKindTransformation> + { + private HoistTransformation _super; + + public HoistStatementKind(HoistTransformation super, ScopeTransformation scope) : base(scope) { _super = super; } + + private (QsCallable, QsStatement) HoistIfContents(QsScope contents) + { + var (targetOp, originalArgumentType) = _super.GenerateOperation(contents); + var targetOpType = ResolvedType.New(ResolvedTypeKind.NewOperation( + Tuple.Create( + originalArgumentType, + ResolvedType.New(ResolvedTypeKind.UnitType)), + targetOp.Signature.Information)); + + var targetTypeArgTypes = _super._CurrentCallable.TypeParamTypes; + var targetOpId = new TypedExpression + ( + ExpressionKind.NewIdentifier(Identifier.NewGlobalCallable(targetOp.FullName), targetTypeArgTypes), + targetTypeArgTypes.IsNull + ? TypeArgsResolution.Empty + : targetTypeArgTypes.Item + .Select(type => Tuple.Create(targetOp.FullName, ((ResolvedTypeKind.TypeParameter)type.Resolution).Item.TypeName, type)) + .ToImmutableArray(), + targetOpType, + new InferredExpressionInformation(false, false), + QsNullable>.Null + ); + + var knownSymbols = contents.KnownSymbols.Variables; + + TypedExpression targetArgs = null; + if (knownSymbols.Any()) + { + targetArgs = CreateValueTupleExpression(knownSymbols.Select(var => CreateIdentifierExpression( + Identifier.NewLocalVariable(var.VariableName), + TypeArgsResolution.Empty, + var.Type)) + .ToArray()); + } + else + { + targetArgs = new TypedExpression + ( + ExpressionKind.UnitValue, + TypeArgsResolution.Empty, + ResolvedType.New(ResolvedTypeKind.UnitType), + new InferredExpressionInformation(false, false), + QsNullable>.Null + ); + } + + var call = new TypedExpression + ( + ExpressionKind.NewCallLikeExpression(targetOpId, targetArgs), + targetTypeArgTypes.IsNull + ? TypeArgsResolution.Empty + : targetTypeArgTypes.Item + .Select(type => Tuple.Create(_super._CurrentCallable.Callable.FullName, ((ResolvedTypeKind.TypeParameter)type.Resolution).Item.TypeName, type)) + .ToImmutableArray(), + ResolvedType.New(ResolvedTypeKind.UnitType), + new InferredExpressionInformation(false, true), + QsNullable>.Null + ); + + return (targetOp, new QsStatement( + QsStatementKind.NewQsExpressionStatement(call), + LocalDeclarations.Empty, + QsNullable.Null, + QsComments.Empty)); + } + + private bool IsScopeSingleCall(QsScope contents) + { + if (contents.Statements.Length != 1) return false; + + return contents.Statements[0].Statement is QsStatementKind.QsExpressionStatement expr + && expr.Item.Expression is ExpressionKind.CallLikeExpression call + && !TypedExpression.IsPartialApplication(expr.Item.Expression) + && call.Item1.Expression is ExpressionKind.Identifier; + } + + public override QsStatementKind onConjugation(QsConjugation stm) + { + var superInWithinBlock = _super._InWithinBlock; + _super._InWithinBlock = true; + var (_, outer) = this.onPositionedBlock(QsNullable.Null, stm.OuterTransformation); + _super._InWithinBlock = superInWithinBlock; + + var (_, inner) = this.onPositionedBlock(QsNullable.Null, stm.InnerTransformation); + + return QsStatementKind.NewQsConjugation(new QsConjugation(outer, inner)); + } + + public override QsStatementKind onReturnStatement(TypedExpression ex) + { + _super._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.ExpressionTransformation(stm.Lhs); + + if (_super._ContainsHoistParamRef) + { + _super._IsValidScope = false; + } + + var rhs = this.ExpressionTransformation(stm.Rhs); + return QsStatementKind.NewQsValueUpdate(new QsValueUpdate(lhs, rhs)); + } + + public override QsStatementKind onConditionalStatement(QsConditionalStatement stm) + { + var contextValidScope = _super._IsValidScope; + var contextHoistParams = _super._CurrentHoistParams; + + var isHoistValid = true; + + var newConditionBlocks = new List>(); + var generatedOperations = new List(); + foreach (var condBlock in stm.ConditionalBlocks) + { + _super._IsValidScope = true; + _super._CurrentHoistParams = condBlock.Item2.Body.KnownSymbols.IsEmpty + ? ImmutableArray>>.Empty + : condBlock.Item2.Body.KnownSymbols.Variables; + + var (expr, block) = this.onPositionedBlock(QsNullable.NewValue(condBlock.Item1), condBlock.Item2); + + // ToDo: Reduce the number of unnecessary generated operations by generalizing + // the condition logic for the conversion and using that condition here + //var (isExprCond, _, _) = IsConditionedOnResultLiteralExpression(expr.Item); + + if (block.Body.Statements.Length > 0 /*&& isExprCond*/ && _super._IsValidScope && !IsScopeSingleCall(block.Body)) // if sub-scope is valid, hoist content + { + // Hoist the scope to its own operation + var (callable, call) = HoistIfContents(block.Body); + block = new QsPositionedBlock( + new QsScope(ImmutableArray.Create(call), block.Body.KnownSymbols), + block.Location, + block.Comments); + newConditionBlocks.Add(Tuple.Create(expr.Item,block)); + generatedOperations.Add(callable); + } + else + { + isHoistValid = false; + break; + } + } + + var newDefault = QsNullable.Null; + if (isHoistValid && stm.Default.IsValue) + { + _super._IsValidScope = true; + _super._CurrentHoistParams = stm.Default.Item.Body.KnownSymbols.IsEmpty + ? ImmutableArray>>.Empty + : stm.Default.Item.Body.KnownSymbols.Variables; + + var (_, block) = this.onPositionedBlock(QsNullable.Null, stm.Default.Item); + if (block.Body.Statements.Length > 0 && _super._IsValidScope && !IsScopeSingleCall(block.Body)) // if sub-scope is valid, hoist content + { + // Hoist the scope to its own operation + var (callable, call) = HoistIfContents(block.Body); + block = new QsPositionedBlock( + new QsScope(ImmutableArray.Create(call), block.Body.KnownSymbols), + block.Location, + block.Comments); + newDefault = QsNullable.NewValue(block); + generatedOperations.Add(callable); + } + else + { + isHoistValid = false; + } + } + + if (isHoistValid) + { + _super._ControlOperations.AddRange(generatedOperations); + } + + _super._CurrentHoistParams = contextHoistParams; + _super._IsValidScope = contextValidScope; + + return isHoistValid + ? QsStatementKind.NewQsConditionalStatement( + new QsConditionalStatement(newConditionBlocks.ToImmutableArray(), newDefault)) + : QsStatementKind.NewQsConditionalStatement( + new QsConditionalStatement(stm.ConditionalBlocks, stm.Default)); + } + + public override QsStatementKind Transform(QsStatementKind kind) + { + _super._ContainsHoistParamRef = false; // Every statement kind starts off false + return base.Transform(kind); + } + } + + private class HoistExpression : ExpressionTransformation + { + private HoistTransformation _super; + + public HoistExpression(HoistTransformation super) : + base(expr => new HoistExpressionKind(super, expr as HoistExpression)) + { _super = super; } + + public override TypedExpression Transform(TypedExpression ex) + { + var contextContainsHoistParamRef = _super._ContainsHoistParamRef; + _super._ContainsHoistParamRef = false; + var rtrn = base.Transform(ex); + + // If the sub context contains a reference, then the super context contains a reference, + // otherwise return the super context to its original value + if (!_super._ContainsHoistParamRef) + { + _super._ContainsHoistParamRef = contextContainsHoistParamRef; + } + + return rtrn; + } + } + + private class HoistExpressionKind : ExpressionKindTransformation + { + private HoistTransformation _super; + + public HoistExpressionKind(HoistTransformation super, HoistExpression expr) : base(expr) { _super = super; } + + public override ExpressionKind onIdentifier(Identifier sym, QsNullable> tArgs) + { + if (sym is Identifier.LocalVariable local && + _super._CurrentHoistParams.Any(param => param.VariableName.Equals(local.Item))) + { + _super._ContainsHoistParamRef = true; + } + return base.onIdentifier(sym, tArgs); + } + } + } + } +} diff --git a/src/QsCompiler/Transformations/CodeOutput.cs b/src/QsCompiler/Transformations/CodeOutput.cs index 8108a6fc63..c82cdfba06 100644 --- a/src/QsCompiler/Transformations/CodeOutput.cs +++ b/src/QsCompiler/Transformations/CodeOutput.cs @@ -818,10 +818,10 @@ public override QsStatementKind onConditionalStatement(QsConditionalStatement st public override QsStatementKind onConjugation(QsConjugation stm) { - this._Scope.CurrentComments = stm.InnerTransformation.Comments; - this.AddBlockStatement(Keywords.qsWithin.id, stm.InnerTransformation.Body, true); this._Scope.CurrentComments = stm.OuterTransformation.Comments; - this.AddBlockStatement(Keywords.qsApply.id, stm.OuterTransformation.Body, false); + this.AddBlockStatement(Keywords.qsWithin.id, stm.OuterTransformation.Body, true); + this._Scope.CurrentComments = stm.InnerTransformation.Comments; + this.AddBlockStatement(Keywords.qsApply.id, stm.InnerTransformation.Body, false); return QsStatementKind.NewQsConjugation(stm); } diff --git a/src/QuantumSdk/Sdk/Sdk.targets b/src/QuantumSdk/Sdk/Sdk.targets index 10bf501bf5..1b336bc7f0 100644 --- a/src/QuantumSdk/Sdk/Sdk.targets +++ b/src/QuantumSdk/Sdk/Sdk.targets @@ -73,8 +73,9 @@ <_QscCommandInputFlag Condition="@(QsharpCompile->Count()) > 0">--input "@(QsharpCompile,'" "')" <_QscCommandReferencesFlag Condition="@(ResolvedQsharpReferences->Count()) > 0">--references "@(ResolvedQsharpReferences,'" "')" <_QscCommandLoadFlag Condition="@(_PrioritizedResolvedQscReferences->Count()) > 0">--load "@(_PrioritizedResolvedQscReferences,'" "')" + <_QscCommandTrimFlag Condition="'$(ResolvedQsharpExecutionTarget)' == 'QuantumProcessorBackend'">--trim 2 <_QscPackageLoadFallbackFoldersFlag Condition="@(ResolvedPackageLoadFallbackFolders->Count()) > 0">--package-load-fallback-folders "@(ResolvedPackageLoadFallbackFolders,'" "')" - <_QscCommandArgs>--proj "$(PathCompatibleAssemblyName)" $(_QscCommandDocsFlag) $(_QscCommandInputFlag) $(_QscCommandOutputFlag) $(_QscCommandReferencesFlag) $(_QscCommandLoadFlag) $(_QscPackageLoadFallbackFoldersFlag) $(AdditionalQscArguments) + <_QscCommandArgs>--proj "$(PathCompatibleAssemblyName)" $(_QscCommandDocsFlag) $(_QscCommandInputFlag) $(_QscCommandOutputFlag) $(_QscCommandReferencesFlag) $(_QscCommandLoadFlag) $(_QscCommandTrimFlag) $(_QscPackageLoadFallbackFoldersFlag) $(AdditionalQscArguments) <_QscCommandArgsFile>$(QscBuildConfigOutputPath)qsc.rsp $(QscExe) build --format MsBuild $(_VerbosityFlag) --response-files $(_QscCommandArgsFile)