diff --git a/src/ProjectTemplates/Quantum.App1/Driver.cs b/src/ProjectTemplates/Quantum.App1/Driver.cs index 5ab9441939..20a7ecae50 100644 --- a/src/ProjectTemplates/Quantum.App1/Driver.cs +++ b/src/ProjectTemplates/Quantum.App1/Driver.cs @@ -1,7 +1,4 @@ -using System; -using System.Threading.Tasks; - -using Microsoft.Quantum.Simulation.Core; +using System.Threading.Tasks; using Microsoft.Quantum.Simulation.Simulators; namespace Quantum.App1 @@ -10,10 +7,8 @@ class Driver { static async Task Main(string[] args) { - using (var qsim = new QuantumSimulator()) - { - await HelloQ.Run(qsim); - } + using var qsim = new QuantumSimulator(); + await HelloQ.Run(qsim); } } } \ No newline at end of file diff --git a/src/QsCompiler/CommandLineTool/Commands/Build.cs b/src/QsCompiler/CommandLineTool/Commands/Build.cs index ba1a7965fd..ae100d9543 100644 --- a/src/QsCompiler/CommandLineTool/Commands/Build.cs +++ b/src/QsCompiler/CommandLineTool/Commands/Build.cs @@ -16,7 +16,7 @@ namespace Microsoft.Quantum.QsCompiler.CommandLineCompiler public static class BuildCompilation { [Verb("build", HelpText = "Builds a compilation unit to run on the Q# quantum simulation framework.")] - public class BuildOptions : Options + public class BuildOptions : CompilationOptions { [Usage(ApplicationAlias = "qsCompiler")] public static IEnumerable UsageExamples @@ -35,7 +35,7 @@ public static IEnumerable UsageExamples } [Option("response-files", Required = true, SetName = RESPONSE_FILES, - HelpText = "Response file(s) providing the command arguments. Required only if no other arguments are specified. This option replaces all other arguments.")] + HelpText = "Response file(s) providing command arguments. Required only if no other arguments are specified. Non-default values for options specified via command line take precedence.")] public IEnumerable ResponseFiles { get; set; } [Option('o', "output", Required = false, SetName = CODE_MODE, @@ -50,14 +50,6 @@ public static IEnumerable UsageExamples HelpText = "Name of the project (needs to be usable as file name).")] public string ProjectName { get; set; } - [Option("load", Required = false, SetName = CODE_MODE, - HelpText = "[Experimental feature] Path to the .NET Core dll(s) defining additional transformations to include in the compilation process.")] - public IEnumerable Plugins { get; set; } - - [Option("trim", Required = false, Default = 1, - HelpText = "[Experimental feature] Integer indicating how much to simplify the syntax tree by eliminating selective abstractions.")] - public int TrimLevel { get; set; } - [Option("emit-dll", Required = false, Default = false, SetName = CODE_MODE, HelpText = "Specifies whether the compiler should emit a .NET Core dll containing the compiled Q# code.")] public bool EmitDll { get; set; } @@ -65,8 +57,30 @@ public static IEnumerable UsageExamples [Option('p', "perf", Required = false, SetName = CODE_MODE, HelpText = "Destination folder where the output of the performance assessment will be generated.")] public string PerfFolder { get; set; } + + + /// + /// Reads the content of all specified response files and processes it using FromResponseFiles. + /// Updates the settings accordingly, prioritizing already specified non-default values over the values from response-files. + /// Returns true and a new BuildOptions object as out parameter with all the settings from response files incorporated. + /// Returns false if the content of the specified response-files could not be processed. + /// + internal static bool IncorporateResponseFiles(BuildOptions options, out BuildOptions incorporated) + { + incorporated = null; + while (options.ResponseFiles != null && options.ResponseFiles.Any()) + { + var fromResponseFiles = FromResponseFiles(options.ResponseFiles); + if (fromResponseFiles == null) return false; + fromResponseFiles.UpdateSetIndependentSettings(options); + options = fromResponseFiles; + } + incorporated = options; + return true; + } } + /// /// Given a string representing the command line arguments, splits them into a suitable string array. /// @@ -120,15 +134,13 @@ public static int Run(BuildOptions options, ConsoleLogger logger) { if (options == null) throw new ArgumentNullException(nameof(options)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - - if (options?.ResponseFiles != null && options.ResponseFiles.Any()) - { options = FromResponseFiles(options.ResponseFiles); } - if (options == null) return ReturnCode.INVALID_ARGUMENTS; + if (!BuildOptions.IncorporateResponseFiles(options, out options)) return ReturnCode.INVALID_ARGUMENTS; var usesPlugins = options.Plugins != null && options.Plugins.Any(); var loadOptions = new CompilationLoader.Configuration { ProjectName = options.ProjectName, + TargetPackageAssembly = options.GetTargetPackageAssemblyPath(logger), GenerateFunctorSupport = true, SkipSyntaxTreeTrimming = options.TrimLevel == 0, ConvertClassicalControl = options.TrimLevel >= 2, @@ -137,7 +149,8 @@ public static int Run(BuildOptions options, ConsoleLogger logger) 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 RewriteSteps = options.Plugins?.Select(step => (step, (string)null)) ?? ImmutableArray<(string, string)>.Empty, - EnableAdditionalChecks = false // todo: enable debug mode? + EnableAdditionalChecks = false, // todo: enable debug mode? + ExposeReferencesViaTestNames = options.ExposeReferencesViaTestNames }; if (options.PerfFolder != null) @@ -154,7 +167,7 @@ public static int Run(BuildOptions options, ConsoleLogger logger) } catch (Exception ex) { - logger.Log(ErrorCode.PublishingPerfResultsFailed, new string[]{options.PerfFolder}); + logger.Log(ErrorCode.PublishingPerfResultsFailed, new string[]{ options.PerfFolder }); logger.Log(ex); } } diff --git a/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs b/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs index 8692affa79..361846dc22 100644 --- a/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs +++ b/src/QsCompiler/CommandLineTool/Commands/Diagnose.cs @@ -23,7 +23,7 @@ namespace Microsoft.Quantum.QsCompiler.CommandLineCompiler public static class DiagnoseCompilation { [Verb("diagnose", HelpText = "Generates intermediate representations of the code to help diagnose issues.")] - public class DiagnoseOptions : Options + public class DiagnoseOptions : CompilationOptions { [Usage(ApplicationAlias = "qsCompiler")] public static IEnumerable UsageExamples @@ -56,14 +56,6 @@ public static IEnumerable UsageExamples [Option("code", Required = false, Default = false, HelpText = "Specifies whether to print the Q# code generated based on the built syntax tree.")] public bool PrintCompiledCode { get; set; } - - [Option("trim", Required = false, Default = 1, - HelpText = "[Experimental feature] Integer indicating how much to simplify the syntax tree by eliminating selective abstractions.")] - public int TrimLevel { get; set; } - - [Option("load", Required = false, SetName = CODE_MODE, - HelpText = "[Experimental feature] Path to the .NET Core dll(s) defining additional transformations to include in the compilation process.")] - public IEnumerable Plugins { get; set; } } /// @@ -136,7 +128,7 @@ private static void PrintContentTokenization(Compilation compilation, ILogger lo private static void PrintSyntaxTree(IEnumerable evaluatedTree, Compilation compilation, ILogger logger) { if (compilation == null) throw new ArgumentNullException(nameof(compilation)); - evaluatedTree = evaluatedTree ?? compilation.SyntaxTree.Values; + evaluatedTree ??= compilation.SyntaxTree.Values; foreach (var file in compilation.SourceFiles) { @@ -166,7 +158,7 @@ void PrintTree(string serialization) => logger.Log( private static void PrintGeneratedQs(IEnumerable evaluatedTree, Compilation compilation, ILogger logger) { if (compilation == null) throw new ArgumentNullException(nameof(compilation)); - evaluatedTree = evaluatedTree ?? compilation.SyntaxTree.Values; + evaluatedTree ??= compilation.SyntaxTree.Values; foreach (var file in compilation.SourceFiles) { @@ -219,12 +211,14 @@ public static int Run(DiagnoseOptions options, ConsoleLogger logger) var loadOptions = new CompilationLoader.Configuration { + TargetPackageAssembly = options.GetTargetPackageAssemblyPath(logger), GenerateFunctorSupport = true, SkipSyntaxTreeTrimming = options.TrimLevel == 0, ConvertClassicalControl = options.TrimLevel >= 2, AttemptFullPreEvaluation = options.TrimLevel > 2, RewriteSteps = options.Plugins?.Select(step => (step, (string)null)) ?? ImmutableArray<(string, string)>.Empty, - EnableAdditionalChecks = true + EnableAdditionalChecks = true, + ExposeReferencesViaTestNames = options.ExposeReferencesViaTestNames }; var loaded = new CompilationLoader(options.LoadSourcesOrSnippet(logger), options.References, loadOptions, logger); if (loaded.VerifiedCompilation == null) return ReturnCode.Status(loaded); diff --git a/src/QsCompiler/CommandLineTool/Options.cs b/src/QsCompiler/CommandLineTool/Options.cs index ddc0afe413..0c3143e56c 100644 --- a/src/QsCompiler/CommandLineTool/Options.cs +++ b/src/QsCompiler/CommandLineTool/Options.cs @@ -17,6 +17,65 @@ namespace Microsoft.Quantum.QsCompiler.CommandLineCompiler { + /// + /// Default values for command line options if nothing is specified. + /// + internal static class DefaultOptions + { + public const string Verbosity = "normal"; + public const Options.LogFormat OutputFormat = Options.LogFormat.Default; + public const int TrimLevel = 1; + } + + + public class CompilationOptions : Options + { + [Option("trim", Required = false, Default = DefaultOptions.TrimLevel, SetName = CODE_MODE, + HelpText = "[Experimental feature] Integer indicating how much to simplify the syntax tree by eliminating selective abstractions.")] + public int TrimLevel { get; set; } + + [Option("load", Required = false, SetName = CODE_MODE, + HelpText = "[Experimental feature] Path to the .NET Core dll(s) defining additional transformations to include in the compilation process.")] + public IEnumerable Plugins { get; set; } + + [Option("target-package", Required = false, SetName = CODE_MODE, + HelpText = "Path to the NuGet package containing target specific information and implementations.")] + public string TargetPackage { get; set; } + + [Option("load-test-names", Required = false, Default = false, SetName = CODE_MODE, + HelpText = "Specifies whether public types and callables declared in referenced assemblies are exposed via their test name defined by the corresponding attribute.")] + public bool ExposeReferencesViaTestNames { get; set; } + + + /// + /// Returns null if TargetPackage is not null or empty, and + /// returns the path to the assembly containing target specific implementations otherwise. + /// If a logger is specified, logs suitable diagnostics if a TargetPackages is not null or empty, + /// but no path to the target package assembly could be determined. + /// This may be the case if no directory at the TargetPackage location exists, or if its files can't be accessed, + /// or more than one dll matches the pattern by which the target package assembly is identified. + /// + public string GetTargetPackageAssemblyPath(ILogger logger = null) + { + if (String.IsNullOrEmpty(this.TargetPackage)) return null; + try + { + // Disclaimer: we may revise that in the future. + var targetPackageAssembly = Directory.GetFiles(this.TargetPackage, "*Intrinsics.dll", SearchOption.AllDirectories).SingleOrDefault(); + if (targetPackageAssembly != null) return targetPackageAssembly; + } + catch (Exception ex) + { + if (Directory.Exists(this.TargetPackage)) logger?.Log(ex); + else logger?.Log(ErrorCode.CouldNotFineTargetPackage, new[] { this.TargetPackage }); + } + + logger?.Log(ErrorCode.CouldNotFindTargetPackageAssembly, new[] { this.TargetPackage }); + return null; + } + } + + public class Options { public enum LogFormat @@ -30,10 +89,14 @@ public enum LogFormat protected const string SNIPPET_MODE = "snippetMode"; protected const string RESPONSE_FILES = "responseFiles"; - [Option('v', "verbosity", Required = false, Default = "normal", + [Option('v', "verbosity", Required = false, Default = DefaultOptions.Verbosity, HelpText = "Specifies the verbosity of the logged output. Valid values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic].")] public string Verbosity { get; set; } + [Option("format", Required = false, Default = DefaultOptions.OutputFormat, + HelpText = "Specifies the output format of the command line compiler.")] + public LogFormat OutputFormat { get; set; } + [Option('i', "input", Required = true, SetName = CODE_MODE, HelpText = "Q# code or name of the Q# file to compile.")] public IEnumerable Input { get; set; } @@ -54,15 +117,25 @@ public enum LogFormat HelpText = "Warnings with the given code(s) will be ignored.")] public IEnumerable NoWarn { get; set; } - [Option("format", Required = false, Default = LogFormat.Default, - HelpText = "Specifies the output format of the command line compiler.")] - public LogFormat OutputFormat { get; set; } - [Option("package-load-fallback-folders", Required = false, SetName = CODE_MODE, - HelpText = "Specifies the directories the compiler will search when a rewrite step dependency could not be found.")] + HelpText = "Specifies the directories the compiler will search when a compiler dependency could not be found.")] public IEnumerable PackageLoadFallbackFolders { get; set; } + /// + /// Updates the settings that can be used independent on the other arguments according to the setting in the given options. + /// Already specified non-default values are prioritized over the values in the given options, + /// unless overwriteNonDefaultValues is set to true. Sequences are merged. + /// + internal void UpdateSetIndependentSettings(Options updates, bool overwriteNonDefaultValues = false) + { + this.Verbosity = overwriteNonDefaultValues || this.Verbosity == DefaultOptions.Verbosity ? updates.Verbosity : this.Verbosity; + this.OutputFormat = overwriteNonDefaultValues || this.OutputFormat == DefaultOptions.OutputFormat ? updates.OutputFormat : this.OutputFormat; + this.NoWarn = (this.NoWarn ?? new int[0]).Concat(updates.NoWarn ?? new int[0]); + this.References = (this.References ?? new string[0]).Concat(updates.References ?? new string[0]); + } + + // routines related to logging /// @@ -87,15 +160,13 @@ string value(PropertyInfo p) /// /// Given a LogFormat, returns a suitable routing for formatting diagnostics. /// - internal static Func LoggingFormat(LogFormat format) - { - switch (format) + internal static Func LoggingFormat(LogFormat format) => + format switch { - case LogFormat.MsBuild: return Formatting.MsBuildFormat; - case LogFormat.Default: return Formatting.HumanReadableFormat; - default: throw new NotImplementedException("unknown output format for logger"); - } - } + LogFormat.MsBuild => Formatting.MsBuildFormat, + LogFormat.Default => Formatting.HumanReadableFormat, + _ => throw new NotImplementedException("unknown output format for logger"), + }; /// /// Creates a suitable logger for the given command line options, diff --git a/src/QsCompiler/CompilationManager/AssemblyLoader.cs b/src/QsCompiler/CompilationManager/AssemblyLoader.cs index d3b41243a2..7c4937112d 100644 --- a/src/QsCompiler/CompilationManager/AssemblyLoader.cs +++ b/src/QsCompiler/CompilationManager/AssemblyLoader.cs @@ -30,44 +30,82 @@ public static class AssemblyLoader /// and returns the loaded content as out parameter. /// Returns false if some of the content could not be loaded successfully, /// possibly because the referenced assembly has been compiled with an older compiler version. + /// If onDeserializationException is specified, invokes the given action on any exception thrown during deserialization. /// Throws an ArgumentNullException if the given uri is null. /// Throws a FileNotFoundException if no file with the given name exists. /// Throws the corresponding exceptions if the information cannot be extracted. /// - public static bool LoadReferencedAssembly(Uri asm, out References.Headers headers, bool ignoreDllResources = false) + public static bool LoadReferencedAssembly(Uri asm, out References.Headers headers, bool ignoreDllResources = false, Action onDeserializationException = null) { if (asm == null) throw new ArgumentNullException(nameof(asm)); if (!CompilationUnitManager.TryGetFileId(asm, out var id) || !File.Exists(asm.LocalPath)) - { throw new FileNotFoundException($"the uri '{asm}' given to the assembly loader is invalid or the file does not exist"); } + { throw new FileNotFoundException($"The uri '{asm}' given to the assembly loader is invalid or the file does not exist."); } using var stream = File.OpenRead(asm.LocalPath); using var assemblyFile = new PEReader(stream); - if (ignoreDllResources || !FromResource(assemblyFile, out var syntaxTree)) + if (ignoreDllResources || !FromResource(assemblyFile, out var compilation, onDeserializationException)) { var attributes = LoadHeaderAttributes(assemblyFile); headers = new References.Headers(id, attributes); return ignoreDllResources || !attributes.Any(); // just means we have no references } - headers = new References.Headers(id, syntaxTree?.Namespaces ?? ImmutableArray.Empty); + headers = new References.Headers(id, compilation?.Namespaces ?? ImmutableArray.Empty); return true; } + /// + /// Loads the Q# data structures in a referenced assembly given the Uri to that assembly, + /// and returns the loaded content as out parameter. + /// Returns false if some of the content could not be loaded successfully, + /// possibly because the referenced assembly has been compiled with an older compiler version. + /// Catches any exception throw upon loading the compilation, and invokes onException with it if such an action has been specified. + /// Sets the out parameter to null if an exception occurred during loading. + /// Throws an ArgumentNullException if the given uri is null. + /// Throws a FileNotFoundException if no file with the given name exists. + /// + public static bool LoadReferencedAssembly(string asmPath, out QsCompilation compilation, Action onException = null) + { + if (asmPath == null) throw new ArgumentNullException(nameof(asmPath)); + if (!File.Exists(asmPath)) throw new FileNotFoundException($"The file '{asmPath}' does not exist."); + + using var stream = File.OpenRead(asmPath); + using var assemblyFile = new PEReader(stream); + try + { + return FromResource(assemblyFile, out compilation, onException); + } + catch (Exception ex) + { + onException?.Invoke(ex); + compilation = null; + return false; + } + } + // tools for loading the compiled syntax tree from the dll resource (later setup for shipping Q# libraries) /// /// Given a stream containing the binary representation of compiled Q# code, returns the corresponding Q# compilation. /// Returns true if the compilation could be deserialized without throwing an exception, and false otherwise. + /// If onDeserializationException is specified, invokes the given action on any exception thrown during deserialization. /// Throws an ArgumentNullException if the given stream is null, but ignores exceptions thrown during deserialization. /// - public static bool LoadSyntaxTree(Stream stream, out QsCompilation compilation) + public static bool LoadSyntaxTree(Stream stream, out QsCompilation compilation, Action onDeserializationException = null) { if (stream == null) throw new ArgumentNullException(nameof(stream)); using var reader = new BsonDataReader(stream); (compilation, reader.ReadRootValueAsArray) = (null, false); - try { compilation = Json.Serializer.Deserialize(reader); } - catch { return false; } - return compilation != null && !compilation.Namespaces.IsDefault && !compilation.EntryPoints.IsDefault; + try + { + compilation = Json.Serializer.Deserialize(reader); + return compilation != null && !compilation.Namespaces.IsDefault && !compilation.EntryPoints.IsDefault; + } + catch (Exception ex) + { + onDeserializationException?.Invoke(ex); + return false; + } } /// @@ -85,10 +123,11 @@ private static ImmutableDictionary Resources(this Meta /// /// Given a reader for the byte stream of a dotnet dll, loads any Q# compilation included as a resource. /// Returns true as well as the loaded compilation if the given dll includes a suitable resource, and returns false otherwise. + /// If onDeserializationException is specified, invokes the given action on any exception thrown during deserialization. /// Throws an ArgumentNullException if any of the given readers is null. /// May throw an exception if the given binary file has been compiled with a different compiler version. /// - private static bool FromResource(PEReader assemblyFile, out QsCompilation compilation) + private static bool FromResource(PEReader assemblyFile, out QsCompilation compilation, Action onDeserializationException = null) { if (assemblyFile == null) throw new ArgumentNullException(nameof(assemblyFile)); var metadataReader = assemblyFile.GetMetadataReader(); @@ -112,7 +151,7 @@ private static bool FromResource(PEReader assemblyFile, out QsCompilation compil // the first four bytes of the resource denote how long the resource is, and are followed by the actual resource data var resourceLength = BitConverter.ToInt32(image.GetContent(absResourceOffset, sizeof(Int32)).ToArray(), 0); var resourceData = image.GetContent(absResourceOffset + sizeof(Int32), resourceLength).ToArray(); - return LoadSyntaxTree(new MemoryStream(resourceData), out compilation); + return LoadSyntaxTree(new MemoryStream(resourceData), out compilation, onDeserializationException); } diff --git a/src/QsCompiler/CompilationManager/CompilationUnit.cs b/src/QsCompiler/CompilationManager/CompilationUnit.cs index 139c6302a1..77140e999b 100644 --- a/src/QsCompiler/CompilationManager/CompilationUnit.cs +++ b/src/QsCompiler/CompilationManager/CompilationUnit.cs @@ -5,13 +5,17 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.Diagnostics; using Microsoft.Quantum.QsCompiler.SymbolManagement; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; +using Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -28,7 +32,7 @@ public class Headers public readonly ImmutableArray<(SpecializationDeclarationHeader, SpecializationImplementation)> Specializations; public readonly ImmutableArray Types; - private Headers(string source, + internal Headers(string source, IEnumerable callables = null, IEnumerable<(SpecializationDeclarationHeader, SpecializationImplementation)> specs = null, IEnumerable types = null) @@ -74,6 +78,30 @@ private static IEnumerable TypeHeaders(IEnumerable<(strin attributes.Select(IsDeclaration("TypeDeclarationAttribute")).Where(v => v != null) .Select(TypeDeclarationHeader.FromJson).Select(built => built.Item2); + /// + /// Renames all declarations in the headers for which an alternative name is specified + /// that may be used when loading a type or callable for testing purposes. + /// Leaves declarations for which no such name is defined unchanged. + /// Does not check whether there are any conflicts when using alternative names. + /// + private static Headers LoadTestNames(string source, Headers headers) + { + var renaming = headers.Callables.Where(callable => !callable.Kind.IsTypeConstructor) + .Select(callable => (callable.QualifiedName, callable.Attributes)) + .Concat(headers.Types.Select(type => (type.QualifiedName, type.Attributes))) + .ToImmutableDictionary( + decl => decl.QualifiedName, + decl => SymbolResolution.TryGetTestName(decl.Attributes).ValueOr(decl.QualifiedName)); + + var rename = new RenameReferences(renaming); + var callables = headers.Callables.Select(rename.OnCallableDeclarationHeader); + var specializations = headers.Specializations.Select( + specialization => (rename.OnSpecializationDeclarationHeader(specialization.Item1), + rename.Namespaces.OnSpecializationImplementation(specialization.Item2))); + var types = headers.Types.Select(rename.OnTypeDeclarationHeader); + return new Headers(source, callables, specializations, types); + } + /// /// Dictionary that maps the id of a referenced assembly (given by its location on disk) to the headers defined in that assembly. @@ -81,7 +109,7 @@ private static IEnumerable TypeHeaders(IEnumerable<(strin public readonly ImmutableDictionary, Headers> Declarations; public static References Empty = - new References(ImmutableDictionary, Headers>.Empty, null); + new References(ImmutableDictionary, Headers>.Empty); /// /// Combines the current references with the given references, and verifies that there are no conflicts. @@ -94,7 +122,7 @@ internal References CombineWith(References other, Action on { if (other == null) throw new ArgumentNullException(nameof(other)); if (this.Declarations.Keys.Intersect(other.Declarations.Keys).Any()) throw new ArgumentException("common references exist"); - return new References (this.Declarations.AddRange(other.Declarations), onError); + return new References (this.Declarations.AddRange(other.Declarations), onError: onError); } /// @@ -105,25 +133,41 @@ internal References CombineWith(References other, Action on /// Throws an ArgumentNullException if the given diagnostics are null. /// internal References Remove(NonNullable source, Action onError = null) => - new References(this.Declarations.Remove(source), onError); + new References(this.Declarations.Remove(source), onError: onError); /// /// Given a dictionary that maps the ids of dll files to the corresponding headers, /// initializes a new set of references based on the given headers and verifies that there are no conflicts. /// Calls the given Action onError with suitable diagnostics if two or more references conflict, /// i.e. if two or more references contain a declaration with the same fully qualified name. + /// If loadTestNames is set to true, then public types and callables declared in referenced assemblies + /// are exposed via their test name defined by the corresponding attribute. /// Throws an ArgumentNullException if the given dictionary of references is null. /// - public References(ImmutableDictionary, Headers> refs, Action onError = null) + public References(ImmutableDictionary, Headers> refs, bool loadTestNames = false, Action onError = null) { this.Declarations = refs ?? throw new ArgumentNullException(nameof(refs)); + if (loadTestNames) + { + this.Declarations = this.Declarations + .ToImmutableDictionary( + reference => reference.Key, + reference => LoadTestNames(reference.Key.Value, reference.Value) + ); + } + if (onError == null) return; - - var conflictingCallables = refs.Values.SelectMany(r => r.Callables) - .GroupBy(c => c.QualifiedName).Where(g => g.Count() != 1) + var conflictingCallables = refs.Values + .SelectMany(r => r.Callables) + .Where(c => Namespace.IsDeclarationAccessible(false, c.Modifiers.Access)) + .GroupBy(c => c.QualifiedName) + .Where(g => g.Count() != 1) .Select(g => (g.Key, String.Join(", ", g.Select(c => c.SourceFile.Value)))); - var conflictingTypes = refs.Values.SelectMany(r => r.Types) - .GroupBy(t => t.QualifiedName).Where(g => g.Count() != 1) + var conflictingTypes = refs.Values + .SelectMany(r => r.Types) + .Where(t => Namespace.IsDeclarationAccessible(false, t.Modifiers.Access)) + .GroupBy(t => t.QualifiedName) + .Where(g => g.Count() != 1) .Select(g => (g.Key, String.Join(", ", g.Select(c => c.SourceFile.Value)))); foreach (var (name, conflicts) in conflictingCallables.Concat(conflictingTypes).Distinct()) @@ -143,6 +187,8 @@ public References(ImmutableDictionary, Headers> refs, Action /// public class CompilationUnit : IReaderWriterLock, IDisposable { + internal static readonly NameDecorator ReferenceDecorator = new NameDecorator("QsReference"); + internal References Externals { get; private set; } internal NamespaceManager GlobalSymbols { get; private set; } private readonly Dictionary CompiledCallables; @@ -344,8 +390,17 @@ internal void UpdateTypes(IEnumerable updates) var compilationExists = this.CompiledTypes.TryGetValue(fullName, out QsCustomType compiled); if (!compilationExists) continue; // may happen if a file has been modified during global type checking - var type = new QsCustomType(compiled.FullName, compiled.Attributes, compiled.SourceFile, header.Location, - compiled.Type, compiled.TypeItems, compiled.Documentation, compiled.Comments); + var type = new QsCustomType( + compiled.FullName, + compiled.Attributes, + compiled.Modifiers, + compiled.SourceFile, + header.Location, + compiled.Type, + compiled.TypeItems, + compiled.Documentation, + compiled.Comments + ); this.CompiledTypes[fullName] = type; } } @@ -396,8 +451,19 @@ internal void UpdateCallables(IEnumerable updates) var defaultSpec = new QsSpecialization(QsSpecializationKind.QsBody, header.QualifiedName, header.Attributes, header.SourceFile, header.Location, QsNullable>.Null, header.Signature, SpecializationImplementation.Intrinsic, ImmutableArray.Empty, QsComments.Empty); - this.CompiledCallables[fullName] = new QsCallable(header.Kind, header.QualifiedName, header.Attributes, header.SourceFile, header.Location, - header.Signature, header.ArgumentTuple, ImmutableArray.Create(defaultSpec), header.Documentation, QsComments.Empty); + this.CompiledCallables[fullName] = new QsCallable( + header.Kind, + header.QualifiedName, + header.Attributes, + header.Modifiers, + header.SourceFile, + header.Location, + header.Signature, + header.ArgumentTuple, + ImmutableArray.Create(defaultSpec), + header.Documentation, + QsComments.Empty + ); continue; } @@ -422,8 +488,19 @@ internal void UpdateCallables(IEnumerable updates) }) .Where(spec => spec != null).ToImmutableArray(); - var callable = new QsCallable(compiled.Kind, compiled.FullName, compiled.Attributes, compiled.SourceFile, header.Location, - compiled.Signature, compiled.ArgumentTuple, specializations, compiled.Documentation, compiled.Comments); + var callable = new QsCallable( + compiled.Kind, + compiled.FullName, + compiled.Attributes, + compiled.Modifiers, + compiled.SourceFile, + header.Location, + compiled.Signature, + compiled.ArgumentTuple, + specializations, + compiled.Documentation, + compiled.Comments + ); this.CompiledCallables[fullName] = callable; } } @@ -441,8 +518,13 @@ private QsCallable GetImportedCallable(CallableDeclarationHeader header) // TODO: this needs to be adapted if we want to support external specializations if (header == null) throw new ArgumentNullException(nameof(header)); var importedSpecs = this.GlobalSymbols.ImportedSpecializations(header.QualifiedName); - var definedSpecs = this.GlobalSymbols.DefinedSpecializations(header.QualifiedName); - QsCompilerError.Verify(definedSpecs.Length == 0, "external specializations are currently not supported"); + if (Namespace.IsDeclarationAccessible(false, header.Modifiers.Access)) + { + var definedSpecs = this.GlobalSymbols.DefinedSpecializations(header.QualifiedName); + QsCompilerError.Verify(definedSpecs.Length == 0, + "external specializations are currently not supported"); + } + var specializations = importedSpecs.Select(imported => { var (specHeader, implementation) = imported; @@ -454,8 +536,19 @@ private QsCallable GetImportedCallable(CallableDeclarationHeader header) implementation, specHeader.Documentation, QsComments.Empty); }) .ToImmutableArray(); - return new QsCallable(header.Kind, header.QualifiedName, header.Attributes, header.SourceFile, header.Location, - header.Signature, header.ArgumentTuple, specializations, header.Documentation, QsComments.Empty); + return new QsCallable( + header.Kind, + header.QualifiedName, + header.Attributes, + header.Modifiers, + header.SourceFile, + header.Location, + header.Signature, + header.ArgumentTuple, + specializations, + header.Documentation, + QsComments.Empty + ); } /// @@ -465,8 +558,17 @@ private QsCallable GetImportedCallable(CallableDeclarationHeader header) private QsCustomType GetImportedType(TypeDeclarationHeader header) { if (header == null) throw new ArgumentNullException(nameof(header)); - return new QsCustomType(header.QualifiedName, header.Attributes, header.SourceFile, header.Location, - header.Type, header.TypeItems, header.Documentation, QsComments.Empty); + return new QsCustomType( + header.QualifiedName, + header.Attributes, + header.Modifiers, + header.SourceFile, + header.Location, + header.Type, + header.TypeItems, + header.Documentation, + QsComments.Empty + ); } /// @@ -532,10 +634,12 @@ public QsCompilation Build() } // build the syntax tree - var callables = this.CompiledCallables.Values.Concat(this.GlobalSymbols.ImportedCallables().Select(this.GetImportedCallable)); var types = this.CompiledTypes.Values.Concat(this.GlobalSymbols.ImportedTypes().Select(this.GetImportedType)); - var tree = CompilationUnit.NewSyntaxTree(callables, types, this.GlobalSymbols.Documentation()); + // Rename imported internal declarations by tagging them with their source file to avoid potentially + // having duplicate names in the syntax tree. + var (taggedCallables, taggedTypes) = TagImportedInternalNames(callables, types); + var tree = NewSyntaxTree(taggedCallables, taggedTypes, this.GlobalSymbols.Documentation()); var entryPoints = tree.Callables().Where(c => c.Attributes.Any(BuiltIn.MarksEntryPoint)).Select(c => c.FullName).ToImmutableArray(); return new QsCompilation(tree, entryPoints); } @@ -635,5 +739,51 @@ internal LocalDeclarations TryGetLocalDeclarations(FileContentManager file, Posi var declarations = implementation?.LocalDeclarationsAt(pos.Subtract(specPos), includeDeclaredAtPosition); return this.PositionedDeclarations(parentCallable, callablePos, specPos, declarations); } + + /// + /// Tags the names of imported internal callables and types with a unique identifier based on the path to their + /// assembly, so that they do not conflict with callables and types defined locally. Renames all references to + /// the tagged declarations found in the given callables and types. + /// + /// The callables to rename and update references in. + /// The types to rename and update references in. + /// The tagged callables and types with references renamed everywhere. + private (IEnumerable, IEnumerable) + TagImportedInternalNames(IEnumerable callables, IEnumerable types) + { + // Assign a unique ID to each reference. + var ids = + callables.Select(callable => callable.SourceFile.Value) + .Concat(types.Select(type => type.SourceFile.Value)) + .Distinct() + .Where(source => Externals.Declarations.ContainsKey(NonNullable.New(source))) + .Select((source, index) => (source, index)) + .ToImmutableDictionary(item => item.source, item => item.index); + + ImmutableDictionary GetMappingForSourceGroup( + IGrouping group) => + group + .Where(item => + !Namespace.IsDeclarationAccessible(false, item.access) && + Externals.Declarations.ContainsKey(NonNullable.New(item.source))) + .ToImmutableDictionary(item => item.name, + item => ReferenceDecorator.Decorate(item.name, ids[item.source])); + + var transformations = + callables.Select(callable => + (name: callable.FullName, source: callable.SourceFile.Value, access: callable.Modifiers.Access)) + .Concat(types.Select(type => + (name: type.FullName, source: type.SourceFile.Value, access: type.Modifiers.Access))) + .GroupBy(item => item.source) + .ToImmutableDictionary( + group => group.Key, + group => new RenameReferences(GetMappingForSourceGroup(group))); + + var taggedCallables = callables.Select( + callable => transformations[callable.SourceFile.Value].Namespaces.OnCallableDeclaration(callable)); + var taggedTypes = types.Select( + type => transformations[type.SourceFile.Value].Namespaces.OnTypeDeclaration(type)); + return (taggedCallables, taggedTypes); + } } } diff --git a/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs b/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs index aee6fbebf4..5ac7811a7d 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs @@ -10,6 +10,7 @@ using Microsoft.Quantum.QsCompiler.Diagnostics; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; +using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.TextProcessing; using Microsoft.Quantum.QsCompiler.Transformations.QsCodeOutput; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -22,9 +23,9 @@ internal static class SuggestedEdits { /// /// Returns the given edit for the specified file as WorkspaceEdit. - /// Throws an ArgumentNullException if the given file or any of the given edits is null. + /// Throws an ArgumentNullException if the given file or any of the given edits is null. /// - private static WorkspaceEdit GetWorkspaceEdit(this FileContentManager file, params TextEdit[] edits) + private static WorkspaceEdit GetWorkspaceEdit(this FileContentManager file, params TextEdit[] edits) { if (file == null) throw new ArgumentNullException(nameof(file)); if (edits == null || edits.Any(edit => edit == null)) throw new ArgumentNullException(nameof(edits)); @@ -38,24 +39,33 @@ private static WorkspaceEdit GetWorkspaceEdit(this FileContentManager file, para } /// - /// Returns all namespaces in which a callable with the name of the symbol at the given position in the given file belongs to. - /// Returns an empty collection if any of the arguments is null or if no unqualified symbol exists at that location. - /// Returns the name of the identifier as out parameter if an unqualified symbol exists at that location. + /// Returns all namespaces in which a callable with the name of the symbol at the given position in the given + /// file belongs to. + /// + /// Returns an empty collection if any of the arguments is null, if no unqualified symbol exists at that + /// location, or if the position is not part of a namespace. + /// + /// Returns the name of the identifier as an out parameter if an unqualified symbol exists at that location. /// private static IEnumerable> NamespaceSuggestionsForIdAtPosition (this FileContentManager file, Position pos, CompilationUnit compilation, out string idName) { var variables = file?.TryGetQsSymbolInfo(pos, true, out CodeFragment _)?.UsedVariables; idName = variables != null && variables.Any() ? variables.Single().Symbol.AsDeclarationName(null) : null; - return idName != null && compilation != null + var nsName = file.TryGetNamespaceAt(pos); + return idName != null && compilation != null && nsName != null ? compilation.GlobalSymbols.NamespacesContainingCallable(NonNullable.New(idName)) : ImmutableArray>.Empty; } /// - /// Returns all namespaces in which a type with the name of the symbol at the given position in the given file belongs to. - /// Returns an empty collection if any of the arguments is null or if no unqualified symbol exists at that location. - /// Returns the name of the type as out parameter if an unqualified symbol exists at that location. + /// Returns all namespaces in which a type with the name of the symbol at the given position in the given file + /// belongs to. + /// + /// Returns an empty collection if any of the arguments is null, if no unqualified symbol exists at that + /// location, or if the position is not part of a namespace. + /// + /// Returns the name of the type as an out parameter if an unqualified symbol exists at that location. /// private static IEnumerable> NamespaceSuggestionsForTypeAtPosition (this FileContentManager file, Position pos, CompilationUnit compilation, out string typeName) @@ -64,14 +74,15 @@ private static IEnumerable> NamespaceSuggestionsForTypeAtPos typeName = types != null && types.Any() && types.Single().Type is QsTypeKind.UserDefinedType udt ? udt.Item.Symbol.AsDeclarationName(null) : null; - return typeName != null && compilation != null + var nsName = file.TryGetNamespaceAt(pos); + return typeName != null && compilation != null && nsName != null ? compilation.GlobalSymbols.NamespacesContainingType(NonNullable.New(typeName)) : ImmutableArray>.Empty; } /// - /// Returns all code fragments in the specified file that overlap with the given range. - /// Returns an empty sequence if any of the given arguments is null. + /// Returns all code fragments in the specified file that overlap with the given range. + /// Returns an empty sequence if any of the given arguments is null. /// private static IEnumerable FragmentsOverlappingWithRange(this FileContentManager file, LSP.Range range) { @@ -92,9 +103,9 @@ private static IEnumerable FragmentsOverlappingWithRange(this File } /// - /// Return an enumerable of suitable edits to add open directives for all given namespaces for which no open directive already exists. - /// Returns an edit for opening a given namespace even if an alias is already defined for that namespace. - /// Returns an empty enumerable if suitable edits could not be determined. + /// Return an enumerable of suitable edits to add open directives for all given namespaces for which no open directive already exists. + /// Returns an edit for opening a given namespace even if an alias is already defined for that namespace. + /// Returns an empty enumerable if suitable edits could not be determined. /// private static IEnumerable OpenDirectiveSuggestions(this FileContentManager file, int lineNr, params NonNullable[] namespaces) { @@ -131,9 +142,9 @@ private static IEnumerable OpenDirectiveSuggestions(this FileContentMa } /// - /// Returns a sequence of suggestions on how errors for ambiguous types and callable in the given diagnostics can be fixed, - /// given the file for which those diagnostics were generated and the corresponding compilation. - /// Returns an empty enumerable if any of the given arguments is null. + /// Returns a sequence of suggestions on how errors for ambiguous types and callable in the given diagnostics can be fixed, + /// given the file for which those diagnostics were generated and the corresponding compilation. + /// Returns an empty enumerable if any of the given arguments is null. /// internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForAmbiguousIdentifiers (this FileContentManager file, CompilationUnit compilation, IEnumerable diagnostics) @@ -159,10 +170,10 @@ private static IEnumerable OpenDirectiveSuggestions(this FileContentMa } /// - /// Returns a sequence of suggestions on how errors for unknown types and callable in the given diagnostics can be fixed, - /// given the file for which those diagnostics were generated and the corresponding compilation. - /// The given line number is used to determine the containing namespace. - /// Returns an empty enumerable if any of the given arguments is null. + /// Returns a sequence of suggestions on how errors for unknown types and callable in the given diagnostics can be fixed, + /// given the file for which those diagnostics were generated and the corresponding compilation. + /// The given line number is used to determine the containing namespace. + /// Returns an empty enumerable if any of the given arguments is null. /// internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForUnknownIdentifiers (this FileContentManager file, CompilationUnit compilation, int lineNr, IEnumerable diagnostics) @@ -181,9 +192,9 @@ private static IEnumerable OpenDirectiveSuggestions(this FileContentMa } /// - /// Returns a sequence of suggestions on how deprecated syntax can be updated based on the generated diagnostics, - /// and given the file for which those diagnostics were generated. - /// Returns an empty enumerable if any of the given arguments is null. + /// Returns a sequence of suggestions on how deprecated syntax can be updated based on the generated diagnostics, + /// and given the file for which those diagnostics were generated. + /// Returns an empty enumerable if any of the given arguments is null. /// internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForDeprecatedSyntax (this FileContentManager file, IEnumerable diagnostics) @@ -197,7 +208,7 @@ private static IEnumerable OpenDirectiveSuggestions(this FileContentMa (string, WorkspaceEdit) ReplaceWith(string text, LSP.Range range) { - bool NeedsWs(Char ch) => Char.IsLetterOrDigit(ch) || ch == '_'; + static bool NeedsWs(Char ch) => Char.IsLetterOrDigit(ch) || ch == '_'; if (range?.Start != null && range.End != null) { var beforeEdit = file.GetLine(range.Start.Line).Text.Substring(0, range.Start.Character); @@ -218,7 +229,7 @@ private static IEnumerable OpenDirectiveSuggestions(this FileContentMa // update deprecated operation characteristics syntax - string CharacteristicsAnnotation(Characteristics c) + static string CharacteristicsAnnotation(Characteristics c) { var charEx = SyntaxTreeToQsharp.CharacteristicsExpression(SymbolResolution.ResolveCharacteristics(c)); return charEx == null ? "" : $"{Keywords.qsCharacteristics.id} {charEx}"; @@ -226,15 +237,16 @@ string CharacteristicsAnnotation(Characteristics c) var suggestionsForOpCharacteristics = deprecatedOpCharacteristics.SelectMany(d => { - // TODO: TryGetQsSymbolInfo currently only returns information about the inner most leafs rather than all types etc. - // Once it returns indeed all types in the fragment, the following code block should be replaced by the commented out code below. + // TODO: TryGetQsSymbolInfo currently only returns information about the inner most leafs rather than all types etc. + // Once it returns indeed all types in the fragment, the following code block should be replaced by the commented out code below. var fragment = file.TryGetFragmentAt(d.Range.Start, out var _); - IEnumerable GetCharacteristics(QsTuple> argTuple) => + + static IEnumerable GetCharacteristics(QsTuple> argTuple) => SyntaxGenerator.ExtractItems(argTuple).SelectMany(item => item.Item2.ExtractCharacteristics()).Distinct(); var characteristicsInFragment = - fragment?.Kind is QsFragmentKind.FunctionDeclaration function ? GetCharacteristics(function.Item2.Argument) : - fragment?.Kind is QsFragmentKind.OperationDeclaration operation ? GetCharacteristics(operation.Item2.Argument) : - fragment?.Kind is QsFragmentKind.TypeDefinition type ? GetCharacteristics(type.Item2) : + fragment?.Kind is QsFragmentKind.FunctionDeclaration function ? GetCharacteristics(function.Item3.Argument) : + fragment?.Kind is QsFragmentKind.OperationDeclaration operation ? GetCharacteristics(operation.Item3.Argument) : + fragment?.Kind is QsFragmentKind.TypeDefinition type ? GetCharacteristics(type.Item3) : Enumerable.Empty(); //var symbolInfo = file.TryGetQsSymbolInfo(d.Range.Start, false, out var fragment); @@ -254,9 +266,9 @@ IEnumerable GetCharacteristics(QsTuple> } /// - /// Returns a sequence of suggestions for update-and-reassign statements based on the generated diagnostics, - /// and given the file for which those diagnostics were generated. - /// Returns an empty enumerable if any of the given arguments is null. + /// Returns a sequence of suggestions for update-and-reassign statements based on the generated diagnostics, + /// and given the file for which those diagnostics were generated. + /// Returns an empty enumerable if any of the given arguments is null. /// internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForUpdateAndReassignStatements (this FileContentManager file, IEnumerable diagnostics) @@ -286,29 +298,44 @@ IEnumerable GetCharacteristics(QsTuple> } /// - /// Returns a sequence of suggestions for replacing ranges over array indices with the corresponding library call, - /// provided the corresponding library is referenced. - /// Returns an empty enumerable if this is not the case or any of the given arguments is null. + /// Returns a sequence of suggestions for replacing ranges over array indices with the corresponding library call, + /// provided the corresponding library is referenced. + /// Returns an empty enumerable if this is not the case or any of the given arguments is null. /// internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForIndexRange (this FileContentManager file, CompilationUnit compilation, LSP.Range range) { - if (file == null || compilation == null || range?.Start == null) return Enumerable.Empty<(string, WorkspaceEdit)>(); - var indexRangeNamespaces = compilation.GlobalSymbols.NamespacesContainingCallable(BuiltIn.IndexRange.Name); - if (!indexRangeNamespaces.Contains(BuiltIn.IndexRange.Namespace)) return Enumerable.Empty<(string, WorkspaceEdit)>(); - var suggestedOpenDir = file.OpenDirectiveSuggestions(range.Start.Line, BuiltIn.IndexRange.Namespace); - - /// Returns true the given expression is of the form "0 .. Length(args) - 1", - /// as well as the range of the entire expression and the argument tuple "(args)" as out parameters. - bool IsIndexRange(QsExpression iterExpr, Position offset, out LSP.Range exprRange, out LSP.Range argRange) + if (file == null || compilation == null || range?.Start == null) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } + + // Ensure that the IndexRange library function exists in this compilation unit. + var nsName = file.TryGetNamespaceAt(range.Start); + if (nsName == null) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } + var indexRange = compilation.GlobalSymbols.TryGetCallable( + new QsQualifiedName(BuiltIn.IndexRange.FullName.Namespace, BuiltIn.IndexRange.FullName.Name), + NonNullable.New(nsName), + file.FileName); + if (!indexRange.IsFound) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } + + /// Returns true the given expression is of the form "0 .. Length(args) - 1", + /// as well as the range of the entire expression and the argument tuple "(args)" as out parameters. + static bool IsIndexRange(QsExpression iterExpr, Position offset, out LSP.Range exprRange, out LSP.Range argRange) { if (iterExpr.Expression is QsExpressionKind.RangeLiteral rangeExpression && iterExpr.Range.IsValue && // iterable expression is a valid range literal rangeExpression.Item1.Expression is QsExpressionKind.IntLiteral intLiteralExpression && intLiteralExpression.Item == 0L && // .. starting at 0 .. - rangeExpression.Item2.Expression is QsExpressionKind.SUB SUBExpression && // .. and ending in subracting .. + rangeExpression.Item2.Expression is QsExpressionKind.SUB SUBExpression && // .. and ending in subtracting .. SUBExpression.Item2.Expression is QsExpressionKind.IntLiteral subIntLiteralExpression && subIntLiteralExpression.Item == 1L && // .. 1 from .. SUBExpression.Item1.Expression is QsExpressionKind.CallLikeExpression callLikeExression && // .. a call .. callLikeExression.Item1.Expression is QsExpressionKind.Identifier identifier && // .. to and identifier .. - identifier.Item1.Symbol is QsSymbolKind.Symbol symName && symName.Item.Value == BuiltIn.Length.Name.Value && // .. "Length" called with .. + identifier.Item1.Symbol is QsSymbolKind.Symbol symName && symName.Item.Value == BuiltIn.Length.FullName.Name.Value && // .. "Length" called with .. callLikeExression.Item2.Expression is QsExpressionKind.ValueTuple valueTuple && callLikeExression.Item2.Range.IsValue) // .. a valid argument tuple { exprRange = DiagnosticTools.GetAbsoluteRange(offset, iterExpr.Range.Item); @@ -321,8 +348,8 @@ callLikeExression.Item1.Expression is QsExpressionKind IndexRangeEdits(CodeFragment fragment) + /// The returned edits do *not* include an edit for adding the corresponding open-directive if necessary. + static IEnumerable IndexRangeEdits(CodeFragment fragment) { if (fragment.Kind is QsFragmentKind.ForLoopIntro forLoopIntro && // todo: in principle we could give these suggestions for any index range IsIndexRange(forLoopIntro.Item2, fragment.GetRange().Start, out var iterExprRange, out var argTupleRange)) @@ -330,7 +357,7 @@ IEnumerable IndexRangeEdits(CodeFragment fragment) yield return new TextEdit() { Range = new LSP.Range() { Start = iterExprRange.Start, End = argTupleRange.Start }, - NewText = BuiltIn.IndexRange.Name.Value + NewText = BuiltIn.IndexRange.FullName.Name.Value }; yield return new TextEdit() { @@ -342,15 +369,16 @@ IEnumerable IndexRangeEdits(CodeFragment fragment) var fragments = file.FragmentsOverlappingWithRange(range); var edits = fragments.SelectMany(IndexRangeEdits); + var suggestedOpenDir = file.OpenDirectiveSuggestions(range.Start.Line, BuiltIn.IndexRange.FullName.Namespace); return edits.Any() ? new[] { ("Use IndexRange to iterate over indices.", file.GetWorkspaceEdit(suggestedOpenDir.Concat(edits).ToArray())) } : Enumerable.Empty<(string, WorkspaceEdit)>(); } /// - /// Returns a sequence of suggestions for removing code that is never executed based on the generated diagnostics, - /// and given the file for which those diagnostics were generated. - /// Returns an empty enumerable if any of the given arguments is null. + /// Returns a sequence of suggestions for removing code that is never executed based on the generated diagnostics, + /// and given the file for which those diagnostics were generated. + /// Returns an empty enumerable if any of the given arguments is null. /// internal static IEnumerable<(string, WorkspaceEdit)> SuggestionsForUnreachableCode (this FileContentManager file, IEnumerable diagnostics) @@ -380,7 +408,7 @@ WorkspaceEdit SuggestedRemoval(Position pos) // determine the whitespace for the replacement string var lastLine = file.GetLine(lastFragToken.Line).Text.Substring(0, lastInScope.GetRange().Start.Character); var trimmedLastLine = lastLine.TrimEnd(); - var whitespace = lastLine.Substring(trimmedLastLine.Length, lastLine.Length - trimmedLastLine.Length); + var whitespace = lastLine[trimmedLastLine.Length..]; // build the replacement string var replaceString = lastBeforeErase.FollowedBy == CodeFragment.MissingDelimiter ? "" : $"{lastBeforeErase.FollowedBy}"; @@ -398,11 +426,11 @@ WorkspaceEdit SuggestedRemoval(Position pos) } /// - /// Returns a sequence of suggestions to insert doc comments for an undocumented declaration that overlap with the given range in the given file. + /// Returns a sequence of suggestions to insert doc comments for an undocumented declaration that overlap with the given range in the given file. /// Returns an empty enumerable if more than one code fragment overlaps with the given range, /// or the overlapping fragment does not contain a declaration, /// or the overlapping fragment contains a declaration that is already documented, - /// or if any of the given arguments is null. + /// or if any of the given arguments is null. /// internal static IEnumerable<(string, WorkspaceEdit)> DocCommentSuggestions(this FileContentManager file, LSP.Range range) { @@ -411,14 +439,14 @@ WorkspaceEdit SuggestedRemoval(Position pos) if (fragment?.Kind == null || overlapping.Count() != 1) return Enumerable.Empty<(string, WorkspaceEdit)>(); // only suggest doc comment directly on the declaration var (nsDecl, callableDecl, typeDecl) = (fragment.Kind.DeclaredNamespace(), fragment.Kind.DeclaredCallable(), fragment.Kind.DeclaredType()); - var declSymbol = nsDecl.IsValue ? nsDecl.Item.Item1.Symbol + var declSymbol = nsDecl.IsValue ? nsDecl.Item.Item1.Symbol : callableDecl.IsValue ? callableDecl.Item.Item1.Symbol : typeDecl.IsValue ? typeDecl.Item.Item1.Symbol : null; var declStart = fragment.GetRange().Start; if (declSymbol == null || file.DocumentingComments(declStart).Any()) return Enumerable.Empty<(string, WorkspaceEdit)>(); // set declStart to the position of the first attribute attached to the declaration - bool EmptyOrFirstAttribute(IEnumerable line, out CodeFragment att) + static bool EmptyOrFirstAttribute(IEnumerable line, out CodeFragment att) { att = line?.Reverse().TakeWhile(t => t.Kind is QsFragmentKind.DeclarationAttribute).LastOrDefault(); return att != null || (line != null && !line.Any()); @@ -436,10 +464,11 @@ bool EmptyOrFirstAttribute(IEnumerable line, out CodeFragment att) var docString = $"{docPrefix}# Summary{endLine}{docPrefix}{endLine}"; var (argTuple, typeParams) = - callableDecl.IsValue ? (callableDecl.Item.Item2.Item2.Argument, callableDecl.Item.Item2.Item2.TypeParameters) : - typeDecl.IsValue ? (typeDecl.Item.Item2, ImmutableArray.Empty) : - (null, ImmutableArray.Empty); - var hasOutput = callableDecl.IsValue && !callableDecl.Item.Item2.Item2.ReturnType.Type.IsUnitType; + callableDecl.IsValue ? (callableDecl.Item.Item2.Item3.Argument, + callableDecl.Item.Item2.Item3.TypeParameters) + : typeDecl.IsValue ? (typeDecl.Item.Item2.Item2, ImmutableArray.Empty) + : (null, ImmutableArray.Empty); + var hasOutput = callableDecl.IsValue && !callableDecl.Item.Item2.Item3.ReturnType.Type.IsUnitType; var args = argTuple == null ? ImmutableArray>.Empty : SyntaxGenerator.ExtractItems(argTuple); docString = String.Concat( @@ -447,7 +476,7 @@ bool EmptyOrFirstAttribute(IEnumerable line, out CodeFragment att) // Document Input Parameters args.Any() ? $"{docPrefix}# Input{endLine}" : String.Empty, String.Concat(args.Select(x => $"{docPrefix}## {x.Item1.Symbol.AsDeclarationName(null)}{endLine}{docPrefix}{endLine}")), - // Document Output + // Document Output hasOutput ? $"{docPrefix}# Output{endLine}{docPrefix}{endLine}" : String.Empty, // Document Type Parameters typeParams.Any() ? $"{docPrefix}# Type Parameters{endLine}" : String.Empty, diff --git a/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs b/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs index d94372d85c..5729fc8548 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs @@ -3,6 +3,7 @@ using Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures; using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SymbolManagement; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; @@ -215,7 +216,7 @@ private static IEnumerable GetCompletionsForKind( .Concat(GetGlobalNamespaceCompletions(compilation, namespacePrefix)) .Concat(GetNamespaceAliasCompletions(file, compilation, position, namespacePrefix)); case CompletionKind.Tags.NamedItem: - return GetNamedItemCompletions(compilation); + return GetNamedItemCompletions(compilation, currentNamespace); case CompletionKind.Tags.Namespace: return GetGlobalNamespaceCompletions(compilation, namespacePrefix) @@ -310,7 +311,8 @@ private static IEnumerable GetLocalCompletions( } /// - /// Returns completions for all callables visible given the current namespace and the list of open namespaces. + /// Returns completions for all accessible callables given the current namespace and the list of open + /// namespaces, or an empty enumerable if the current namespace is null or symbols haven't been resolved yet. /// /// /// Thrown when any argument except is null. @@ -328,12 +330,12 @@ private static IEnumerable GetCallableCompletions( if (openNamespaces == null) throw new ArgumentNullException(nameof(openNamespaces)); - if (!compilation.GlobalSymbols.ContainsResolutions) + if (currentNamespace == null || !compilation.GlobalSymbols.ContainsResolutions) return Array.Empty(); return - compilation.GlobalSymbols.DefinedCallables() - .Concat(compilation.GlobalSymbols.ImportedCallables()) - .Where(callable => IsVisible(callable.QualifiedName, currentNamespace, openNamespaces)) + compilation.GlobalSymbols.AccessibleCallables() + .Where(callable => + IsAccessibleAsUnqualifiedName(callable.QualifiedName, currentNamespace, openNamespaces)) .Select(callable => new CompletionItem() { Label = callable.QualifiedName.Name.Value, @@ -348,7 +350,8 @@ private static IEnumerable GetCallableCompletions( } /// - /// Returns completions for all types visible given the current namespace and the list of open namespaces. + /// Returns completions for all accessible types given the current namespace and the list of open namespaces, or + /// an empty enumerable if the current namespace is null or symbols haven't been resolved yet. /// /// /// Thrown when any argument except is null. @@ -366,12 +369,11 @@ private static IEnumerable GetTypeCompletions( if (openNamespaces == null) throw new ArgumentNullException(nameof(openNamespaces)); - if (!compilation.GlobalSymbols.ContainsResolutions) + if (currentNamespace == null || !compilation.GlobalSymbols.ContainsResolutions) return Array.Empty(); return - compilation.GlobalSymbols.DefinedTypes() - .Concat(compilation.GlobalSymbols.ImportedTypes()) - .Where(type => IsVisible(type.QualifiedName, currentNamespace, openNamespaces)) + compilation.GlobalSymbols.AccessibleTypes() + .Where(type => IsAccessibleAsUnqualifiedName(type.QualifiedName, currentNamespace, openNamespaces)) .Select(type => new CompletionItem() { Label = type.QualifiedName.Name.Value, @@ -385,18 +387,21 @@ private static IEnumerable GetTypeCompletions( } /// - /// Returns completions for all named items in any type. + /// Returns completions for all named items in any type that are accessible from the given namespace, or an + /// empty enumerable if the current namespace is null or symbols haven't been resolved yet. /// - /// Thrown when the argument is null. - private static IEnumerable GetNamedItemCompletions(CompilationUnit compilation) + /// + /// Thrown when any argument except is null. + /// + private static IEnumerable GetNamedItemCompletions( + CompilationUnit compilation, string currentNamespace) { if (compilation == null) throw new ArgumentNullException(nameof(compilation)); - if (!compilation.GlobalSymbols.ContainsResolutions) + if (currentNamespace == null || !compilation.GlobalSymbols.ContainsResolutions) return Array.Empty(); - return compilation.GlobalSymbols.DefinedTypes() - .Concat(compilation.GlobalSymbols.ImportedTypes()) + return compilation.GlobalSymbols.AccessibleTypes() .SelectMany(type => ExtractItems(type.TypeItems)) .Where(item => item.IsNamed) .Select(item => new CompletionItem() @@ -438,8 +443,8 @@ private static IEnumerable GetGlobalNamespaceCompletions( } /// - /// Returns completions for namespace aliases with the given prefix that are visible at the given position in - /// the file. + /// Returns completions for namespace aliases with the given prefix that are accessible from the given position + /// in the file. /// /// Note: a dot will be added after the given prefix if it is not the empty string, and doesn't already end with /// a dot. @@ -499,9 +504,9 @@ private static string TryGetDocumentation( { case CompletionItemKind.Function: case CompletionItemKind.Constructor: - var callable = compilation.GlobalSymbols.TryGetCallable( + var result = compilation.GlobalSymbols.TryGetCallable( data.QualifiedName, data.QualifiedName.Namespace, NonNullable.New(data.SourceFile)); - if (callable.IsNull) + if (!(result is ResolutionResult.Found callable)) return null; var signature = callable.Item.PrintSignature(); var documentation = callable.Item.Documentation.PrintSummary(useMarkdown); @@ -510,15 +515,15 @@ private static string TryGetDocumentation( var type = compilation.GlobalSymbols.TryGetType( data.QualifiedName, data.QualifiedName.Namespace, NonNullable.New(data.SourceFile)) - .Item; - return type?.Documentation.PrintSummary(useMarkdown).Trim(); + as ResolutionResult.Found; + return type?.Item.Documentation.PrintSummary(useMarkdown).Trim(); default: return null; } } /// - /// Returns the names of all namespaces that have been opened without an alias and are visible from the given + /// Returns the names of all namespaces that have been opened without an alias and are accessible from the given /// position in the file. Returns an empty enumerator if the position is invalid. /// /// Thrown when any argument is null. @@ -714,14 +719,15 @@ private static CompletionList ToCompletionList(this IEnumerable }; /// - /// Returns true if the qualified name is visible given the current namespace and a list of open namespaces. + /// Returns true if the declaration with the given qualified name would be accessible if it was referenced using + /// its unqualified name, given the current namespace and a list of open namespaces. /// - /// Names that start with "_" are treated as "private"; they are only visible from the namespace in which they - /// are declared. + /// Note: Names that start with "_" are treated as "private;" they are only accessible from the namespace in + /// which they are declared. /// - private static bool IsVisible(QsQualifiedName qualifiedName, - string currentNamespace, - IEnumerable openNamespaces) => + private static bool IsAccessibleAsUnqualifiedName(QsQualifiedName qualifiedName, + string currentNamespace, + IEnumerable openNamespaces) => openNamespaces.Contains(qualifiedName.Namespace.Value) && (!qualifiedName.Name.Value.StartsWith("_") || qualifiedName.Namespace.Value == currentNamespace); } diff --git a/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs b/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs index 0a0750f4a7..7a5fe606ba 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures; using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SymbolManagement; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; @@ -248,13 +249,27 @@ List FunctorApplications(ref QsExpression ex) // extracting and adapting the relevant information for the called callable - var ns = NonNullable.New(nsName); - var methodDecl = id.Item1.Symbol is QsSymbolKind.Symbol sym - ? compilation.GlobalSymbols.TryResolveAndGetCallable(sym.Item, ns, file.FileName).Item1 - : id.Item1.Symbol is QsSymbolKind.QualifiedSymbol qualSym - ? compilation.GlobalSymbols.TryGetCallable(new QsQualifiedName(qualSym.Item1, qualSym.Item2), ns, file.FileName) - : QsNullable.Null; - if (methodDecl.IsNull) return null; + ResolutionResult.Found methodDecl; + if (id.Item1.Symbol is QsSymbolKind.Symbol sym) + { + methodDecl = + compilation.GlobalSymbols.TryResolveAndGetCallable(sym.Item, + NonNullable.New(nsName), + file.FileName) + as ResolutionResult.Found; + } + else if (id.Item1.Symbol is QsSymbolKind.QualifiedSymbol qualSym) + { + methodDecl = + compilation.GlobalSymbols.TryGetCallable(new QsQualifiedName(qualSym.Item1, qualSym.Item2), + NonNullable.New(nsName), + file.FileName) + as ResolutionResult.Found; + } + else + { + return null; + } var (documentation, argTuple) = (methodDecl.Item.Documentation, methodDecl.Item.ArgumentTuple); var nrCtlApplications = functors.Where(f => f.Equals(QsFunctor.Controlled)).Count(); diff --git a/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs b/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs index 501d7d73c7..f404855810 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures; using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SymbolManagement; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; @@ -188,17 +189,20 @@ internal static bool TryGetReferences( if (nsName == null) return false; var ns = NonNullable.New(nsName); - QsQualifiedName fullName = null; + var result = ResolutionResult.NotFound; if (sym.Symbol is QsSymbolKind.Symbol name) { - var header = compilation.GlobalSymbols.TryResolveAndGetCallable(name.Item, ns, file.FileName).Item1; - if (header.IsValue) fullName = header.Item.QualifiedName; + result = compilation.GlobalSymbols.TryResolveAndGetCallable(name.Item, ns, file.FileName); } - if (sym.Symbol is QsSymbolKind.QualifiedSymbol qualName) + else if (sym.Symbol is QsSymbolKind.QualifiedSymbol qualifiedName) { - var header = compilation.GlobalSymbols.TryGetCallable(new QsQualifiedName(qualName.Item1, qualName.Item2), ns, file.FileName); - if (header.IsValue) fullName = header.Item.QualifiedName; + result = compilation.GlobalSymbols.TryGetCallable( + new QsQualifiedName(qualifiedName.Item1, qualifiedName.Item2), + ns, + file.FileName); } + var fullName = result is ResolutionResult.Found header ? header.Item.QualifiedName : null; + return compilation.TryGetReferences(fullName, out declarationLocation, out referenceLocations, limitToSourceFiles); } diff --git a/src/QsCompiler/CompilationManager/ProjectManager.cs b/src/QsCompiler/CompilationManager/ProjectManager.cs index 339c48ed51..d42cb675f0 100644 --- a/src/QsCompiler/CompilationManager/ProjectManager.cs +++ b/src/QsCompiler/CompilationManager/ProjectManager.cs @@ -22,18 +22,20 @@ public class ProjectInformation public readonly string Version; public readonly string OutputPath; + public readonly bool ExposeReferencesViaTestNames; public readonly ImmutableArray SourceFiles; public readonly ImmutableArray ProjectReferences; public readonly ImmutableArray References; internal static ProjectInformation Empty(string version, string outputPath) => - new ProjectInformation(version, outputPath, Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()); + new ProjectInformation(version, outputPath, false, Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()); - public ProjectInformation(string version, string outputPath, + public ProjectInformation(string version, string outputPath, bool loadTestNames, IEnumerable sourceFiles, IEnumerable projectReferences, IEnumerable references) { this.Version = version ?? ""; this.OutputPath = outputPath ?? throw new ArgumentNullException(nameof(outputPath)); + this.ExposeReferencesViaTestNames = loadTestNames; this.SourceFiles = sourceFiles?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(sourceFiles)); this.ProjectReferences = projectReferences?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(projectReferences)); this.References = references?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(references)); @@ -46,6 +48,7 @@ private class Project : IDisposable { public readonly Uri ProjectFile; public Uri OutputPath { get; private set; } + private bool ExposeReferencesViaTestNames; private bool IsLoaded; /// @@ -141,6 +144,7 @@ internal Project(Uri projectFile, ProjectInformation projectInfo, private void SetProjectInformation(ProjectInformation projectInfo) { if (projectInfo == null) throw new ArgumentNullException(nameof(projectInfo)); + this.ExposeReferencesViaTestNames = projectInfo.ExposeReferencesViaTestNames; this.IsLoaded = false; var outputPath = projectInfo.OutputPath; @@ -257,7 +261,7 @@ private Task LoadProjectReferencesAsync( projectReferences, GetProjectOutputPath(projectOutputPaths, diagnostics), diagnostics.Add, this.Manager.LogException); - this.LoadedProjectReferences = new References(loadedHeaders, null); + this.LoadedProjectReferences = new References(loadedHeaders); var importedDeclarations = this.LoadedReferences.CombineWith(this.LoadedProjectReferences, (code,args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); this.ProjectReferenceDiagnostics = diagnostics.ToImmutableArray(); @@ -284,12 +288,12 @@ private void ReloadProjectReference(IDictionary projectOutputPaths, Ur var loadedHeaders = ProjectManager.LoadProjectReferences( new string[] { projectReference.LocalPath }, GetProjectOutputPath(projectOutputPaths, diagnostics), diagnostics.Add, this.Manager.LogException); - var loaded = new References(loadedHeaders, null); + var loaded = new References(loadedHeaders); QsCompilerError.Verify(!loaded.Declarations.Any() || (loaded.Declarations.Count == 1 && loaded.Declarations.First().Key.Value == projRefId.Value), $"loaded references upon loading {projectReference.LocalPath}: {String.Join(", ", loaded.Declarations.Select(r => r.Value))}"); - this.LoadedProjectReferences = this.LoadedProjectReferences.Remove(projRefId, null).CombineWith(loaded, null); + this.LoadedProjectReferences = this.LoadedProjectReferences.Remove(projRefId).CombineWith(loaded); var importedDeclarations = this.LoadedReferences.CombineWith(this.LoadedProjectReferences, (code, args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); @@ -316,7 +320,7 @@ private Task LoadReferencedAssembliesAsync(IEnumerable references, bool var loadedHeaders = ProjectManager.LoadReferencedAssemblies(references, diagnostics.Add, this.Manager.LogException); - this.LoadedReferences = new References(loadedHeaders, null); + this.LoadedReferences = new References(loadedHeaders); var importedDeclarations = this.LoadedReferences.CombineWith(this.LoadedProjectReferences, (code, args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); this.ReferenceDiagnostics = diagnostics.ToImmutableArray(); @@ -338,12 +342,12 @@ private void ReloadReferencedAssembly(Uri reference) var diagnostics = new List(); var loadedHeaders = ProjectManager.LoadReferencedAssemblies(new string[] { reference.LocalPath }, diagnostics.Add, this.Manager.LogException); - var loaded = new References(loadedHeaders, null); + var loaded = new References(loadedHeaders); QsCompilerError.Verify(!loaded.Declarations.Any() || (loaded.Declarations.Count == 1 && loaded.Declarations.First().Key.Value == refId.Value), $"loaded references upon loading {reference.LocalPath}: {String.Join(", ", loaded.Declarations.Select(r => r.Value))}"); - this.LoadedReferences = this.LoadedReferences.Remove(refId, null).CombineWith(loaded, null); + this.LoadedReferences = this.LoadedReferences.Remove(refId).CombineWith(loaded); var importedDeclarations = this.LoadedReferences.CombineWith(this.LoadedProjectReferences, (code, args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); @@ -470,7 +474,7 @@ public Task ReloadProjectReferenceAsync(IDictionary projectOutputPaths public Task ReloadSourceFileAsync(Uri sourceFile, Func openInEditor = null) { if (sourceFile == null) throw new ArgumentNullException(nameof(sourceFile)); - openInEditor = openInEditor ?? (_ => null); + openInEditor ??= (_ => null); return this.Processing.QueueForExecutionAsync(() => { @@ -589,7 +593,7 @@ private Func, Uri, IEnumerable> Migrat if (openInEditor == null) throw new ArgumentNullException(nameof(openInEditor)); return (filesAddedToProject, projFile) => { - filesAddedToProject = filesAddedToProject ?? ImmutableHashSet.Empty; + filesAddedToProject ??= ImmutableHashSet.Empty; var openFiles = filesAddedToProject.Select(openInEditor).Where(m => m != null).ToImmutableArray(); var removals = openFiles.Select(file => { @@ -618,7 +622,7 @@ private Action, Task> MigrateToDefaultManager(Func { if (removal.IsCanceled) return; - filesRemovedFromProject = filesRemovedFromProject ?? ImmutableHashSet.Empty; + filesRemovedFromProject ??= ImmutableHashSet.Empty; Task.WaitAll(removal); // we *need* to wait here in order to make sure that change notifications are processed in order!! var openFiles = filesRemovedFromProject.Select(openInEditor).Where(m => m != null).ToImmutableHashSet(); foreach (var file in openFiles) @@ -642,7 +646,7 @@ public Task LoadProjectsAsync(IEnumerable projectFiles, ProjectInformation. { if (projectFiles == null || projectFiles.Contains(null)) throw new ArgumentNullException(nameof(projectFiles)); if (projectLoader == null) throw new ArgumentNullException(nameof(projectLoader)); - openInEditor = openInEditor ?? (_ => null); + openInEditor ??= (_ => null); return this.Load.QueueForExecutionAsync(() => { @@ -672,7 +676,7 @@ public Task ProjectChangedOnDiskAsync(Uri projectFile, ProjectInformation.Loader { if (projectFile == null) throw new ArgumentNullException(nameof(projectFile)); if (projectLoader == null) throw new ArgumentNullException(nameof(projectLoader)); - openInEditor = openInEditor ?? (_ => null); + openInEditor ??= (_ => null); // TODO: allow to cancel this task via cancellation token? return this.Load.QueueForExecutionAsync(() => diff --git a/src/QsCompiler/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index 011af90375..99934ce13e 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -111,13 +111,13 @@ internal static ImmutableArray DocumentingComments(this FileContentManag /// /// Returns the HeaderItems corresponding to all type declarations with a valid name in the given file, or null if the given file is null. /// - private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>>)> GetTypeDeclarationHeaderItems + private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>>>)> GetTypeDeclarationHeaderItems (this FileContentManager file) => file.GetHeaderItems(file?.TypeDeclarationTokens(), frag => frag.Kind.DeclaredType(), null); /// /// Returns the HeaderItems corresponding to all callable declarations with a valid name in the given file, or null if the given file is null. /// - private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>)> GetCallableDeclarationHeaderItems + private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>)> GetCallableDeclarationHeaderItems (this FileContentManager file) => file.GetHeaderItems(file?.CallableDeclarationTokens(), frag => frag.Kind.DeclaredCallable(), null); /// @@ -128,7 +128,7 @@ internal static ImmutableArray DocumentingComments(this FileContentManag /// The function returns Null if the Kind of the given fragment is null. /// private static QsNullable)>> SpecializationDeclaration - (HeaderEntry> parent, CodeFragment fragment) + (HeaderEntry> parent, CodeFragment fragment) { var specDecl = fragment.Kind?.DeclaredSpecialization(); var Null = QsNullable)>>.Null; @@ -226,7 +226,7 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position // add all type declarations var typesToCompile = AddItems(file.GetTypeDeclarationHeaderItems(), - (pos, name, decl, att, doc) => (ContainingParent(pos, namespaces)).TryAddType(file.FileName, Location(pos, name.Item2), name, decl, att, doc), + (pos, name, decl, att, doc) => (ContainingParent(pos, namespaces)).TryAddType(file.FileName, Location(pos, name.Item2), name, decl.Item2, att, decl.Item1, doc), file.FileName.Value, diagnostics); var tokensToCompile = new List<(QsQualifiedName, (QsComments, IEnumerable))>(); @@ -238,7 +238,7 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position // add all callable declarations var callablesToCompile = AddItems(file.GetCallableDeclarationHeaderItems(), - (pos, name, decl, att, doc) => (ContainingParent(pos, namespaces)).TryAddCallableDeclaration(file.FileName, Location(pos, name.Item2), name, decl, att, doc), + (pos, name, decl, att, doc) => (ContainingParent(pos, namespaces)).TryAddCallableDeclaration(file.FileName, Location(pos, name.Item2), name, Tuple.Create(decl.Item1, decl.Item3), att, decl.Item2, doc), file.FileName.Value, diagnostics); // add all callable specilizations -> TOOD: needs to be adapted for specializations outside the declaration body (not yet supported) @@ -271,7 +271,7 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position /// Throws an ArgumentNullException if either the given namespace or diagnostics are null. /// private static List AddSpecializationsToNamespace(FileContentManager file, Namespace ns, - (CodeFragment.TokenIndex, HeaderEntry>) parent, List diagnostics) + (CodeFragment.TokenIndex, HeaderEntry>) parent, List diagnostics) { if (ns == null) throw new ArgumentNullException(nameof(ns)); if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); @@ -1346,19 +1346,23 @@ internal static List RunTypeChecking (CompilationUnit compilation, // check that the declarations for the types and callables to be built from the given FragmentTrees exist in the given CompilationUnit var typeRoots = roots.Where(root => root.Value.Item2.Specializations == null); var typeDeclarations = typeRoots.ToImmutableDictionary( - root => root.Key, root => + root => root.Key, + root => { - var info = compilation.GlobalSymbols.TryGetType(root.Key, root.Value.Item2.Namespace, root.Value.Item2.Source); - if (info.IsNull) throw new ArgumentException("type to build is no longer present in the given NamespaceManager"); - return info.Item; + var result = compilation.GlobalSymbols.TryGetType(root.Key, root.Value.Item2.Namespace, root.Value.Item2.Source); + return result is ResolutionResult.Found type + ? type.Item + : throw new ArgumentException("type to build is no longer present in the given NamespaceManager"); }); var callableRoots = roots.Where(root => root.Value.Item2.Specializations != null); var callableDeclarations = callableRoots.ToImmutableDictionary( - root => root.Key, root => + root => root.Key, + root => { - var info = compilation.GlobalSymbols.TryGetCallable(root.Key, root.Value.Item2.Namespace, root.Value.Item2.Source); - if (info.IsNull) throw new ArgumentException("callable to build is no longer present in the given NamespaceManager"); - return info.Item; + var result = compilation.GlobalSymbols.TryGetCallable(root.Key, root.Value.Item2.Namespace, root.Value.Item2.Source); + return result is ResolutionResult.Found callable + ? callable.Item + : throw new ArgumentException("callable to build is no longer present in the given NamespaceManager"); }); (QsQualifiedName, ImmutableArray) GetSpecializations @@ -1389,14 +1393,33 @@ QsCallable GetCallable((QsQualifiedName, ImmutableArray) specI } symbolTracker.EndScope(); QsCompilerError.Verify(symbolTracker.AllScopesClosed, "all scopes should be closed"); - return new QsCallable(info.Kind, parent, info.Attributes, info.SourceFile, QsNullable.Null, - info.Signature, info.ArgumentTuple, specs, info.Documentation, roots[parent].Item1); + return new QsCallable( + info.Kind, + parent, + info.Attributes, + info.Modifiers, + info.SourceFile, + QsNullable.Null, + info.Signature, + info.ArgumentTuple, + specs, + info.Documentation, + roots[parent].Item1 + ); } var callables = callableRoots.Select(GetSpecializations).Select(GetCallable).ToImmutableArray(); var types = typeDeclarations.Select(decl => new QsCustomType( - decl.Key, decl.Value.Attributes, decl.Value.SourceFile, decl.Value.Location, - decl.Value.Type, decl.Value.TypeItems, decl.Value.Documentation, roots[decl.Key].Item1)).ToImmutableArray(); + decl.Key, + decl.Value.Attributes, + decl.Value.Modifiers, + decl.Value.SourceFile, + decl.Value.Location, + decl.Value.Type, + decl.Value.TypeItems, + decl.Value.Documentation, + roots[decl.Key].Item1 + )).ToImmutableArray(); if (cancellationToken.IsCancellationRequested) return null; compilation.UpdateCallables(callables); diff --git a/src/QsCompiler/Compiler/CompilationLoader.cs b/src/QsCompiler/Compiler/CompilationLoader.cs index 5bd3c0e3fa..1fa31c74b1 100644 --- a/src/QsCompiler/Compiler/CompilationLoader.cs +++ b/src/QsCompiler/Compiler/CompilationLoader.cs @@ -135,6 +135,11 @@ public struct Configuration /// public bool LoadReferencesBasedOnGeneratedCsharp; /// + /// If set to true, then public types and callables declared in referenced assemblies + /// are exposed via their test name defined by the corresponding attribute. + /// + public bool ExposeReferencesViaTestNames; + /// /// Contains a sequence of tuples with the path to a dotnet dll containing one or more rewrite steps /// (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. @@ -151,7 +156,13 @@ public struct Configuration /// 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; + public IReadOnlyDictionary AssemblyConstants; + /// + /// Path to the assembly that contains a syntax tree with target specific implementations for certain functions and operations. + /// The functions and operations defined in that assembly replace the ones declarated within the compilation unit. + /// If no path is specified here or the specified path is null then this compilation step is omitted. + /// + public string TargetPackageAssembly; /// /// Indicates whether a serialization of the syntax tree needs to be generated. @@ -190,6 +201,7 @@ private class ExecutionStatus internal Status ReferenceLoading = Status.NotRun; internal Status PluginLoading = Status.NotRun; internal Status Validation = Status.NotRun; + internal Status TargetSpecificReplacements = Status.NotRun; internal Status FunctorSupport = Status.NotRun; internal Status PreEvaluation = Status.NotRun; internal Status TreeTrimming = Status.NotRun; @@ -212,6 +224,7 @@ internal bool Success(Configuration options, bool isExe) => this.ReferenceLoading <= 0 && WasSuccessful(true, this.Validation) && WasSuccessful(true, this.PluginLoading) && + WasSuccessful(!String.IsNullOrWhiteSpace(options.TargetPackageAssembly), this.TargetSpecificReplacements) && WasSuccessful(options.GenerateFunctorSupport, this.FunctorSupport) && WasSuccessful(options.AttemptFullPreEvaluation, this.PreEvaluation) && WasSuccessful(!options.SkipSyntaxTreeTrimming, this.TreeTrimming) && @@ -247,6 +260,12 @@ internal bool Success(Configuration options, bool isExe) => /// public Status Validation => this.CompilationStatus.Validation; /// + /// Indicates whether target specific implementations for functions and operations + /// have been used to replace the ones declarated within the compilation unit. + /// This step is only executed if the specified configuration contains the path to the target package. + /// + public Status TargetSpecificReplacements => this.CompilationStatus.TargetSpecificReplacements; + /// /// Indicates whether all specializations were generated successfully. /// This rewrite step is only executed if the corresponding configuration is specified. /// @@ -390,7 +409,11 @@ public CompilationLoader(SourceLoader loadSources, ReferenceLoader loadReference ?? 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)) + var references = loadReferences?.Invoke( + refs => this.LoadAssemblies( + refs, + loadTestNames: this.Config.ExposeReferencesViaTestNames, + ignoreDllResources: this.Config.LoadReferencesBasedOnGeneratedCsharp)) ?? throw new ArgumentNullException("unable to load referenced binary files"); RaiseCompilationTaskEnd("OverallCompilation", "ReferenceLoading"); @@ -414,10 +437,9 @@ public CompilationLoader(SourceLoader loadSources, ReferenceLoader loadReference 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) + if (!String.IsNullOrWhiteSpace(this.Config.TargetPackageAssembly)) { - status = this.ExecuteRewriteStep(rewriteStep, this.CompilationOutput, out var transformed); - return status == Status.Succeeded ? transformed : this.CompilationOutput; + this.ReplaceTargetSpecificImplementations(thisDllUri, out this.CompilationOutput); } if (this.Config.ConvertClassicalControl) @@ -508,66 +530,6 @@ QsCompilation ExecuteAsAtomicTransformation(RewriteSteps.LoadedStep rewriteStep, 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. @@ -685,6 +647,130 @@ private void PrintLoadedRewriteSteps(IEnumerable rewrit } + // private helper methods used during construction + + /// + /// 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)); + + /// + /// Executes the given rewrite step on the current CompilationOutput, and updates the given status accordingly. + /// Sets the CompilationOutput to the transformed compilation if the status indicates success. + /// + private QsCompilation ExecuteAsAtomicTransformation(RewriteSteps.LoadedStep rewriteStep, ref Status status) + { + status = this.ExecuteRewriteStep(rewriteStep, this.CompilationOutput, out var transformed); + return status == Status.Succeeded ? transformed : this.CompilationOutput; + } + + /// + /// Attempts to load the target package assembly specified in the configuration, + /// logging diagnostics when the loading fails or the corresponding configuration is not specified. + /// Updates the compilation status accordingly. + /// Executes the transformation to replace target specific implementations as atomic rewrite step, + /// returning the transformed compilation as out parameter. + /// Sets the out parameter to the unmodified CompilationOutput if the replacement fails. + /// Returns a boolean value indicating whether the returned compilation has been modified. + /// + private bool ReplaceTargetSpecificImplementations(Uri rewriteStepOrigin, out QsCompilation transformed) + { + try + { + var targetDll = Path.GetFullPath(this.Config.TargetPackageAssembly); + var loaded = AssemblyLoader.LoadReferencedAssembly( + targetDll, + out var targetIntrinsics, + ex => this.LogAndUpdate(ref this.CompilationStatus.TargetSpecificReplacements, ex)); + + if (loaded) + { + var rewriteStep = new RewriteSteps.LoadedStep(new IntrinsicResolution(targetIntrinsics), typeof(IRewriteStep), rewriteStepOrigin); + transformed = ExecuteAsAtomicTransformation(rewriteStep, ref this.CompilationStatus.TargetSpecificReplacements); + return true; + } + else + { + this.LogAndUpdate(ref this.CompilationStatus.TargetSpecificReplacements, ErrorCode.FailedToLoadTargetPackageAssembly, new[] { targetDll }); + } + } + catch (Exception ex) + { + this.LogAndUpdate(ref this.CompilationStatus.TargetSpecificReplacements, ErrorCode.InvalidTargetPackageAssemblyPath, new[] { this.Config.TargetPackageAssembly }); + this.LogAndUpdate(ref this.CompilationStatus.TargetSpecificReplacements, ex); + } + transformed = this.CompilationOutput; + return false; + } + + /// + /// 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; + } + + // routines for loading from and dumping to files /// @@ -706,11 +792,13 @@ private ImmutableDictionary LoadSourceFiles(IEnumerable sou /// /// Used to load the content of the specified assembly references from disk. + /// If loadTestNames is set to true, then public types and callables declared in referenced assemblies + /// are exposed via their test name defined by the corresponding attribute. /// Returns the loaded content of the references. /// Logs suitable diagnostics in the process and modifies the compilation status accordingly. /// Prints all loaded files using PrintResolvedAssemblies. /// - private References LoadAssemblies(IEnumerable refs, bool ignoreDllResources) + private References LoadAssemblies(IEnumerable refs, bool loadTestNames, bool ignoreDllResources) { this.CompilationStatus.ReferenceLoading = 0; if (refs == null) this.Logger?.Log(WarningCode.ReferencesSetToNull, Enumerable.Empty()); @@ -718,7 +806,7 @@ private References LoadAssemblies(IEnumerable refs, bool ignoreDllResour 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))); + var references = new References(headers, loadTestNames, (code, args) => onDiagnostic(Errors.LoadError(code, args, projId))); this.PrintResolvedAssemblies(references.Declarations.Keys); return references; } @@ -909,17 +997,5 @@ string FullDirectoryName(string dir) => 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/RewriteSteps/ClassicallyControlled.cs b/src/QsCompiler/Compiler/RewriteSteps/ClassicallyControlled.cs index 01410bf9ff..9e62c97956 100644 --- a/src/QsCompiler/Compiler/RewriteSteps/ClassicallyControlled.cs +++ b/src/QsCompiler/Compiler/RewriteSteps/ClassicallyControlled.cs @@ -2,8 +2,8 @@ // Licensed under the MIT License. using System.Collections.Generic; -using System.Linq; -using Microsoft.Quantum.QsCompiler.DataTypes; +using System.Linq; +using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.Transformations.ClassicallyControlled; @@ -45,20 +45,20 @@ public bool PreconditionVerification(QsCompilation compilation) 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.ApplyIfZero.FullName.Name, + BuiltIn.ApplyIfZeroA.FullName.Name, + BuiltIn.ApplyIfZeroC.FullName.Name, + BuiltIn.ApplyIfZeroCA.FullName.Name, - BuiltIn.ApplyIfOne.Name, - BuiltIn.ApplyIfOneA.Name, - BuiltIn.ApplyIfOneC.Name, - BuiltIn.ApplyIfOneCA.Name, + BuiltIn.ApplyIfOne.FullName.Name, + BuiltIn.ApplyIfOneA.FullName.Name, + BuiltIn.ApplyIfOneC.FullName.Name, + BuiltIn.ApplyIfOneCA.FullName.Name, - BuiltIn.ApplyIfElseR.Name, - BuiltIn.ApplyIfElseRA.Name, - BuiltIn.ApplyIfElseRC.Name, - BuiltIn.ApplyIfElseRCA.Name + BuiltIn.ApplyIfElseR.FullName.Name, + BuiltIn.ApplyIfElseRA.FullName.Name, + BuiltIn.ApplyIfElseRC.FullName.Name, + BuiltIn.ApplyIfElseRCA.FullName.Name }; return requiredBuiltIns.All(builtIn => providedOperations.Any(provided => provided.Equals(builtIn))); diff --git a/src/QsCompiler/Core/ConstructorExtensions.fs b/src/QsCompiler/Core/ConstructorExtensions.fs index ce3f4f7964..2c3a8226ec 100644 --- a/src/QsCompiler/Core/ConstructorExtensions.fs +++ b/src/QsCompiler/Core/ConstructorExtensions.fs @@ -195,10 +195,11 @@ type QsSpecialization with static member NewControlledAdjoint = QsSpecialization.New QsControlledAdjoint type QsCallable with - static member New kind (source, location) (name, attributes, argTuple, signature, specializations : IEnumerable<_>, documentation, comments) = { + static member New kind (source, location) (name, attributes, modifiers, argTuple, signature, specializations : IEnumerable<_>, documentation, comments) = { Kind = kind FullName = name Attributes = attributes + Modifiers = modifiers SourceFile = source Location = location Signature = signature @@ -212,9 +213,10 @@ type QsCallable with static member NewTypeConstructor = QsCallable.New QsCallableKind.TypeConstructor type QsCustomType with - static member New (source, location) (name, attributes, items, underlyingType, documentation, comments) = { + static member New (source, location) (name, attributes, modifiers, items, underlyingType, documentation, comments) = { FullName = name Attributes = attributes + Modifiers = modifiers SourceFile = source Location = location Type = underlyingType diff --git a/src/QsCompiler/Core/DeclarationHeaders.fs b/src/QsCompiler/Core/DeclarationHeaders.fs index ee46da56c0..6bf15d3f58 100644 --- a/src/QsCompiler/Core/DeclarationHeaders.fs +++ b/src/QsCompiler/Core/DeclarationHeaders.fs @@ -96,6 +96,7 @@ module DeclarationHeader = type TypeDeclarationHeader = { QualifiedName : QsQualifiedName Attributes : ImmutableArray + Modifiers : Modifiers SourceFile : NonNullable Position : DeclarationHeader.Offset SymbolRange : DeclarationHeader.Range @@ -111,6 +112,7 @@ type TypeDeclarationHeader = { static member New (customType : QsCustomType) = { QualifiedName = customType.FullName Attributes = customType.Attributes + Modifiers = customType.Modifiers SourceFile = customType.SourceFile Position = customType.Location |> DeclarationHeader.CreateOffset SymbolRange = customType.Location |> DeclarationHeader.CreateRange @@ -135,6 +137,7 @@ type CallableDeclarationHeader = { Kind : QsCallableKind QualifiedName : QsQualifiedName Attributes : ImmutableArray + Modifiers : Modifiers SourceFile : NonNullable Position : DeclarationHeader.Offset SymbolRange : DeclarationHeader.Range @@ -151,6 +154,7 @@ type CallableDeclarationHeader = { Kind = callable.Kind QualifiedName = callable.FullName Attributes = callable.Attributes + Modifiers = callable.Modifiers SourceFile = callable.SourceFile Position = callable.Location |> DeclarationHeader.CreateOffset SymbolRange = callable.Location |> DeclarationHeader.CreateRange diff --git a/src/QsCompiler/Core/Dependencies.fs b/src/QsCompiler/Core/Dependencies.fs index 36f4bf7d75..1a01f29fd2 100644 --- a/src/QsCompiler/Core/Dependencies.fs +++ b/src/QsCompiler/Core/Dependencies.fs @@ -6,18 +6,22 @@ namespace Microsoft.Quantum.QsCompiler open System.Collections.Immutable open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.SyntaxTree -open Microsoft.Quantum.QsCompiler.SyntaxTokens + + +type BuiltInKind = + | Attribute + | Function of TypeParameters : ImmutableArray> + | Operation of TypeParameters : ImmutableArray> * IsSelfAdjoint : bool type BuiltIn = { - /// contains the name of the callable - Name : NonNullable - /// contains the name of the namespace in which the callable is defined - Namespace : NonNullable - /// contains the names of the type parameters without the leading tick (') - TypeParameters : ImmutableArray> + /// contains the fully qualified name of the built-in + FullName : QsQualifiedName + /// contains the specific kind of built-in this is, as well as information specific to that kind + Kind : BuiltInKind } - with + with + static member CoreNamespace = NonNullable.New "Microsoft.Quantum.Core" static member CanonNamespace = NonNullable.New "Microsoft.Quantum.Canon" static member IntrinsicNamespace = NonNullable.New "Microsoft.Quantum.Intrinsic" @@ -29,191 +33,179 @@ type BuiltIn = { static member NamespacesToAutoOpen = ImmutableHashSet.Create (BuiltIn.CoreNamespace) /// Returns all valid targets for executing Q# code. - static member ValidExecutionTargets = + static member ValidExecutionTargets = // Note: If this is adapted, then the error message for InvalidExecutionTargetForTest needs to be adapted as well. - ["QuantumSimulator"; "ToffoliSimulator"; "ResourcesEstimator"] + ["QuantumSimulator"; "ToffoliSimulator"; "ResourcesEstimator"] |> ImmutableHashSet.CreateRange - /// Returns true if the given attribute marks the corresponding declaration as entry point. - static member MarksEntryPoint (att : QsDeclarationAttribute) = att.TypeId |> function - | Value tId -> tId.Namespace.Value = BuiltIn.EntryPoint.Namespace.Value && tId.Name.Value = BuiltIn.EntryPoint.Name.Value + /// Returns true if the given attribute marks the corresponding declaration as entry point. + static member MarksEntryPoint (att : QsDeclarationAttribute) = att.TypeId |> function + | Value tId -> tId.Namespace.Value = BuiltIn.EntryPoint.FullName.Namespace.Value && tId.Name.Value = BuiltIn.EntryPoint.FullName.Name.Value + | Null -> false + + /// Returns true if the given attribute marks the corresponding declaration as deprecated. + static member MarksDeprecation (att : QsDeclarationAttribute) = att.TypeId |> function + | Value tId -> tId.Namespace.Value = BuiltIn.Deprecated.FullName.Namespace.Value && tId.Name.Value = BuiltIn.Deprecated.FullName.Name.Value | Null -> false - /// Returns true if the given attribute marks the corresponding declaration as deprecated. - static member MarksDeprecation (att : QsDeclarationAttribute) = att.TypeId |> function - | Value tId -> tId.Namespace.Value = BuiltIn.Deprecated.Namespace.Value && tId.Name.Value = BuiltIn.Deprecated.Name.Value + /// Returns true if the given attribute marks the corresponding declaration as unit test. + static member MarksTestOperation (att : QsDeclarationAttribute) = att.TypeId |> function + | Value tId -> tId.Namespace.Value = BuiltIn.Test.FullName.Namespace.Value && tId.Name.Value = BuiltIn.Test.FullName.Name.Value | Null -> false - /// Returns true if the given attribute marks the corresponding declaration as unit test. - static member MarksTestOperation (att : QsDeclarationAttribute) = att.TypeId |> function - | Value tId -> tId.Namespace.Value = BuiltIn.Test.Namespace.Value && tId.Name.Value = BuiltIn.Test.Name.Value + /// Returns true if the given attribute defines an alternative name that may be used when loading a type or callable for testing purposes. + static member internal DefinesNameForTesting (att : QsDeclarationAttribute) = att.TypeId |> function + | Value tId -> tId.Namespace.Value = BuiltIn.EnableTestingViaName.FullName.Namespace.Value && tId.Name.Value = BuiltIn.Test.FullName.Name.Value | Null -> false - // hard dependencies in Microsoft.Quantum.Core + // dependencies in Microsoft.Quantum.Core static member Length = { - Name = "Length" |> NonNullable.New - Namespace = BuiltIn.CoreNamespace - TypeParameters = ImmutableArray.Create("T" |> NonNullable.New) + FullName = {Name = "Length" |> NonNullable.New; Namespace = BuiltIn.CoreNamespace} + Kind = Function (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New)) } static member RangeReverse = { - Name = "RangeReverse" |> NonNullable.New - Namespace = BuiltIn.CoreNamespace - TypeParameters = ImmutableArray.Empty + FullName = {Name = "RangeReverse" |> NonNullable.New; Namespace = BuiltIn.CoreNamespace} + Kind = Function (TypeParameters = ImmutableArray.Empty) } static member Attribute = { - Name = "Attribute" |> NonNullable.New - Namespace = BuiltIn.CoreNamespace - TypeParameters = ImmutableArray.Empty + FullName = {Name = "Attribute" |> NonNullable.New; Namespace = BuiltIn.CoreNamespace} + Kind = Attribute } static member EntryPoint = { - Name = "EntryPoint" |> NonNullable.New - Namespace = BuiltIn.CoreNamespace - TypeParameters = ImmutableArray.Empty + FullName = {Name = "EntryPoint" |> NonNullable.New; Namespace = BuiltIn.CoreNamespace} + Kind = Attribute } static member Deprecated = { - Name = "Deprecated" |> NonNullable.New - Namespace = BuiltIn.CoreNamespace - TypeParameters = ImmutableArray.Empty + FullName = {Name = "Deprecated" |> NonNullable.New; Namespace = BuiltIn.CoreNamespace} + Kind = Attribute } + // dependencies in Microsoft.Quantum.Diagnostics + static member Test = { - Name = "Test" |> NonNullable.New - Namespace = BuiltIn.DiagnosticsNamespace - TypeParameters = ImmutableArray.Empty + FullName = {Name = "Test" |> NonNullable.New; Namespace = BuiltIn.DiagnosticsNamespace} + Kind = Attribute + } + + static member EnableTestingViaName = { + FullName = {Name = "EnableTestingViaName" |> NonNullable.New; Namespace = BuiltIn.DiagnosticsNamespace} + Kind = Attribute } - // hard dependencies in Microsoft.Quantum.Canon + // dependencies in Microsoft.Quantum.Canon static member NoOp = { - Name = "NoOp" |> NonNullable.New - Namespace = BuiltIn.CanonNamespace - TypeParameters = ImmutableArray.Create("T" |> NonNullable.New) + FullName = {Name = "NoOp" |> NonNullable.New; Namespace = BuiltIn.CanonNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New), IsSelfAdjoint = false) } - // hard dependencies in Microsoft.Quantum.Simulation.QuantumProcessor.Extensions + // dependencies in Microsoft.Quantum.Simulation.QuantumProcessor.Extensions // This is expected to have type <'T, 'U>((Result[], Result[], (('T => Unit), 'T) , (('U => Unit), 'U)) => Unit) static member ApplyConditionally = { - Name = "ApplyConditionally" |> NonNullable.New - Namespace = BuiltIn.ClassicallyControlledNamespace - TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New) + FullName = {Name = "ApplyConditionally" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New), IsSelfAdjoint = false) } // This is expected to have type <'T, 'U>((Result[], Result[], (('T => Unit is Adj), 'T) , (('U => Unit is Adj), 'U)) => Unit is Adj) static member ApplyConditionallyA = { - Name = "ApplyConditionallyA" |> NonNullable.New - Namespace = BuiltIn.ClassicallyControlledNamespace - TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New) + FullName = {Name = "ApplyConditionallyA" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New), IsSelfAdjoint = false) } // This is expected to have type <'T, 'U>((Result[], Result[], (('T => Unit is Ctl), 'T) , (('U => Unit is Ctl), 'U)) => Unit is Ctl) static member ApplyConditionallyC = { - Name = "ApplyConditionallyC" |> NonNullable.New - Namespace = BuiltIn.ClassicallyControlledNamespace - TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New) + FullName = {Name = "ApplyConditionallyC" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New), IsSelfAdjoint = false) } // This is expected to have type <'T, 'U>((Result[], Result[], (('T => Unit is Adj + Ctl), 'T) , (('U => Unit is Adj + Ctl), 'U)) => Unit is Adj + Ctl) static member ApplyConditionallyCA = { - Name = "ApplyConditionallyCA" |> NonNullable.New - Namespace = BuiltIn.ClassicallyControlledNamespace - TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New) + FullName = {Name = "ApplyConditionallyCA" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfZero" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfZeroA" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfZeroC" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfZeroCA" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfOne" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfOneA" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfOneC" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfOneCA" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfElseR" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfElseRA" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfElseRC" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New), IsSelfAdjoint = false) } // 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) + FullName = {Name = "ApplyIfElseRCA" |> NonNullable.New; Namespace = BuiltIn.ClassicallyControlledNamespace} + Kind = Operation (TypeParameters = ImmutableArray.Create("T" |> NonNullable.New, "U" |> NonNullable.New), IsSelfAdjoint = false) } - // "weak dependencies" in other namespaces (e.g. things used for code actions) + // dependencies in other namespaces (e.g. things used for code actions) static member IndexRange = { - Name = "IndexRange" |> NonNullable.New - Namespace = BuiltIn.StandardArrayNamespace - TypeParameters = ImmutableArray.Empty + FullName = {Name = "IndexRange" |> NonNullable.New; Namespace = BuiltIn.StandardArrayNamespace} + Kind = Function (TypeParameters = ImmutableArray.Empty) } diff --git a/src/QsCompiler/Core/NamespaceTransformation.fs b/src/QsCompiler/Core/NamespaceTransformation.fs index 471d6bf6d0..e258301ea6 100644 --- a/src/QsCompiler/Core/NamespaceTransformation.fs +++ b/src/QsCompiler/Core/NamespaceTransformation.fs @@ -178,7 +178,7 @@ type NamespaceTransformationBase internal (options : TransformationOptions, _int let specializations = c.Specializations |> Seq.sortBy (fun c -> c.Kind) |> Seq.map this.OnSpecializationDeclaration |> ImmutableArray.CreateRange let doc = this.OnDocumentation c.Documentation let comments = c.Comments - QsCallable.New c.Kind (source, loc) |> Node.BuildOr c (c.FullName, attributes, argTuple, signature, specializations, doc, comments) + QsCallable.New c.Kind (source, loc) |> Node.BuildOr c (c.FullName, attributes, c.Modifiers, argTuple, signature, specializations, doc, comments) abstract member OnOperation : QsCallable -> QsCallable default this.OnOperation c = this.OnCallableKind c @@ -205,7 +205,7 @@ type NamespaceTransformationBase internal (options : TransformationOptions, _int let typeItems = this.OnTypeItems t.TypeItems let doc = this.OnDocumentation t.Documentation let comments = t.Comments - QsCustomType.New (source, loc) |> Node.BuildOr t (t.FullName, attributes, typeItems, underlyingType, doc, comments) + QsCustomType.New (source, loc) |> Node.BuildOr t (t.FullName, attributes, t.Modifiers, typeItems, underlyingType, doc, comments) // transformation roots called on each namespace or namespace element diff --git a/src/QsCompiler/Core/SymbolResolution.fs b/src/QsCompiler/Core/SymbolResolution.fs index 921ecd96f5..da6665f4fc 100644 --- a/src/QsCompiler/Core/SymbolResolution.fs +++ b/src/QsCompiler/Core/SymbolResolution.fs @@ -22,8 +22,8 @@ type AttributeAnnotation = { Comments : QsComments } with - static member internal NonInterpolatedStringArgument inner = function - | Item arg -> inner arg |> function + static member internal NonInterpolatedStringArgument inner = function + | Item arg -> inner arg |> function | StringLiteral (str, interpol) when interpol.Length = 0 -> str.Value | _ -> null | _ -> null @@ -37,6 +37,7 @@ type internal Resolution<'T,'R> = internal { Resolved : QsNullable<'R> DefinedAttributes : ImmutableArray ResolvedAttributes : ImmutableArray + Modifiers : Modifiers Documentation : ImmutableArray } @@ -47,92 +48,197 @@ type internal ResolvedGenerator = internal { Directive : QsNullable } -/// used to group the relevant sets of specializations (i.e. group them according to type- and set-arguments) +/// used to group the relevant sets of specializations (i.e. group them according to type- and set-arguments) type SpecializationBundleProperties = internal { BundleInfo : CallableInformation - DefinedGenerators : ImmutableDictionary -} with + DefinedGenerators : ImmutableDictionary +} with - /// Given the type- and set-arguments associated with a certain specialization, + /// Given the type- and set-arguments associated with a certain specialization, /// determines the corresponding unique identifier for all specializations with the same type- and set-arguments. - static member public BundleId (typeArgs : QsNullable>) = + static member public BundleId (typeArgs : QsNullable>) = typeArgs |> QsNullable<_>.Map (fun args -> (args |> Seq.map (fun t -> t.WithoutRangeInfo)).ToImmutableArray()) - /// Returns an identifier for the bundle to which the given specialization declaration belongs to. - /// Throws an InvalidOperationException if no (partial) resolution is defined for the given specialization. - static member internal BundleId (spec : Resolution<_,_>) = + /// Returns an identifier for the bundle to which the given specialization declaration belongs to. + /// Throws an InvalidOperationException if no (partial) resolution is defined for the given specialization. + static member internal BundleId (spec : Resolution<_,_>) = match spec.Resolved with | Null -> InvalidOperationException "cannot determine id for unresolved specialization" |> raise - | Value (gen : ResolvedGenerator) -> SpecializationBundleProperties.BundleId gen.TypeArguments + | Value (gen : ResolvedGenerator) -> SpecializationBundleProperties.BundleId gen.TypeArguments - /// Given a function that associates an item of the given array with a particular set of type- and set-arguments, - /// as well as a function that associates it with a certain specialization kind, - /// returns a dictionary that contains a dictionary mapping the specialization kind to the corresponding item for each set of type arguments. - /// The keys for the returned dictionary are the BundleIds for particular sets of type- and characteristics-arguments. - static member public Bundle (getTypeArgs : Func<_,_>, getKind : Func<_,QsSpecializationKind>) (specs : IEnumerable<'T>) = + /// Given a function that associates an item of the given array with a particular set of type- and set-arguments, + /// as well as a function that associates it with a certain specialization kind, + /// returns a dictionary that contains a dictionary mapping the specialization kind to the corresponding item for each set of type arguments. + /// The keys for the returned dictionary are the BundleIds for particular sets of type- and characteristics-arguments. + static member public Bundle (getTypeArgs : Func<_,_>, getKind : Func<_,QsSpecializationKind>) (specs : IEnumerable<'T>) = specs.ToLookup(new Func<_,_>(getTypeArgs.Invoke >> SpecializationBundleProperties.BundleId)).ToDictionary( - (fun group -> group.Key), + (fun group -> group.Key), (fun group -> group.ToImmutableDictionary(getKind))) +/// Represents the outcome of resolving a symbol. +type ResolutionResult<'T> = + /// A result indicating that the symbol resolved successfully. + | Found of 'T + /// A result indicating that an unqualified symbol is ambiguous, and it is possible to resolve it to more than one + /// namespace. Includes the list of possible namespaces. + | Ambiguous of NonNullable seq + /// A result indicating that the symbol resolved to a declaration which is not accessible from the location + /// referencing it. + | Inaccessible + /// A result indicating that no declaration with that name was found. + | NotFound + +/// Tools for processing resolution results. +module internal ResolutionResult = + /// Applies the mapping function to the value of a Found result. If the result is not a Found, returns it unchanged. + let internal Map mapping = function + | Found value -> Found (mapping value) + | Ambiguous namespaces -> Ambiguous namespaces + | Inaccessible -> Inaccessible + | NotFound -> NotFound + + /// Converts the resolution result to an option containing the Found value. + let internal ToOption = function + | Found value -> Some value + | _ -> None + + /// Converts the resolution result to a 0- or 1-element list containing the Found value if the result is a Found. + let private ToList = ToOption >> Option.toList + + /// Sorts the sequence of results in order of Found, Ambiguous, Inaccessible, and NotFound. + /// + /// If the sequence contains a Found result, the rest of the sequence after it is not automatically evaluated. + /// Otherwise, the whole sequence may be evaluated by calling sort. + let private Sort results = + let choosers = [ + function | Found value -> Some (Found value) | _ -> None + function | Ambiguous namespaces -> Some (Ambiguous namespaces) | _ -> None + function | Inaccessible -> Some Inaccessible | _ -> None + function | NotFound -> Some NotFound | _ -> None + ] + choosers + |> Seq.map (fun chooser -> Seq.choose chooser results) + |> Seq.concat + + /// Returns the first item of the sequence of results if there is one, or NotFound if the sequence is empty. + let private TryHead<'T> : seq> -> ResolutionResult<'T> = + Seq.tryHead >> Option.defaultValue NotFound + + /// Returns the first Found result in the sequence if it exists. If not, returns the first Ambiguous result if it + /// exists. Repeats for Inaccessible and NotFound in this order. If the sequence is empty, returns NotFound. + let internal TryFirstBest<'T> : seq> -> ResolutionResult<'T> = + Sort >> TryHead + + /// Returns a Found result if there is only one in the sequence. If there is more than one, returns an Ambiguous + /// result containing the namespaces of all Found results given by applying nsGetter to each value. Otherwise, + /// returns the same value as TryFirstBest. + let internal TryExactlyOne<'T> (nsGetter : 'T -> NonNullable) + (results : seq>) : ResolutionResult<'T> = + let found = results |> Seq.filter (function | Found _ -> true | _ -> false) + if Seq.length found > 1 + then found + |> Seq.map (Map nsGetter >> ToList) + |> Seq.concat + |> Ambiguous + else found + |> Seq.tryExactlyOne + |> Option.defaultWith (fun () -> TryFirstBest results) + + /// Returns a Found result if there is only one in the sequence. If there is more than one, raises an exception. + /// Otherwise, returns the same value as ResolutionResult.tryFirst. + let internal ExactlyOne<'T> : seq> -> ResolutionResult<'T> = + TryExactlyOne (fun _ -> NonNullable<_>.New "") >> function + | Ambiguous _ -> QsCompilerError.Raise "Resolution is ambiguous" + Exception () |> raise + | result -> result + + /// Returns true if the resolution result indicates that a resolution exists (Found, Ambiguous or Inaccessible). + let internal Exists = function + | Found _ + | Ambiguous _ + | Inaccessible -> true + | NotFound -> false + + /// Returns true if the resolution result indicates that a resolution is accessible (Found or Ambiguous). + let internal IsAccessible = function + | Found _ + | Ambiguous _ -> true + | Inaccessible + | NotFound -> false + + module SymbolResolution = // routines for giving deprecation warnings - /// Returns true if any one of the given unresolved attributes indicates a deprecation. - let internal IndicatesDeprecation checkQualification attribute = attribute.Id.Symbol |> function - | Symbol sym -> sym.Value = BuiltIn.Deprecated.Name.Value && checkQualification "" - | QualifiedSymbol (ns, sym) -> sym.Value = BuiltIn.Deprecated.Name.Value && (ns.Value = BuiltIn.Deprecated.Namespace.Value || checkQualification ns.Value) + /// Returns true if any one of the given unresolved attributes indicates a deprecation. + let internal IndicatesDeprecation checkQualification attribute = attribute.Id.Symbol |> function + | Symbol sym -> sym.Value = BuiltIn.Deprecated.FullName.Name.Value && checkQualification "" + | QualifiedSymbol (ns, sym) -> sym.Value = BuiltIn.Deprecated.FullName.Name.Value && (ns.Value = BuiltIn.Deprecated.FullName.Namespace.Value || checkQualification ns.Value) | _ -> false - /// Given the redirection extracted by TryFindRedirect, - /// returns an array with diagnostics for using a type or callable with the given name at the given range. - /// Returns an empty array if no redirection was determined, i.e. the given redirection was Null. + /// Given the redirection extracted by TryFindRedirect, + /// returns an array with diagnostics for using a type or callable with the given name at the given range. + /// Returns an empty array if no redirection was determined, i.e. the given redirection was Null. let GenerateDeprecationWarning (fullName : QsQualifiedName, range) redirect = redirect |> function - | Value redirect -> + | Value redirect -> let usedName = sprintf "%s.%s" fullName.Namespace.Value fullName.Name.Value if String.IsNullOrWhiteSpace redirect then [| range |> QsCompilerDiagnostic.Warning (WarningCode.DeprecationWithoutRedirect, [usedName]) |] else [| range |> QsCompilerDiagnostic.Warning (WarningCode.DeprecationWithRedirect, [usedName; redirect]) |] | Null -> [| |] - /// Applies the given getArgument function to the given attributes, - /// and extracts and returns the non-interpolated string argument for all attributes for which getArgument returned Some. - /// The returned sequence may contain null if the argument of an attribute for which getArgument returned Some was not a non-interpolated string. - let private StringArgument (getArgument, getInner) attributes = + /// Applies the given getArgument function to the given attributes, + /// and extracts and returns the non-interpolated string argument for all attributes for which getArgument returned Some. + /// The returned sequence may contain null if the argument of an attribute for which getArgument returned Some was not a non-interpolated string. + let private StringArgument (getArgument, getInner) attributes = let argument = getArgument >> function | Some redirect -> redirect |> AttributeAnnotation.NonInterpolatedStringArgument getInner |> Some | None -> None - attributes |> Seq.choose argument + attributes |> Seq.choose argument - /// Checks whether the given attributes indicate that the corresponding declaration has been deprecated. - /// Returns a string containing the name of the type or callable to use instead as Value if this is the case, or Null otherwise. - /// The returned string is empty or null if the declaration has been deprecated but an alternative is not specified or could not be determined. - /// If several attributes indicate deprecation, a redirection is suggested based on the first deprecation attribute. - let internal TryFindRedirectInUnresolved checkQualification attributes = + /// Checks whether the given attributes indicate that the corresponding declaration has been deprecated. + /// Returns a string containing the name of the type or callable to use instead as Value if this is the case, or Null otherwise. + /// The returned string is empty or null if the declaration has been deprecated but an alternative is not specified or could not be determined. + /// If several attributes indicate deprecation, a redirection is suggested based on the first deprecation attribute. + let internal TryFindRedirectInUnresolved checkQualification attributes = let getRedirect (att : AttributeAnnotation) = if att |> IndicatesDeprecation checkQualification then Some att.Argument else None StringArgument (getRedirect, fun ex -> ex.Expression) attributes |> Seq.tryHead |> QsNullable<_>.FromOption - /// Checks whether the given attributes indicate that the corresponding declaration has been deprecated. - /// Returns a string containing the name of the type or callable to use instead as Value if this is the case, or Null otherwise. - /// The returned string is empty or null if the declaration has been deprecated but an alternative is not specified or could not be determined. - /// If several attributes indicate deprecation, a redirection is suggested based on the first deprecation attribute. - let TryFindRedirect attributes = + /// Checks whether the given attributes indicate that the corresponding declaration has been deprecated. + /// Returns a string containing the name of the type or callable to use instead as Value if this is the case, or Null otherwise. + /// The returned string is empty or null if the declaration has been deprecated but an alternative is not specified or could not be determined. + /// If several attributes indicate deprecation, a redirection is suggested based on the first deprecation attribute. + let TryFindRedirect attributes = let getRedirect (att : QsDeclarationAttribute) = if att |> BuiltIn.MarksDeprecation then Some att.Argument else None StringArgument (getRedirect, fun ex -> ex.Expression) attributes |> Seq.tryHead |> QsNullable<_>.FromOption - /// Checks whether the given attributes indicate that the corresponding declaration contains a unit test. - /// Returns a sequence of strings defining all execution targets on which the test should be run. Invalid execution targets are set to null. - /// The returned sequence is empty if the declaration does not contain a unit test. - let TryFindTestTargets attributes = + /// Checks whether the given attributes define an alternative name that may be used when loading a type or callable for testing purposes. + /// Returns the qualified name to use as Value if such a name is defined, or Null otherwise. + /// If several attributes define such a name the returned Value is based on the first such attribute. + let TryGetTestName attributes = + let getTestName (att : QsDeclarationAttribute) = if att |> BuiltIn.DefinesNameForTesting then Some att.Argument else None + let qualifiedName testName = + let matchQualifiedName = SyntaxGenerator.FullyQualifiedName.Match testName + let asQualifiedName (str : string) = + let pieces = str.Split '.' + {Namespace = String.Join('.', pieces.Take(pieces.Length-1)) |> NonNullable.New; Name = pieces.Last() |> NonNullable.New} + if matchQualifiedName.Success then Some (matchQualifiedName.Value |> asQualifiedName) else None + StringArgument (getTestName, fun ex -> ex.Expression) attributes |> Seq.tryHead |> Option.bind qualifiedName |> QsNullable<_>.FromOption + + /// Checks whether the given attributes indicate that the corresponding declaration contains a unit test. + /// Returns a sequence of strings defining all execution targets on which the test should be run. Invalid execution targets are set to null. + /// The returned sequence is empty if the declaration does not contain a unit test. + let TryFindTestTargets attributes = let getTarget (att : QsDeclarationAttribute) = if att |> BuiltIn.MarksTestOperation then Some att.Argument else None let validTargets = BuiltIn.ValidExecutionTargets.ToImmutableDictionary(fun t -> t.ToLowerInvariant()) - let targetName (target : string) = + let targetName (target : string) = if target = null then null elif SyntaxGenerator.FullyQualifiedName.IsMatch target then target else target.ToLowerInvariant() |> validTargets.TryGetValue |> function | true, valid -> valid | false, _ -> null - StringArgument (getTarget, fun ex -> ex.Expression) attributes + StringArgument (getTarget, fun ex -> ex.Expression) attributes |> Seq.map targetName |> ImmutableHashSet.CreateRange @@ -142,7 +248,7 @@ module SymbolResolution = let rec ResolveCharacteristics (ex : Characteristics) = // needs to preserve set parameters match ex.Characteristics with | EmptySet -> ResolvedCharacteristics.Empty - | SimpleSet s -> SimpleSet s |> ResolvedCharacteristics.New + | SimpleSet s -> SimpleSet s |> ResolvedCharacteristics.New | Intersection (s1, s2) -> Intersection (ResolveCharacteristics s1, ResolveCharacteristics s2) |> ResolvedCharacteristics.New | Union (s1, s2) -> Union (ResolveCharacteristics s1, ResolveCharacteristics s2) |> ResolvedCharacteristics.New | InvalidSetExpr -> InvalidSetExpr |> ResolvedCharacteristics.New @@ -153,35 +259,35 @@ module SymbolResolution = let ts, errs = (inner.Select fst).ToImmutableArray(), inner.Select snd |> Array.concat build ts, errs - /// Helper function for ResolveCallableSignature that verifies whether the given type parameters and the given return type - /// are fully resolved by an argument of the given the argument type. Generates and returns suitable warnings if this is not the case. - let private TypeParameterResolutionWarnings (argumentType : ResolvedType) (returnType : ResolvedType, range) typeParams = + /// Helper function for ResolveCallableSignature that verifies whether the given type parameters and the given return type + /// are fully resolved by an argument of the given the argument type. Generates and returns suitable warnings if this is not the case. + let private TypeParameterResolutionWarnings (argumentType : ResolvedType) (returnType : ResolvedType, range) typeParams = // FIXME: this verification needs to be done for each specialization individually once type specializations are fully supported - let typeParamsResolvedByArg = + let typeParamsResolvedByArg = let getTypeParams (t : ResolvedType) = t.Resolution |> function - | QsTypeKind.TypeParameter (tp : QsTypeParameter) -> [tp.TypeName].AsEnumerable() + | QsTypeKind.TypeParameter (tp : QsTypeParameter) -> [tp.TypeName].AsEnumerable() | _ -> Enumerable.Empty() argumentType.ExtractAll getTypeParams |> Seq.toList - let excessTypeParamWarn = + let excessTypeParamWarn = let isUnresolvedByArg = function | (ValidName name, range) -> if typeParamsResolvedByArg.Contains name then None else range |> QsCompilerDiagnostic.Warning (WarningCode.TypeParameterNotResolvedByArgument, []) |> Some | _ -> None typeParams |> List.choose isUnresolvedByArg - let unresolvableReturnType = - let isUnresolved = function - | QsTypeKind.TypeParameter (tp : QsTypeParameter) -> not (typeParamsResolvedByArg |> List.contains tp.TypeName) + let unresolvableReturnType = + let isUnresolved = function + | QsTypeKind.TypeParameter (tp : QsTypeParameter) -> not (typeParamsResolvedByArg |> List.contains tp.TypeName) | _ -> false returnType.Exists isUnresolved let returnTypeErr = range |> QsCompilerDiagnostic.Warning (WarningCode.ReturnTypeNotResolvedByArgument, []) if unresolvableReturnType then returnTypeErr :: excessTypeParamWarn |> List.toArray else excessTypeParamWarn |> List.toArray - /// Helper function for ResolveCallableSignature that resolves the given argument tuple - /// using the given routine to resolve the declared item types. - /// Throws an ArgumentException if the given argument is a QsTupleItem opposed to a QsTuple. - let private ResolveArgumentTuple (resolveSymbol, resolveType) arg = - let resolveArg (qsSym : QsSymbol, symType) = + /// Helper function for ResolveCallableSignature that resolves the given argument tuple + /// using the given routine to resolve the declared item types. + /// Throws an ArgumentException if the given argument is a QsTupleItem opposed to a QsTuple. + let private ResolveArgumentTuple (resolveSymbol, resolveType) arg = + let resolveArg (qsSym : QsSymbol, symType) = let range = qsSym.Range.ValueOr QsCompilerDiagnostic.DefaultRange let t, tErrs = symType |> resolveType let variable, symErrs = resolveSymbol (qsSym.Symbol, range) t @@ -196,26 +302,26 @@ module SymbolResolution = | _ -> ArgumentException "the argument to a callable needs to be a QsTuple" |> raise /// Returns the LocalVariableDeclaration with the given name and type for an item within a type or callable declaration. - /// Correspondingly, the item is immutable, has no quantum dependencies, - /// the position information is set to null, and the range is set to the given one. + /// Correspondingly, the item is immutable, has no quantum dependencies, + /// the position information is set to null, and the range is set to the given one. let private DeclarationArgument range (name, t) = let info = {IsMutable = false; HasLocalQuantumDependency = false} {VariableName = name; Type = t; InferredInformation = info; Position = Null; Range = range} - /// Give a list with the characteristics of all specializations as well as a routine for type resolution, + /// Give a list with the characteristics of all specializations as well as a routine for type resolution, /// fully resolves the given callable signature as well as its argument tuple. - /// The position offset information for variables declared in the argument tuple will be set to Null. + /// The position offset information for variables declared in the argument tuple will be set to Null. /// Returns the resolved signature and argument tuple, as well as an array with the diagnostics created during resolution. - /// Throws an ArgumentException if the given list of specialization characteristics is empty. + /// Throws an ArgumentException if the given list of specialization characteristics is empty. let internal ResolveCallableSignature (resolveType, specBundleInfos : CallableInformation list) (signature : CallableSignature) = - let orDefault (range : QsNullable<_>) = range.ValueOr QsCompilerDiagnostic.DefaultRange - let typeParams, tpErrs = - signature.TypeParameters |> Seq.fold (fun (tps, errs) qsSym -> + let orDefault (range : QsNullable<_>) = range.ValueOr QsCompilerDiagnostic.DefaultRange + let typeParams, tpErrs = + signature.TypeParameters |> Seq.fold (fun (tps, errs) qsSym -> let range = qsSym.Range |> orDefault let invalidTp = InvalidName, range - match qsSym.Symbol with + match qsSym.Symbol with | QsSymbolKind.InvalidSymbol -> invalidTp :: tps, errs - | QsSymbolKind.Symbol sym -> + | QsSymbolKind.Symbol sym -> if not (tps |> List.exists (fst >> (=)(ValidName sym))) then (ValidName sym, range) :: tps, errs else invalidTp :: tps, (range |> QsCompilerDiagnostic.Error (ErrorCode.TypeParameterRedeclaration, [sym.Value])) :: errs | _ -> invalidTp :: tps, (range |> QsCompilerDiagnostic.Error (ErrorCode.InvalidTypeParameterDeclaration, [])) :: errs @@ -231,7 +337,7 @@ module SymbolResolution = let argTuple, inErr = signature.Argument |> ResolveArgumentTuple (resolveArg, resolveType) let argType = argTuple.ResolveWith (fun x -> x.Type.WithoutRangeInfo) let returnType, outErr = signature.ReturnType |> resolveType - let resolvedParams, resErrs = + let resolvedParams, resErrs = let errs = TypeParameterResolutionWarnings argType (returnType, signature.ReturnType.Range |> orDefault) typeParams (typeParams |> Seq.map fst).ToImmutableArray(), errs let callableInfo = CallableInformation.Common specBundleInfos @@ -239,28 +345,28 @@ module SymbolResolution = (resolvedSig, argTuple), [inErr; outErr; resErrs; tpErrs] |> Array.concat /// Give a routine for type resolution, fully resolves the given user defined type as well as its items. - /// The position offset information for the declared named items will be set to Null. + /// The position offset information for the declared named items will be set to Null. /// Returns the underlying type as well as the item tuple, along with an array with the diagnostics created during resolution. - /// Throws an ArgumentException if the given type tuple is an empty QsTuple. - let internal ResolveTypeDeclaration resolveType (udtTuple : QsTuple) = + /// Throws an ArgumentException if the given type tuple is an empty QsTuple. + let internal ResolveTypeDeclaration resolveType (udtTuple : QsTuple) = let itemDeclarations = new List>>() let resolveItem (sym, range) t = sym |> function - | QsSymbolKind.MissingSymbol + | QsSymbolKind.MissingSymbol | QsSymbolKind.InvalidSymbol -> Anonymous t, [||] - | QsSymbolKind.Symbol sym when itemDeclarations.Exists (fun item -> item.VariableName.Value = sym.Value) -> + | QsSymbolKind.Symbol sym when itemDeclarations.Exists (fun item -> item.VariableName.Value = sym.Value) -> Anonymous t, [| range |> QsCompilerDiagnostic.Error (ErrorCode.NamedItemAlreadyExists, [sym.Value]) |] - | QsSymbolKind.Symbol sym -> + | QsSymbolKind.Symbol sym -> let info = {IsMutable = false; HasLocalQuantumDependency = false} itemDeclarations.Add { VariableName = sym; Type = t; InferredInformation = info; Position = Null; Range = range } - (sym, t) |> DeclarationArgument range |> Named, [||] + (sym, t) |> DeclarationArgument range |> Named, [||] | _ -> Anonymous t, [| range |> QsCompilerDiagnostic.Error (ErrorCode.ExpectingUnqualifiedSymbol, []) |] - let argTuple, errs = udtTuple |> function + let argTuple, errs = udtTuple |> function | QsTuple items when items.Length = 0 -> ArgumentException "underlying type in type declaration cannot be an empty tuple" |> raise | QsTuple _ -> udtTuple |> ResolveArgumentTuple (resolveItem, resolveType) | QsTupleItem _ -> ImmutableArray.Create udtTuple |> QsTuple |> ResolveArgumentTuple (resolveItem, resolveType) - let underlyingType = argTuple.ResolveWith (function + let underlyingType = argTuple.ResolveWith (function | Anonymous t -> t.WithoutRangeInfo - | Named x -> x.Type.WithoutRangeInfo) + | Named x -> x.Type.WithoutRangeInfo) (underlyingType, argTuple), errs /// Fully (i.e. recursively) resolves the given Q# type used within the given parent in the given source file. @@ -272,7 +378,7 @@ module SymbolResolution = /// Returns the resolved type as well as an array with diagnostics. /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is inconsistent with the defined callables. /// May throw an ArgumentException if no namespace with the given name exists, or the given source file is not listed as source of that namespace. - /// Throws a NonSupportedException if the QsType to resolve contains a MissingType. + /// Throws a NotSupportedException if the QsType to resolve contains a MissingType. let rec internal ResolveType (processUDT, processTypeParameter) (qsType : QsType) = let resolve = ResolveType (processUDT, processTypeParameter) let asResolvedType t = ResolvedType.New (true, t) @@ -280,14 +386,14 @@ module SymbolResolution = let invalid = InvalidType |> asResolvedType let range = qsType.Range.ValueOr QsCompilerDiagnostic.DefaultRange - match qsType.Type with + match qsType.Type with | ArrayType baseType -> [baseType] |> AccumulateInner resolve (buildWith (fun ts -> ArrayType ts.[0])) - | TupleType items -> items |> AccumulateInner resolve (buildWith TupleType) - | QsTypeKind.TypeParameter sym -> sym.Symbol |> function + | TupleType items -> items |> AccumulateInner resolve (buildWith TupleType) + | QsTypeKind.TypeParameter sym -> sym.Symbol |> function | Symbol name -> processTypeParameter (name, sym.Range) |> fun (k, errs) -> k |> asResolvedType, errs | InvalidSymbol -> invalid, [||] | _ -> invalid, [| range |> QsCompilerDiagnostic.Error (ErrorCode.ExpectingUnqualifiedSymbol, []) |] - | QsTypeKind.Operation ((arg,res), characteristics) -> + | QsTypeKind.Operation ((arg,res), characteristics) -> let opInfo = {Characteristics = characteristics |> ResolveCharacteristics; InferredInformation = InferredCallableInformation.NoInformation} let builder (ts : ImmutableArray<_>) = QsTypeKind.Operation ((ts.[0], ts.[1]), opInfo) [arg; res] |> AccumulateInner resolve (buildWith builder) @@ -297,25 +403,25 @@ module SymbolResolution = | QualifiedSymbol (ns, sym) -> processUDT ((Some ns, sym), name.Range) |> fun (k, errs) -> k |> asResolvedType, errs | InvalidSymbol -> invalid, [||] | MissingSymbol | OmittedSymbols | SymbolTuple _ -> invalid, [| range |> QsCompilerDiagnostic.Error (ErrorCode.ExpectingIdentifier, []) |] - | UnitType -> QsTypeKind.UnitType |> asResolvedType, [||] - | Int -> QsTypeKind.Int |> asResolvedType, [||] - | BigInt -> QsTypeKind.BigInt |> asResolvedType, [||] - | Double -> QsTypeKind.Double |> asResolvedType, [||] - | Bool -> QsTypeKind.Bool |> asResolvedType, [||] - | String -> QsTypeKind.String |> asResolvedType, [||] - | Qubit -> QsTypeKind.Qubit |> asResolvedType, [||] - | Result -> QsTypeKind.Result |> asResolvedType, [||] - | Pauli -> QsTypeKind.Pauli |> asResolvedType, [||] - | Range -> QsTypeKind.Range |> asResolvedType, [||] - | InvalidType -> QsTypeKind.InvalidType |> asResolvedType, [||] - | MissingType -> NotSupportedException "missing type cannot be resolved" |> raise - - /// Resolves the given attribute using the given function getAttribute to resolve the type id and expected argument type. - /// Generates suitable diagnostics if a suitable attribute cannot be determined, - /// or if the attribute argument contains expressions that are not supported, - /// or if the resolved argument type does not match the expected argument type. - /// The TypeId in the resolved attribute is set to Null if the unresolved Id is not a valid identifier - /// or if the correct attribute cannot be determined, and is set to the corresponding type identifier otherwise. + | UnitType -> QsTypeKind.UnitType |> asResolvedType, [||] + | Int -> QsTypeKind.Int |> asResolvedType, [||] + | BigInt -> QsTypeKind.BigInt |> asResolvedType, [||] + | Double -> QsTypeKind.Double |> asResolvedType, [||] + | Bool -> QsTypeKind.Bool |> asResolvedType, [||] + | String -> QsTypeKind.String |> asResolvedType, [||] + | Qubit -> QsTypeKind.Qubit |> asResolvedType, [||] + | Result -> QsTypeKind.Result |> asResolvedType, [||] + | Pauli -> QsTypeKind.Pauli |> asResolvedType, [||] + | Range -> QsTypeKind.Range |> asResolvedType, [||] + | InvalidType -> QsTypeKind.InvalidType |> asResolvedType, [||] + | MissingType -> NotSupportedException "missing type cannot be resolved" |> raise + + /// Resolves the given attribute using the given function getAttribute to resolve the type id and expected argument type. + /// Generates suitable diagnostics if a suitable attribute cannot be determined, + /// or if the attribute argument contains expressions that are not supported, + /// or if the resolved argument type does not match the expected argument type. + /// The TypeId in the resolved attribute is set to Null if the unresolved Id is not a valid identifier + /// or if the correct attribute cannot be determined, and is set to the corresponding type identifier otherwise. let internal ResolveAttribute getAttribute (attribute : AttributeAnnotation) = let asTypedExression range (exKind, exType) = { Expression = exKind @@ -323,16 +429,16 @@ module SymbolResolution = ResolvedType = exType |> ResolvedType.New InferredInformation = {IsMutable = false; HasLocalQuantumDependency = false} Range = range} - let invalidExpr range = (InvalidExpr, InvalidType) |> asTypedExression range + let invalidExpr range = (InvalidExpr, InvalidType) |> asTypedExression range let orDefault (range : QsNullable<_>) = range.ValueOr QsCompilerDiagnostic.DefaultRange - // We may in the future decide to support arbitary expressions as long as they can be evaluated at compile time. - // At that point it may make sense to replace this with the standard resolution routine for typed expressions. - // For now we support only a restrictive set of valid arguments. - let rec ArgExression (ex : QsExpression) : TypedExpression * QsCompilerDiagnostic[] = + // We may in the future decide to support arbitary expressions as long as they can be evaluated at compile time. + // At that point it may make sense to replace this with the standard resolution routine for typed expressions. + // For now we support only a restrictive set of valid arguments. + let rec ArgExression (ex : QsExpression) : TypedExpression * QsCompilerDiagnostic[] = let diagnostic code range = range |> orDefault |> QsCompilerDiagnostic.Error (code, []) // NOTE: if this is adapted, adapt the header hash as well - match ex.Expression with + match ex.Expression with | UnitValue -> (UnitValue, UnitType) |> asTypedExression ex.Range, [||] | DoubleLiteral d -> (DoubleLiteral d, Double) |> asTypedExression ex.Range, [||] | IntLiteral i -> (IntLiteral i, Int) |> asTypedExression ex.Range, [||] @@ -343,109 +449,109 @@ module SymbolResolution = | StringLiteral (s, exs) -> if exs.Length <> 0 then invalidExpr ex.Range, [| ex.Range |> diagnostic ErrorCode.InterpolatedStringInAttribute |] else (StringLiteral (s, ImmutableArray.Empty), String) |> asTypedExression ex.Range, [||] - | ValueTuple vs -> + | ValueTuple vs -> let innerExs, errs = aggregateInner vs let types = (innerExs |> Seq.map (fun ex -> ex.ResolvedType)).ToImmutableArray() (ValueTuple innerExs, TupleType types) |> asTypedExression ex.Range, errs | ValueArray vs -> let innerExs, errs = aggregateInner vs - // we can make the following simple check since / as long as there is no variance behavior + // we can make the following simple check since / as long as there is no variance behavior // for any of the supported attribute argument types - let typeIfValid (ex : TypedExpression) = + let typeIfValid (ex : TypedExpression) = match ex.ResolvedType.Resolution with | InvalidType -> None | _ -> Some ex.ResolvedType - match innerExs |> Seq.choose typeIfValid |> Seq.distinct |> Seq.toList with + match innerExs |> Seq.choose typeIfValid |> Seq.distinct |> Seq.toList with | [bt] -> (ValueArray innerExs, ArrayType bt) |> asTypedExression ex.Range, errs | [] when innerExs.Length <> 0 -> (ValueArray innerExs, ResolvedType.New InvalidType |> ArrayType) |> asTypedExression ex.Range, errs | [] -> invalidExpr ex.Range, errs |> Array.append [| ex.Range |> diagnostic ErrorCode.EmptyValueArray |] - | _ -> invalidExpr ex.Range, errs |> Array.append [| ex.Range |> diagnostic ErrorCode.ArrayBaseTypeMismatch |] - | NewArray (bt, idx) -> + | _ -> invalidExpr ex.Range, errs |> Array.append [| ex.Range |> diagnostic ErrorCode.ArrayBaseTypeMismatch |] + | NewArray (bt, idx) -> let onUdt (_, udtRange) = InvalidType, [| udtRange |> diagnostic ErrorCode.ArgumentOfUserDefinedTypeInAttribute |] - let onTypeParam (_, tpRange) = InvalidType, [| tpRange |> diagnostic ErrorCode.TypeParameterizedArgumentInAttribute |] - let resBaseType, typeErrs = ResolveType (onUdt, onTypeParam) bt + let onTypeParam (_, tpRange) = InvalidType, [| tpRange |> diagnostic ErrorCode.TypeParameterizedArgumentInAttribute |] + let resBaseType, typeErrs = ResolveType (onUdt, onTypeParam) bt let resIdx, idxErrs = ArgExression idx (NewArray (resBaseType, resIdx), ArrayType resBaseType) |> asTypedExression ex.Range, Array.concat [typeErrs; idxErrs] // TODO: detect constructor calls | InvalidExpr -> invalidExpr ex.Range, [||] - | _ -> invalidExpr ex.Range, [| ex.Range |> diagnostic ErrorCode.InvalidAttributeArgument |] - and aggregateInner vs = + | _ -> invalidExpr ex.Range, [| ex.Range |> diagnostic ErrorCode.InvalidAttributeArgument |] + and aggregateInner vs = let innerExs, errs = vs |> Seq.map ArgExression |> Seq.toList |> List.unzip innerExs.ToImmutableArray(), Array.concat errs - // Any user defined type that has been decorated with the attribute + // Any user defined type that has been decorated with the attribute // "Attribute" defined in Microsoft.Quantum.Core may be used as attribute. let resArg, argErrs = ArgExression attribute.Argument let buildAttribute id = {TypeId = id; Argument = resArg; Offset = attribute.Position; Comments = attribute.Comments} let getAttribute (ns, sym) = getAttribute ((ns, sym), attribute.Id.Range) |> function | None, errs -> Null |> buildAttribute, errs |> Array.append argErrs | Some (name, argType : ResolvedType), errs -> - // we can make the following simple check since / as long as there is no variance behavior + // we can make the following simple check since / as long as there is no variance behavior // for any of the supported attribute argument types let isError (msg : QsCompilerDiagnostic) = msg.Diagnostic |> function | Error _ -> true | _ -> false let argIsInvalid = resArg.ResolvedType.Resolution = InvalidType || argErrs |> Array.exists isError if resArg.ResolvedType.WithoutRangeInfo <> argType.WithoutRangeInfo && not argIsInvalid then let mismatchErr = attribute.Argument.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.AttributeArgumentTypeMismatch, []) - Value name |> buildAttribute, Array.concat [errs; argErrs; [| mismatchErr |]] + Value name |> buildAttribute, Array.concat [errs; argErrs; [| mismatchErr |]] else Value name |> buildAttribute, errs |> Array.append argErrs - match attribute.Id.Symbol with - | Symbol sym -> getAttribute (None, sym) + match attribute.Id.Symbol with + | Symbol sym -> getAttribute (None, sym) | QualifiedSymbol (ns, sym) -> getAttribute (Some ns, sym) | InvalidSymbol -> Null |> buildAttribute, argErrs - | MissingSymbol | OmittedSymbols | SymbolTuple _ -> - Null |> buildAttribute, [| attribute.Id.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.InvalidAttributeIdentifier, []) |] + | MissingSymbol | OmittedSymbols | SymbolTuple _ -> + Null |> buildAttribute, [| attribute.Id.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.InvalidAttributeIdentifier, []) |] // private routines for resolving specialization generation directives /// Resolves the given specialization generator to a suitable implementation under the assumption - /// that at least one specialization for the same type- and set-arguments has been declared as intrinsic. - /// In particular, resolves the given generator to either and intrinsic implementation, + /// that at least one specialization for the same type- and set-arguments has been declared as intrinsic. + /// In particular, resolves the given generator to either and intrinsic implementation, /// or to the generator directive "self" if allowSelf is set to true. - /// Returns the generated implementation as Value, along with an array of diagnostics. - /// Does *not* generate diagnostics for things that do not require semantic information to detect - /// (these should be detected and raised upon context verification). - let private NeedsToBeIntrinsic (gen : QsSpecializationGenerator, allowSelf) = + /// Returns the generated implementation as Value, along with an array of diagnostics. + /// Does *not* generate diagnostics for things that do not require semantic information to detect + /// (these should be detected and raised upon context verification). + let private NeedsToBeIntrinsic (gen : QsSpecializationGenerator, allowSelf) = let genRange = gen.Range.ValueOr QsCompilerDiagnostic.DefaultRange let isSelfInverse = function | SelfInverse -> true | _ -> false let isInvalid = function | InvalidGenerator -> true | _ -> false - match gen.Generator with + match gen.Generator with | QsSpecializationGeneratorKind.Intrinsic | QsSpecializationGeneratorKind.AutoGenerated -> Intrinsic |> Value, [||] - | QsSpecializationGeneratorKind.UserDefinedImplementation _ -> - Intrinsic |> Value, [| genRange |> QsCompilerDiagnostic.Error (ErrorCode.UserDefinedImplementationForIntrinsic, []) |] - | QsSpecializationGeneratorKind.FunctorGenerationDirective dir -> + | QsSpecializationGeneratorKind.UserDefinedImplementation _ -> + Intrinsic |> Value, [| genRange |> QsCompilerDiagnostic.Error (ErrorCode.UserDefinedImplementationForIntrinsic, []) |] + | QsSpecializationGeneratorKind.FunctorGenerationDirective dir -> if isSelfInverse dir then (if allowSelf then Generated SelfInverse else Intrinsic) |> Value, [||] // a context error is raised if self is not valid elif isInvalid dir then Intrinsic |> Value, [||] else Intrinsic |> Value, [| genRange |> QsCompilerDiagnostic.Warning (WarningCode.GeneratorDirectiveWillBeIgnored, []) |] - /// Resolves the given specialization generator to a "self" generator directive, - /// and returns it as Value along with an array of diagnostics. - /// Does *not* generate diagnostics for things that do not require semantic information to detect - /// (these should be detected and raised upon context verification). - let private NeedsToBeSelfInverse (gen : QsSpecializationGenerator) = + /// Resolves the given specialization generator to a "self" generator directive, + /// and returns it as Value along with an array of diagnostics. + /// Does *not* generate diagnostics for things that do not require semantic information to detect + /// (these should be detected and raised upon context verification). + let private NeedsToBeSelfInverse (gen : QsSpecializationGenerator) = let genRange = gen.Range.ValueOr QsCompilerDiagnostic.DefaultRange let isSelfInverse = function | SelfInverse -> true | _ -> false let isInvalid = function | InvalidGenerator -> true | _ -> false let diagnostics = gen.Generator |> function | QsSpecializationGeneratorKind.Intrinsic | QsSpecializationGeneratorKind.AutoGenerated -> [||] - | QsSpecializationGeneratorKind.UserDefinedImplementation _ -> - [| genRange |> QsCompilerDiagnostic.Error (ErrorCode.NonSelfGeneratorForSelfadjoint, []) |] - | QsSpecializationGeneratorKind.FunctorGenerationDirective dir -> - if isSelfInverse dir || isInvalid dir then [||] + | QsSpecializationGeneratorKind.UserDefinedImplementation _ -> + [| genRange |> QsCompilerDiagnostic.Error (ErrorCode.NonSelfGeneratorForSelfadjoint, []) |] + | QsSpecializationGeneratorKind.FunctorGenerationDirective dir -> + if isSelfInverse dir || isInvalid dir then [||] else [| genRange |> QsCompilerDiagnostic.Error (ErrorCode.NonSelfGeneratorForSelfadjoint, []) |] Generated SelfInverse |> Value, diagnostics - /// Given the generator of a body specialization declaration, - /// returns Null if the generator indicates a user defined specialization, and returns the resolved implementation as Value otherwise. - /// Generates and returns an array of diagnostics. - /// If containsIntrinsic is set to true, generates a suitable error if the generator to resolve is incompatible, and resolves it to "intrinsic". - /// The resolution corresponds to an invalid generator directive unless the generator is either intrinsic, user defined or "auto". - /// Does *not* generate diagnostics for things that do not require semantic information to detect - /// (these should be detected and raised upon context verification). - let private ResolveBodyGeneratorDirective (opInfo : InferredCallableInformation) (gen : QsSpecializationGenerator) = + /// Given the generator of a body specialization declaration, + /// returns Null if the generator indicates a user defined specialization, and returns the resolved implementation as Value otherwise. + /// Generates and returns an array of diagnostics. + /// If containsIntrinsic is set to true, generates a suitable error if the generator to resolve is incompatible, and resolves it to "intrinsic". + /// The resolution corresponds to an invalid generator directive unless the generator is either intrinsic, user defined or "auto". + /// Does *not* generate diagnostics for things that do not require semantic information to detect + /// (these should be detected and raised upon context verification). + let private ResolveBodyGeneratorDirective (opInfo : InferredCallableInformation) (gen : QsSpecializationGenerator) = if opInfo.IsIntrinsic then NeedsToBeIntrinsic (gen, false) else gen.Generator |> function | QsSpecializationGeneratorKind.Intrinsic -> Intrinsic |> Value, [||] @@ -454,15 +560,15 @@ module SymbolResolution = | Distribute | SelfInverse | Invert | InvalidGenerator -> Generated InvalidGenerator |> Value, [||] // a context error is raised in this case | QsSpecializationGeneratorKind.AutoGenerated -> Intrinsic |> Value, [||] // todo: generate based on controlled if possible? - /// Given the generator of an adjoint specialization declaration, - /// returns Null if the generator indicates a user defined specialization, and returns the resolved implementation as Value otherwise. - /// Generates and returns an array of diagnostics. + /// Given the generator of an adjoint specialization declaration, + /// returns Null if the generator indicates a user defined specialization, and returns the resolved implementation as Value otherwise. + /// Generates and returns an array of diagnostics. /// If containsIntrinsic is set to true, generates a suitable error if the generator to resolve is incompatible, - /// and resolves it to either "intrinsic" or - if containsSelfInverse is set to true - to "self". + /// and resolves it to either "intrinsic" or - if containsSelfInverse is set to true - to "self". /// Otherwise it resolves any valid directive to either an "invert" or a "self" directive depending on whether containsSelfInverse is set to true. - /// Does *not* generate diagnostics for things that do not require semantic information to detect - /// (these should be detected and raised upon context verification). - let private ResolveAdjointGeneratorDirective (info : InferredCallableInformation) (gen : QsSpecializationGenerator) = + /// Does *not* generate diagnostics for things that do not require semantic information to detect + /// (these should be detected and raised upon context verification). + let private ResolveAdjointGeneratorDirective (info : InferredCallableInformation) (gen : QsSpecializationGenerator) = if info.IsSelfAdjoint then NeedsToBeSelfInverse gen elif info.IsIntrinsic then NeedsToBeIntrinsic (gen, true) else gen.Generator |> function @@ -470,16 +576,16 @@ module SymbolResolution = | QsSpecializationGeneratorKind.UserDefinedImplementation _ -> Null, [||] | QsSpecializationGeneratorKind.FunctorGenerationDirective dir -> dir |> function | Distribute -> Generated InvalidGenerator |> Value, [||] // a context error is raised in this case - | SelfInverse | Invert | InvalidGenerator -> Generated dir |> Value, [||] + | SelfInverse | Invert | InvalidGenerator -> Generated dir |> Value, [||] | QsSpecializationGeneratorKind.AutoGenerated -> Generated Invert |> Value, [||] - /// Given the generator of a controlled specialization declaration, - /// returns Null if the generator indicates a user defined specialization, and returns the resolved implementation as Value otherwise. - /// Generates and returns an array of diagnostics. - /// If containsIntrinsic is set to true, generates a suitable error if the generator to resolve is incompatible, and resolves it to "intrinsic". - /// Otherwise resolves any valid directive to a "distribute" directive. - /// Does *not* generate diagnostics for things that do not require semantic information to detect - /// (these should be detected and raised upon context verification). + /// Given the generator of a controlled specialization declaration, + /// returns Null if the generator indicates a user defined specialization, and returns the resolved implementation as Value otherwise. + /// Generates and returns an array of diagnostics. + /// If containsIntrinsic is set to true, generates a suitable error if the generator to resolve is incompatible, and resolves it to "intrinsic". + /// Otherwise resolves any valid directive to a "distribute" directive. + /// Does *not* generate diagnostics for things that do not require semantic information to detect + /// (these should be detected and raised upon context verification). let private ResolveControlledGeneratorDirective (info : InferredCallableInformation) (gen : QsSpecializationGenerator) = if info.IsIntrinsic then NeedsToBeIntrinsic (gen, false) else gen.Generator |> function @@ -487,21 +593,21 @@ module SymbolResolution = | QsSpecializationGeneratorKind.UserDefinedImplementation _ -> Null, [||] | QsSpecializationGeneratorKind.FunctorGenerationDirective dir -> dir |> function | SelfInverse | Invert -> Generated InvalidGenerator |> Value, [||] // a context error is raised in this case - | Distribute | InvalidGenerator -> Generated dir |> Value, [||] + | Distribute | InvalidGenerator -> Generated dir |> Value, [||] | QsSpecializationGeneratorKind.AutoGenerated -> Generated Distribute |> Value, [||] - /// Given the generator of a controlled adjoint specialization declaration, - /// returns Null if the generator indicates a user defined specialization, and returns the resolved implementation as Value otherwise. - /// Generates and returns an array of diagnostics. + /// Given the generator of a controlled adjoint specialization declaration, + /// returns Null if the generator indicates a user defined specialization, and returns the resolved implementation as Value otherwise. + /// Generates and returns an array of diagnostics. /// If containsIntrinsic is set to true, generates a suitable error if the generator to resolve is incompatible, - /// and resolves it to either "intrinsic" or - if containsSelfInverse is set to true - to "self". - /// If this is not the case, all specialization generator directives are resolved to themselves unless they are specified as "auto". - /// If an automatic determination of a suitable directive is requested (as indicated by "auto"), then it is resolved to + /// and resolves it to either "intrinsic" or - if containsSelfInverse is set to true - to "self". + /// If this is not the case, all specialization generator directives are resolved to themselves unless they are specified as "auto". + /// If an automatic determination of a suitable directive is requested (as indicated by "auto"), then it is resolved to /// a) a self inverse directive, if the corresponding adjoint specialization is self inverse, and - /// b) to "invert" if the corresponding adjoint specialization is compiler generated but the controlled version is user defined, and - /// b) to a "distribute" directive to be applied to the adjoint version otherwise. - /// Does *not* generate diagnostics for things that do not require semantic information to detect - /// (these should be detected and raised upon context verification). + /// b) to "invert" if the corresponding adjoint specialization is compiler generated but the controlled version is user defined, and + /// b) to a "distribute" directive to be applied to the adjoint version otherwise. + /// Does *not* generate diagnostics for things that do not require semantic information to detect + /// (these should be detected and raised upon context verification). let private ResolveControlledAdjointGeneratorDirective (adjGenKind, ctlGenKind) (info : InferredCallableInformation) (gen : QsSpecializationGenerator) = if info.IsSelfAdjoint then NeedsToBeSelfInverse gen elif info.IsIntrinsic then NeedsToBeIntrinsic (gen, true) @@ -509,53 +615,53 @@ module SymbolResolution = | QsSpecializationGeneratorKind.Intrinsic -> Intrinsic |> Value, [||] | QsSpecializationGeneratorKind.UserDefinedImplementation _ -> Null, [||] | QsSpecializationGeneratorKind.FunctorGenerationDirective dir -> dir |> function - | Distribute | SelfInverse | Invert | InvalidGenerator -> Generated dir |> Value, [||] + | Distribute | SelfInverse | Invert | InvalidGenerator -> Generated dir |> Value, [||] | QsSpecializationGeneratorKind.AutoGenerated -> (ctlGenKind, adjGenKind) |> function - | UserDefinedImplementation _, FunctorGenerationDirective _ + | UserDefinedImplementation _, FunctorGenerationDirective _ | UserDefinedImplementation _, AutoGenerated -> Generated Invert |> Value, [||] | _ -> Generated Distribute |> Value, [||] // routines for resolving specialization declarations - /// Resolves the type- and set-arguments of the given specialization using the given function. - /// Returns the resolved arguments as well as an array of diagnostics. - /// Does nothing and simply returns the resolution of the given specialization if a resolution has already been set. - let internal ResolveTypeArgument typeResolution (_, spec : Resolution) = - let resolveGenerator () = + /// Resolves the type- and set-arguments of the given specialization using the given function. + /// Returns the resolved arguments as well as an array of diagnostics. + /// Does nothing and simply returns the resolution of the given specialization if a resolution has already been set. + let internal ResolveTypeArgument typeResolution (_, spec : Resolution) = + let resolveGenerator () = let typeArgs, tErrs = spec.Defined.TypeArguments |> function | Null -> Null, [||] - | Value targs -> - let resolved, errs = targs |> Seq.map typeResolution |> Seq.toList |> List.unzip + | Value targs -> + let resolved, errs = targs |> Seq.map typeResolution |> Seq.toList |> List.unzip resolved.ToImmutableArray() |> Value, errs |> Array.concat - let resolvedGen = {TypeArguments = typeArgs; Information = CallableInformation.Invalid; Directive = Null} - Value resolvedGen, tErrs |> Array.map (fun msg -> spec.Position, msg) - match spec.Resolved with + let resolvedGen = {TypeArguments = typeArgs; Information = CallableInformation.Invalid; Directive = Null} + Value resolvedGen, tErrs |> Array.map (fun msg -> spec.Position, msg) + match spec.Resolved with | Value resolvedGen -> Value resolvedGen, [||] | Null -> resolveGenerator() - /// Given a dictionary of all existing specializations for a particular set of type- and set-arguments - /// that maps the specialization kind to the corresponding generator, as well as the characteristics and location of the callable declaration, - /// determines the resolved characteristics of the specializations for these type- and set-arguments. - /// Calls generateSpecialization for each missing specialization kind that can be inferred. - /// Returns the resolved characteristics as well as an array of diagnostics. - let private InferCharacteristicsAndMetadata generateSpecialization (specKinds : ImmutableDictionary<_,_>) (characteristics : Characteristics, declLocation : QsLocation) = + /// Given a dictionary of all existing specializations for a particular set of type- and set-arguments + /// that maps the specialization kind to the corresponding generator, as well as the characteristics and location of the callable declaration, + /// determines the resolved characteristics of the specializations for these type- and set-arguments. + /// Calls generateSpecialization for each missing specialization kind that can be inferred. + /// Returns the resolved characteristics as well as an array of diagnostics. + let private InferCharacteristicsAndMetadata generateSpecialization (specKinds : ImmutableDictionary<_,_>) (characteristics : Characteristics, declLocation : QsLocation) = let adjExists, ctlExists = specKinds.ContainsKey QsAdjoint, specKinds.ContainsKey QsControlled let bodyExists, ctlAdjExists = specKinds.ContainsKey QsBody, specKinds.ContainsKey QsControlledAdjoint let annotRange cond = if cond then characteristics.Range else Null - let resolved, (supportsAdj, adjRange), (supportsCtl, ctlRange) = + let resolved, (supportsAdj, adjRange), (supportsCtl, ctlRange) = let declCharacteristics = ResolveCharacteristics characteristics - if not declCharacteristics.AreInvalid then + if not declCharacteristics.AreInvalid then let supported = declCharacteristics.GetProperties() - let adjSup, ctlSup = supported.Contains Adjointable, supported.Contains Controllable + let adjSup, ctlSup = supported.Contains Adjointable, supported.Contains Controllable let additional = ResolvedCharacteristics.FromProperties (seq { if (adjExists || ctlAdjExists) && not adjSup then yield Adjointable if (ctlExists || ctlAdjExists) && not ctlSup then yield Controllable }) let adj, ctl = (adjExists || ctlAdjExists || adjSup, annotRange adjSup), (ctlExists || ctlAdjExists || ctlSup, annotRange ctlSup) Union (declCharacteristics, additional) |> ResolvedCharacteristics.New, adj, ctl else declCharacteristics, (adjExists || ctlAdjExists, Null), (ctlExists || ctlAdjExists, Null) - let metadata = + let metadata = let isIntrinsic = function | QsSpecializationGeneratorKind.Intrinsic -> true | _ -> false let intrinsic = specKinds.Values |> Seq.map (fun g -> g.Generator) |> Seq.exists isIntrinsic let selfGenerator = specKinds.Values |> Seq.exists (fun gen -> gen.Generator = FunctorGenerationDirective SelfInverse) @@ -566,32 +672,32 @@ module SymbolResolution = if supportsAdj && not adjExists then yield generateSpecialization QsAdjoint (declLocation, adjRange) if supportsCtl && not ctlExists then yield generateSpecialization QsControlled (declLocation, ctlRange) if supportsAdj && supportsCtl && not ctlAdjExists then yield generateSpecialization QsControlledAdjoint (declLocation, ctlAdjRange) - if not bodyExists then + if not bodyExists then yield generateSpecialization QsBody (declLocation, Null) yield [| declLocation.Range |> QsCompilerDiagnostic.Warning (WarningCode.MissingBodyDeclaration, []) |] ] let isError (m : QsCompilerDiagnostic) = m.Diagnostic |> function | Error _ -> true | _ -> false - if errs |> Array.exists isError then InvalidSetExpr |> ResolvedCharacteristics.New, metadata, errs + if errs |> Array.exists isError then InvalidSetExpr |> ResolvedCharacteristics.New, metadata, errs else resolved, metadata, errs - /// Given the signature and source file of a callable as well as all specializations defined for it, constructs + /// Given the signature and source file of a callable as well as all specializations defined for it, constructs /// a dictionary that contains the bundle properties for each set of type- and set-arguments for which the callable has been specialized. /// The keys of the dictionary are given by the BundleIds obtained for the type- and set-arguments in question. /// Calls generateSpecialization for each specialization that is not listed in the given collection of specializations but can be inferred, - /// either based on the declared characteristics of the parent callable or based on other existing specializations. - /// Returns the constructed dictionary as well as an array of diagnostics. - /// Throws an InvalidOperationException if no (partial) resolution is defined for any one of the given specializations. - let internal GetBundleProperties generateSpecialization (parentSignature : Resolution, source) (definedSpecs : IEnumerable<_>) = + /// either based on the declared characteristics of the parent callable or based on other existing specializations. + /// Returns the constructed dictionary as well as an array of diagnostics. + /// Throws an InvalidOperationException if no (partial) resolution is defined for any one of the given specializations. + let internal GetBundleProperties generateSpecialization (parentSignature : Resolution, source) (definedSpecs : IEnumerable<_>) = let declCharacteristics = parentSignature.Defined.Characteristics // if we allow to specialize for certain set parameters, then these need to be resolved in parent let declLocation = {Offset = parentSignature.Position; Range = parentSignature.Range} let definedSpecs = definedSpecs.ToLookup (snd >> snd >> (SpecializationBundleProperties.BundleId : Resolution<_,_> -> _), id) let mutable errs = [] - let bundleProps (relevantSpecs : IEnumerable<_>, definedArgs) = + let bundleProps (relevantSpecs : IEnumerable<_>, definedArgs) = let gens, bundleErrs = let relevantSpecs = relevantSpecs.ToLookup(fst, snd) let positionedErr errCode (specSource, res : Resolution<_,_>) = specSource, (res.Position, res.Range |> QsCompilerDiagnostic.Error (errCode, [])) - let errs = - (relevantSpecs.[QsBody].Skip(1) |> Seq.map (positionedErr ErrorCode.RedefinitionOfBody)) + let errs = + (relevantSpecs.[QsBody].Skip(1) |> Seq.map (positionedErr ErrorCode.RedefinitionOfBody)) |> Seq.append (relevantSpecs.[QsAdjoint].Skip(1) |> Seq.map (positionedErr ErrorCode.RedefinitionOfAdjoint)) |> Seq.append (relevantSpecs.[QsControlled].Skip(1) |> Seq.map (positionedErr ErrorCode.RedefinitionOfControlled)) |> Seq.append (relevantSpecs.[QsControlledAdjoint].Skip(1) |> Seq.map (positionedErr ErrorCode.RedefinitionOfControlledAdjoint)) @@ -605,25 +711,25 @@ module SymbolResolution = for group in definedSpecs do props.Add(group.Key, bundleProps(group, (group.First() |> snd |> snd).Defined.TypeArguments)) props.ToImmutableDictionary(), errs - /// Given a dictionary that maps the BundleId for each set of type- and set-arguments for which the callable has been specialized + /// Given a dictionary that maps the BundleId for each set of type- and set-arguments for which the callable has been specialized /// to the corresponding bundle properties determines the resolution for the given specialization of the given kind. - /// Returns the resolved generator as well as an array of diagnostics generated during resolution. - /// Throws an InvalidOperationException if no (partial) resolution is defined for the given specialization. - /// Fails with the standard KeyNotFoundException if the given specialization is not part of a specialization bundle in the given properties dictionary. - let internal ResolveGenerator (properties : ImmutableDictionary<_,_>) (kind, spec : Resolution) = + /// Returns the resolved generator as well as an array of diagnostics generated during resolution. + /// Throws an InvalidOperationException if no (partial) resolution is defined for the given specialization. + /// Fails with the standard KeyNotFoundException if the given specialization is not part of a specialization bundle in the given properties dictionary. + let internal ResolveGenerator (properties : ImmutableDictionary<_,_>) (kind, spec : Resolution) = let bundle : SpecializationBundleProperties = properties.[SpecializationBundleProperties.BundleId spec] let impl, err = kind |> function | QsBody -> ResolveBodyGeneratorDirective bundle.BundleInfo.InferredInformation spec.Defined | QsAdjoint -> ResolveAdjointGeneratorDirective bundle.BundleInfo.InferredInformation spec.Defined | QsControlled -> ResolveControlledGeneratorDirective bundle.BundleInfo.InferredInformation spec.Defined - | QsControlledAdjoint -> + | QsControlledAdjoint -> let getGenKindOrAuto kind = bundle.DefinedGenerators.TryGetValue kind |> function | true, (gen : QsSpecializationGenerator) -> gen.Generator - | false, _ -> AutoGenerated // automatically inserted specializations won't be part of the bundle + | false, _ -> AutoGenerated // automatically inserted specializations won't be part of the bundle let adjGen, ctlGen = getGenKindOrAuto QsAdjoint, getGenKindOrAuto QsControlled ResolveControlledAdjointGeneratorDirective (adjGen, ctlGen) bundle.BundleInfo.InferredInformation spec.Defined let dir = impl |> function | Value (Generated dir) -> Value dir | _ -> Null let resolvedGen = spec.Resolved |> QsNullable<_>.Map (fun resolution -> {resolution with Information = bundle.BundleInfo; Directive = dir}) - resolvedGen, err |> Array.map (fun msg -> spec.Position, msg) + resolvedGen, err |> Array.map (fun msg -> spec.Position, msg) diff --git a/src/QsCompiler/Core/SymbolTable.fs b/src/QsCompiler/Core/SymbolTable.fs index 251c45ad80..53347d6a5e 100644 --- a/src/QsCompiler/Core/SymbolTable.fs +++ b/src/QsCompiler/Core/SymbolTable.fs @@ -13,30 +13,33 @@ open Microsoft.Quantum.QsCompiler.Diagnostics open Microsoft.Quantum.QsCompiler.SymbolResolution open Microsoft.Quantum.QsCompiler.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxGenerator -open Microsoft.Quantum.QsCompiler.SyntaxTokens +open Microsoft.Quantum.QsCompiler.SyntaxTokens open Microsoft.Quantum.QsCompiler.SyntaxTree open Newtonsoft.Json -/// Note that this class is *not* threadsafe! +/// Represents the partial declaration of a namespace in a single file. +/// +/// Note that this class is *not* thread-safe, and access modifiers are always ignored when looking up declarations. type private PartialNamespace private (name : NonNullable, source : NonNullable, documentation : IEnumerable>, - openNS : IEnumerable, string>>, + openNS : IEnumerable, string>>, typeDecl : IEnumerable, Resolution, ResolvedType * QsTuple<_>>>>, callableDecl : IEnumerable, QsCallableKind * Resolution>>>, - specializations : IEnumerable, List>>>) = + specializations : IEnumerable, List>>>) = let keySelector (item : KeyValuePair<'k,'v>) = item.Key let valueSelector (item : KeyValuePair<'k,'v>) = item.Value - let unresolved (location : QsLocation) (definition, attributes, doc) = { - Defined = definition; + let unresolved (location : QsLocation) (definition, attributes, modifiers, doc) = { + Defined = definition DefinedAttributes = attributes - Resolved = Null; + Resolved = Null ResolvedAttributes = ImmutableArray.Empty - Position = location.Offset; - Range = location.Range; + Modifiers = modifiers + Position = location.Offset + Range = location.Range Documentation = doc } @@ -49,24 +52,24 @@ type private PartialNamespace private /// the key is the name of the type let TypeDeclarations = typeDecl.ToDictionary(keySelector, valueSelector) /// dictionary of callables declared within this namespace and file - /// includes functions, operations, *and* (auto-gnerated) type constructors + /// includes functions, operations, *and* (auto-generated) type constructors /// the key is the name of the callable let CallableDeclarations = callableDecl.ToDictionary(keySelector, valueSelector) /// dictionary of callable specializations declared within this namespace and file /// -> note that all specializations that are declared in a namespace *have* to extend a declarations in the same namespace, /// -> however, they may be declared in a source file (or even compilation unit) that is different from the one of the original declaration /// the key is the name of the callable, and the key to the returned map is the specialization kind (body, adjoint, controlled, or controlled adjoint) - let CallableSpecializations = + let CallableSpecializations = let specs = specializations |> Seq.map (fun entry -> entry.Key, (entry.Value.ToList())) specs.ToDictionary(fst, snd) /// constructor taking the name of the namespace as well as the name of the file it is declared in as arguments - internal new (name, source) = new PartialNamespace(name, source, [], [new KeyValuePair<_,_>(name, null)], [], [], []) + internal new (name, source) = new PartialNamespace(name, source, [], [new KeyValuePair<_,_>(name, null)], [], [], []) /// returns a new PartialNamespace that is an exact copy of this one /// -> any modification of the returned PartialNamespace is not reflected in this one member this.Copy() = - let openNS = OpenNamespaces + let openNS = OpenNamespaces let doc = AssociatedDocumentation let typeDecl = TypeDeclarations let callableDecl = CallableDeclarations @@ -89,16 +92,16 @@ type private PartialNamespace private /// callables defined within this (part of) the namespace (includes auto-generated type constructors) /// -> NOTE: the returned enumerable is *not* immutable and may change over time! member internal this.DefinedCallables = CallableDeclarations.Select(fun item -> item.Key, item.Value) - + /// returns a dictionary with all currently known namespace short names and which namespace they represent - member internal this.NamespaceShortNames = + member internal this.NamespaceShortNames = let shortNames = this.ImportedNamespaces |> Seq.filter (fun kv -> kv.Value <> null) shortNames.ToImmutableDictionary((fun kv -> NonNullable.New kv.Value), (fun kv -> kv.Key)) /// Gets the type with the given name from the dictionary of declared types. /// Fails with the standard key does not exist error if no such declaration exists. member internal this.GetType tName = TypeDeclarations.[tName] - member internal this.ContainsType = TypeDeclarations.ContainsKey + member internal this.ContainsType = TypeDeclarations.ContainsKey member internal this.TryGetType = TypeDeclarations.TryGetValue /// Gets the callable with the given name from the dictionary of declared callable. @@ -107,120 +110,120 @@ type private PartialNamespace private member internal this.ContainsCallable = CallableDeclarations.ContainsKey member internal this.TryGetCallable = CallableDeclarations.TryGetValue - /// Given a callable name, returns all specializations for it defined within this part of the namespace. + /// Given a callable name, returns all specializations for it defined within this part of the namespace. /// NOTE: The verification of whether a callable with that name has been declared in this namespace needs to be done by the calling routine. - member internal this.GetSpecializations cName = - match CallableSpecializations.TryGetValue cName with + member internal this.GetSpecializations cName = + match CallableSpecializations.TryGetValue cName with | true, specs -> specs.ToImmutableArray() | false, _ -> ImmutableArray.Empty // mustn't fail, since the query is valid even if the callable is declared in another file - /// Adds the given lines of documentation to the list of documenting sections - /// associated with this namespace within this source file. - member this.AddDocumentation (doc : IEnumerable<_>) = + /// Adds the given lines of documentation to the list of documenting sections + /// associated with this namespace within this source file. + member this.AddDocumentation (doc : IEnumerable<_>) = AssociatedDocumentation.Add(doc.ToImmutableArray()) /// If the given namespace name is not already listened as imported, adds the given namespace name to the list of open namespaces. - /// -> Note that this routine will fail with the standard dictionary.Add error if an open directive for the given namespace name already exists. - /// -> The verification of whether a namespace with the given name exists in the first place needs to be done by the calling routine. - member this.AddOpenDirective (openedNS, alias) = - OpenNamespaces.Add(openedNS, alias) + /// -> Note that this routine will fail with the standard dictionary.Add error if an open directive for the given namespace name already exists. + /// -> The verification of whether a namespace with the given name exists in the first place needs to be done by the calling routine. + member this.AddOpenDirective (openedNS, alias) = + OpenNamespaces.Add(openedNS, alias) /// Adds the given type declaration for the given type name to the dictionary of declared types. /// Adds the corresponding type constructor to the dictionary of declared callables. /// The given location is associated with both the type constructur and the type itself and accessible via the record properties Position and SymbolRange. /// -> Note that this routine will fail with the standard dictionary.Add error if either a type or a callable with that name already exists. - member this.AddType (location : QsLocation) (tName, typeTuple, attributes, documentation) = + member this.AddType (location : QsLocation) (tName, typeTuple, attributes, modifiers, documentation) = let mutable anonItemId = 0 let withoutRange sym = {Symbol = sym; Range = Null} - let replaceAnonymous (itemName : QsSymbol, itemType) = // positional info for types in type constructors is removed upon resolution - let anonItemName () = + let replaceAnonymous (itemName : QsSymbol, itemType) = // positional info for types in type constructors is removed upon resolution + let anonItemName () = anonItemId <- anonItemId + 1 sprintf "__Item%i__" anonItemId |> NonNullable.New match itemName.Symbol with | MissingSymbol -> QsTupleItem (Symbol (anonItemName()) |> withoutRange, itemType) | _ -> QsTupleItem (itemName.Symbol |> withoutRange, itemType) // no range info in auto-generated constructor - let constructorSignature = - let constructorArgument = - let rec buildItem = function + let constructorSignature = + let constructorArgument = + let rec buildItem = function | QsTuple args -> (args |> Seq.map buildItem).ToImmutableArray() |> QsTuple | QsTupleItem (n, t) -> replaceAnonymous (n, t) - match typeTuple with + match typeTuple with | QsTupleItem (n, t) -> ImmutableArray.Create (replaceAnonymous (n, t)) |> QsTuple | QsTuple _ -> buildItem typeTuple let returnType = {Type = UserDefinedType (QualifiedSymbol (this.Name, tName) |> withoutRange); Range = Null} {TypeParameters = ImmutableArray.Empty; Argument = constructorArgument; ReturnType = returnType; Characteristics = {Characteristics = EmptySet; Range = Null}} // There are a couple of reasons not just blindly attach all attributes associated with the type to the constructor: - // For one, we would need to make sure that the range information for duplications is stripped such that e.g. rename commands are not executed multiple times. + // For one, we would need to make sure that the range information for duplications is stripped such that e.g. rename commands are not executed multiple times. // We would furthermore have to adapt the entry point verification logic below, since type constructors are not valid entry points. let deprecationWithoutRedirect = { - Id = {Symbol = Symbol BuiltIn.Deprecated.Name; Range = Null} + Id = {Symbol = Symbol BuiltIn.Deprecated.FullName.Name; Range = Null} Argument = {Expression = StringLiteral (NonNullable.New "", ImmutableArray.Empty); Range = Null} Position = location.Offset - Comments = QsComments.Empty} + Comments = QsComments.Empty} let constructorAttr = // we will attach any attribute that likely indicates a deprecation to the type constructor as well - let validDeprecatedQualification qual = String.IsNullOrWhiteSpace qual || qual = BuiltIn.Deprecated.Namespace.Value - if attributes |> Seq.exists (SymbolResolution.IndicatesDeprecation validDeprecatedQualification) then ImmutableArray.Create deprecationWithoutRedirect + let validDeprecatedQualification qual = String.IsNullOrWhiteSpace qual || qual = BuiltIn.Deprecated.FullName.Namespace.Value + if attributes |> Seq.exists (SymbolResolution.IndicatesDeprecation validDeprecatedQualification) then ImmutableArray.Create deprecationWithoutRedirect else ImmutableArray.Empty - TypeDeclarations.Add(tName, (typeTuple, attributes, documentation) |> unresolved location) - this.AddCallableDeclaration location (tName, (TypeConstructor, constructorSignature), constructorAttr, ImmutableArray.Empty) + TypeDeclarations.Add(tName, (typeTuple, attributes, modifiers, documentation) |> unresolved location) + this.AddCallableDeclaration location (tName, (TypeConstructor, constructorSignature), constructorAttr, modifiers, ImmutableArray.Empty) let bodyGen = {TypeArguments = Null; Generator = QsSpecializationGeneratorKind.Intrinsic; Range = Value location.Range} - this.AddCallableSpecialization location QsBody (tName, bodyGen, ImmutableArray.Empty, ImmutableArray.Empty) + this.AddCallableSpecialization location QsBody (tName, bodyGen, ImmutableArray.Empty, ImmutableArray.Empty) - /// Adds a callable declaration of the given kind (operation or function) + /// Adds a callable declaration of the given kind (operation or function) /// with the given callable name and signature to the dictionary of declared callables. /// The given location is associated with the callable declaration and accessible via the record properties Position and SymbolRange. /// -> Note that this routine will fail with the standard dictionary.Add error if a callable with that name already exists. - member this.AddCallableDeclaration location (cName, (kind, signature), attributes, documentation) = - CallableDeclarations.Add(cName, (kind, (signature, attributes, documentation) |> unresolved location)) + member this.AddCallableDeclaration location (cName, (kind, signature), attributes, modifiers, documentation) = + CallableDeclarations.Add(cName, (kind, (signature, attributes, modifiers, documentation) |> unresolved location)) - /// Adds the callable specialization defined by the given kind and generator for the callable of the given name to the dictionary of declared specializations. - /// The given location is associated with the given specialization and accessible via the record properties Position and HeaderRange. - /// -> Note that the verification of whether the corresponding callable declaration exists within the namespace is up to the calling routine. - /// *IMPORTANT*: both the verification of whether the length of the given array of type specialization + /// Adds the callable specialization defined by the given kind and generator for the callable of the given name to the dictionary of declared specializations. + /// The given location is associated with the given specialization and accessible via the record properties Position and HeaderRange. + /// -> Note that the verification of whether the corresponding callable declaration exists within the namespace is up to the calling routine. + /// *IMPORTANT*: both the verification of whether the length of the given array of type specialization /// matches the number of type parameters in the callable declaration, and whether a specialization that clashes with this one /// already exists is up to the calling routine! member this.AddCallableSpecialization location kind (cName, generator : QsSpecializationGenerator, attributes, documentation) = // NOTE: all types that are not specialized need to be resolved according to the file in which the callable is declared, // but all specialized types need to be resolved according to *this* file - let spec = kind, (generator, attributes, documentation) |> unresolved location + let spec = kind, (generator, attributes, {Access = DefaultAccess}, documentation) |> unresolved location match CallableSpecializations.TryGetValue cName with | true, specs -> specs.Add spec // it is up to the namespace to verify the type specializations | false, _ -> CallableSpecializations.Add(cName, new List<_>([spec])) /// Deletes the *explicitly* defined specialization at the specified location for the callable with the given name. /// Does not delete specializations that have been inserted by the compiler, i.e. specializations whose location matches the callable declaration location. - /// Returns the number of removed specializations. + /// Returns the number of removed specializations. /// Throws the standard key does not exist exception if no specialization for the callable with that name exists. - member internal this.RemoveCallableSpecialization (location : QsLocation) cName = - match CallableDeclarations.TryGetValue cName with - | true, (_, decl) when decl.Position = location.Offset && decl.Range = location.Range -> 0 + member internal this.RemoveCallableSpecialization (location : QsLocation) cName = + match CallableDeclarations.TryGetValue cName with + | true, (_, decl) when decl.Position = location.Offset && decl.Range = location.Range -> 0 | _ -> CallableSpecializations.[cName].RemoveAll (fun (_, res) -> location.Offset = res.Position && location.Range = res.Range) /// Sets the resolution for the type with the given name to the given type, and replaces the resolved attributes with the given values. /// Throws the standard key does not exist exception if no type with that name exists. - member internal this.SetTypeResolution (tName, resolvedType, resAttributes) = + member internal this.SetTypeResolution (tName, resolvedType, resAttributes) = let qsType = TypeDeclarations.[tName] TypeDeclarations.[tName] <- {qsType with Resolved = resolvedType; ResolvedAttributes = resAttributes} /// Sets the resolution for the signature of the callable with the given name to the given signature, /// and replaces the resolved attributes with the given values. /// Throws the standard key does not exist exception if no callable with that name exists. - member internal this.SetCallableResolution (cName, resolvedSignature, resAttributes) = + member internal this.SetCallableResolution (cName, resolvedSignature, resAttributes) = let (kind, signature) = CallableDeclarations.[cName] CallableDeclarations.[cName] <- (kind, {signature with Resolved = resolvedSignature; ResolvedAttributes = resAttributes}) - /// Applies the given functions computing the resolution of attributes and the generation directive - /// to all defined specializations of the callable with the given name, - /// and sets its resolution and resolved attributes to the computed values. - /// Collects and returns an array of all diagnostics generated by computeResolution. + /// Applies the given functions computing the resolution of attributes and the generation directive + /// to all defined specializations of the callable with the given name, + /// and sets its resolution and resolved attributes to the computed values. + /// Collects and returns an array of all diagnostics generated by computeResolution. /// Does nothing and returns an empty array if no specialization for a callable with the given name exists within this partial namespace. - member internal this.SetSpecializationResolutions (cName, computeResolution, getResAttributes) = - match CallableSpecializations.TryGetValue cName with - | true, specs -> + member internal this.SetSpecializationResolutions (cName, computeResolution, getResAttributes) = + match CallableSpecializations.TryGetValue cName with + | true, specs -> [|0 .. specs.Count - 1|] |> Array.collect (fun index -> let kind, spec = specs.[index] let resAttr, attErrs = getResAttributes this.Source spec @@ -230,7 +233,12 @@ type private PartialNamespace private | false, _ -> [||] -/// Note that this class is *not* threadsafe! +/// Represents a namespace and all of its declarations. +/// +/// This class is *not* thread-safe. +/// +/// Access modifiers are taken into consideration when resolving symbols. Some methods bypass this (e.g., when returning +/// a list of all declarations). Individual methods will mention if they adhere to symbol accessibility. and Namespace private (name, parts : IEnumerable,PartialNamespace>>, CallablesInReferences : ImmutableDictionary, CallableDeclarationHeader>, @@ -243,26 +251,30 @@ and Namespace private let mutable TypesDefinedInAllSourcesCache = null let mutable CallablesDefinedInAllSourcesCache = null - /// Given a non-nullable string, returns true if either a callable or a type with that name already exists - /// either in a source file, or in a referenced assembly. - let IsDefined arg = - CallablesInReferences.ContainsKey arg || TypesInReferences.ContainsKey arg || - Parts.Values.Any (fun partial -> partial.ContainsCallable arg || partial.ContainsType arg) - - /// Given a selector, determines the source files for which the given selector returns a Value. - /// Returns that Value if exactly one such source file exists, or Null if no such file exists. - /// Note that files contained in referenced assemblies are *not* considered to be source files for the namespace! - /// Throws an ArgumentException if the selector returns a Value for more than one source file. - let FromSingleSource (selector : PartialNamespace -> _ Option) = - let sources = Parts.Values |> Seq.choose selector - if sources.Count() > 1 then ArgumentException "given selector selects more than one partial namespace" |> raise - if sources.Any() then Value (sources.Single()) else Null - + /// Returns true if the name is available for use in a new declaration. + let isNameAvailable name = + let isAvailableWith declarationGetter accessibilityGetter sameAssembly = + match declarationGetter name with + | true, value -> not <| Namespace.IsDeclarationAccessible (sameAssembly, accessibilityGetter value) + | false, _ -> true + + isAvailableWith CallablesInReferences.TryGetValue (fun c -> c.Modifiers.Access) false && + isAvailableWith TypesInReferences.TryGetValue (fun t -> t.Modifiers.Access) false && + Parts.Values.All (fun partial -> + isAvailableWith partial.TryGetCallable (fun c -> (snd c).Modifiers.Access) true && + isAvailableWith partial.TryGetType (fun t -> t.Modifiers.Access) true) + + /// Returns whether a declaration is accessible from the calling location, given whether the calling location is in + /// the same assembly as the declaration, and the declaration's access modifier. + static member IsDeclarationAccessible (sameAssembly, access) = + match access with + | DefaultAccess -> true + | Internal -> sameAssembly /// name of the namespace member this.Name = name /// Immutable array with the names of all source files that contain (a part of) the namespace. - /// Note that files contained in referenced assemblies that implement part of the namespace + /// Note that files contained in referenced assemblies that implement part of the namespace /// are *not* considered to be source files within the context of this Namespace instance! member internal this.Sources = Parts.Keys.ToImmutableHashSet() /// contains all types declared within one of the referenced assemblies as part of this namespace @@ -274,14 +286,14 @@ and Namespace private /// constructor taking the name of the namespace as well the name of the files in which (part of) it is declared in as arguments, /// as well as the information about all types and callables declared in referenced assemblies that belong to this namespace - internal new (name, sources, callablesInRefs : IEnumerable<_>, specializationsInRefs : IEnumerable<_>, typesInRefs : IEnumerable<_>) = + internal new (name, sources, callablesInRefs : IEnumerable<_>, specializationsInRefs : IEnumerable<_>, typesInRefs : IEnumerable<_>) = let initialSources = sources |> Seq.distinct |> Seq.map (fun source -> new KeyValuePair<_,_>(source, new PartialNamespace(name, source))) let typesInRefs = typesInRefs.Where (fun (header : TypeDeclarationHeader) -> header.QualifiedName.Namespace = name) let callablesInRefs = callablesInRefs.Where(fun (header : CallableDeclarationHeader) -> header.QualifiedName.Namespace = name) let specializationsInRefs = specializationsInRefs.Where(fun (header : SpecializationDeclarationHeader, _) -> header.Parent.Namespace = name) // ignore ambiguous/clashing references - let FilterUnique (g : IGrouping<_,_>) = + let FilterUnique (g : IGrouping<_,_>) = if g.Count() > 1 then None else g.Single() |> Some let typesInRefs = typesInRefs.GroupBy(fun t -> t.QualifiedName.Name) |> Seq.choose FilterUnique @@ -293,70 +305,84 @@ and Namespace private new Namespace(name, initialSources, callables, specializations, types) - /// returns true if the namespace currently contains no source files or referenced content - member this.IsEmpty = - not (this.Sources.Any() || this.TypesInReferencedAssemblies.Any() || + /// returns true if the namespace currently contains no source files or referenced content + member this.IsEmpty = + not (this.Sources.Any() || this.TypesInReferencedAssemblies.Any() || this.CallablesInReferencedAssemblies.Any() || this.SpecializationsInReferencedAssemblies.Any()) /// returns a new Namespace that is an exact (deep) copy of this one /// -> any modification of the returned Namespace is not reflected in this one - member this.Copy() = + member this.Copy() = let partials = Parts |> Seq.map (fun part -> new KeyValuePair<_,_>(part.Key, part.Value.Copy())) new Namespace(name, partials, CallablesInReferences, SpecializationsInReferences, TypesInReferences) - /// Returns a lookup that given the name of a source file, + /// Returns a lookup that given the name of a source file, /// returns all documentation associated with this namespace defined in that file. - member internal this.Documentation = - Parts.Values.SelectMany(fun partial -> + member internal this.Documentation = + Parts.Values.SelectMany(fun partial -> partial.Documentation |> Seq.map (fun doc -> partial.Source, doc)).ToLookup(fst, snd) /// Returns all namespaces that are open or aliased in the given source file for this namespace. - /// The returned dictionary maps the names of the opened or aliased namespace to its alias if such an alias exists, - /// and in particular also contains an entry for the namespace itself. + /// The returned dictionary maps the names of the opened or aliased namespace to its alias if such an alias exists, + /// and in particular also contains an entry for the namespace itself. /// Throws an ArgumentException if the given source file is not listed as source file for (part of) the namespace. - member internal this.ImportedNamespaces source = - match Parts.TryGetValue source with + member internal this.ImportedNamespaces source = + match Parts.TryGetValue source with | true, partial -> partial.ImportedNamespaces | false, _ -> ArgumentException "given source file is not listed as a source file for this namespace" |> raise /// Returns a dictionary with all currently known namespace short names within the given source file and which namespace they represent. /// Throws an ArgumentException if the given source file is not listed as source file for (part of) the namespace. member internal this.NamespaceShortNames source = - match Parts.TryGetValue source with + match Parts.TryGetValue source with | true, partial -> partial.NamespaceShortNames | false, _ -> ArgumentException "given source file is not listed as a source file for this namespace" |> raise - /// If a type with the given name is defined in the specified source file or reference, - /// checks if that type has been marked as attribute and returns its underlying type if it has. - /// A type is considered to be marked as attribute if the list of defined attributes contains an attribute - /// with name "Attribute" that is qualified by any of the given possible qualifications. + /// If a type with the given name is defined in the specified source file or reference, + /// checks if that type has been marked as attribute and returns its underlying type if it has. + /// A type is considered to be marked as attribute if the list of defined attributes contains an attribute + /// with name "Attribute" that is qualified by any of the given possible qualifications. /// If the list of possible qualifications contains an empty string, then the "Attribute" may be unqualified. /// Throws an ArgumentException if no such type exists in any of the references and the source file is not listed as source file of the namespace. - /// Throws an InvalidOperationExeception if the corresponding type has not been resolved. - member internal this.TryGetAttributeDeclaredIn source (attName, possibleQualifications : _ seq) = - let marksAttribute (t : QsDeclarationAttribute) = t.TypeId |> function - | Value id -> id.Namespace.Value = BuiltIn.Attribute.Namespace.Value && id.Name.Value = BuiltIn.Attribute.Name.Value + /// Throws an InvalidOperationExeception if the corresponding type has not been resolved. + member internal this.TryGetAttributeDeclaredIn source (attName, possibleQualifications : _ seq) = + let marksAttribute (t : QsDeclarationAttribute) = t.TypeId |> function + | Value id -> + id.Namespace.Value = BuiltIn.Attribute.FullName.Namespace.Value && + id.Name.Value = BuiltIn.Attribute.FullName.Name.Value | Null -> false - let missingResolutionException () = InvalidOperationException "cannot get unresolved attribute" |> raise - let compareAttributeName (att : AttributeAnnotation) = att.Id.Symbol |> function - | Symbol sym when sym.Value = BuiltIn.Attribute.Name.Value && possibleQualifications.Contains "" -> true - | QualifiedSymbol (ns, sym) when sym.Value = BuiltIn.Attribute.Name.Value && possibleQualifications.Contains ns.Value -> true + + let missingResolutionException () = InvalidOperationException "cannot get unresolved attribute" |> raise + + let compareAttributeName (att : AttributeAnnotation) = + match att.Id.Symbol with + | Symbol sym when sym.Value = BuiltIn.Attribute.FullName.Name.Value && possibleQualifications.Contains "" -> + true + | QualifiedSymbol (ns, sym) when sym.Value = BuiltIn.Attribute.FullName.Name.Value && + possibleQualifications.Contains ns.Value -> + true | _ -> false - match TypesInReferences.TryGetValue attName with - | true, tDecl -> if tDecl.Attributes |> Seq.exists marksAttribute then Some tDecl.Type else None - | false, _ -> Parts.TryGetValue source |> function // ok only because/if we have covered that the type is not in a reference! - | false, _ -> ArgumentException "given source file is not listed as source of the namespace" |> raise - | true, partialNS -> partialNS.TryGetType attName |> function - | true, resolution when resolution.DefinedAttributes |> Seq.exists compareAttributeName -> - resolution.Resolved.ValueOrApply missingResolutionException |> fst |> Some - | _ -> None + + match Parts.TryGetValue source with + | true, partial -> + match partial.TryGetType attName with + | true, resolution when Seq.exists compareAttributeName resolution.DefinedAttributes -> + resolution.Resolved.ValueOrApply missingResolutionException |> fst |> Some + | _ -> None + | false, _ -> + match TypesInReferences.TryGetValue attName with + | true, qsType -> + if Seq.exists marksAttribute qsType.Attributes + then Some qsType.Type + else None + | false, _ -> ArgumentException "Given source file is not part of the namespace" |> raise /// Returns the type with the given name defined in the given source file within this namespace. /// Note that files contained in referenced assemblies are *not* considered to be source files for the namespace! - /// Throws an ArgumentException if the source file is not listed as source file of the namespace, - /// or if no type with the given name exists within this namespace in that source file. + /// Throws an ArgumentException if the source file is not listed as source file of the namespace, + /// or if no type with the given name exists within this namespace in that source file. member internal this.TypeInSource source tName = - match Parts.TryGetValue source with + match Parts.TryGetValue source with | true, partial -> partial.TryGetType tName |> function | true, typeDecl -> typeDecl | false, _ -> ArgumentException "no type with that name exist in the given source" |> raise @@ -365,125 +391,171 @@ and Namespace private /// Returns all types defined in the given source file within this namespace. /// Note that files contained in referenced assemblies are *not* considered to be source files for the namespace! /// Throws an ArgumentException if the source file is not listed as source file of the namespace. - member internal this.TypesDefinedInSource source = - match Parts.TryGetValue source with + member internal this.TypesDefinedInSource source = + match Parts.TryGetValue source with | true, partial -> partial.DefinedTypes.ToImmutableArray() | false, _ -> ArgumentException "given source file is not listed as source of the namespace" |> raise /// Returns all types defined in a source file associated with this namespace. /// This excludes types that are defined in files contained in referenced assemblies. - member internal this.TypesDefinedInAllSources () = + member internal this.TypesDefinedInAllSources () = if TypesDefinedInAllSourcesCache = null then - let getInfos (partial : PartialNamespace) = + let getInfos (partial : PartialNamespace) = partial.DefinedTypes |> Seq.map (fun (tName, decl) -> tName, (partial.Source, decl)) TypesDefinedInAllSourcesCache <- (Parts.Values.SelectMany getInfos).ToImmutableDictionary(fst, snd) TypesDefinedInAllSourcesCache /// Returns the callable with the given name defined in the given source file within this namespace. /// Note that files contained in referenced assemblies are *not* considered to be source files for the namespace! - /// Throws an ArgumentException if the source file is not listed as source file of the namespace, - /// or if no callable with the given name exists within this namespace in that source file. - member internal this.CallableInSource source cName = - match Parts.TryGetValue source with + /// Throws an ArgumentException if the source file is not listed as source file of the namespace, + /// or if no callable with the given name exists within this namespace in that source file. + member internal this.CallableInSource source cName = + match Parts.TryGetValue source with | true, partial -> partial.TryGetCallable cName |> function | true, callable -> callable | false, _ -> ArgumentException "no callable with that name exist in the given source" |> raise | false, _ -> ArgumentException "given source file is not listed as source of the namespace" |> raise /// Returns all callables defined in the given source file within this namespace. - /// Callables include operations, functions, and auto-genrated type constructors for declared types. + /// Callables include operations, functions, and auto-generated type constructors for declared types. /// Note that files contained in referenced assemblies are *not* considered to be source files for the namespace! /// Throws an ArgumentException if the source file is not listed as source file of the namespace. - member internal this.CallablesDefinedInSource source = - match Parts.TryGetValue source with + member internal this.CallablesDefinedInSource source = + match Parts.TryGetValue source with | true, partial -> partial.DefinedCallables.ToImmutableArray() | false, _ -> ArgumentException "given source file is not listed as source of the namespace" |> raise /// Returns all callables defined in a source file associated with this namespace. /// This excludes callables that are defined in files contained in referenced assemblies. - /// Callables include operations, functions, and auto-genrated type constructors for declared types. - member internal this.CallablesDefinedInAllSources () = + /// Callables include operations, functions, and auto-generated type constructors for declared types. + member internal this.CallablesDefinedInAllSources () = if CallablesDefinedInAllSourcesCache = null then - let getInfos (partial : PartialNamespace) = + let getInfos (partial : PartialNamespace) = partial.DefinedCallables |> Seq.map (fun (cName, decl) -> cName, (partial.Source, decl)) CallablesDefinedInAllSourcesCache <- (Parts.Values.SelectMany getInfos).ToImmutableDictionary(fst, snd) CallablesDefinedInAllSourcesCache /// Returns all specializations for the callable with the given name defined in a source file associated with this namespace, /// This excludes specializations that are defined in files contained in referenced assemblies. - /// Throws an ArgumentException if no callable with the given name is defined in this namespace. - member internal this.SpecializationsDefinedInAllSources cName = - match this.ContainsCallable cName with - | Value _ -> (Parts.Values.SelectMany (fun partial -> - partial.GetSpecializations cName |> Seq.map (fun (kind, decl) -> kind, (partial.Source, decl)))).ToImmutableArray() - | Null -> ArgumentException "no callable with the given name exist within the namespace" |> raise - - /// If this namespace contains a declaration for the given type name, - /// returns a Value with the name of the source file or the name of the file within a referenced assembly - /// in which it is declared as well as a string option indicating the redirection for the type if it has been deprecated. - /// Returns Null otherwise. - /// Whether the type has been deprecated is determined by checking the associated attributes for an attribute with the corresponding name. - /// Note that if the type is declared in a source files, the *unresolved* attributes will be checked. - /// In that case checkDeprecation is used to validate the namespace qualification of the attribute. - /// If checkDeprecation is not specified, it is assumed that no qualification is needed in the relevant namespace and source file. - member this.ContainsType (tName, ?checkDeprecation : (string -> bool)) = - let checkDeprecation = defaultArg checkDeprecation (fun qual -> String.IsNullOrWhiteSpace qual || qual = BuiltIn.Deprecated.Namespace.Value) - match TypesInReferences.TryGetValue tName with - | true, tDecl -> Value (tDecl.SourceFile, tDecl.Attributes |> SymbolResolution.TryFindRedirect) - | false, _ -> FromSingleSource (fun partialNS -> partialNS.TryGetType tName |> function - | true, tDecl -> Some (partialNS.Source, tDecl.DefinedAttributes |> SymbolResolution.TryFindRedirectInUnresolved checkDeprecation) - | false, _ -> None) - - /// If this namespace contains the declaration for the given callable name, - /// returns a Value with the name of the source file or the name of the file within a referenced assembly - /// in which it is declared as well as a string option indicating the redirection for the callable if it has been deprecated. - /// Returns Null otherwise. - /// If the given callable corresponds to the (auto-generated) type constructor for a user defined type, - /// returns the file in which that type is declared as source. - /// Whether the callable has been deprecated is determined by checking the associated attributes for an attribute with the corresponding name. - /// Note that if the type is declared in a source files, the *unresolved* attributes will be checked. - /// In that case checkDeprecation is used to validate the namespace qualification of the attribute. - /// If checkDeprecation is not specified, it is assumed that no qualification is needed in the relevant namespace and source file. - member this.ContainsCallable (cName, ?checkDeprecation : (string -> bool)) = - let checkDeprecation = defaultArg checkDeprecation (fun qual -> String.IsNullOrWhiteSpace qual || qual = BuiltIn.Deprecated.Namespace.Value) - match CallablesInReferences.TryGetValue cName with - | true, cDecl -> Value (cDecl.SourceFile, cDecl.Attributes |> SymbolResolution.TryFindRedirect) - | false, _ -> FromSingleSource (fun partialNS -> partialNS.TryGetCallable cName |> function - | true, (_, cDecl) -> Some (partialNS.Source, cDecl.DefinedAttributes |> SymbolResolution.TryFindRedirectInUnresolved checkDeprecation) - | false, _ -> None) + /// Throws an ArgumentException if no callable with the given name is defined in this namespace. + member internal this.SpecializationsDefinedInAllSources cName = + let getSpecializationInPartial (partial : PartialNamespace) = + partial.GetSpecializations cName + |> Seq.map (fun (kind, decl) -> kind, (partial.Source, decl)) + + if this.TryFindCallable cName |> ResolutionResult.Exists + then (Parts.Values.SelectMany getSpecializationInPartial).ToImmutableArray() + else ArgumentException "no callable with the given name exist within the namespace" |> raise + + + /// Returns a resolution result for the type with the given name containing the name of the source file or + /// referenced assembly in which it is declared, a string indicating the redirection if it has been deprecated, and + /// its access modifier. Resolution is based on accessibility to source files in this compilation unit. + /// + /// Whether the type has been deprecated is determined by checking the associated attributes for an attribute with + /// the corresponding name. Note that if the type is declared in a source files, the *unresolved* attributes will be + /// checked. In that case checkDeprecation is used to validate the namespace qualification of the attribute. If + /// checkDeprecation is not specified, it is assumed that no qualification is needed in the relevant namespace and + /// source file. + member this.TryFindType (tName, ?checkDeprecation : (string -> bool)) = + let checkDeprecation = + defaultArg checkDeprecation + (fun qual -> String.IsNullOrWhiteSpace qual || qual = BuiltIn.Deprecated.FullName.Namespace.Value) + + let fromReferences = + match TypesInReferences.TryGetValue tName with + | true, qsType -> + if Namespace.IsDeclarationAccessible (false, qsType.Modifiers.Access) + then Found (qsType.SourceFile, + SymbolResolution.TryFindRedirect qsType.Attributes, + qsType.Modifiers.Access) + else Inaccessible + | false, _ -> NotFound + + let findInPartial (partial : PartialNamespace) = + match partial.TryGetType tName with + | true, qsType -> + if Namespace.IsDeclarationAccessible (true, qsType.Modifiers.Access) + then Found (partial.Source, + SymbolResolution.TryFindRedirectInUnresolved checkDeprecation qsType.DefinedAttributes, + qsType.Modifiers.Access) + else Inaccessible + | false, _ -> NotFound + + seq { yield fromReferences + yield Seq.map findInPartial Parts.Values |> ResolutionResult.ExactlyOne } + |> ResolutionResult.TryFirstBest + + /// Returns a resolution result for the callable with the given name containing the name of the source file or + /// referenced assembly in which it is declared, and a string indicating the redirection if it has been deprecated. + /// Resolution is based on accessibility to source files in this compilation unit. + /// + /// If the given callable corresponds to the (auto-generated) type constructor for a user defined type, returns the + /// file in which that type is declared as the source. + /// + /// Whether the callable has been deprecated is determined by checking the associated attributes for an attribute + /// with the corresponding name. Note that if the type is declared in a source files, the *unresolved* attributes + /// will be checked. In that case checkDeprecation is used to validate the namespace qualification of the attribute. + /// If checkDeprecation is not specified, it is assumed that no qualification is needed in the relevant namespace + /// and source file. + member this.TryFindCallable (cName, ?checkDeprecation : (string -> bool)) = + let checkDeprecation = + defaultArg checkDeprecation + (fun qual -> String.IsNullOrWhiteSpace qual || qual = BuiltIn.Deprecated.FullName.Namespace.Value) + + let fromReferences = + match CallablesInReferences.TryGetValue cName with + | true, callable -> + if Namespace.IsDeclarationAccessible (false, callable.Modifiers.Access) + then Found (callable.SourceFile, SymbolResolution.TryFindRedirect callable.Attributes) + else Inaccessible + | false, _ -> NotFound + + let findInPartial (partial : PartialNamespace) = + match partial.TryGetCallable cName with + | true, (_, callable) -> + if Namespace.IsDeclarationAccessible (true, callable.Modifiers.Access) + then Found (partial.Source, + SymbolResolution.TryFindRedirectInUnresolved checkDeprecation callable.DefinedAttributes) + else Inaccessible + | false, _ -> NotFound + + seq { yield fromReferences + yield Seq.map findInPartial Parts.Values |> ResolutionResult.ExactlyOne } + |> ResolutionResult.TryFirstBest /// Sets the resolution for the type with the given name in the given source file to the given type, /// and replaces the resolved attributes with the given values. /// Fails with the standard key does not exist error if no such source file exists or no type with that name exists in that file. - member internal this.SetTypeResolution source (tName, resolution, resAttributes) = + member internal this.SetTypeResolution source (tName, resolution, resAttributes) = TypesDefinedInAllSourcesCache <- null CallablesDefinedInAllSourcesCache <- null Parts.[source].SetTypeResolution (tName, resolution, resAttributes) - /// Sets the resolution for the signature of the callable with the given name in the given source file + /// Sets the resolution for the signature of the callable with the given name in the given source file /// to the given signature, and replaces the resolved attributes with the given values. /// Fails with the standard key does not exist error if no such source file exists or no callable with that name exists in that file. - member internal this.SetCallableResolution source (cName, resolution, resAttributes) = + member internal this.SetCallableResolution source (cName, resolution, resAttributes) = CallablesDefinedInAllSourcesCache <- null Parts.[source].SetCallableResolution (cName, resolution, resAttributes) - /// Applies the given functions computing the resolution of attributes and the generation directive - /// to all defined specializations of the callable with the given name, - /// and sets its resolution and resolved attributes to the computed values. + /// Applies the given functions computing the resolution of attributes and the generation directive + /// to all defined specializations of the callable with the given name, + /// and sets its resolution and resolved attributes to the computed values. /// Returns a list with the name of the source file and each generated diagnostic. /// Fails with the standard key does not exist error if no callable with that name exists. - member internal this.SetSpecializationResolutions (cName, computeResolution, getResAttributes) = + member internal this.SetSpecializationResolutions (cName, computeResolution, getResAttributes) = CallablesDefinedInAllSourcesCache <- null - let setResolutions (partial : PartialNamespace) = - partial.SetSpecializationResolutions (cName, computeResolution, getResAttributes) + let setResolutions (partial : PartialNamespace) = + partial.SetSpecializationResolutions (cName, computeResolution, getResAttributes) |> Array.map (fun err -> partial.Source, err) Parts.Values |> Seq.map setResolutions |> Seq.toList - /// If the given source is not currently listed as source file for (part of) the namespace, + /// If the given source is not currently listed as source file for (part of) the namespace, /// adds the given file name to the list of sources and returns true. - /// Returns false otherwise. - member internal this.TryAddSource source = - if not (Parts.ContainsKey source) then + /// Returns false otherwise. + member internal this.TryAddSource source = + if not (Parts.ContainsKey source) then TypesDefinedInAllSourcesCache <- null CallablesDefinedInAllSourcesCache <- null Parts.Add(source, new PartialNamespace(this.Name, source)) @@ -493,30 +565,30 @@ and Namespace private /// If the given source is currently listed as source file for (part of) the namespace, /// removes it from that list (and all declarations along with it) and returns true. /// Returns false otherwise. - member internal this.TryRemoveSource source = + member internal this.TryRemoveSource source = if Parts.Remove source then TypesDefinedInAllSourcesCache <- null CallablesDefinedInAllSourcesCache <- null true else false - /// Adds the given lines of documentation to the list of documenting sections - /// associated with this namespace within the given source file. + /// Adds the given lines of documentation to the list of documenting sections + /// associated with this namespace within the given source file. /// Throws an ArgumentException if the given source file is not listed as a source for (part of) the namespace. member this.AddDocumentation source doc = - match Parts.TryGetValue source with + match Parts.TryGetValue source with | true, partial -> partial.AddDocumentation doc | false, _ -> ArgumentException "given source is not listed as a source of (parts of) the namespace" |> raise /// Adds the given namespace name to the list of opened namespaces for the part of the namespace defined in the given source file. - /// Generates suitable diagnostics at the given range if the given namespace has already been opened and/or opened under a different alias, - /// or if the given alias is already in use for a different namespace. + /// Generates suitable diagnostics at the given range if the given namespace has already been opened and/or opened under a different alias, + /// or if the given alias is already in use for a different namespace. /// Throws an ArgumentException if the given source file is not listed as a source for (part of) the namespace. - member internal this.TryAddOpenDirective source (openedNS, nsRange) (alias, aliasRange) = + member internal this.TryAddOpenDirective source (openedNS, nsRange) (alias, aliasRange) = let alias = if String.IsNullOrWhiteSpace alias then null else alias.Trim() let aliasIsSameAs str = (str = null && alias = null) || (str <> null && alias <> null && str = alias) - match Parts.TryGetValue source with - | true, partial -> + match Parts.TryGetValue source with + | true, partial -> let imported = partial.ImportedNamespaces match imported.TryGetValue openedNS with | true, existing when aliasIsSameAs existing && existing = null -> [| nsRange |> QsCompilerDiagnostic.Warning (WarningCode.NamespaceAleadyOpen, []) |] @@ -524,80 +596,94 @@ and Namespace private | true, existing when existing <> null -> [| nsRange |> QsCompilerDiagnostic.Error (ErrorCode.AliasForNamespaceAlreadyExists, [existing]) |] | true, _ -> [| nsRange |> QsCompilerDiagnostic.Error (ErrorCode.AliasForOpenedNamespace, []) |] | false, _ when alias <> null && imported.ContainsValue alias -> [| aliasRange |> QsCompilerDiagnostic.Error (ErrorCode.InvalidNamespaceAliasName, [alias]) |] - | false, _ -> + | false, _ -> TypesDefinedInAllSourcesCache <- null CallablesDefinedInAllSourcesCache <- null partial.AddOpenDirective(openedNS, alias); [||] | false, _ -> ArgumentException "given source is not listed as a source of (parts of) the namespace" |> raise - /// If no type with the given name exists in this namespace, adds the given type declaration - /// as well as the corresponding constructor declaration to the given source, and returns an empty array. - /// The given location is associated with both the type constructur and the type itself and accessible via the record properties Position and SymbolRange. + /// If no type with the given name exists in this namespace, adds the given type declaration + /// as well as the corresponding constructor declaration to the given source, and returns an empty array. + /// The given location is associated with both the type constructur and the type itself and accessible via the record properties Position and SymbolRange. /// If a type or callable with that name already exists, returns an array of suitable diagnostics. /// Throws an ArgumentException if the given source file is not listed as a source for (part of) the namespace. - member this.TryAddType (source, location) ((tName, tRange), typeTuple, attributes, documentation) : QsCompilerDiagnostic[] = - match Parts.TryGetValue source with - | true, partial when not (IsDefined tName) -> + member this.TryAddType (source, location) ((tName, tRange), typeTuple, attributes, modifiers, documentation) : QsCompilerDiagnostic[] = + match Parts.TryGetValue source with + | true, partial when isNameAvailable tName -> TypesDefinedInAllSourcesCache <- null CallablesDefinedInAllSourcesCache <- null - partial.AddType location (tName, typeTuple, attributes, documentation); [||] - | true, _ -> this.ContainsType tName |> function - | Value _ -> [| tRange |> QsCompilerDiagnostic.Error (ErrorCode.TypeRedefinition, [tName.Value]) |] - | Null -> [| tRange |> QsCompilerDiagnostic.Error (ErrorCode.TypeConstructorOverlapWithCallable, [tName.Value]) |] + partial.AddType location (tName, typeTuple, attributes, modifiers, documentation); [||] + | true, _ -> + match this.TryFindType tName with + | Found _ + | Ambiguous _ -> + [| tRange |> QsCompilerDiagnostic.Error (ErrorCode.TypeRedefinition, [tName.Value]) |] + | _ -> + [| tRange |> QsCompilerDiagnostic.Error (ErrorCode.TypeConstructorOverlapWithCallable, [tName.Value]) |] | false, _ -> ArgumentException "given source is not listed as a source of (parts of) the namespace" |> raise - /// If no callable (function, operation, or type constructor) with the given name exists in this namespace, - /// adds a declaration for the callable of the given kind (operation or function) with the given name and signature - /// to the given source, and returns an empty array. - /// The given location is associated with the callable declaration and accessible via the record properties Position and SymbolRange. + /// If no callable (function, operation, or type constructor) with the given name exists in this namespace, + /// adds a declaration for the callable of the given kind (operation or function) with the given name and signature + /// to the given source, and returns an empty array. + /// The given location is associated with the callable declaration and accessible via the record properties Position and SymbolRange. /// If a callable with that name already exists, returns an array of suitable diagnostics. /// Throws an ArgumentException if the given source file is not listed as a source for (part of) the namespace. - member this.TryAddCallableDeclaration (source, location) ((cName, cRange), (kind, signature), attributes, documentation) = - match Parts.TryGetValue source with - | true, partial when not (IsDefined cName) -> + member this.TryAddCallableDeclaration (source, location) ((cName, cRange), (kind, signature), attributes, modifiers, documentation) = + match Parts.TryGetValue source with + | true, partial when isNameAvailable cName -> CallablesDefinedInAllSourcesCache <- null - partial.AddCallableDeclaration location (cName, (kind, signature), attributes, documentation); [||] - | true, _ -> this.ContainsType cName |> function - | Value _ -> [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.CallableOverlapWithTypeConstructor, [cName.Value]) |] - | Null -> [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.CallableRedefinition, [cName.Value]) |] + partial.AddCallableDeclaration location (cName, (kind, signature), attributes, modifiers, documentation); [||] + | true, _ -> + match this.TryFindType cName with + | Found _ + | Ambiguous _ -> + [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.CallableOverlapWithTypeConstructor, [cName.Value]) |] + | _ -> + [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.CallableRedefinition, [cName.Value]) |] | false, _ -> ArgumentException "given source is not listed as a source of (parts of) the namespace" |> raise /// If a declaration for a callable of the given name exists within this namespace, /// verifies that no specialization of the given kind that clashes with the give specialization already exists, /// and adds the specialization defined by the given generator for the given kind to the dictionary of specializations in the given source. - /// The given location is associated with the given specialization and accessible via the record properties Position and HeaderRange. + /// The given location is associated with the given specialization and accessible via the record properties Position and HeaderRange. /// Returns an array with suitable diagnostics if a clashing specialization already exists, and/or - /// if the length of the type arguments in the given generator does not match the number of type parameters of the callable declaration. + /// if the length of the type arguments in the given generator does not match the number of type parameters of the callable declaration. /// If no declaration for the given callable name exists within this namespace, returns an array with suitable diagnostics. /// Throws an ArgumentException if the given source file is not listed as a source for (part of) the namespace. /// IMPORTANT: The verification of whether the given specialization kind (body, adjoint, controlled, or controlled adjoint) may exist - /// for the given callable is up to the calling routine. + /// for the given callable is up to the calling routine. member this.TryAddCallableSpecialization kind (source, location : QsLocation) ((cName, cRange), generator : QsSpecializationGenerator, attributes, documentation) = let getRelevantDeclInfo (declSource : NonNullable) = let unitOrInvalid fct = function - | Item item -> fct item |> function | UnitType | InvalidType -> true | _ -> false + | Item item -> match fct item with + | UnitType + | InvalidType -> true + | _ -> false | _ -> false - match CallablesInReferences.TryGetValue cName with - | true, cDecl -> - let unitReturn = cDecl.Signature.ReturnType |> unitOrInvalid (fun (t : ResolvedType) -> t.Resolution) - unitReturn, cDecl.Signature.TypeParameters.Length - | false, _ -> - let _, cDecl = Parts.[declSource].GetCallable cName // ok only because/if we have covered that the callable is not in a reference! + + // Check if the declaration's source file is local first, then look in references. + match Parts.TryGetValue declSource with + | true, partial -> + let _, cDecl = partial.GetCallable cName let unitReturn = cDecl.Defined.ReturnType |> unitOrInvalid (fun (t : QsType) -> t.Type) unitReturn, cDecl.Defined.TypeParameters.Length + | false, _ -> + let cDecl = CallablesInReferences.[cName] + let unitReturn = cDecl.Signature.ReturnType |> unitOrInvalid (fun (t : ResolvedType) -> t.Resolution) + unitReturn, cDecl.Signature.TypeParameters.Length - match Parts.TryGetValue source with + match Parts.TryGetValue source with | true, partial -> - match this.ContainsCallable cName with - | Value (declSource, _) -> - let AddAndClearCache () = + match this.TryFindCallable cName with + | Found (declSource, _) -> + let AddAndClearCache () = CallablesDefinedInAllSourcesCache <- null partial.AddCallableSpecialization location kind (cName, generator, attributes, documentation) // verify that the given specializations are indeed compatible with the defined type parameters let qFunctorSupport, nrTypeParams = getRelevantDeclInfo declSource - let givenNrTypeParams = generator.TypeArguments |> function | Value args -> Some args.Length | Null -> None + let givenNrTypeParams = generator.TypeArguments |> function | Value args -> Some args.Length | Null -> None if givenNrTypeParams.IsSome && givenNrTypeParams.Value <> nrTypeParams then - [| location.Range |> QsCompilerDiagnostic.Error (ErrorCode.TypeSpecializationMismatch, [nrTypeParams.ToString()]) |] + [| location.Range |> QsCompilerDiagnostic.Error (ErrorCode.TypeSpecializationMismatch, [nrTypeParams.ToString()]) |] // verify if a unit return value is required for the given specialization kind elif not qFunctorSupport then kind |> function | QsBody -> AddAndClearCache(); [||] @@ -605,38 +691,43 @@ and Namespace private | QsControlled -> [| location.Range |> QsCompilerDiagnostic.Error (ErrorCode.RequiredUnitReturnForControlled, []) |] | QsControlledAdjoint -> [| location.Range |> QsCompilerDiagnostic.Error (ErrorCode.RequiredUnitReturnForControlledAdjoint, []) |] else AddAndClearCache(); [||] - | Null -> [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.SpecializationForUnknownCallable, [cName.Value]) |] + | _ -> [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.SpecializationForUnknownCallable, [cName.Value]) |] | false, _ -> ArgumentException "given source is not listed as a source of (parts of) the namespace" |> raise - /// Adds an auto-generated specialization of the given kind to the callable with the given name and declaration in the specified source file. - /// Sets the location to the same location as the callable declaration, with the range set to the message range if the given message range is not Null. - /// Return the diagnostics generated upon adding the specialization. + /// Adds an auto-generated specialization of the given kind to the callable with the given name and declaration in the specified source file. + /// Sets the location to the same location as the callable declaration, with the range set to the message range if the given message range is not Null. + /// Return the diagnostics generated upon adding the specialization. /// Throws an ArgumentException if the given source file is not listed as a source for (part of) the namespace. member internal this.InsertSpecialization (kind, typeArgs) (parentName : NonNullable, source) (declLocation : QsLocation, msgRange : QsRangeInfo) = let location = {Offset = declLocation.Offset; Range = msgRange.ValueOr declLocation.Range} let generator = {TypeArguments = typeArgs; Generator = AutoGenerated; Range = msgRange} let doc = ImmutableArray.Create(sprintf "automatically generated %A specialization for %s.%s" kind this.Name.Value parentName.Value) - this.TryAddCallableSpecialization kind (source, location) ((parentName, declLocation.Range), generator, ImmutableArray.Empty, doc) + this.TryAddCallableSpecialization kind (source, location) ((parentName, declLocation.Range), generator, ImmutableArray.Empty, doc) /// Deletes the specialization(s) defined at the specified location and source file for the callable with the given name. /// Returns the number of removed specializations. /// Throws an ArgumentException if the given source file is not listed as a source for (part of) the namespace. /// Throws the standard key does not exist exception if no callable with that name exists. member internal this.RemoveSpecialization (source, location) cName = - match Parts.TryGetValue source with + match Parts.TryGetValue source with | true, partial -> partial.RemoveCallableSpecialization location cName | false, _ -> ArgumentException "given source is not listed as a source of (parts of) the namespace" |> raise /// Threadsafe class for global symbol management. -/// Takes a lookup for all callables and for all types declared within one of the assemblies -/// referenced by the compilation unit this namespace manager belongs to. -/// The key for the given lookups is the name of the namespace the declarations belongs to. -and NamespaceManager +/// +/// Takes a lookup for all callables and for all types declared within one of the assemblies referenced by the +/// compilation unit this namespace manager belongs to. The key for the given lookups is the name of the namespace the +/// declarations belongs to. +/// +/// The namespace manager takes access modifiers into consideration when resolving symbols. Some methods bypass this +/// (e.g., when returning a list of all declarations). Individual methods document whether they follow or ignore access +/// modifiers. +and NamespaceManager (syncRoot : IReaderWriterLock, - callablesInRefs : IEnumerable, + callablesInRefs : IEnumerable, specializationsInRefs : IEnumerable, - typesInRefs : IEnumerable) = + typesInRefs : IEnumerable) = // This class itself does not use any concurrency, // so anything that is accessible within the class only does not apply any locks. // IMPORTANT: the syncRoot is intentionally not exposed externally, since with this class supporting mutation @@ -649,7 +740,7 @@ and NamespaceManager /// dictionary with all declared namespaces /// the key is the name of the namespace - let Namespaces = + let Namespaces = let namespaces = new Dictionary, Namespace>() let callables = callablesInRefs.ToLookup(fun header -> header.QualifiedName.Namespace) let specializations = specializationsInRefs.ToLookup(fun (header,_) -> header.Parent.Namespace) @@ -660,46 +751,42 @@ and NamespaceManager namespaces.Add (nsName, new Namespace(nsName, [], callables.[nsName], specializations.[nsName], types.[nsName])) namespaces - /// Returns the full name of all entry points currently resolved in any of the tracked source files. - let GetEntryPoints () = - let entryPoints = Namespaces.Values |> Seq.collect (fun ns -> + /// Returns the full name of all entry points currently resolved in any of the tracked source files. + let GetEntryPoints () = + let entryPoints = Namespaces.Values |> Seq.collect (fun ns -> ns.CallablesDefinedInAllSources() |> Seq.choose (fun kvPair -> let cName, (source, (_, decl)) = kvPair.Key, kvPair.Value if decl.ResolvedAttributes |> Seq.exists BuiltIn.MarksEntryPoint then Some ({Namespace = ns.Name; Name = cName}, source) else None)) entryPoints.ToImmutableArray() - /// If a namespace with the given name exists, returns that namespace + /// If a namespace with the given name exists, returns that namespace /// as well as all imported namespaces for that namespace in the given source file. /// Filters namespaces that have been imported under a different name. /// Filters all unknown namespaces, i.e. imported namespaces that are not managed by this namespace manager. - /// Throws an ArgumentException if no namespace with the given name exists, - /// or the given source file is not listed as source of that namespace. - let OpenNamespaces (nsName, source) = - let isKnownAndNotAliased (kv : KeyValuePair<_,_>) = + /// Throws an ArgumentException if no namespace with the given name exists, + /// or the given source file is not listed as source of that namespace. + let OpenNamespaces (nsName, source) = + let isKnownAndNotAliased (kv : KeyValuePair<_,_>) = if kv.Value <> null then None else Namespaces.TryGetValue kv.Key |> function | true, ns -> Some ns | false, _ -> None - match Namespaces.TryGetValue nsName with - | true, ns -> ns, ns.ImportedNamespaces source |> Seq.choose isKnownAndNotAliased |> Seq.toList + match Namespaces.TryGetValue nsName with + | true, ns -> ns, ns.ImportedNamespaces source |> Seq.choose isKnownAndNotAliased |> Seq.toList | false, _ -> ArgumentException("no namespace with the given name exists") |> raise - /// If the given function containsSymbol returns a Value for the namespace with the given name, - /// returns a list containing only a single tuple with the given namespace name and the returned value. - /// Otherwise returns a list with all possible (namespaceName, returnedValue) tuples that yielded non-Null values - /// for namespaces opened within the given namespace and source file. - /// Throws an ArgumentException if no namespace with the given name exists, - /// or the given source file is not listed as source of that namespace. - let PossibleResolutions containsSymbol (nsName, source) = - let containsSource (arg : Namespace) = - containsSymbol arg |> QsNullable<_>.Map (fun source -> (arg.Name, source)) - let currentNS, importedNS = OpenNamespaces (nsName, source) - currentNS |> containsSource |> function - | Value (nsName, source) -> [(nsName, source)] - | Null -> importedNS |> List.choose (containsSource >> function | Value v -> Some v | Null -> None) + /// Calls the resolver function on each namespace opened within the given namespace name and source file, and + /// attempts to find an unambiguous resolution. + let resolveInOpenNamespaces resolver (nsName, source) = + let resolveWithNsName (ns : Namespace) = + resolver ns |> ResolutionResult.Map (fun value -> (ns.Name, value)) + let currentNs, importedNs = OpenNamespaces (nsName, source) + seq { yield resolveWithNsName currentNs + yield Seq.map resolveWithNsName importedNs |> ResolutionResult.TryExactlyOne fst } + |> ResolutionResult.TryFirstBest /// Given a qualifier for a symbol name, returns the corresponding namespace as Some - /// if such a namespace or such a namespace short name within the given parent namespace and source file exists. + /// if such a namespace or such a namespace short name within the given parent namespace and source file exists. /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent namespace does not exist. - let TryResolveQualifier qualifier (nsName, source) = + let TryResolveQualifier qualifier (nsName, source) = match Namespaces.TryGetValue qualifier with | false, _ -> Namespaces.TryGetValue nsName |> function // check if qualifier is a namespace short name | true, parentNS -> (parentNS.NamespaceShortNames source).TryGetValue qualifier |> function @@ -711,193 +798,318 @@ and NamespaceManager | true, ns -> Some ns /// Returns the possible qualifications for the built-in type or callable used in the given namespace and source. - /// where the given source may either be the name of a source file or of a referenced assembly. - /// If the given source is not listed as source file of the namespace, assumes that the source if one of the references - /// and returns the namespace name of the given built in type or callable as only possible qualification. - /// Throws an ArgumentException if no namespace with the given name exists. - let PossibleQualifications (nsName, source) (builtIn : BuiltIn) = - match Namespaces.TryGetValue nsName with - | true, ns when ns.Sources.Contains source -> (ns.ImportedNamespaces source).TryGetValue builtIn.Namespace |> function - | true, null when ns.ContainsType builtIn.Name = Null || nsName.Value = builtIn.Namespace.Value -> [""; builtIn.Namespace.Value] - | true, null -> [builtIn.Namespace.Value] // the built-in type or callable is shadowed - | true, alias -> [alias; builtIn.Namespace.Value] - | false, _ -> [builtIn.Namespace.Value] - | true, _ -> [builtIn.Namespace.Value]; + /// where the given source may either be the name of a source file or of a referenced assembly. + /// If the given source is not listed as source file of the namespace, assumes that the source if one of the references + /// and returns the namespace name of the given built in type or callable as only possible qualification. + /// Throws an ArgumentException if no namespace with the given name exists. + let PossibleQualifications (nsName, source) (builtIn : BuiltIn) = + match Namespaces.TryGetValue nsName with + | true, ns when ns.Sources.Contains source -> + match (ns.ImportedNamespaces source).TryGetValue builtIn.FullName.Namespace with + | true, null when not (ns.TryFindType builtIn.FullName.Name |> ResolutionResult.IsAccessible) || + nsName.Value = builtIn.FullName.Namespace.Value -> + [""; builtIn.FullName.Namespace.Value] + | true, null -> [builtIn.FullName.Namespace.Value] // the built-in type or callable is shadowed + | true, alias -> [alias; builtIn.FullName.Namespace.Value] + | false, _ -> [builtIn.FullName.Namespace.Value] + | true, _ -> [builtIn.FullName.Namespace.Value]; | false, _ -> ArgumentException "no namespace with the given name exists" |> raise - /// Given the qualified or unqualfied name of a type used within the given parent namespace and source file, determines if such a type exists - /// and returns its full name and the source file or referenced assembly in which it is defined as Some if it does. - /// Returns None if no such type exist, or if the type name is unqualified and ambiguous. + /// Given the qualified or unqualified name of a type used within the given parent namespace and source file, + /// determines if such a type is accessible, and returns its namespace name and the source file or referenced + /// assembly in which it is defined as Some if it is. + /// + /// Returns None if no such type exists, the type is inaccessible, or if the type name is unqualified and ambiguous. + /// /// Generates and returns an array with suitable diagnostics. - /// Throws an ArgumentException if the given parent namespace does not exist, - /// or if no source file with the given name is listed as source of that namespace. - let TryResolveTypeName (parentNS, source) ((nsName, symName), symRange) = - let orDefault (range : QsNullable<_>) = range.ValueOr QsCompilerDiagnostic.DefaultRange - let checkQualificationForDeprecation qual = BuiltIn.Deprecated |> PossibleQualifications (parentNS, source) |> Seq.contains qual - let buildAndReturn (ns, declSource, deprecation, errs) = - let deprecatedWarnings = deprecation |> SymbolResolution.GenerateDeprecationWarning ({Namespace = ns; Name = symName}, symRange |> orDefault) - Some ({Namespace = ns; Name = symName; Range = symRange}, declSource), deprecatedWarnings |> Array.append errs - let tryFind (parentNS, source) (tName, tRange) = - match (parentNS, source) |> PossibleResolutions (fun ns -> ns.ContainsType (tName, checkQualificationForDeprecation)) with - | [] -> Null, [| tRange |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.UnknownType, [tName.Value]) |] - | [(nsName, (declSource, deprecated))] -> Value (nsName, declSource, deprecated), [||] - | resolutions -> - let diagArg = String.Join(", ", resolutions.Select (fun (ns,_) -> ns.Value)) - Null, [| tRange |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.AmbiguousType, [tName.Value; diagArg]) |] - match nsName with - | None -> tryFind (parentNS, source) (symName, symRange) |> function - | Value (ns, declSource, deprecation), errs -> buildAndReturn (ns, declSource, deprecation, errs) - | Null, errs -> None, errs - | Some qualifier -> (parentNS, source) |> TryResolveQualifier qualifier |> function - | None -> None, [| symRange |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.UnknownNamespace, [qualifier.Value]) |] - | Some ns -> - ns.ContainsType (symName, checkQualificationForDeprecation) |> function - | Value (declSource, deprecation) -> buildAndReturn (ns.Name, declSource, deprecation, [||]) - | Null -> None, [| symRange |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.UnknownTypeInNamespace, [symName.Value; qualifier.Value]) |] - - /// Given the name of the namespace as well as the source file in which the attribute occurs, resolves the given attribute. - /// Generates suitable diagnostics if a suitable attribute cannot be determined, - /// or if the attribute argument contains expressions that are not supported, - /// or if the resolved argument type does not match the expected argument type. + /// + /// Throws an ArgumentException if the given parent namespace does not exist, or if no source file with the given + /// name is listed as source of that namespace. + let tryResolveTypeName (parentNS, source) ((nsName, symName), symRange : QsRangeInfo) = + let checkQualificationForDeprecation qual = + BuiltIn.Deprecated |> PossibleQualifications (parentNS, source) |> Seq.contains qual + + let success ns declSource deprecation access errs = + let warnings = + SymbolResolution.GenerateDeprecationWarning + ({Namespace = ns; Name = symName}, symRange.ValueOr QsCompilerDiagnostic.DefaultRange) + deprecation + Some ({Namespace = ns; Name = symName; Range = symRange}, declSource, access), Array.append errs warnings + + let error code args = + None, [| QsCompilerDiagnostic.Error (code, args) (symRange.ValueOr QsCompilerDiagnostic.DefaultRange) |] + + let findUnqualified () = + match resolveInOpenNamespaces (fun ns -> ns.TryFindType (symName, checkQualificationForDeprecation)) + (parentNS, source) with + | Found (nsName, (declSource, deprecation, access)) -> success nsName declSource deprecation access [||] + | Ambiguous namespaces -> + let names = String.Join(", ", Seq.map (fun (ns : NonNullable) -> ns.Value) namespaces) + error ErrorCode.AmbiguousType [symName.Value; names] + | Inaccessible -> error ErrorCode.InaccessibleType [symName.Value] + | NotFound -> error ErrorCode.UnknownType [symName.Value] + + let findQualified (ns : Namespace) qualifier = + match ns.TryFindType (symName, checkQualificationForDeprecation) with + | Found (declSource, deprecation, access) -> success ns.Name declSource deprecation access [||] + | Ambiguous _ -> QsCompilerError.Raise "Qualified name should not be ambiguous" + Exception () |> raise + | Inaccessible -> error ErrorCode.InaccessibleTypeInNamespace [symName.Value; qualifier] + | NotFound -> error ErrorCode.UnknownTypeInNamespace [symName.Value; qualifier] + + match nsName with + | None -> findUnqualified () + | Some qualifier -> + match TryResolveQualifier qualifier (parentNS, source) with + | None -> error ErrorCode.UnknownNamespace [qualifier.Value] + | Some ns -> findQualified ns qualifier.Value + + /// Fully (i.e. recursively) resolves the given Q# type used within the given parent in the given source file. The + /// resolution consists of replacing all unqualified names for user defined types by their qualified name. + /// + /// Generates an array of diagnostics for the cases where no user defined type of the specified name (qualified or + /// unqualified) can be found, or if the type is inaccessible. In that case, resolves the user defined type by + /// replacing it with the Q# type denoting an invalid type. + /// + /// Diagnostics can be generated in additional cases when UDTs are referenced by returning an array of diagnostics + /// from the given checkUdt function. + /// + /// Verifies that all used type parameters are defined in the given list of type parameters, and generates suitable + /// diagnostics if they are not, replacing them by the Q# type denoting an invalid type. Returns the resolved type + /// as well as an array with diagnostics. + /// + /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is + /// consistent with the defined callables. + /// + /// May throw an exception if the given parent and/or source file is inconsistent with the defined declarations. + /// Throws a NotSupportedException if the QsType to resolve contains a MissingType. + let resolveType (parent : QsQualifiedName, tpNames, source) qsType checkUdt = + let processUDT = tryResolveTypeName (parent.Namespace, source) >> function + | Some (udt, _, access), errs -> UserDefinedType udt, Array.append errs (checkUdt (udt, access)) + | None, errs -> InvalidType, errs + let processTP (symName, symRange) = + if tpNames |> Seq.contains symName + then TypeParameter {Origin = parent; TypeName = symName; Range = symRange}, [||] + else InvalidType, [| symRange.ValueOr QsCompilerDiagnostic.DefaultRange + |> QsCompilerDiagnostic.Error (ErrorCode.UnknownTypeParameterName, [symName.Value]) |] + syncRoot.EnterReadLock() + try SymbolResolution.ResolveType (processUDT, processTP) qsType + finally syncRoot.ExitReadLock() + + /// Compares the accessibility of the parent declaration with the accessibility of the UDT being referenced. If the + /// accessibility of a referenced type is less than the accessibility of the parent, returns a diagnostic using the + /// given error code. Otherwise, returns an empty array. + let checkUdtAccessibility code + (parent : NonNullable, parentAccess) + (udt : UserDefinedType, udtAccess) = + if parentAccess = DefaultAccess && udtAccess = Internal + then [| QsCompilerDiagnostic.Error (code, [udt.Name.Value; parent.Value]) + (udt.Range.ValueOr QsCompilerDiagnostic.DefaultRange) |] + else [||] + + + /// Given the name of the namespace as well as the source file in which the attribute occurs, resolves the given + /// attribute. + /// + /// Generates suitable diagnostics if a suitable attribute cannot be found or is not accessible, if the attribute + /// argument contains expressions that are not supported, or if the resolved argument type does not match the + /// expected argument type. + /// /// Returns the resolved attribute as well as the generated diagnostics. - /// The TypeId in the resolved attribute is set to Null if the unresolved Id is not a valid identifier - /// or if the correct attribute cannot be determined, and is set to the corresponding type identifier otherwise. - /// May throw an ArgumentException if the given parent namespace does not exist, - /// or if no source file with the given name is listed as source of that namespace. + /// + /// The TypeId in the resolved attribute is set to Null if the unresolved Id is not a valid identifier or if the + /// correct attribute cannot be determined, and is set to the corresponding type identifier otherwise. + /// + /// May throw an ArgumentException if the given parent namespace does not exist or if no source file with the given + /// name is listed as source of that namespace. member private this.ResolveAttribute (parentNS, source) attribute = let getAttribute ((nsName, symName), symRange) = - match TryResolveTypeName (parentNS, source) ((nsName, symName), symRange) with - | Some (udt, declSource), errs -> // declSource may be the name of an assembly! + match tryResolveTypeName (parentNS, source) ((nsName, symName), symRange) with + | Some (udt, declSource, _), errs -> // declSource may be the name of an assembly! let fullName = sprintf "%s.%s" udt.Namespace.Value udt.Name.Value let validQualifications = BuiltIn.Attribute |> PossibleQualifications (udt.Namespace, declSource) - match Namespaces.TryGetValue udt.Namespace with - | true, ns -> ns.TryGetAttributeDeclaredIn declSource (udt.Name, validQualifications) |> function - | None -> None, [| symRange.ValueOr QsCompilerDiagnostic.DefaultRange |> QsCompilerDiagnostic.Error (ErrorCode.NotMarkedAsAttribute, [fullName]) |] + match Namespaces.TryGetValue udt.Namespace with + | true, ns -> ns.TryGetAttributeDeclaredIn declSource (udt.Name, validQualifications) |> function + | None -> None, [| symRange.ValueOr QsCompilerDiagnostic.DefaultRange |> QsCompilerDiagnostic.Error (ErrorCode.NotMarkedAsAttribute, [fullName]) |] | Some argType -> Some (udt, argType), errs | false, _ -> QsCompilerError.Raise "namespace for defined type not found"; None, errs | None, errs -> None, errs let resolved, msgs = SymbolResolution.ResolveAttribute getAttribute attribute resolved, msgs |> Array.map (fun m -> attribute.Position, m) - /// Resolves the DefinedAttributes of the given declaration using ResolveAttribute and validates any entry points, if any. - /// Returns the resolved attributes as well as an array with diagnostics along with the declaration position. - /// Each entry in the returned array of attributes is the resolution for the corresponding entry in the array of defined attributes. - /// May throw an ArgumentException if no parent callable with the given name exists. - member private this.ResolveAttributes (parent : QsQualifiedName, source) (decl : Resolution<'T,_>) = + /// Resolves the DefinedAttributes of the given declaration using ResolveAttribute and validates any entry points, if any. + /// Returns the resolved attributes as well as an array with diagnostics along with the declaration position. + /// Each entry in the returned array of attributes is the resolution for the corresponding entry in the array of defined attributes. + /// May throw an ArgumentException if no parent callable with the given name exists. + member private this.ResolveAttributes (parent : QsQualifiedName, source) (decl : Resolution<'T,_>) = + let isBuiltIn (builtIn : BuiltIn) (tId : UserDefinedType) = + tId.Namespace.Value = builtIn.FullName.Namespace.Value && tId.Name.Value = builtIn.FullName.Name.Value let attr, msgs = decl.DefinedAttributes |> Seq.map (this.ResolveAttribute (parent.Namespace, source)) |> Seq.toList |> List.unzip let errs = new List<_>(msgs |> Seq.collect id) - let validateAttributes (alreadyDefined : int list, resAttr) (att : QsDeclarationAttribute) = - let returnInvalid msg = + let validateAttributes (alreadyDefined : int list, resAttr) (att : QsDeclarationAttribute) = + let returnInvalid msg = errs.AddRange msg alreadyDefined, {att with TypeId = Null} :: resAttr match att.TypeId with // known attribute - | Value tId -> + | Value tId -> let orDefault (range : QsNullable<_>) = range.ValueOr QsCompilerDiagnostic.DefaultRange - let attributeHash = - if tId.Namespace.Value = BuiltIn.Deprecated.Namespace.Value && tId.Name.Value = BuiltIn.Deprecated.Name.Value then hash (tId.Namespace.Value, tId.Name.Value) + let attributeHash = + if tId |> isBuiltIn BuiltIn.Deprecated then hash (tId.Namespace.Value, tId.Name.Value) + elif tId |> isBuiltIn BuiltIn.EnableTestingViaName then hash (tId.Namespace.Value, tId.Name.Value) else hash (tId.Namespace.Value, tId.Name.Value, NamespaceManager.ExpressionHash att.Argument) // the attribute is a duplication of another attribute on this declaration - if alreadyDefined.Contains attributeHash then - (att.Offset, tId.Range |> orDefault - |> QsCompilerDiagnostic.Warning (WarningCode.DuplicateAttribute, [tId.Name.Value])) + if alreadyDefined.Contains attributeHash then + (att.Offset, tId.Range |> orDefault + |> QsCompilerDiagnostic.Warning (WarningCode.DuplicateAttribute, [tId.Name.Value])) |> Seq.singleton |> returnInvalid - // the attribute marks an entry point - elif tId.Namespace.Value = BuiltIn.EntryPoint.Namespace.Value && tId.Name.Value = BuiltIn.EntryPoint.Name.Value then + // the attribute marks an entry point + elif tId |> isBuiltIn BuiltIn.EntryPoint then match box decl.Defined with - | :? CallableSignature as signature when not (signature.TypeParameters.Any()) -> - let validateArgAndReturnTypes (qsType : QsType) = + | :? CallableSignature as signature when not (signature.TypeParameters.Any()) -> + let validateArgAndReturnTypes (qsType : QsType) = qsType.ExtractAll (fun t -> t.Type |> function // ExtractAll recurs on all subtypes (e.g. callable in- and output types as well) - | Qubit -> (decl.Position, t.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.QubitTypeInEntryPointSignature, [])) |> Seq.singleton - | QsTypeKind.Operation _ -> (decl.Position, t.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.CallableTypeInEntryPointSignature, [])) |> Seq.singleton - | QsTypeKind.Function _ -> (decl.Position, t.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.CallableTypeInEntryPointSignature, [])) |> Seq.singleton - | UserDefinedType _ -> (decl.Position, t.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.UserDefinedTypeInEntryPointSignature, [])) |> Seq.singleton + | Qubit -> (decl.Position, t.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.QubitTypeInEntryPointSignature, [])) |> Seq.singleton + | QsTypeKind.Operation _ -> (decl.Position, t.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.CallableTypeInEntryPointSignature, [])) |> Seq.singleton + | QsTypeKind.Function _ -> (decl.Position, t.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.CallableTypeInEntryPointSignature, [])) |> Seq.singleton + | UserDefinedType _ -> (decl.Position, t.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.UserDefinedTypeInEntryPointSignature, [])) |> Seq.singleton | _ -> Seq.empty) - let argErrs = signature.Argument.Items.Select(snd).Append signature.ReturnType |> Seq.collect validateArgAndReturnTypes + let argErrs = signature.Argument.Items.Select(snd).Append signature.ReturnType |> Seq.collect validateArgAndReturnTypes let hasCharacteristics = signature.Characteristics.Characteristics |> function | EmptySet | InvalidSetExpr -> false | _ -> true - match Namespaces.TryGetValue parent.Namespace with + match Namespaces.TryGetValue parent.Namespace with | false, _ -> ArgumentException "no namespace with the given name exists" |> raise | true, ns when not ((ns.SpecializationsDefinedInAllSources parent.Name).Any(fst >> (<>)QsBody) || hasCharacteristics) -> () | _ -> errs.Add (decl.Position, signature.Characteristics.Range.ValueOr decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.InvalidEntryPointSpecialization, [])) - if argErrs.Any() then returnInvalid argErrs + if argErrs.Any() then returnInvalid argErrs else GetEntryPoints() |> Seq.tryHead |> function | None -> attributeHash :: alreadyDefined, att :: resAttr - | Some (epName, epSource) -> + | Some (epName, epSource) -> let msgArgs = [sprintf "%s.%s" epName.Namespace.Value epName.Name.Value; epSource.Value] - (att.Offset, tId.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.MultipleEntryPoints, msgArgs)) |> Seq.singleton |> returnInvalid + (att.Offset, tId.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.MultipleEntryPoints, msgArgs)) |> Seq.singleton |> returnInvalid | _ -> (att.Offset, tId.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.InvalidEntryPointPlacement, [])) |> Seq.singleton |> returnInvalid - + // the attribute marks a unit test - elif tId.Namespace.Value = BuiltIn.Test.Namespace.Value && tId.Name.Value = BuiltIn.Test.Name.Value then - let isUnitToUnit (signature : CallableSignature) = - let isUnitType = function + elif tId |> isBuiltIn BuiltIn.Test then + let isUnitToUnit (signature : CallableSignature) = + let isUnitType = function | Tuple _ | Missing -> false | Item (itemType : QsType) -> itemType.Type = UnitType | _ -> true // invalid type - match signature.Argument.Items |> Seq.toList with + match signature.Argument.Items |> Seq.toList with | [] -> signature.ReturnType |> isUnitType | [(_, argType)] -> argType |> isUnitType && signature.ReturnType |> isUnitType | _ -> false - match box decl.Defined with - | :? CallableSignature as signature when signature |> isUnitToUnit && not (signature.TypeParameters.Any()) -> + match box decl.Defined with + | :? CallableSignature as signature when signature |> isUnitToUnit && not (signature.TypeParameters.Any()) -> let arg = att.Argument |> AttributeAnnotation.NonInterpolatedStringArgument (fun ex -> ex.Expression) let validExecutionTargets = BuiltIn.ValidExecutionTargets |> Seq.map (fun x -> x.ToLowerInvariant()) if arg <> null && (validExecutionTargets |> Seq.contains (arg.ToLowerInvariant()) || SyntaxGenerator.FullyQualifiedName.IsMatch arg) then - attributeHash :: alreadyDefined, att :: resAttr - else (att.Offset, att.Argument.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.InvalidExecutionTargetForTest, [])) |> Seq.singleton |> returnInvalid + attributeHash :: alreadyDefined, att :: resAttr + else (att.Offset, att.Argument.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.InvalidExecutionTargetForTest, [])) |> Seq.singleton |> returnInvalid | _ -> (att.Offset, tId.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.InvalidTestAttributePlacement, [])) |> Seq.singleton |> returnInvalid + // the attribute defines an alternative name for testing purposes + elif tId |> isBuiltIn BuiltIn.EnableTestingViaName then + let arg = att.Argument |> AttributeAnnotation.NonInterpolatedStringArgument (fun ex -> ex.Expression) + match box decl.Defined with + | :? QsSpecializationGenerator -> + (att.Offset, tId.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.AttributeInvalidOnSpecialization, [tId.Name.Value])) |> Seq.singleton |> returnInvalid + | _ when SyntaxGenerator.FullyQualifiedName.IsMatch arg -> attributeHash :: alreadyDefined, att :: resAttr + | _ -> (att.Offset, tId.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.ExpectingFullNameAsAttributeArgument, [tId.Name.Value])) |> Seq.singleton |> returnInvalid + + // the attribute marks an attribute + elif tId |> isBuiltIn BuiltIn.Attribute then + match box decl.Defined with + | :? CallableSignature -> + (att.Offset, tId.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.AttributeInvalidOnCallable, [tId.Name.Value])) |> Seq.singleton |> returnInvalid + | :? QsSpecializationGenerator -> + (att.Offset, tId.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.AttributeInvalidOnSpecialization, [tId.Name.Value])) |> Seq.singleton |> returnInvalid + | _ -> attributeHash :: alreadyDefined, att :: resAttr + + // the attribute marks a deprecation + elif tId |> isBuiltIn BuiltIn.Deprecated then + match box decl.Defined with + | :? QsSpecializationGenerator -> + (att.Offset, tId.Range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.AttributeInvalidOnSpecialization, [tId.Name.Value])) |> Seq.singleton |> returnInvalid + | _ -> attributeHash :: alreadyDefined, att :: resAttr + // the attribute is another kind of attribute that requires no further verification at this point - else attributeHash :: alreadyDefined, att :: resAttr + else attributeHash :: alreadyDefined, att :: resAttr // unknown attribute, and an error has already been generated | _ -> alreadyDefined, att :: resAttr let resAttr = attr |> List.fold validateAttributes ([], []) |> snd resAttr.Reverse() |> ImmutableArray.CreateRange, errs.ToArray() - /// Fully (i.e. recursively) resolves the given Q# type used within the given parent in the given source file. - /// The resolution consists of replacing all unqualified names for user defined types by their qualified name. - /// Generates an array of diagnostics for the cases where no user defined type of the specified name (qualified or unqualified) can be found. - /// In that case, resolves the user defined type by replacing it with the Q# type denoting an invalid type. - /// Verifies that all used type parameters are defined in the given list of type parameters, - /// and generates suitable diagnostics if they are not, replacing them by the Q# type denoting an invalid type. - /// Returns the resolved type as well as an array with diagnostics. - /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is consistent with the defined callables. - /// May throw an exception if the given parent and/or source file is inconsistent with the defined declarations. - /// Throws a NonSupportedException if the QsType to resolve contains a MissingType. - member this.ResolveType (parent : QsQualifiedName, tpNames : ImmutableArray<_>, source : NonNullable) (qsType : QsType) : ResolvedType * QsCompilerDiagnostic[] = - let processUDT = TryResolveTypeName (parent.Namespace, source) >> function - | Some (udt, _), errs -> UserDefinedType udt, errs - | None, errs -> InvalidType, errs - let processTP (symName, symRange) = - if tpNames |> Seq.contains symName then TypeParameter {Origin = parent; TypeName = symName; Range = symRange}, [||] - else InvalidType, [| symRange.ValueOr QsCompilerDiagnostic.DefaultRange |> QsCompilerDiagnostic.Error (ErrorCode.UnknownTypeParameterName, [symName.Value]) |] - syncRoot.EnterReadLock() - try SymbolResolution.ResolveType (processUDT, processTP) qsType - finally syncRoot.ExitReadLock() - - /// Resolves the underlying type as well as all named and unnamed items for the given type declaration in the specified source file. - /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is consistent with the defined types. - /// May throw an exception if the given parent and/or source file is inconsistent with the defined types. - /// Throws an ArgumentException if the given type tuple is an empty QsTuple. - member private this.ResolveTypeDeclaration (fullName : QsQualifiedName, source) typeTuple = - let resolveType = this.ResolveType (fullName, ImmutableArray<_>.Empty, source) // currently type parameters for udts are not supported + /// Fully (i.e. recursively) resolves the given Q# type used within the given parent in the given source file. The + /// resolution consists of replacing all unqualified names for user defined types by their qualified name. + /// + /// Generates an array of diagnostics for the cases where no user defined type of the specified name (qualified or + /// unqualified) can be found or the type is inaccessible. In that case, resolves the user defined type by replacing + /// it with the Q# type denoting an invalid type. + /// + /// Verifies that all used type parameters are defined in the given list of type parameters, and generates suitable + /// diagnostics if they are not, replacing them by the Q# type denoting an invalid type. Returns the resolved type + /// as well as an array with diagnostics. + /// + /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is + /// consistent with the defined callables. + /// + /// May throw an exception if the given parent and/or source file is inconsistent with the defined declarations. + /// Throws a NotSupportedException if the QsType to resolve contains a MissingType. + member this.ResolveType (parent : QsQualifiedName, tpNames : ImmutableArray<_>, source : NonNullable) + (qsType : QsType) + : ResolvedType * QsCompilerDiagnostic[] = + resolveType (parent, tpNames, source) qsType (fun _ -> [||]) + + /// Resolves the underlying type as well as all named and unnamed items for the given type declaration in the + /// specified source file using ResolveType. + /// + /// Generates the same diagnostics as ResolveType, as well as additional diagnostics when the accessibility of the + /// type declaration is greater than the accessibility of any part of its underlying type. + /// + /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is + /// consistent with the defined types. + /// + /// May throw an exception if the given parent and/or source file is inconsistent with the defined types. Throws an + /// ArgumentException if the given type tuple is an empty QsTuple. + member private this.ResolveTypeDeclaration (fullName, source, modifiers) typeTuple = + // Currently, type parameters for UDTs are not supported. + let resolveType qsType = + resolveType + (fullName, ImmutableArray<_>.Empty, source) + qsType + (checkUdtAccessibility ErrorCode.TypeLessAccessibleThanParentType (fullName.Name, modifiers.Access)) SymbolResolution.ResolveTypeDeclaration resolveType typeTuple - /// Given the namespace and the name of the callable that the given signature belongs to, as well as its kind and the source file it is declared in, - /// fully resolves all Q# types in the signature using ResolveType. - /// Returns a new signature with the resolved types, the resolved argument tuple, as well as the array of diagnostics created during type resolution. - /// The position offset information for the variables declared in the argument tuple will be set to Null. - /// Positional information within types is set to Null if the parent callable is a type constructor. - /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is consistent with the defined callables. - /// May throw an exception if the given parent and/or source file is inconsistent with the defined callables. - /// Throws an ArgumentException if the given list of characteristics is empty. - member private this.ResolveCallableSignature (parentKind, parentName : QsQualifiedName, source) (signature : CallableSignature, specBundleCharacteristics) = - let resolveType tpNames t = - let res, errs = this.ResolveType (parentName, tpNames, source) t + /// Given the namespace and the name of the callable that the given signature belongs to, as well as its kind and + /// the source file it is declared in, fully resolves all Q# types in the signature using ResolveType. + /// + /// Generates the same diagnostics as ResolveType, as well as additional diagnostics when the accessibility of the + /// callable declaration is greater than the accessibility of any type in its signature. + /// + /// Returns a new signature with the resolved types, the resolved argument tuple, as well as the array of + /// diagnostics created during type resolution. + /// + /// The position offset information for the variables declared in the argument tuple will be set to Null. Positional + /// information within types is set to Null if the parent callable is a type constructor. + /// + /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is + /// consistent with the defined callables. + /// + /// May throw an exception if the given parent and/or source file is inconsistent with the defined callables. Throws + /// an ArgumentException if the given list of characteristics is empty. + member private this.ResolveCallableSignature (parentKind, parentName, source, access) + (signature, specBundleCharacteristics) = + let resolveType tpNames qsType = + let res, errs = + resolveType + (parentName, tpNames, source) + qsType + (checkUdtAccessibility ErrorCode.TypeLessAccessibleThanParentCallable (parentName.Name, access)) if parentKind <> TypeConstructor then res, errs else res.WithoutRangeInfo, errs // strip positional info for auto-generated type constructors SymbolResolution.ResolveCallableSignature (resolveType, specBundleCharacteristics) signature @@ -905,10 +1117,10 @@ and NamespaceManager /// Sets the Resolved property for all type and callable declarations to Null, and the ResolvedAttributes to an empty array. /// Unless the clearing is forced, does nothing if the symbols are not currently resolved. - member private this.ClearResolutions ?force = + member private this.ClearResolutions ?force = let force = defaultArg force false if this.ContainsResolutions || force then - for ns in Namespaces.Values do + for ns in Namespaces.Values do for kvPair in ns.TypesDefinedInAllSources() do ns.SetTypeResolution (fst kvPair.Value) (kvPair.Key, Null, ImmutableArray.Empty) for kvPair in ns.CallablesDefinedInAllSources() do @@ -918,41 +1130,41 @@ and NamespaceManager /// Resolves and caches the attached attributes and underlying type of the types declared in all source files of each namespace. /// Returns the diagnostics generated upon resolution as well as the root position and file for each diagnostic as tuple. - member private this.CacheTypeResolution () = + member private this.CacheTypeResolution () = let sortedNamespaces = Namespaces.Values |> Seq.sortBy (fun ns -> ns.Name.Value) |> Seq.toList // Since attributes are declared as types, we first need to resolve all types ... let resolutionDiagnostics = sortedNamespaces |> Seq.collect (fun ns -> ns.TypesDefinedInAllSources() |> Seq.collect (fun kvPair -> let tName, (source, qsType) = kvPair.Key, kvPair.Value let fullName = {Namespace = ns.Name; Name = tName} - let resolved, msgs = qsType.Defined |> this.ResolveTypeDeclaration (fullName, source) + let resolved, msgs = qsType.Defined |> this.ResolveTypeDeclaration (fullName, source, qsType.Modifiers) ns.SetTypeResolution source (tName, resolved |> Value, ImmutableArray.Empty) msgs |> Array.map (fun msg -> source, (qsType.Position, msg)))) - // ... before we can resolve the corresponding attributes. + // ... before we can resolve the corresponding attributes. let attributeDiagnostics = sortedNamespaces |> Seq.collect (fun ns -> ns.TypesDefinedInAllSources() |> Seq.collect (fun kvPair -> let tName, (source, qsType) = kvPair.Key, kvPair.Value let parentName = {Namespace = ns.Name; Name = tName} - let resolvedAttributes, msgs = this.ResolveAttributes (parentName, source) qsType - ns.SetTypeResolution source (tName, qsType.Resolved, resolvedAttributes) + let resolvedAttributes, msgs = this.ResolveAttributes (parentName, source) qsType + ns.SetTypeResolution source (tName, qsType.Resolved, resolvedAttributes) msgs |> Array.map (fun msg -> source, msg))) resolutionDiagnostics.Concat(attributeDiagnostics).ToArray() - /// Resolves and caches all attached attributes and specialization generation directives for all callables - /// declared in all source files of each namespace, inserting inferred specializations if necessary and removing invalid specializations. - /// Then resolves and caches the signature of the callables themselves. + /// Resolves and caches all attached attributes and specialization generation directives for all callables + /// declared in all source files of each namespace, inserting inferred specializations if necessary and removing invalid specializations. + /// Then resolves and caches the signature of the callables themselves. /// Returns the diagnostics generated upon resolution as well as the root position and file for each diagnostic as tuple. /// IMPORTANT: does *not* return diagnostics generated for type constructors - suitable diagnostics need to be generated upon type resolution. - /// Throws an InvalidOperationException if the types corresponding to the attributes to resolve have not been resolved. - member private this.CacheCallableResolutions () = + /// Throws an InvalidOperationException if the types corresponding to the attributes to resolve have not been resolved. + member private this.CacheCallableResolutions () = // TODO: this needs to be adapted if we support external specializations - let diagnostics = Namespaces.Values |> Seq.sortBy (fun ns -> ns.Name.Value) |> Seq.collect (fun ns -> + let diagnostics = Namespaces.Values |> Seq.sortBy (fun ns -> ns.Name.Value) |> Seq.collect (fun ns -> ns.CallablesDefinedInAllSources() |> Seq.sortBy (fun kv -> kv.Key) |> Seq.collect (fun kvPair -> let source, (kind, signature) = kvPair.Value let parent = {Namespace = ns.Name; Name = kvPair.Key} // we first need to resolve the type arguments to determine the right sets of specializations to consider - let typeArgsResolution specSource = + let typeArgsResolution specSource = let typeResolution = this.ResolveType (parent, ImmutableArray.Empty, specSource) // do not allow using type parameters within type specializations! SymbolResolution.ResolveTypeArgument typeResolution let mutable errs = ns.SetSpecializationResolutions (parent.Name, typeArgsResolution, fun _ _ -> ImmutableArray.Empty, [||]) @@ -966,21 +1178,21 @@ and NamespaceManager // we remove the specializations which could not be bundled and resolve the newly inserted ones for (specSource, (errPos, d)) in bundleErrs do - match d.Diagnostic with + match d.Diagnostic with | Information _ | Warning _ -> () - | Error errCode -> + | Error errCode -> let removed = ns.RemoveSpecialization (specSource, {Offset = errPos; Range = d.Range}) parent.Name QsCompilerError.Verify ((removed <= 1), sprintf "removed %i specializations based on error code %s" removed (errCode.ToString())) let autoResErrs = ns.SetSpecializationResolutions (parent.Name, typeArgsResolution, fun _ _ -> ImmutableArray.Empty, [||]) // only then can we resolve the generators themselves, as well as the callable and specialization attributes let callableAttributes, attrErrs = this.ResolveAttributes (parent, source) signature - let resolution _ = SymbolResolution.ResolveGenerator props + let resolution _ = SymbolResolution.ResolveGenerator props let specErrs = ns.SetSpecializationResolutions (parent.Name, resolution, fun attSource -> this.ResolveAttributes (parent, attSource)) // and finally we resolve the overall signature (whose characteristics are the intersection of the one of all bundles) let characteristics = props.Values |> Seq.map (fun bundle -> bundle.BundleInfo) |> Seq.toList - let resolved, msgs = (signature.Defined, characteristics) |> this.ResolveCallableSignature (kind, parent, source) // no positional info for type constructors + let resolved, msgs = (signature.Defined, characteristics) |> this.ResolveCallableSignature (kind, parent, source, signature.Modifiers.Access) // no positional info for type constructors ns.SetCallableResolution source (parent.Name, resolved |> Value, callableAttributes) errs <- (attrErrs |> Array.map (fun m -> source, m)) :: (msgs |> Array.map (fun m -> source, (signature.Position, m))) :: errs @@ -994,28 +1206,28 @@ and NamespaceManager member this.VersionNumber = versionNumber /// set to true if all types have been fully resolved and false otherwise - member this.ContainsResolutions + member this.ContainsResolutions with get() = containsResolutions and private set value = containsResolutions <- value /// For each given namespace, automatically adds an open directive to all partial namespaces - /// in all source files, if a namespace with that name indeed exists and is part of this compilation. - /// Independent on whether the symbols have already been resolved, proceeds to resolves - /// all types and callables as well as their attributes defined throughout all namespaces and caches the resolution. - /// Returns the diagnostics generated during resolution + /// in all source files, if a namespace with that name indeed exists and is part of this compilation. + /// Independent on whether the symbols have already been resolved, proceeds to resolves + /// all types and callables as well as their attributes defined throughout all namespaces and caches the resolution. + /// Returns the diagnostics generated during resolution /// together with the Position of the declaration for which the diagnostics were generated. - member this.ResolveAll (autoOpen : ImmutableHashSet<_>) = + member this.ResolveAll (autoOpen : ImmutableHashSet<_>) = // TODO: this needs to be adapted if we support external specializations syncRoot.EnterWriteLock() versionNumber <- versionNumber + 1 try let autoOpen = if autoOpen <> null then autoOpen else ImmutableHashSet.Empty - let nsToAutoOpen = autoOpen.Intersect Namespaces.Keys + let nsToAutoOpen = autoOpen.Intersect Namespaces.Keys let zeroRange = (QsPositionInfo.Zero, QsPositionInfo.Zero) - for ns in Namespaces.Values do - for source in ns.Sources do + for ns in Namespaces.Values do + for source in ns.Sources do for opened in nsToAutoOpen do this.AddOpenDirective (opened, zeroRange) (null, Value zeroRange) (ns.Name, source) |> ignore - // We need to resolve types before we resolve callables, + // We need to resolve types before we resolve callables, // since the attribute resolution for callables relies on the corresponding types having been resolved. let typeDiagnostics = this.CacheTypeResolution() let callableDiagnostics = this.CacheCallableResolutions() @@ -1023,44 +1235,44 @@ and NamespaceManager callableDiagnostics.Concat(typeDiagnostics).ToLookup(fst, snd) finally syncRoot.ExitWriteLock() - /// Returns a dictionary that maps each namespace name to a look-up + /// Returns a dictionary that maps each namespace name to a look-up /// that for each source file name contains the names of all imported namespaces in that file and namespace. - member this.Documentation () = + member this.Documentation () = syncRoot.EnterReadLock() try let docs = Namespaces.Values |> Seq.map (fun ns -> ns.Name, ns.Documentation) docs.ToImmutableDictionary(fst, snd) finally syncRoot.ExitReadLock() /// Returns a look-up that contains the names of all namespaces imported within a certain source file for the given namespace. - /// Throws an ArgumentException if no namespace with the given name exists. - member this.OpenDirectives nsName = + /// Throws an ArgumentException if no namespace with the given name exists. + member this.OpenDirectives nsName = syncRoot.EnterReadLock() - try match Namespaces.TryGetValue nsName with - | true, ns -> - let imported = ns.Sources |> Seq.collect (fun source -> - ns.ImportedNamespaces source |> Seq.choose (fun imported -> + try match Namespaces.TryGetValue nsName with + | true, ns -> + let imported = ns.Sources |> Seq.collect (fun source -> + ns.ImportedNamespaces source |> Seq.choose (fun imported -> if imported.Key <> ns.Name then Some (source, new ValueTuple<_,_>(imported.Key, imported.Value)) else None)) imported.ToLookup(fst, snd) | false, _ -> ArgumentException "no namespace with the given name exists" |> raise finally syncRoot.ExitReadLock() - /// Returns the headers of all imported specializations for callable with the given name. + /// Returns the headers of all imported specializations for callable with the given name. /// Throws an ArgumentException if no namespace or no callable with the given name exists. - member this.ImportedSpecializations (parent : QsQualifiedName) = + member this.ImportedSpecializations (parent : QsQualifiedName) = // TODO: this may need to be adapted if we support external specializations syncRoot.EnterReadLock() - try let imported = Namespaces.TryGetValue parent.Namespace |> function + try let imported = Namespaces.TryGetValue parent.Namespace |> function | false, _ -> ArgumentException "no namespace with the given name exists" |> raise | true, ns -> ns.SpecializationsInReferencedAssemblies.[parent.Name].ToImmutableArray() - if imported.Length <> 0 then imported - else ArgumentException "no specializations for a callable with the given name have been imported" |> raise + if imported.Length <> 0 then imported + else ArgumentException "no specializations for a callable with the given name have been imported" |> raise finally syncRoot.ExitReadLock() - /// Returns the resolved generation directive (if any) as well as the specialization headers + /// Returns the resolved generation directive (if any) as well as the specialization headers /// for all specializations defined in source files for the callable with the given name. /// Throws an ArgumentException if no namespace or no callable with the given name exists. - /// Throws an InvalidOperationException if the symbols are not currently resolved. - member this.DefinedSpecializations (parent : QsQualifiedName) = + /// Throws an InvalidOperationException if the symbols are not currently resolved. + member this.DefinedSpecializations (parent : QsQualifiedName) = let notResolvedException = InvalidOperationException "specializations are not resolved" syncRoot.EnterReadLock() try if not this.ContainsResolutions then notResolvedException |> raise @@ -1083,7 +1295,8 @@ and NamespaceManager defined.ToImmutableArray() finally syncRoot.ExitReadLock() - /// Returns the source file and CallableDeclarationHeader of all callables imported from referenced assemblies. + /// Returns the source file and CallableDeclarationHeader of all callables imported from referenced assemblies, + /// regardless of accessibility. member this.ImportedCallables () = // TODO: this needs to be adapted if we support external specializations syncRoot.EnterReadLock() @@ -1091,13 +1304,14 @@ and NamespaceManager imported.ToImmutableArray() finally syncRoot.ExitReadLock() - /// Returns the declaration headers for all callables defined in source files. - /// Throws an InvalidOperationException if the symbols are not currently resolved. + /// Returns the declaration headers for all callables defined in source files, regardless of accessibility. + /// + /// Throws an InvalidOperationException if the symbols are not currently resolved. member this.DefinedCallables () = let notResolvedException = InvalidOperationException "callables are not resolved" syncRoot.EnterReadLock() try if not this.ContainsResolutions then notResolvedException |> raise - let defined = Namespaces.Values |> Seq.collect (fun ns -> + let defined = Namespaces.Values |> Seq.collect (fun ns -> ns.CallablesDefinedInAllSources() |> Seq.choose (fun kvPair -> let cName, (source, (kind, declaration)) = kvPair.Key, kvPair.Value match declaration.Resolved with @@ -1106,6 +1320,7 @@ and NamespaceManager Kind = kind QualifiedName = {Namespace = ns.Name; Name = cName} Attributes = declaration.ResolvedAttributes + Modifiers = declaration.Modifiers SourceFile = source Position = DeclarationHeader.Offset.Defined declaration.Position SymbolRange = DeclarationHeader.Range.Defined declaration.Range @@ -1116,20 +1331,34 @@ and NamespaceManager defined.ToImmutableArray() finally syncRoot.ExitReadLock() - /// Returns the source file and TypeDeclarationHeader of all types imported from referenced assemblies. - member this.ImportedTypes() = + /// Returns the declaration headers for all callables (either defined in source files or imported from referenced + /// assemblies) that are accessible from source files in the compilation unit. + /// + /// Throws an InvalidOperationException if the symbols are not currently resolved. + member this.AccessibleCallables () = + Seq.append + (Seq.map (fun callable -> callable, true) (this.DefinedCallables())) + (Seq.map (fun callable -> callable, false) (this.ImportedCallables())) + |> Seq.filter (fun (callable, sameAssembly) -> + Namespace.IsDeclarationAccessible (sameAssembly, callable.Modifiers.Access)) + |> Seq.map fst + + /// Returns the source file and TypeDeclarationHeader of all types imported from referenced assemblies, regardless + /// of accessibility. + member this.ImportedTypes() = syncRoot.EnterReadLock() try let imported = Namespaces.Values |> Seq.collect (fun ns -> ns.TypesInReferencedAssemblies.Values) imported.ToImmutableArray() finally syncRoot.ExitReadLock() - /// Returns the declaration headers for all types defined in source files. - /// Throws an InvalidOperationException if the symbols are not currently resolved. + /// Returns the declaration headers for all types defined in source files, regardless of accessibility. + /// + /// Throws an InvalidOperationException if the symbols are not currently resolved. member this.DefinedTypes () = let notResolvedException = InvalidOperationException "types are not resolved" syncRoot.EnterReadLock() try if not this.ContainsResolutions then notResolvedException |> raise - let defined = Namespaces.Values |> Seq.collect (fun ns -> + let defined = Namespaces.Values |> Seq.collect (fun ns -> ns.TypesDefinedInAllSources() |> Seq.choose (fun kvPair -> let tName, (source, qsType) = kvPair.Key, kvPair.Value match qsType.Resolved with @@ -1137,9 +1366,10 @@ and NamespaceManager | Value (underlyingType, items) -> Some { QualifiedName = {Namespace = ns.Name; Name = tName} Attributes = qsType.ResolvedAttributes + Modifiers = qsType.Modifiers SourceFile = source Position = DeclarationHeader.Offset.Defined qsType.Position - SymbolRange = DeclarationHeader.Range.Defined qsType.Range + SymbolRange = DeclarationHeader.Range.Defined qsType.Range Type = underlyingType TypeItems = items Documentation = qsType.Documentation @@ -1147,6 +1377,18 @@ and NamespaceManager defined.ToImmutableArray() finally syncRoot.ExitReadLock() + /// Returns the declaration headers for all types (either defined in source files or imported from referenced + /// assemblies) that are accessible from source files in the compilation unit. + /// + /// Throws an InvalidOperationException if the symbols are not currently resolved. + member this.AccessibleTypes () = + Seq.append + (Seq.map (fun qsType -> qsType, true) (this.DefinedTypes())) + (Seq.map (fun qsType -> qsType, false) (this.ImportedTypes())) + |> Seq.filter (fun (qsType, sameAssembly) -> + Namespace.IsDeclarationAccessible (sameAssembly, qsType.Modifiers.Access)) + |> Seq.map fst + /// removes the given source file and all its content from all namespaces member this.RemoveSource source = syncRoot.EnterWriteLock() @@ -1155,34 +1397,34 @@ and NamespaceManager let keys = Namespaces.Keys |> List.ofSeq for key in keys do if Namespaces.[key].IsEmpty then Namespaces.Remove key |> ignore this.ClearResolutions() - finally syncRoot.ExitWriteLock() + finally syncRoot.ExitWriteLock() /// clears all content from the symbol table - member this.Clear() = + member this.Clear() = syncRoot.EnterWriteLock() versionNumber <- versionNumber + 1 try this.ContainsResolutions <- true Namespaces.Clear() - finally syncRoot.ExitWriteLock() - + finally syncRoot.ExitWriteLock() + /// Adds every namespace along with all its content to target. - /// IMPORTANT: if a namespace already exists in the target, replaces it! + /// IMPORTANT: if a namespace already exists in the target, replaces it! member this.CopyTo (target : NamespaceManager) = syncRoot.EnterReadLock() try for ns in Namespaces.Values do target.AddOrReplaceNamespace ns // ns will be deep copied finally syncRoot.ExitReadLock() - /// If a namespace with the given name exists, - /// makes a (deep) copy of that namespace and - if the given source file is not already listed as source for that namespace - + /// If a namespace with the given name exists, + /// makes a (deep) copy of that namespace and - if the given source file is not already listed as source for that namespace - /// adds the given source to the list of sources for the made copy, before returning the copy. /// If no namespace with the given name exists, returns a new Namespace with the given source file listed as source. - /// NOTE: This routine does *not* modify this symbol table, - /// and any modification to the returned namespace won't be reflected here - + /// NOTE: This routine does *not* modify this symbol table, + /// and any modification to the returned namespace won't be reflected here - /// use AddOrReplaceNamespace to push back the modifications into the symbol table. member this.CopyForExtension (nsName, source) = syncRoot.EnterReadLock() - try match Namespaces.TryGetValue nsName with + try match Namespaces.TryGetValue nsName with | true, NS -> let copy = NS.Copy() if copy.TryAddSource source then copy @@ -1191,7 +1433,7 @@ and NamespaceManager finally syncRoot.ExitReadLock() /// Given a Namespace, makes a (deep) copy of that Namespace and replaces the existing namespace with that name - /// by that copy, if such a namespace already exists, or adds the copy as a new namespace. + /// by that copy, if such a namespace already exists, or adds the copy as a new namespace. /// -> Any modification to the namespace after pushing it into the symbol table (i.e. calling this routine) won't be reflected here. member this.AddOrReplaceNamespace (ns : Namespace) = syncRoot.EnterWriteLock() @@ -1200,129 +1442,198 @@ and NamespaceManager this.ClearResolutions true // force the clearing, since otherwise the newly added namespace may not be cleared finally syncRoot.ExitWriteLock() - /// Adds the opened namespace to the list of imported namespaces for the given source and namespace. - /// If the namespace to list as imported does not exists, or if the given alias cannot be used as namespace short name, - /// adds the corresponding diagnostics to an array of diagnostics and returns them. - /// Returns an empty array otherwise. + /// Adds the opened namespace to the list of imported namespaces for the given source and namespace. + /// If the namespace to list as imported does not exists, or if the given alias cannot be used as namespace short name, + /// adds the corresponding diagnostics to an array of diagnostics and returns them. + /// Returns an empty array otherwise. /// Throws an Argument exception if the given namespace or source file for which to add the open directive does not exist. - member this.AddOpenDirective (opened, openedRange) (alias, aliasRange) (nsName, source) = + member this.AddOpenDirective (opened, openedRange) (alias, aliasRange) (nsName, source) = syncRoot.EnterWriteLock() versionNumber <- versionNumber + 1 try this.ClearResolutions() - match Namespaces.TryGetValue nsName with - | true, ns when ns.Sources.Contains source -> - let validAlias = String.IsNullOrWhiteSpace alias || NonNullable.New (alias.Trim()) |> Namespaces.ContainsKey |> not + match Namespaces.TryGetValue nsName with + | true, ns when ns.Sources.Contains source -> + let validAlias = String.IsNullOrWhiteSpace alias || NonNullable.New (alias.Trim()) |> Namespaces.ContainsKey |> not if validAlias && Namespaces.ContainsKey opened then ns.TryAddOpenDirective source (opened, openedRange) (alias, aliasRange.ValueOr openedRange) elif validAlias then [| openedRange |> QsCompilerDiagnostic.Error (ErrorCode.UnknownNamespace, [opened.Value]) |] else [| aliasRange.ValueOr openedRange |> QsCompilerDiagnostic.Error (ErrorCode.InvalidNamespaceAliasName, [alias]) |] | true, _ -> ArgumentException "given source file is not listed as source of the given namespace" |> raise - | false, _ -> ArgumentException "no such namespace exists" |> raise + | false, _ -> ArgumentException "no such namespace exists" |> raise finally syncRoot.ExitWriteLock() - /// Given a qualified callable name, returns the corresponding CallableDeclarationHeader as Value, - /// if the qualifier can be resolved within the given parent namespace and source file, and such a callable indeed exists. - /// If the callable is not defined an any of the references and the source file containing the callable declaration is specified (i.e. declSource is Some), - /// throws the corresponding exception if no such callable exists in that file. - /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent namespace does not exist. + /// Given a qualified callable name, returns the corresponding CallableDeclarationHeader in a ResolutionResult if + /// the qualifier can be resolved within the given parent namespace and source file, and the callable is accessible. + /// + /// If the callable is not defined an any of the references and the source file containing the callable declaration + /// is specified (i.e. declSource is Some), throws the corresponding exception if no such callable exists in that + /// file. + /// + /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent + /// namespace does not exist. member private this.TryGetCallableHeader (callableName : QsQualifiedName, declSource) (nsName, source) = - let BuildHeader fullName (source, kind, declaration : Resolution<_,_>) = - let fallback () = (declaration.Defined, [CallableInformation.Invalid]) |> this.ResolveCallableSignature (kind, callableName, source) |> fst + let buildHeader fullName (source, kind, declaration) = + let fallback () = + (declaration.Defined, [CallableInformation.Invalid]) + |> this.ResolveCallableSignature (kind, callableName, source, declaration.Modifiers.Access) + |> fst let resolvedSignature, argTuple = declaration.Resolved.ValueOrApply fallback - Value { + { Kind = kind QualifiedName = fullName Attributes = declaration.ResolvedAttributes + Modifiers = declaration.Modifiers SourceFile = source Position = DeclarationHeader.Offset.Defined declaration.Position - SymbolRange = DeclarationHeader.Range.Defined declaration.Range + SymbolRange = DeclarationHeader.Range.Defined declaration.Range Signature = resolvedSignature ArgumentTuple = argTuple Documentation = declaration.Documentation } + + let findInReferences (ns : Namespace) = + match ns.CallablesInReferencedAssemblies.TryGetValue callableName.Name with + | true, callable -> + if Namespace.IsDeclarationAccessible (false, callable.Modifiers.Access) + then Found callable + else Inaccessible + | false, _ -> NotFound + + let findInSources (ns : Namespace) = function + | Some source -> + // OK to use CallableInSource because this is only evaluated if the callable is not in a + // reference. + let kind, declaration = ns.CallableInSource source callableName.Name + if Namespace.IsDeclarationAccessible (true, declaration.Modifiers.Access) + then Found (buildHeader {callableName with Namespace = ns.Name} (source, kind, declaration)) + else Inaccessible + | None -> + match ns.CallablesDefinedInAllSources().TryGetValue callableName.Name with + | true, (source, (kind, declaration)) -> + if Namespace.IsDeclarationAccessible (true, declaration.Modifiers.Access) + then Found (buildHeader {callableName with Namespace = ns.Name} (source, kind, declaration)) + else Inaccessible + | false, _ -> NotFound + syncRoot.EnterReadLock() try match (nsName, source) |> TryResolveQualifier callableName.Namespace with - | None -> Null - | Some ns -> ns.CallablesInReferencedAssemblies.TryGetValue callableName.Name |> function - | true, cDecl -> Value cDecl - | false, _ -> declSource |> function - | Some source -> - let kind, decl = ns.CallableInSource source callableName.Name // ok only because/if we have covered that the callable is not in a reference! - BuildHeader {callableName with Namespace = ns.Name} (source, kind, decl) - | None -> - ns.CallablesDefinedInAllSources().TryGetValue callableName.Name |> function - | true, (source, (kind, decl)) -> BuildHeader {callableName with Namespace = ns.Name} (source, kind, decl) - | false, _ -> Null + | None -> NotFound + | Some ns -> + seq { yield findInReferences ns + yield findInSources ns declSource } + |> ResolutionResult.TryFirstBest finally syncRoot.ExitReadLock() - /// Given a qualified callable name, returns the corresponding CallableDeclarationHeader as Value, - /// if the qualifier can be resolved within the given parent namespace and source file, and such a callable indeed exists. - /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent namespace does not exist. - member this.TryGetCallable (callableName : QsQualifiedName) (nsName, source) = this.TryGetCallableHeader (callableName, None) (nsName, source) + /// Given a qualified callable name, returns the corresponding CallableDeclarationHeader in a ResolutionResult if + /// the qualifier can be resolved within the given parent namespace and source file, and the callable is accessible. + /// + /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent + /// namespace does not exist. + member this.TryGetCallable (callableName : QsQualifiedName) (nsName, source) = + this.TryGetCallableHeader (callableName, None) (nsName, source) + + /// Given an unqualified callable name, returns the corresponding CallableDeclarationHeader in a ResolutionResult if + /// the qualifier can be uniquely resolved within the given parent namespace and source file, and the callable is + /// accessible. + /// + /// Returns an Ambiguous result with a list with namespaces containing a type with that name if the name cannot be + /// uniquely resolved. + member this.TryResolveAndGetCallable cName (nsName, source) = + let toHeader (declaredNs, (declaredSource, _)) = + match this.TryGetCallableHeader ({Namespace = declaredNs; Name = cName}, Some declaredSource) + (nsName, source) with + | Found value -> value + | _ -> QsCompilerError.Raise "Expected to find the header corresponding to a possible resolution" + Exception () |> raise - /// If the given callable name can be uniquely resolved within the given namespace and source file, - /// returns the CallableDeclarationHeader with the information on that callable as Value. - /// Returns Null as well as a list with namespaces containing a callable with that name if this is not the case. - member this.TryResolveAndGetCallable cName (nsName, source) = syncRoot.EnterReadLock() - try match (nsName, source) |> PossibleResolutions (fun ns -> ns.ContainsCallable cName) with - | [(declNS, (declSource, _))] -> this.TryGetCallableHeader ({Namespace = declNS; Name = cName}, Some declSource) (nsName, source) |> function - | Null -> QsCompilerError.Raise "failed to get the callable information about a resolved callable"; Null, Seq.empty - | info -> info, seq {yield declNS} - | resolutions -> Null, resolutions.Select fst + try resolveInOpenNamespaces (fun ns -> ns.TryFindCallable cName) (nsName, source) + |> ResolutionResult.Map toHeader finally syncRoot.ExitReadLock() - /// Given a qualified type name, returns the corresponding TypeDeclarationHeader as Value, - /// if the qualifier can be resolved within the given parent namespace and source file, and such a type indeed exists. - /// If the type is not defined an any of the references and the source file containing the type declaration is specified (i.e. declSource is Some), - /// throws the corresponding exception if no such type exists in that file. - /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent namespace does not exist. + /// Given a qualified type name, returns the corresponding TypeDeclarationHeader in a ResolutionResult if the + /// qualifier can be resolved within the given parent namespace and source file, and the type is accessible. + /// + /// If the type is not defined an any of the references and the source file containing the type declaration is + /// specified (i.e. declSource is Some), throws the corresponding exception if no such type exists in that file. + /// + /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent + /// namespace does not exist. member private this.TryGetTypeHeader (typeName : QsQualifiedName, declSource) (nsName, source) = - let BuildHeader fullName (source, declaration) = - let fallback () = declaration.Defined |> this.ResolveTypeDeclaration (typeName, source) |> fst + let buildHeader fullName (source, declaration) = + let fallback () = + declaration.Defined |> this.ResolveTypeDeclaration (typeName, source, declaration.Modifiers) |> fst let underlyingType, items = declaration.Resolved.ValueOrApply fallback - Value { + { QualifiedName = fullName Attributes = declaration.ResolvedAttributes + Modifiers = declaration.Modifiers SourceFile = source Position = DeclarationHeader.Offset.Defined declaration.Position - SymbolRange = DeclarationHeader.Range.Defined declaration.Range + SymbolRange = DeclarationHeader.Range.Defined declaration.Range Type = underlyingType TypeItems = items Documentation = declaration.Documentation } + + let findInReferences (ns : Namespace) = + match ns.TypesInReferencedAssemblies.TryGetValue typeName.Name with + | true, qsType -> + if Namespace.IsDeclarationAccessible (false, qsType.Modifiers.Access) + then Found qsType + else Inaccessible + | false, _ -> NotFound + + let findInSources (ns : Namespace) = function + | Some source -> + // OK to use TypeInSource because this is only evaluated if the type is not in a reference. + let declaration = ns.TypeInSource source typeName.Name + if Namespace.IsDeclarationAccessible (true, declaration.Modifiers.Access) + then Found (buildHeader {typeName with Namespace = ns.Name} (source, declaration)) + else Inaccessible + | None -> + match ns.TypesDefinedInAllSources().TryGetValue typeName.Name with + | true, (source, declaration) -> + if Namespace.IsDeclarationAccessible (true, declaration.Modifiers.Access) + then Found (buildHeader {typeName with Namespace = ns.Name} (source, declaration)) + else Inaccessible + | false, _ -> NotFound + syncRoot.EnterReadLock() - try match (nsName, source) |> TryResolveQualifier typeName.Namespace with - | None -> Null - | Some ns -> ns.TypesInReferencedAssemblies.TryGetValue typeName.Name |> function - | true, tDecl -> Value tDecl - | false, _ -> declSource |> function - | Some source -> - let decl = ns.TypeInSource source typeName.Name // ok only because/if we have covered that the type is not in a reference! - BuildHeader {typeName with Namespace = ns.Name} (source, decl) - | None -> - ns.TypesDefinedInAllSources().TryGetValue typeName.Name |> function - | true, (source, decl) -> BuildHeader {typeName with Namespace = ns.Name} (source, decl) - | false, _ -> Null + try match (nsName, source) |> TryResolveQualifier typeName.Namespace with + | None -> NotFound + | Some ns -> + seq { yield findInReferences ns + yield findInSources ns declSource } + |> ResolutionResult.TryFirstBest finally syncRoot.ExitReadLock() - /// Given a qualified type name, returns the corresponding TypeDeclarationHeader as Value, - /// if the qualifier can be resolved within the given parent namespace and source file, and such a type indeed exists. - /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent namespace does not exist. - member this.TryGetType (typeName : QsQualifiedName) (nsName, source) = this.TryGetTypeHeader (typeName, None) (nsName, source) + /// Given a qualified type name, returns the corresponding TypeDeclarationHeader in a ResolutionResult if the + /// qualifier can be resolved within the given parent namespace and source file, and the type is accessible. + /// + /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent + /// namespace does not exist. + member this.TryGetType (typeName : QsQualifiedName) (nsName, source) = + this.TryGetTypeHeader (typeName, None) (nsName, source) + + /// Given an unqualified type name, returns the corresponding TypeDeclarationHeader in a ResolutionResult if the + /// qualifier can be uniquely resolved within the given parent namespace and source file, and the type is + /// accessible. + /// + /// Returns an Ambiguous result with a list with namespaces containing a type with that name if the name cannot be + /// uniquely resolved. + member this.TryResolveAndGetType tName (nsName, source) = + let toHeader (declaredNs, (declaredSource, _, _)) = + match this.TryGetTypeHeader ({Namespace = declaredNs; Name = tName}, Some declaredSource) + (nsName, source) with + | Found value -> value + | _ -> QsCompilerError.Raise "Expected to find the header corresponding to a possible resolution" + Exception () |> raise - /// If the given type name can be uniquely resolved within the given namespace and source file, - /// returns the TypeDeclarationHeader with the information on that type as Value. - /// Returns Null as well as a list with namespaces containing a type with that name if this is not the case. - member this.TryResolveAndGetType tName (nsName, source) = syncRoot.EnterReadLock() - try match (nsName, source) |> PossibleResolutions (fun ns -> ns.ContainsType tName) with - | [(declNS, (declSource, _))] -> this.TryGetTypeHeader ({Namespace = declNS; Name = tName}, Some declSource) (nsName, source) |> function - | Null -> QsCompilerError.Raise "failed to get the type information about a resolved type"; Null, Seq.empty - | info -> info, seq {yield declNS} - | resolutions -> Null, resolutions.Select fst + try resolveInOpenNamespaces (fun ns -> ns.TryFindType tName) (nsName, source) + |> ResolutionResult.Map toHeader finally syncRoot.ExitReadLock() - /// Returns the fully qualified namespace name of the given namespace alias (short name). If the alias is already a fully qualified name, /// returns the name unchanged. Returns null if no such name exists within the given parent namespace and source file. /// Throws an ArgumentException if the given parent namespace does not exist. @@ -1333,20 +1644,26 @@ and NamespaceManager | Some ns -> ns.Name.Value finally syncRoot.ExitReadLock() - /// Returns the names of all namespaces in which a callable with the given name is declared. - member this.NamespacesContainingCallable cName = + /// Returns the names of all namespaces in which a callable is declared that has the given name and is accessible + /// from source files in the compilation unit. + member this.NamespacesContainingCallable cName = // FIXME: we need to handle the case where a callable/type with the same qualified name is declared in several references! syncRoot.EnterReadLock() - try let containsCallable (ns : Namespace) = ns.ContainsCallable cName |> QsNullable<_>.Map (fun _ -> ns.Name) - (Namespaces.Values |> QsNullable<_>.Choose containsCallable).ToImmutableArray() + try Namespaces.Values + |> Seq.choose (fun ns -> + ns.TryFindCallable cName |> ResolutionResult.ToOption |> Option.map (fun _ -> ns.Name)) + |> fun namespaces -> namespaces.ToImmutableArray () finally syncRoot.ExitReadLock() - /// Returns the names of all namespaces in which a type with the given name is declared. - member this.NamespacesContainingType tName = + /// Returns the names of all namespaces in which a type is declared that has the given name and is accessible from + /// source files in the compilation unit. + member this.NamespacesContainingType tName = // FIXME: we need to handle the case where a callable/type with the same qualified name is declared in several references! syncRoot.EnterReadLock() - try let containsType (ns : Namespace) = ns.ContainsType tName |> QsNullable<_>.Map (fun _ -> ns.Name) - (Namespaces.Values |> QsNullable<_>.Choose containsType).ToImmutableArray() + try Namespaces.Values + |> Seq.choose (fun ns -> + ns.TryFindType tName |> ResolutionResult.ToOption |> Option.map (fun _ -> ns.Name)) + |> fun namespaces -> namespaces.ToImmutableArray () finally syncRoot.ExitReadLock() /// Returns the name of all namespaces declared in source files or referenced assemblies. @@ -1383,60 +1700,59 @@ and NamespaceManager /// All entries in the source file have to be fully resolved beforehand. /// That hash does not contain any information about the imported namespaces, positional information, or about any documentation. /// Returns the generated hash as well as a separate hash providing information about the imported namespaces. - /// Throws an InvalidOperationException if the given source file contains unresolved entries. - member this.HeaderHash source = + /// Throws an InvalidOperationException if the given source file contains unresolved entries. + member this.HeaderHash source = let invalidOperationEx = InvalidOperationException "everything needs to be resolved before constructing the HeaderString" if not this.ContainsResolutions then invalidOperationEx |> raise - let inconsistentStateException () = + let inconsistentStateException () = QsCompilerError.Raise "contains unresolved entries despite supposedly being resolved" invalidOperationEx |> raise - let attributesHash (attributes : QsDeclarationAttribute seq) = + let attributesHash (attributes : QsDeclarationAttribute seq) = let getHash arg (id : UserDefinedType) = hash (id.Namespace.Value, id.Name.Value, NamespaceManager.ExpressionHash arg) attributes |> QsNullable<_>.Choose (fun att -> att.TypeId |> QsNullable<_>.Map (getHash att.Argument)) |> Seq.toList let callableHash (kind, (signature,_), specs, attributes : QsDeclarationAttribute seq) = - let signatureHash (signature : ResolvedSignature) = + let signatureHash (signature : ResolvedSignature) = let argStr = signature.ArgumentType |> NamespaceManager.TypeHash let reStr = signature.ReturnType |> NamespaceManager.TypeHash let nameOrInvalid = function | InvalidName -> InvalidName |> JsonConvert.SerializeObject | ValidName sym -> sym.Value let typeParams = signature.TypeParameters |> Seq.map nameOrInvalid |> Seq.toList hash (argStr, reStr, typeParams) let specsStr = - let genHash (gen : ResolvedGenerator) = + let genHash (gen : ResolvedGenerator) = let tArgs = gen.TypeArguments |> QsNullable<_>.Map (fun tArgs -> tArgs |> Seq.map NamespaceManager.TypeHash |> Seq.toList) hash (gen.Directive, hash tArgs) let kinds, gens = specs |> Seq.sort |> Seq.toList |> List.unzip hash (kinds, gens |> List.map genHash) hash (kind, specsStr, signatureHash signature, attributes |> attributesHash) - let typeHash (t, typeItems : QsTuple, attributes) = + let typeHash (t, typeItems : QsTuple, attributes) = let getItemHash (itemName, itemType) = hash (itemName, NamespaceManager.TypeHash itemType) let namedItems = typeItems.Items |> Seq.choose (function | Named item -> Some item | _ -> None) let itemHashes = namedItems.Select (fun d -> d.VariableName, d.Type) |> Seq.map getItemHash hash (NamespaceManager.TypeHash t, itemHashes |> Seq.toList, attributes |> attributesHash) syncRoot.EnterReadLock() - try let relevantNamespaces = - Namespaces.Values - |> Seq.filter (fun ns -> ns.Sources.Contains source) + try let relevantNamespaces = + Namespaces.Values + |> Seq.filter (fun ns -> ns.Sources.Contains source) |> Seq.sortBy (fun ns -> ns.Name) |> Seq.toList - let callables = relevantNamespaces |> Seq.collect (fun ns -> - let inSource = ns.CallablesDefinedInSource source |> Seq.sortBy (fun (cName, _) -> cName.Value) - inSource |> Seq.map (fun (cName, (kind, signature)) -> - let specs = ns.SpecializationsDefinedInAllSources cName |> Seq.map (fun (kind, (_, resolution)) -> + let callables = relevantNamespaces |> Seq.collect (fun ns -> + let inSource = ns.CallablesDefinedInSource source |> Seq.sortBy (fun (cName, _) -> cName.Value) + inSource |> Seq.map (fun (cName, (kind, signature)) -> + let specs = ns.SpecializationsDefinedInAllSources cName |> Seq.map (fun (kind, (_, resolution)) -> kind, resolution.Resolved.ValueOrApply inconsistentStateException) let resolved = signature.Resolved.ValueOrApply inconsistentStateException ns.Name.Value, cName.Value, (kind, resolved, specs, signature.ResolvedAttributes))) - let types = relevantNamespaces |> Seq.collect (fun ns -> + let types = relevantNamespaces |> Seq.collect (fun ns -> let inSources = ns.TypesDefinedInSource source |> Seq.sortBy (fun (tName,_) -> tName.Value) inSources |> Seq.map (fun (tName, qsType) -> let resolved, resItems = qsType.Resolved.ValueOrApply inconsistentStateException ns.Name.Value, tName.Value, (resolved, resItems, qsType.ResolvedAttributes))) - let imports = relevantNamespaces |> Seq.collect (fun ns -> - ns.ImportedNamespaces source |> Seq.sortBy (fun x -> x.Value) |> Seq.map (fun opened -> ns.Name.Value, opened.Value)) + let imports = relevantNamespaces |> Seq.collect (fun ns -> + ns.ImportedNamespaces source |> Seq.sortBy (fun x -> x.Value) |> Seq.map (fun opened -> ns.Name.Value, opened.Value)) let callablesHash = callables |> Seq.map (fun (ns, name, c) -> (ns, name, callableHash c)) |> Seq.toList |> hash let typesHash = types |> Seq.map (fun (ns, name, t) -> ns, name, typeHash t) |> Seq.toList |> hash let importsHash = imports |> Seq.toList |> hash hash (callablesHash, typesHash), importsHash finally syncRoot.ExitReadLock() - diff --git a/src/QsCompiler/Core/SyntaxGenerator.fs b/src/QsCompiler/Core/SyntaxGenerator.fs index d7dfe233d9..245cbfa417 100644 --- a/src/QsCompiler/Core/SyntaxGenerator.fs +++ b/src/QsCompiler/Core/SyntaxGenerator.fs @@ -16,29 +16,29 @@ open Microsoft.Quantum.QsCompiler.Transformations.Core // transformations used to strip range information for auto-generated syntax -type private StripPositionInfoFromType (parent : StripPositionInfo) = +type private StripPositionInfoFromType (parent : StripPositionInfo) = inherit TypeTransformation(parent) override this.OnRangeInformation _ = Null -and private StripPositionInfoFromExpression (parent : StripPositionInfo) = +and private StripPositionInfoFromExpression (parent : StripPositionInfo) = inherit ExpressionTransformation(parent) override this.OnRangeInformation _ = Null -and private StripPositionInfoFromStatement(parent : StripPositionInfo) = +and private StripPositionInfoFromStatement(parent : StripPositionInfo) = inherit StatementTransformation(parent) override this.OnLocation _ = Null -and private StripPositionInfoFromNamespace(parent : StripPositionInfo) = +and private StripPositionInfoFromNamespace(parent : StripPositionInfo) = inherit NamespaceTransformation(parent) override this.OnLocation _ = Null -and public StripPositionInfo private (_internal_) = +and public StripPositionInfo private (_internal_) = inherit SyntaxTreeTransformation() static let defaultInstance = new StripPositionInfo() new () as this = - StripPositionInfo("_internal_") then - this.Types <- new StripPositionInfoFromType(this) + StripPositionInfo("_internal_") then + this.Types <- new StripPositionInfoFromType(this) this.Expressions <- new StripPositionInfoFromExpression(this) this.Statements <- new StripPositionInfoFromStatement(this) this.Namespaces <- new StripPositionInfoFromNamespace(this) @@ -50,46 +50,46 @@ and public StripPositionInfo private (_internal_) = static member public Apply a = defaultInstance.Namespaces.OnNamespace a -module SyntaxGenerator = +module SyntaxGenerator = - /// Matches only if the string consists of a fully qualified name and nothing else. - let internal FullyQualifiedName = + /// Matches only if the string consists of a fully qualified name and nothing else. + let internal FullyQualifiedName = new Regex(@"^[\p{L}_][\p{L}\p{Nd}_]*(\.[\p{L}_][\p{L}\p{Nd}_]*)+$") - // literal expresssions + // literal expressions /// Builds an immutable typed expression of the given kind and type kind, - /// setting the quantum dependency to the given value and assuming no type parameter resolutions. - /// Sets the range information for the built expression to Null. - let private AutoGeneratedExpression kind exTypeKind qDep = + /// setting the quantum dependency to the given value and assuming no type parameter resolutions. + /// Sets the range information for the built expression to Null. + let private AutoGeneratedExpression kind exTypeKind qDep = let noInferredInfo = InferredExpressionInformation.New (false, quantumDep = qDep) TypedExpression.New (kind, ImmutableDictionary.Empty, exTypeKind |> ResolvedType.New, noInferredInfo, QsRangeInfo.Null) - /// Creates a typed expression that corresponds to a Unit value. - /// Sets the range information for the built expression to Null. - let UnitValue = + /// Creates a typed expression that corresponds to a Unit value. + /// Sets the range information for the built expression to Null. + let UnitValue = AutoGeneratedExpression UnitValue QsTypeKind.UnitType false - /// Creates a typed expression that corresponds to an Int literal with the given value. - /// Sets the range information for the built expression to Null. - let IntLiteral v = + /// Creates a typed expression that corresponds to an Int literal with the given value. + /// Sets the range information for the built expression to Null. + let IntLiteral v = AutoGeneratedExpression (IntLiteral v) QsTypeKind.Int false - /// Creates a typed expression that corresponds to a BigInt literal with the given value. - /// Sets the range information for the built expression to Null. - let BigIntLiteral (v : int) = + /// Creates a typed expression that corresponds to a BigInt literal with the given value. + /// Sets the range information for the built expression to Null. + let BigIntLiteral (v : int) = AutoGeneratedExpression (BigIntLiteral (bigint v)) QsTypeKind.BigInt false - /// Creates a typed expression that corresponds to a Double literal with the given value. - /// Sets the range information for the built expression to Null. - let DoubleLiteral v = + /// Creates a typed expression that corresponds to a Double literal with the given value. + /// Sets the range information for the built expression to Null. + let DoubleLiteral v = AutoGeneratedExpression (DoubleLiteral v) QsTypeKind.Double false - /// Creates a typed expression that corresponds to a Range literal with the given left hand side and right hand side. - /// Sets the range information for the built expression to Null. - /// Does *not* verify the given left and right hand side. - let RangeLiteral (lhs, rhs) = + /// Creates a typed expression that corresponds to a Range literal with the given left hand side and right hand side. + /// Sets the range information for the built expression to Null. + /// Does *not* verify the given left and right hand side. + let RangeLiteral (lhs, rhs) = AutoGeneratedExpression (RangeLiteral (lhs, rhs)) QsTypeKind.Range false @@ -97,81 +97,85 @@ module SyntaxGenerator = /// Creates a typed expression corresponding to a call to a non-type-parametrized callable. /// Does *not* verify whether the given lhs and rhs besides - /// throwing an ArgumentException if the type of the given lhs is valid but not a Function or Operation type. - let private CallNonGeneric (lhs : TypedExpression, rhs : TypedExpression) = - let kind = CallLikeExpression (lhs, rhs) + /// throwing an ArgumentException if the type of the given lhs is valid but not a Function or Operation type. + let private CallNonGeneric (lhs : TypedExpression, rhs : TypedExpression) = + let kind = CallLikeExpression (lhs, rhs) let quantumDep = lhs.InferredInformation.HasLocalQuantumDependency || rhs.InferredInformation.HasLocalQuantumDependency - match lhs.ResolvedType.Resolution with + match lhs.ResolvedType.Resolution with | QsTypeKind.InvalidType -> AutoGeneratedExpression kind InvalidType quantumDep - | QsTypeKind.Operation ((_, ot), _) + | QsTypeKind.Operation ((_, ot), _) | QsTypeKind.Function (_,ot) -> AutoGeneratedExpression kind ot.Resolution quantumDep | _ -> ArgumentException "given lhs is not callable" |> raise /// Given a typed expression of type range, - /// creates a typed expression that when executed will generate the reverse sequence for the given range. + /// creates a typed expression that when executed will generate the reverse sequence for the given range. /// Assumes that the RangeReverse function is part of the standard library. - /// Throws an ArgumentException if the given expression is of a valid type but not of type Range. - let private ReverseRange (ex : TypedExpression) = - let buildCallToReverse ex = - let kind = Identifier.GlobalCallable {Namespace = BuiltIn.RangeReverse.Namespace; Name = BuiltIn.RangeReverse.Name} + /// Throws an ArgumentException if the given expression is of a valid type but not of type Range. + let private ReverseRange (ex : TypedExpression) = + let buildCallToReverse ex = + let kind = Identifier.GlobalCallable BuiltIn.RangeReverse.FullName let exTypeKind = QsTypeKind.Function (QsTypeKind.Range |> ResolvedType.New, QsTypeKind.Range |> ResolvedType.New) let reverse = AutoGeneratedExpression (QsExpressionKind.Identifier (kind, Null)) exTypeKind false - CallNonGeneric (reverse, ex) - match ex.ResolvedType.Resolution with + CallNonGeneric (reverse, ex) + match ex.ResolvedType.Resolution with | QsTypeKind.InvalidType | QsTypeKind.Range _ -> buildCallToReverse ex | _ -> ArgumentException "given expression is not a range" |> raise /// Builds a range expression for the given lhs and rhs of the range operator. /// Does *not* verify the type of the given lhs or rhs. - let private RangeExpression (lhs : TypedExpression, rhs : TypedExpression) = + let private RangeExpression (lhs : TypedExpression, rhs : TypedExpression) = let kind = QsExpressionKind.RangeLiteral (lhs, rhs) let quantumDep = lhs.InferredInformation.HasLocalQuantumDependency || rhs.InferredInformation.HasLocalQuantumDependency AutoGeneratedExpression kind QsTypeKind.Range quantumDep - /// Creates a typed expression that corresponds to a call to the Length function. - /// The Length function needs to be part of the QsCore library, and its type parameter name needs to match the one here. - /// Throws an ArgumentException if the given expression is not of type array or of invalid type. - let private Length (ex : TypedExpression) = - let callableName = {Namespace = BuiltIn.Length.Namespace; Name = BuiltIn.Length.Name} + /// Creates a typed expression that corresponds to a call to the Length function. + /// The Length function needs to be part of the QsCore library, and its type parameter name needs to match the one here. + /// Throws an ArgumentException if the given expression is not of type array or of invalid type. + let private Length (ex : TypedExpression) = + let callableName = BuiltIn.Length.FullName let kind = Identifier.GlobalCallable callableName - let typeParameter = QsTypeParameter.New (callableName, BuiltIn.Length.TypeParameters.[0], Null) + let typeParameterName = + match BuiltIn.Length.Kind with + | BuiltInKind.Function typeParams -> typeParams.[0] + | _ -> ArgumentException "Length is expected to be a function" |> raise + let typeParameter = QsTypeParameter.New (callableName, typeParameterName, Null) let genArrayType = QsTypeKind.ArrayType (QsTypeKind.TypeParameter typeParameter |> ResolvedType.New) |> ResolvedType.New let exTypeKind = QsTypeKind.Function (genArrayType, QsTypeKind.Int |> ResolvedType.New) let length = AutoGeneratedExpression (QsExpressionKind.Identifier (kind, Null)) exTypeKind false - let callToLength tpRes = + let callToLength tpRes = let resolutions = (seq { yield (typeParameter.Origin, typeParameter.TypeName, tpRes) }).ToImmutableArray() {CallNonGeneric (length, ex) with TypeArguments = resolutions} - match ex.ResolvedType.Resolution with + match ex.ResolvedType.Resolution with | ArrayType b -> callToLength b | InvalidType -> callToLength ex.ResolvedType | _ -> ArgumentException "the given expression is not of array type" |> raise - - /// Creates a typed expression that corresponds to subracting one from the call to the Length function. - /// Sets any range information in the built expression to Null, including inner expressions. - /// Throws an ArgumentException if the given expression is not of type array or of invalid type. - let LengthMinusOne (ex : TypedExpression) = + + /// Creates a typed expression that corresponds to subtracting one from the call to the Length function. + /// Sets any range information in the built expression to Null, including inner expressions. + /// Throws an ArgumentException if the given expression is not of type array or of invalid type. + let LengthMinusOne (ex : TypedExpression) = let callToLength = ex |> StripPositionInfo.Apply |> Length let kind = QsExpressionKind.SUB (callToLength, IntLiteral 1L) AutoGeneratedExpression kind QsTypeKind.Int callToLength.InferredInformation.HasLocalQuantumDependency /// Given a typed expression of array type, /// creates a typed expression that when evaluated returns a new array with the order of the elements reversed. - /// Throws an ArgumentException if the given expression is not of type array or of invalid type. + /// Throws an ArgumentException if the given expression is not of type array or of invalid type. let private ReverseArray (ex : TypedExpression) = let built = let reversingRange = RangeExpression (RangeExpression (LengthMinusOne ex, IntLiteral -1L), IntLiteral 0L) let kind = ArrayItem (ex, reversingRange) AutoGeneratedExpression kind ex.ResolvedType.Resolution ex.InferredInformation.HasLocalQuantumDependency - match ex.ResolvedType.Resolution with - | ArrayType _ + match ex.ResolvedType.Resolution with + | ArrayType _ | InvalidType -> built | _ -> ArgumentException "the given expression is not of array type" |> raise /// Given a typed expression of a type that supports iteration, /// creates a typed expression that when evaluated returns the reversed sequence. - /// Throws an ArgumentException if the given expression is of a valid type but not either of type Range or of array type. - let ReverseIterable (ex : TypedExpression) = + /// Throws an ArgumentException if the given expression is of a valid type but not either of type Range or of array type. + let ReverseIterable (ex : TypedExpression) = let ex = StripPositionInfo.Apply ex match ex.ResolvedType.Resolution with | QsTypeKind.Range -> ReverseRange ex @@ -179,10 +183,10 @@ module SyntaxGenerator = | QsTypeKind.InvalidType -> ex | _ -> ArgumentException "the given expression is not iterable" |> raise - /// Returns a boolean expression that evaluates to true if the given expression is negative. - /// Returns an invalid expression of type Bool if the given expression is invalid. - /// Throws an ArgumentException if the type of the given expression does not support arithmetic. - let IsNegative (ex : TypedExpression) = + /// Returns a boolean expression that evaluates to true if the given expression is negative. + /// Returns an invalid expression of type Bool if the given expression is invalid. + /// Throws an ArgumentException if the type of the given expression does not support arithmetic. + let IsNegative (ex : TypedExpression) = let kind = ex.ResolvedType.Resolution |> function | Int -> LT (ex, IntLiteral 0L) | BigInt -> LT (ex, BigIntLiteral 0) @@ -197,107 +201,107 @@ module SyntaxGenerator = let private QubitArray = Qubit |> ResolvedType.New |> ArrayType |> ResolvedType.New /// Given a QsTuple, recursively extracts and returns all of its items. - let ExtractItems (this : QsTuple<_>) = + let ExtractItems (this : QsTuple<_>) = this.Items.ToImmutableArray() - /// Strips all range information from the given signature. - let WithoutRangeInfo (signature : ResolvedSignature) = + /// Strips all range information from the given signature. + let WithoutRangeInfo (signature : ResolvedSignature) = let argType = signature.ArgumentType |> StripPositionInfo.Apply let returnType = signature.ReturnType |> StripPositionInfo.Apply ResolvedSignature.New ((argType, returnType), signature.Information, signature.TypeParameters) - /// Given the resolved argument type of an operation, returns the argument type of its controlled version. - let AddControlQubits (argT : ResolvedType) = + /// Given the resolved argument type of an operation, returns the argument type of its controlled version. + let AddControlQubits (argT : ResolvedType) = [QubitArray; argT].ToImmutableArray() |> TupleType |> ResolvedType.New - /// Given a resolved signature, returns the corresponding signature for the controlled version. - let BuildControlled (this : ResolvedSignature) = + /// Given a resolved signature, returns the corresponding signature for the controlled version. + let BuildControlled (this : ResolvedSignature) = { this with ArgumentType = this.ArgumentType |> AddControlQubits } /// Given an argument tuple of a callable, the name and the range of the control qubits symbol, as well as the position offset for that range, - /// builds and returns the argument tuple for the controlled specialization. - /// Throws an ArgumentException if the given argument tuple is not a QsTuple. - let WithControlQubits arg offset (name, symRange : QsNullable<_>) = + /// builds and returns the argument tuple for the controlled specialization. + /// Throws an ArgumentException if the given argument tuple is not a QsTuple. + let WithControlQubits arg offset (name, symRange : QsNullable<_>) = let range = symRange.ValueOr QsCompilerDiagnostic.DefaultRange let ctlQs = LocalVariableDeclaration.New false ((offset, range), name, QubitArray, false) - let unitArg = + let unitArg = let argName = NonNullable<_>.New InternalUse.UnitArgument |> ValidName; let unitT = UnitType |> ResolvedType.New LocalVariableDeclaration.New false ((offset,range), argName, unitT, false) |> QsTupleItem // the range is not accurate here, but also irrelevant - match arg with + match arg with | QsTuple ts when ts.Length = 0 -> [ctlQs |> QsTupleItem; unitArg].ToImmutableArray() | QsTuple ts when ts.Length = 1 -> [ctlQs |> QsTupleItem; ts.[0]].ToImmutableArray() | QsTuple _ -> [ctlQs |> QsTupleItem; arg].ToImmutableArray() | _ -> ArgumentException "expecting the given argument tuple to be a QsTuple" |> raise |> QsTuple - /// Given a typed expression that is used as argument to an operation and a typed expression for the control qubits, + /// Given a typed expression that is used as argument to an operation and a typed expression for the control qubits, /// combines them to a suitable argument for the controlled version of the originally called operation under the assumption that the argument was correct. - /// The range information for the built expression is set to Null. + /// The range information for the built expression is set to Null. /// Throws an ArgumentException if the given expression for the control qubits is a valid type but not of type Qubit[]. - let ArgumentWithControlQubits (arg : TypedExpression) (ctlQs : TypedExpression) = + let ArgumentWithControlQubits (arg : TypedExpression) (ctlQs : TypedExpression) = let isInvalid = function | Tuple _ | Item _ | Missing -> false | _ -> true - if ctlQs.ResolvedType.Resolution <> QubitArray.Resolution && not (ctlQs.ResolvedType |> isInvalid) then + if ctlQs.ResolvedType.Resolution <> QubitArray.Resolution && not (ctlQs.ResolvedType |> isInvalid) then new ArgumentException "expression for the control qubits is valid but not of type Qubit[]" |> raise - let buildControlledArgument orig = + let buildControlledArgument orig = let kind = QsExpressionKind.ValueTuple ([ctlQs; orig].ToImmutableArray()) let quantumDep = orig.InferredInformation.HasLocalQuantumDependency || ctlQs.InferredInformation.HasLocalQuantumDependency let exInfo = InferredExpressionInformation.New (isMutable = false, quantumDep = quantumDep) TypedExpression.New (kind, orig.TypeParameterResolutions, AddControlQubits orig.ResolvedType, exInfo, QsRangeInfo.Null) buildControlledArgument arg - /// Returns the name of the control qubits + /// Returns the name of the control qubits /// if the given argument tuple is consistent with the argument tuple of a controlled specialization. - /// Return null otherwise, or if the name of the control qubits is invalid. + /// Return null otherwise, or if the name of the control qubits is invalid. let ControlledFunctorArgument arg = - let getItemName = function - | QsTupleItem (item : LocalVariableDeclaration) -> - item.VariableName |> function + let getItemName = function + | QsTupleItem (item : LocalVariableDeclaration) -> + item.VariableName |> function | ValidName name -> name.Value | InvalidName -> null | _ -> null - match arg with + match arg with | QsTuple ts when ts.Length = 2 -> ts.[0] |> getItemName | _ -> null /// Creates a typed expression that corresponds to an immutable local variable with the given name /// that contains an expression of type Qubit[] and has no quantum dependencies. - let ImmutableQubitArrayWithName name = + let ImmutableQubitArrayWithName name = let kind = (Identifier.LocalVariable name, Null) |> QsExpressionKind.Identifier let exTypeKind = QsTypeKind.ArrayType (QsTypeKind.Qubit |> ResolvedType.New) AutoGeneratedExpression kind exTypeKind false /// Given a typed expression of operation type, creates a typed expression corresponding to a Controlled application on that operation. - /// Creates an expression of invalid type if the given expression is invalid. + /// Creates an expression of invalid type if the given expression is invalid. /// Blindly builds the controlled application if the characteristics of the given operation are invalid. /// Throws an ArgumentException if the given expression is valid but not of an operation type, or if it does not support the Controlled functor. - let ControlledOperation (ex : TypedExpression) = + let ControlledOperation (ex : TypedExpression) = let ex = StripPositionInfo.Apply ex let kind = QsExpressionKind.ControlledApplication ex let built exTypeKind = AutoGeneratedExpression kind exTypeKind ex.InferredInformation.HasLocalQuantumDependency let ctlOpType ((it, ot), opInfo) = QsTypeKind.Operation ((AddControlQubits it, ot), opInfo) - match ex.ResolvedType.Resolution with + match ex.ResolvedType.Resolution with | QsTypeKind.InvalidType -> built InvalidType - | QsTypeKind.Operation ((it,ot), opInfo) when opInfo.Characteristics.AreInvalid -> ctlOpType ((it, ot), opInfo) |> built - | QsTypeKind.Operation ((it,ot), opInfo) -> opInfo.Characteristics.SupportedFunctors |> function + | QsTypeKind.Operation ((it,ot), opInfo) when opInfo.Characteristics.AreInvalid -> ctlOpType ((it, ot), opInfo) |> built + | QsTypeKind.Operation ((it,ot), opInfo) -> opInfo.Characteristics.SupportedFunctors |> function | Value functors when functors.Contains Controlled -> ctlOpType ((it, ot), opInfo) |> built - | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise - | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise + | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise + | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise /// Given a typed expression of operation type, creates a typed expression corresponding to a Adjoint application on that operation. - /// Creates an expression of invalid type if the given expression is invalid. + /// Creates an expression of invalid type if the given expression is invalid. /// Blindly builds the adjoint application if the characteristics of the given operation are invalid. /// Throws an ArgumentException if the given expression is valid but not of an operation type, or if it does not support the Adjoint functor. - let AdjointOperation (ex : TypedExpression) = + let AdjointOperation (ex : TypedExpression) = let ex = StripPositionInfo.Apply ex let kind = QsExpressionKind.AdjointApplication (ex) let built = AutoGeneratedExpression kind ex.ResolvedType.Resolution ex.InferredInformation.HasLocalQuantumDependency - match ex.ResolvedType.Resolution with + match ex.ResolvedType.Resolution with | QsTypeKind.InvalidType -> built | QsTypeKind.Operation (_, opInfo) when opInfo.Characteristics.AreInvalid -> built - | QsTypeKind.Operation (_, opInfo) -> opInfo.Characteristics.SupportedFunctors |> function + | QsTypeKind.Operation (_, opInfo) -> opInfo.Characteristics.SupportedFunctors |> function | Value functors when functors.Contains Adjoint -> built - | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise - | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise + | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise + | _ -> ArgumentException "given expression does not correspond to a suitable operation" |> raise diff --git a/src/QsCompiler/DataStructures/DataTypes.fs b/src/QsCompiler/DataStructures/DataTypes.fs index 92b86673ed..1bbe527321 100644 --- a/src/QsCompiler/DataStructures/DataTypes.fs +++ b/src/QsCompiler/DataStructures/DataTypes.fs @@ -13,11 +13,16 @@ type QsNullable<'T> = // to avoid having to include the F# core in the C# part o | Null | Value of 'T + /// If the given nullable has a value, applies the given function to it and returns the result, which must be + /// another nullable. Returns Null otherwise. + static member Bind fct = function + | Null -> Null + | Value v -> fct v + /// If the given nullable has a value, applies the given function to it and returns the result as Value, /// and returns Null otherwise. - static member Map fct = function - | Null -> Null - | Value v -> Value (fct v) + static member Map fct = + QsNullable<_>.Bind (fct >> Value) /// If the given nullable has a value, applies the given function to it. static member Iter fct = function diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index e960a53ee0..195a631224 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -187,6 +187,8 @@ type ErrorCode = | AliasForOpenedNamespace = 6019 | InvalidNamespaceAliasName = 6020 // i.e. the chosen alias already exists | ConflictInReferences = 6021 + | InaccessibleType = 6022 + | InaccessibleCallable = 6023 | ExpectingUnqualifiedSymbol = 6101 | ExpectingItemName = 6102 @@ -197,6 +199,8 @@ type ErrorCode = | UnknownTypeParameterName = 6107 | UnknownItemName = 6108 | NotMarkedAsAttribute = 6109 + | InaccessibleTypeInNamespace = 6110 + | InaccessibleCallableInNamespace = 6111 | ArgumentTupleShapeMismatch = 6201 | ArgumentTupleMismatch = 6202 @@ -234,6 +238,9 @@ type ErrorCode = | InvalidEntryPointSpecialization = 6235 | InvalidTestAttributePlacement = 6236 | InvalidExecutionTargetForTest = 6237 + | ExpectingFullNameAsAttributeArgument = 6238 + | AttributeInvalidOnSpecialization = 6239 + | AttributeInvalidOnCallable = 6240 | TypeMismatchInReturn = 6301 | TypeMismatchInValueUpdate = 6302 @@ -251,6 +258,8 @@ type ErrorCode = | RUSloopWithinAutoInversion = 6315 | QuantumDependencyOutsideExprStatement = 6316 | InvalidReassignmentInApplyBlock = 6317 + | TypeLessAccessibleThanParentType = 6318 + | TypeLessAccessibleThanParentCallable = 6319 | UnexpectedCommandLineCompilerException = 7001 | MissingInputFileOrSnippet = 7002 @@ -267,7 +276,11 @@ type ErrorCode = | UnknownCompilerPlugin = 7015 | CouldNotLoadCompilerPlugin = 7016 | CouldNotInstantiateRewriteStep = 7017 - | UnexpectedCompilerException = 7018 + | CouldNotFineTargetPackage = 7018 + | CouldNotFindTargetPackageAssembly = 7019 + | InvalidTargetPackageAssemblyPath = 7020 + | FailedToLoadTargetPackageAssembly = 7021 + | UnexpectedCompilerException = 7022 | FunctorGenerationFailed = 7101 | TreeTrimmingFailed = 7102 @@ -526,6 +539,8 @@ type DiagnosticItem = | ErrorCode.TypeConstructorOverlapWithCallable -> "Invalid type declaration. A function or operation with the name \"{0}\" already exists." | ErrorCode.UnknownType -> "No type with the name \"{0}\" exists in any of the open namespaces." | ErrorCode.AmbiguousType -> "Multiple open namespaces contain a type with the name \"{0}\". Use a fully qualified name instead. Open namespaces containing a type with that name are {1}." + | ErrorCode.InaccessibleType -> "The type {0} exists in an open namespace, but is not accessible from here." + | ErrorCode.InaccessibleCallable -> "The callable {0} exists in an open namespace, but is not accessible from here." | ErrorCode.AmbiguousCallable -> "Multiple open namespaces contain a callable with the name \"{0}\". Use a fully qualified name instead. Open namespaces containing a callable with that name are {1}." | ErrorCode.TypeSpecializationMismatch -> "Invalid specialization declaration. The type specializations do not match the expected number of type parameters. Expecting {0} type argument(s)." | ErrorCode.SpecializationForUnknownCallable -> "No callable with the name \"{0}\" exists in the current namespace. Specializations need to be declared in the same namespace as the callable they extend." @@ -550,6 +565,8 @@ type DiagnosticItem = | ErrorCode.UnknownTypeParameterName -> "No type parameter with the name \"{0}\" exists." | ErrorCode.UnknownItemName -> "The type {0} does not define an item with name \"{1}\"." | ErrorCode.NotMarkedAsAttribute -> "The type {0} is not marked as an attribute. Add \"@Attribute()\" above its declaration to indicate that it may be used as attribute." + | ErrorCode.InaccessibleTypeInNamespace -> "The type {0} in namespace {1} is not accessible from here." + | ErrorCode.InaccessibleCallableInNamespace -> "The callable {0} in namespace {1} is not accessible from here." | ErrorCode.ArgumentTupleShapeMismatch -> "The shape of the given tuple does not match the expected type. Got an argument of type {0}, expecting one of type {1} instead." | ErrorCode.ArgumentTupleMismatch -> "The type of the given tuple does not match the expected type. Got an argument of type {0}, expecting one of type {1} instead." @@ -587,6 +604,9 @@ type DiagnosticItem = | ErrorCode.InvalidEntryPointSpecialization -> "Entry points cannot have any other specializations besides the default body." | ErrorCode.InvalidTestAttributePlacement -> "Invalid test attribute. Test attributes may only occur on callables that have no arguments and return Unit." | ErrorCode.InvalidExecutionTargetForTest -> "Invalid execution target. Currently, valid execution targets for tests are the QuantumSimulator, the ToffoliSimulator, or the ResourcesEstimator." + | ErrorCode.ExpectingFullNameAsAttributeArgument -> "Invalid attribute argument. Expecting a fully qualified name as argument to the {0} attribute." + | ErrorCode.AttributeInvalidOnSpecialization -> "Invalid attribute placement. The attribute {0} cannot be attached to a specialization declaration." + | ErrorCode.AttributeInvalidOnCallable -> "Invalid attribute placement. The attribute {0} cannot be attached to a callable declaration." | ErrorCode.TypeMismatchInReturn -> "The type {0} of the given expression is not compatible with the expected return type {1}." | ErrorCode.TypeMismatchInValueUpdate -> "The type {0} of the given expression is not compatible with the type {1} of the identifier." @@ -604,6 +624,8 @@ type DiagnosticItem = | ErrorCode.RUSloopWithinAutoInversion -> "Auto-generation of inversions is not supported for operations that contain repeat-until-success-loops." | ErrorCode.QuantumDependencyOutsideExprStatement -> "Auto-generation of inversions is not supported for operations that contain operation calls outside expression statements." | ErrorCode.InvalidReassignmentInApplyBlock -> "Variables that are used in the within-block (specifying the outer transformation) cannot be reassigned in the apply-block (specifying the inner transformation)." + | ErrorCode.TypeLessAccessibleThanParentType -> "The type {0} is less accessible than the parent type {1}." + | ErrorCode.TypeLessAccessibleThanParentCallable -> "The type {0} is less accessible than the callable {1}." | ErrorCode.UnexpectedCommandLineCompilerException -> "The command line compiler threw an exception." | ErrorCode.MissingInputFileOrSnippet -> "The command line compiler needs a list of files or a code snippet to process." @@ -619,7 +641,11 @@ type DiagnosticItem = | ErrorCode.SourceFilesMissing -> "No source files have been specified." | ErrorCode.UnknownCompilerPlugin -> "Could not find the .NET Core library \"{0}\" specifying transformations to perform as part of the compilation process." | ErrorCode.CouldNotLoadCompilerPlugin -> "Unable to load the file \"{0}\" specifying transformations to perform as part of the compilation process. The file needs to be a suitable .NET Core library." - | ErrorCode.CouldNotInstantiateRewriteStep -> "Could not instantiate the type {0} in \"{1}\" specifying a rewrite step. The type may not have a parameterless constructor. " + | ErrorCode.CouldNotInstantiateRewriteStep -> "Could not instantiate the type {0} in \"{1}\" specifying a rewrite step. The type may not have a parameterless constructor." + | ErrorCode.CouldNotFineTargetPackage -> "Could not find the directory \"{0}\" containing target specific information." + | ErrorCode.CouldNotFindTargetPackageAssembly -> "Could not find the assembly specifying target specific implementations within the target package \"{0}\"." + | ErrorCode.InvalidTargetPackageAssemblyPath -> "Could not find the file \"{0}\" that specifies target specific implementations." + | ErrorCode.FailedToLoadTargetPackageAssembly -> "Unable to load target specific implementations from \"{0}\"." | ErrorCode.UnexpectedCompilerException -> "The compiler threw an exception." | ErrorCode.FunctorGenerationFailed -> "Auto-generation of functor specialization(s) failed." diff --git a/src/QsCompiler/DataStructures/ReservedKeywords.fs b/src/QsCompiler/DataStructures/ReservedKeywords.fs index c6dfb3462b..5afde67b98 100644 --- a/src/QsCompiler/DataStructures/ReservedKeywords.fs +++ b/src/QsCompiler/DataStructures/ReservedKeywords.fs @@ -155,6 +155,9 @@ module Declarations = /// keyword for a Q# declaration let Namespace = "namespace" + + /// keyword for a Q# declaration modifier + let Internal = "internal" /// contains keywords for Q# directives diff --git a/src/QsCompiler/DataStructures/SyntaxTokens.fs b/src/QsCompiler/DataStructures/SyntaxTokens.fs index 4af35c0fed..d1d85df8fd 100644 --- a/src/QsCompiler/DataStructures/SyntaxTokens.fs +++ b/src/QsCompiler/DataStructures/SyntaxTokens.fs @@ -192,6 +192,23 @@ type CallableSignature = { Characteristics : Characteristics } +/// Defines where a global declaration may be accessed. +[] +type AccessModifier = + /// For callables and types, the default access modifier is public, which means the type or callable can be used + /// from anywhere. For specializations, the default access modifier is the same as the parent callable. + | DefaultAccess + /// Internal access means that a type or callable may only be used from within the compilation unit in which it is + /// declared. + | Internal + +/// Used to represent Q# keywords that may be attached to a declaration to modify its visibility or behavior. +[] +type Modifiers = { + /// Defines where a global declaration may be accessed. + Access : AccessModifier +} + type QsFragmentKind = | ExpressionStatement of QsExpression | ReturnStatement of QsExpression @@ -214,9 +231,9 @@ type QsFragmentKind = | AdjointDeclaration of QsSpecializationGenerator | ControlledDeclaration of QsSpecializationGenerator | ControlledAdjointDeclaration of QsSpecializationGenerator -| OperationDeclaration of QsSymbol * CallableSignature -| FunctionDeclaration of QsSymbol * CallableSignature -| TypeDefinition of QsSymbol * QsTuple +| OperationDeclaration of Modifiers * QsSymbol * CallableSignature +| FunctionDeclaration of Modifiers * QsSymbol * CallableSignature +| TypeDefinition of Modifiers * QsSymbol * QsTuple | DeclarationAttribute of QsSymbol * QsExpression | OpenDirective of QsSymbol * QsNullable | NamespaceDeclaration of QsSymbol diff --git a/src/QsCompiler/DataStructures/SyntaxTree.fs b/src/QsCompiler/DataStructures/SyntaxTree.fs index aa2c70be09..3d6e8b8fa1 100644 --- a/src/QsCompiler/DataStructures/SyntaxTree.fs +++ b/src/QsCompiler/DataStructures/SyntaxTree.fs @@ -661,6 +661,7 @@ type QsSpecialization = { with member this.AddAttribute att = {this with Attributes = this.Attributes.Add att} member this.WithImplementation impl = {this with Implementation = impl} + member this.WithParent (getName : Func<_,_>) = {this with Parent = getName.Invoke(this.Parent)} /// describes a Q# function, operation, or type constructor @@ -671,6 +672,8 @@ type QsCallable = { FullName : QsQualifiedName /// contains all attributes associated with the callable Attributes : ImmutableArray + /// Represents the Q# keywords attached to the declaration that modify its behavior. + Modifiers : Modifiers /// identifier for the file the callable is declared in SourceFile : NonNullable /// Contains the location information for the declared callable. @@ -714,6 +717,8 @@ type QsCustomType = { FullName : QsQualifiedName /// contains all attributes associated with the type Attributes : ImmutableArray + /// Represents the Q# keywords attached to the declaration that modify its behavior. + Modifiers : Modifiers /// identifier for the file the type is declared in SourceFile : NonNullable /// Contains the location information for the declared type. @@ -735,6 +740,7 @@ type QsCustomType = { } with member this.AddAttribute att = {this with Attributes = this.Attributes.Add att} + member this.WithFullName (getName : Func<_,_>) = {this with FullName = getName.Invoke(this.FullName)} /// Describes a valid Q# namespace element. @@ -780,8 +786,3 @@ type QsCompilation = { /// In the case of a library the array is empty. EntryPoints : ImmutableArray } - - - - - diff --git a/src/QsCompiler/DocumentationParser/DocComment.cs b/src/QsCompiler/DocumentationParser/DocComment.cs index 0c501cb27b..527ee9a4df 100644 --- a/src/QsCompiler/DocumentationParser/DocComment.cs +++ b/src/QsCompiler/DocumentationParser/DocComment.cs @@ -101,7 +101,7 @@ public class DocComment /// The name of the replacement element for deprecated elements, if given public DocComment(IEnumerable docComments, string name, bool deprecated, string replacement) { - string GetHeadingText(HeadingBlock heading) + static string GetHeadingText(HeadingBlock heading) { var sb = new StringBuilder(); foreach (var item in heading.Inline) @@ -111,7 +111,7 @@ string GetHeadingText(HeadingBlock heading) return sb.ToString(); } - string GetParagraphText(LeafBlock leaf) + static string GetParagraphText(LeafBlock leaf) { var sb = new StringBuilder(); foreach (var item in leaf.Inline) @@ -121,7 +121,7 @@ string GetParagraphText(LeafBlock leaf) return sb.ToString(); } - string ToMarkdown(IEnumerable blocks) + static string ToMarkdown(IEnumerable blocks) { var writer = new StringWriter(); var renderer = new NormalizeRenderer(writer); @@ -136,7 +136,7 @@ string ToMarkdown(IEnumerable blocks) return writer.ToString().TrimEnd().Replace('\n', '\r'); } - List>> BreakIntoSections(IEnumerable blocks, int level) + static List>> BreakIntoSections(IEnumerable blocks, int level) { var key = ""; var accum = new List(); @@ -174,7 +174,7 @@ List>> BreakIntoSections(IEnumerable block return result; } - void ParseListSection(IEnumerable blocks, List accum, bool lowerCase) + static void ParseListSection(IEnumerable blocks, List accum, bool lowerCase) { foreach (var block in blocks) { @@ -202,7 +202,7 @@ void ParseListSection(IEnumerable blocks, List accum, bool lowerC } } - void ParseMapSection(IEnumerable blocks, Dictionary accum) + static void ParseMapSection(IEnumerable blocks, Dictionary accum) { var subsections = BreakIntoSections(blocks, 2); foreach ((var key, var subs) in subsections) @@ -214,7 +214,7 @@ void ParseMapSection(IEnumerable blocks, Dictionary accum } // First element is not matching, second is matching - (List, List) PartitionNestedSection(IEnumerable blocks, int level, string name) + static (List, List) PartitionNestedSection(IEnumerable blocks, int level, string name) { var inMatch = false; var result = (new List(), new List()); diff --git a/src/QsCompiler/DocumentationParser/DocNamespace.cs b/src/QsCompiler/DocumentationParser/DocNamespace.cs index d32cc887b4..4f70b62d99 100644 --- a/src/QsCompiler/DocumentationParser/DocNamespace.cs +++ b/src/QsCompiler/DocumentationParser/DocNamespace.cs @@ -3,11 +3,11 @@ #nullable enable using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; using YamlDotNet.RepresentationModel; @@ -34,11 +34,11 @@ internal class DocNamespace internal DocNamespace(QsNamespace ns, IEnumerable? sourceFiles = null) { var sourceFileSet = sourceFiles == null ? null : new HashSet(sourceFiles); - bool IsVisible(NonNullable qualifiedName, NonNullable source) + bool IsVisible(NonNullable source, AccessModifier access, NonNullable qualifiedName) { var name = qualifiedName.Value; var includeInDocs = sourceFileSet == null || sourceFileSet.Contains(source.Value); - return includeInDocs && !(name.StartsWith("_") || name.EndsWith("_") + return includeInDocs && access.IsDefaultAccess && !(name.StartsWith("_") || name.EndsWith("_") || name.EndsWith("Impl", StringComparison.InvariantCultureIgnoreCase) || name.EndsWith("ImplA", StringComparison.InvariantCultureIgnoreCase) || name.EndsWith("ImplC", StringComparison.InvariantCultureIgnoreCase) @@ -65,7 +65,7 @@ bool IsVisible(NonNullable qualifiedName, NonNullable source) if (item is QsNamespaceElement.QsCallable c) { var callable = c.Item; - if (IsVisible(callable.FullName.Name, callable.SourceFile) && + if (IsVisible(callable.SourceFile, callable.Modifiers.Access, callable.FullName.Name) && (callable.Kind != QsCallableKind.TypeConstructor)) { items.Add(new DocCallable(name, callable)); @@ -74,7 +74,7 @@ bool IsVisible(NonNullable qualifiedName, NonNullable source) else if (item is QsNamespaceElement.QsCustomType u) { var udt = u.Item; - if (IsVisible(udt.FullName.Name, udt.SourceFile)) + if (IsVisible(udt.SourceFile, udt.Modifiers.Access, udt.FullName.Name)) { items.Add(new DocUdt(name, udt)); } @@ -213,10 +213,13 @@ void WriteItem(DocItem i) } /// - /// Writes the YAML file for this namespace. + /// Writes the YAML file for this namespace to a stream. /// - /// The directory to write the file to - internal void WriteToFile(string directoryPath) + /// The stream to write to. + /// + /// The mapping node representing the preexisting contents of this namespace's YAML file. + /// + internal void WriteToStream(Stream stream, YamlMappingNode? rootNode = null) { string ToSequenceKey(string itemTypeName) { @@ -239,8 +242,7 @@ string ToSequenceKey(string itemTypeName) return ""; } - var rootNode = Utils.ReadYamlFile(directoryPath, name) as YamlMappingNode ?? new YamlMappingNode(); - + rootNode ??= new YamlMappingNode(); rootNode.AddStringMapping(Utils.UidKey, uid); rootNode.AddStringMapping(Utils.NameKey, name); if (!String.IsNullOrEmpty(summary)) @@ -251,7 +253,7 @@ string ToSequenceKey(string itemTypeName) var itemTypeNodes = new Dictionary>(); // Collect existing items - foreach (var itemType in new []{Utils.FunctionKind, Utils.OperationKind, Utils.UdtKind}) + foreach (var itemType in new[] { Utils.FunctionKind, Utils.OperationKind, Utils.UdtKind }) { var seqKey = ToSequenceKey(itemType); var thisList = new SortedDictionary(); @@ -308,14 +310,22 @@ string ToSequenceKey(string itemTypeName) } var doc = new YamlDocument(rootNode); - var stream = new YamlStream(doc); + var yamlStream = new YamlStream(doc); + using var output = new StreamWriter(stream); + output.WriteLine("### " + Utils.QsNamespaceYamlMime); + yamlStream.Save(output, false); + } + + /// + /// Writes the YAML file for this namespace. + /// + /// The directory to write the file to + internal void WriteToFile(string directoryPath) + { + var rootNode = Utils.ReadYamlFile(directoryPath, name) as YamlMappingNode; var tocFileName = Path.Combine(directoryPath, name + Utils.YamlExtension); - using (var text = new StreamWriter(File.Open(tocFileName, FileMode.Create))) - { - text.WriteLine("### " + Utils.QsNamespaceYamlMime); - stream.Save(text, false); - } + WriteToStream(File.Open(tocFileName, FileMode.Create), rootNode); } } } diff --git a/src/QsCompiler/DocumentationParser/Utils.cs b/src/QsCompiler/DocumentationParser/Utils.cs index 1b79293474..1b65c46103 100644 --- a/src/QsCompiler/DocumentationParser/Utils.cs +++ b/src/QsCompiler/DocumentationParser/Utils.cs @@ -340,6 +340,7 @@ internal static string CallableToSyntax(QsCallable callable) /// The syntax string internal static string CustomTypeToSyntax(QsCustomType customType) { + // TODO: Include modifiers. var sb = new StringBuilder(); sb.Append("newtype "); sb.Append(customType.FullName.Name.Value); diff --git a/src/QsCompiler/LanguageServer/EditorState.cs b/src/QsCompiler/LanguageServer/EditorState.cs index e107caa095..3a7e9decf6 100644 --- a/src/QsCompiler/LanguageServer/EditorState.cs +++ b/src/QsCompiler/LanguageServer/EditorState.cs @@ -108,10 +108,11 @@ internal bool QsProjectLoader(Uri projectFile, out ProjectInformation info) var projectReferences = GetItemsByType(projectInstance, "ProjectReference"); var references = GetItemsByType(projectInstance, "Reference"); var version = projectInstance.GetPropertyValue("QsharpLangVersion"); + var loadTestNames = "true".Equals(projectInstance.GetPropertyValue("ExposeReferencesViaTestNames"), StringComparison.InvariantCultureIgnoreCase); var telemetryMeas = new Dictionary { ["sources"] = sourceFiles.Count() }; this.SendTelemetry("project-load", telemetryProps, telemetryMeas); // does not send anything unless the corresponding flag is defined upon compilation - info = new ProjectInformation(version, outputPath, sourceFiles, projectReferences, references); + info = new ProjectInformation(version, outputPath, loadTestNames, sourceFiles, projectReferences, references); return true; } diff --git a/src/QsCompiler/SyntaxProcessor/DeclarationVerification.fs b/src/QsCompiler/SyntaxProcessor/DeclarationVerification.fs index 05045779bc..9f8c6a0961 100644 --- a/src/QsCompiler/SyntaxProcessor/DeclarationVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/DeclarationVerification.fs @@ -66,12 +66,12 @@ let public OpenedNamespaceName this onInvalid = this |> OpenedNamespace |> NameOnly onInvalid /// If the given fragment kind is a type declaration, -/// returns the symbol for the type as well as the declared underlying type as Value. +/// returns the symbol for the type as well as the declared underlying type and modifiers as Value. /// Returns Null otherwise. [] let public DeclaredType this = match this with - | TypeDefinition (sym, decl) -> (sym, decl) |> Value + | TypeDefinition (mods, sym, decl) -> (sym, (mods, decl)) |> Value | _ -> Null /// If the given fragment kind is a type declaration, @@ -83,13 +83,13 @@ let public DeclaredTypeName this onInvalid = this |> DeclaredType |> NameOnly onInvalid /// If the given fragment kind is a callable declaration, -/// returns the symbol for the callable as well as its declared kind and signature as Value. +/// returns the symbol for the callable as well as its declared kind, signature and modifiers as Value. /// Returns Null otherwise. [] let public DeclaredCallable this = match this with - | FunctionDeclaration (sym, decl) -> (sym, (QsCallableKind.Function, decl)) |> Value - | OperationDeclaration (sym, decl) -> (sym, (QsCallableKind.Operation, decl)) |> Value + | FunctionDeclaration (mods, sym, decl) -> (sym, (QsCallableKind.Function, mods, decl)) |> Value + | OperationDeclaration (mods, sym, decl) -> (sym, (QsCallableKind.Operation, mods, decl)) |> Value | _ -> Null /// If the given fragment kind is a callable declaration, diff --git a/src/QsCompiler/SyntaxProcessor/SymbolTracker.fs b/src/QsCompiler/SyntaxProcessor/SymbolTracker.fs index ad1333f429..3b3aec8f50 100644 --- a/src/QsCompiler/SyntaxProcessor/SymbolTracker.fs +++ b/src/QsCompiler/SyntaxProcessor/SymbolTracker.fs @@ -6,7 +6,6 @@ namespace Microsoft.Quantum.QsCompiler.SymbolTracker open System open System.Collections.Generic open System.Collections.Immutable -open System.Linq open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.Diagnostics @@ -71,11 +70,11 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// IMPORTANT: these need to be adapted if we want to support type specializations and/or external specializations! let parentIsOperation, typeParameters, expectedReturnType = match GlobalSymbols().TryGetCallable parent (parent.Namespace, sourceFile) with - | Null -> ArgumentException "the given NamespaceManager does not contain a callable with the given parent name" |> raise - | Value decl -> + | Found decl -> let isOperation = decl.Kind |> function | QsCallableKind.Operation -> true | _ -> false let validTypeParams = decl.Signature.TypeParameters |> Seq.choose (function | ValidName name -> Some name | InvalidName -> None) isOperation, validTypeParams.ToImmutableArray(), decl.Signature.ReturnType |> StripPositionInfo.Apply // NOTE: valid only since we do not yet support external and/or type specializations + | _ -> ArgumentException "the given NamespaceManager does not contain a callable with the given parent name" |> raise /// If a local variable with the given name is visible on the current scope, /// returns the dictionary that contains its declaration as Value. @@ -93,7 +92,7 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// source file, namespace, and callable associated with this symbol tracker instance. let globalTypeWithName (ns, name : NonNullable) = ns |> function | None -> GlobalSymbols().TryResolveAndGetType name (parent.Namespace, sourceFile) - | Some nsName -> GlobalSymbols().TryGetType (QsQualifiedName.New (nsName, name)) (parent.Namespace, sourceFile), Seq.empty + | Some nsName -> GlobalSymbols().TryGetType (QsQualifiedName.New (nsName, name)) (parent.Namespace, sourceFile) /// If a callable declaration (including type constructors!) for a callable with the given name exists in GlobalSymbols, /// returns a its header information as Value. Returns Null otherwise. @@ -101,7 +100,7 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// source file, namespace, and callable associated with this symbol tracker instance. let globalCallableWithName (ns, name : NonNullable) = ns |> function | None -> GlobalSymbols().TryResolveAndGetCallable name (parent.Namespace, sourceFile) - | Some nsName -> GlobalSymbols().TryGetCallable (QsQualifiedName.New (nsName, name)) (parent.Namespace, sourceFile), Seq.empty + | Some nsName -> GlobalSymbols().TryGetCallable (QsQualifiedName.New (nsName, name)) (parent.Namespace, sourceFile) /// the namespace and callable declaration within which the symbols tracked by this SymbolTracker instance are used member this.Parent = parent @@ -150,8 +149,8 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// Throws an InvalidOperationException if no scope is currently open. member this.TryAddVariableDeclartion (decl : LocalVariableDeclaration>) = if pushedScopes.Length = 0 then InvalidOperationException "no scope is currently open" |> raise - if (globalTypeWithName (None, decl.VariableName)) |> fst <> Null then false, [| decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.GlobalTypeAlreadyExists, [decl.VariableName.Value]) |] - elif (globalCallableWithName (None, decl.VariableName)) |> fst <> Null then false, [| decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.GlobalCallableAlreadyExists, [decl.VariableName.Value]) |] + if (globalTypeWithName (None, decl.VariableName)) <> NotFound then false, [| decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.GlobalTypeAlreadyExists, [decl.VariableName.Value]) |] + elif (globalCallableWithName (None, decl.VariableName)) <> NotFound then false, [| decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.GlobalCallableAlreadyExists, [decl.VariableName.Value]) |] elif (localVariableWithName decl.VariableName) <> Null then false, [| decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.LocalVariableAlreadyExists, [decl.VariableName.Value]) |] else pushedScopes.Head.LocalVariables.Add(decl.VariableName, decl); true, [||] @@ -210,18 +209,24 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif let idType = kind ((argType, returnType), decl.Information) |> ResolvedType.New LocalVariableDeclaration.New false (defaultLoc, GlobalCallable fullName, idType, false), decl.TypeParameters - let resolveGlobal (sym : NonNullable) input = + let addDiagnosticForSymbol code args = + qsSym.RangeOrDefault |> QsCompilerDiagnostic.Error (code, args) |> addDiagnostic + invalid + + let resolveGlobal (ns : NonNullable option, sym : NonNullable) input = match input with - | Value (decl : CallableDeclarationHeader), _ -> decl.Kind |> function + | Found (decl : CallableDeclarationHeader) -> decl.Kind |> function | QsCallableKind.Operation -> buildCallable QsTypeKind.Operation decl.QualifiedName decl.Signature decl.Attributes | QsCallableKind.TypeConstructor | QsCallableKind.Function -> buildCallable (fst >> QsTypeKind.Function) decl.QualifiedName decl.Signature decl.Attributes - | Null, (possibleResolutions : NonNullable seq) -> - let resolutionStrings = String.Join(", ", possibleResolutions |> Seq.map (fun nsName -> nsName.Value)) - let ambiguousCallableErr = (ErrorCode.AmbiguousCallable, [sym.Value; resolutionStrings]) - let errCode = if possibleResolutions.Count() > 1 then ambiguousCallableErr else (ErrorCode.UnknownIdentifier, [sym.Value]) - qsSym.RangeOrDefault |> QsCompilerDiagnostic.Error errCode |> addDiagnostic; - invalid + | Ambiguous possibilities -> + let possibleNames = String.Join(", ", possibilities |> Seq.map (fun nsName -> nsName.Value)) + addDiagnosticForSymbol ErrorCode.AmbiguousCallable [sym.Value; possibleNames] + | Inaccessible -> + match ns with + | None -> addDiagnosticForSymbol ErrorCode.InaccessibleCallable [sym.Value] + | Some ns -> addDiagnosticForSymbol ErrorCode.InaccessibleCallableInNamespace [sym.Value; ns.Value] + | NotFound -> addDiagnosticForSymbol ErrorCode.UnknownIdentifier [sym.Value] let resolveNative sym = match localVariableWithName sym with @@ -229,14 +234,13 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif let decl = dict.[sym] let properties = (defaultLoc, LocalVariable sym, decl.Type |> StripPositionInfo.Apply, decl.InferredInformation.HasLocalQuantumDependency) properties |> LocalVariableDeclaration.New decl.InferredInformation.IsMutable, ImmutableArray<_>.Empty - | Null -> globalCallableWithName (None, sym) |> resolveGlobal sym + | Null -> globalCallableWithName (None, sym) |> resolveGlobal (None, sym) match qsSym.Symbol with | InvalidSymbol -> invalid | Symbol sym -> resolveNative sym - | QualifiedSymbol (ns, sym) -> globalCallableWithName (Some ns, sym) |> resolveGlobal sym - | _ -> qsSym.RangeOrDefault |> QsCompilerDiagnostic.Error (ErrorCode.ExpectingIdentifier, []) |> addDiagnostic; - invalid + | QualifiedSymbol (ns, sym) -> globalCallableWithName (Some ns, sym) |> resolveGlobal (Some ns, sym) + | _ -> addDiagnosticForSymbol ErrorCode.ExpectingIdentifier [] /// Given a Q# type, resolves it calling the NamespaceManager associated with this symbol tracker. /// For each diagnostic generated during the resolution, calls the given addDiagnostics function on it. @@ -250,9 +254,11 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// Adds a suitable error using the given function and returns None if no declaration can be found. member private this.TryGetTypeDeclaration addError (udt : UserDefinedType) = match globalTypeWithName (Some udt.Namespace, udt.Name) with - | Value decl, _ -> Value decl - | Null, _ -> // may occur when the return type of a referenced callable is defined in an assembly that is not referenced - addError (ErrorCode.IndirectlyReferencedExpressionType, [sprintf "%s.%s" udt.Namespace.Value udt.Name.Value]); Null + | Found decl -> Value decl + | _ -> + // may occur when the return type of a referenced callable is defined in an assembly that is not referenced + addError (ErrorCode.IndirectlyReferencedExpressionType, [sprintf "%s.%s" udt.Namespace.Value udt.Name.Value]) + Null /// Given the fully qualified name of a user defined type, returns its underlying type where all range information is stripped. /// Adds a suitable diagnostic and returns an invalid type if the underlying type could not be determined. diff --git a/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs b/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs index 1ae3433ed8..3158e6fda2 100644 --- a/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs +++ b/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs @@ -152,34 +152,34 @@ let public SymbolInformation fragmentKind = let chooseValues = QsNullable<_>.Choose id >> Seq.toList let addVariable var (syms, ts, exs) = var :: syms, ts, exs fragmentKind |> function - | QsFragmentKind.ExpressionStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.ReturnStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.FailStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.MutableBinding (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.ImmutableBinding (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.ValueUpdate (lhs, rhs) -> [], ([lhs;rhs], []) |> collectWith SymbolsFromExpr - | QsFragmentKind.IfClause ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.ElifClause ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.ElseClause -> [], ([] , [], []) - | QsFragmentKind.ForLoopIntro (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.WhileLoopIntro ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.RepeatIntro -> [], ([] , [], []) - | QsFragmentKind.UntilSuccess (ex,_) -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.WithinBlockIntro -> [], ([] , [], []) - | QsFragmentKind.ApplyBlockIntro -> [], ([] , [], []) - | QsFragmentKind.UsingBlockIntro (sym, init) -> sym |> SymbolDeclarations, init |> VariablesInInitializer - | QsFragmentKind.BorrowingBlockIntro (sym, init) -> sym |> SymbolDeclarations, init |> VariablesInInitializer - | QsFragmentKind.BodyDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) - | QsFragmentKind.AdjointDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) - | QsFragmentKind.ControlledDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) - | QsFragmentKind.ControlledAdjointDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) - | QsFragmentKind.OperationDeclaration (n, signature) -> (n, signature) |> SymbolsInCallableDeclaration - | QsFragmentKind.FunctionDeclaration (n, signature) -> (n, signature) |> SymbolsInCallableDeclaration - | QsFragmentKind.TypeDefinition (sym, t) -> (sym, t) |> SymbolsInArgumentTuple - | QsFragmentKind.DeclarationAttribute (sym, ex) -> [], ([AttributeAsCallExpr (sym, ex)], []) |> collectWith SymbolsFromExpr |> addVariable sym - | QsFragmentKind.NamespaceDeclaration sym -> sym |> SymbolDeclarations, ([], [], []) - | QsFragmentKind.OpenDirective (nsName, alias) -> [alias] |> chooseValues, ([nsName], [], []) - | QsFragmentKind.InvalidFragment _ -> [], ([], [], []) + | QsFragmentKind.ExpressionStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.ReturnStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.FailStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.MutableBinding (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.ImmutableBinding (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.ValueUpdate (lhs, rhs) -> [], ([lhs;rhs], []) |> collectWith SymbolsFromExpr + | QsFragmentKind.IfClause ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.ElifClause ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.ElseClause -> [], ([] , [], []) + | QsFragmentKind.ForLoopIntro (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.WhileLoopIntro ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.RepeatIntro -> [], ([] , [], []) + | QsFragmentKind.UntilSuccess (ex,_) -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.WithinBlockIntro -> [], ([] , [], []) + | QsFragmentKind.ApplyBlockIntro -> [], ([] , [], []) + | QsFragmentKind.UsingBlockIntro (sym, init) -> sym |> SymbolDeclarations, init |> VariablesInInitializer + | QsFragmentKind.BorrowingBlockIntro (sym, init) -> sym |> SymbolDeclarations, init |> VariablesInInitializer + | QsFragmentKind.BodyDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) + | QsFragmentKind.AdjointDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) + | QsFragmentKind.ControlledDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) + | QsFragmentKind.ControlledAdjointDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) + | QsFragmentKind.OperationDeclaration (_, n, signature) -> (n, signature) |> SymbolsInCallableDeclaration + | QsFragmentKind.FunctionDeclaration (_, n, signature) -> (n, signature) |> SymbolsInCallableDeclaration + | QsFragmentKind.TypeDefinition (_, sym, t) -> (sym, t) |> SymbolsInArgumentTuple + | QsFragmentKind.DeclarationAttribute (sym, ex) -> [], ([AttributeAsCallExpr (sym, ex)], []) |> collectWith SymbolsFromExpr |> addVariable sym + | QsFragmentKind.NamespaceDeclaration sym -> sym |> SymbolDeclarations, ([], [], []) + | QsFragmentKind.OpenDirective (nsName, alias) -> [alias] |> chooseValues, ([nsName], [], []) + | QsFragmentKind.InvalidFragment _ -> [], ([], [], []) |> SymbolInformation.New let rec private ExpressionsInInitializer item = item.Initializer |> function @@ -211,13 +211,13 @@ let public CallExpressions fragmentKind = let private tryResolveWith resolve extract (currentNS, source) = function | QsSymbolKind.Symbol sym -> try resolve sym (currentNS, source) |> function - | Value decl, _ -> Some decl, Some sym - | Null, _ -> None, Some sym + | Found decl -> Some decl, Some sym + | _ -> None, Some sym with | :? ArgumentException -> None, Some sym | QsSymbolKind.QualifiedSymbol (ns, sym) -> try extract {Namespace = ns; Name = sym} (currentNS, source) |> function - | Value decl -> Some decl, Some sym - | Null -> None, None + | Found decl -> Some decl, Some sym + | _ -> None, None with | :? ArgumentException -> None, None | _ -> None, None @@ -230,6 +230,10 @@ let private globalCallableResolution (symbolTable : NamespaceManager) (currentNS let private newLine = " \n" // spaces first here so it will work with markdown as well let private withNewLine line = sprintf "%s%s" line newLine +/// Converts the first character of the string to uppercase. +let private toUpperFirst (s : string) = + s.[0..0].ToUpper() + s.[1..] + let private AsDocComment (doc : string seq) = if doc = null then null elif doc.Any() then @@ -259,6 +263,12 @@ let private namespaceDocumentation (docs : ILookup, Immutabl let allDoc = docs.SelectMany(fun entry -> entry.SelectMany(fun d -> d.AsEnumerable())) // the key is the source file PrintSummary allDoc markdown +/// Adds a string describing the modifiers in front of the string describing a kind of declaration. +let private showModifiers kind modifiers = + match modifiers.Access with + | DefaultAccess -> kind + | Internal -> "internal " + kind + type private TName () = inherit SyntaxTreeToQsharp.TypeTransformation() override this.OnCharacteristicsExpression characteristics = @@ -283,12 +293,13 @@ let private CharacteristicsAnnotation (ex, format) = let public TypeInfo (symbolTable : NamespaceManager) (currentNS, source) (qsType : QsType) markdown = let udtInfo udt = match udt |> globalTypeResolution symbolTable (currentNS, source) with - | Some decl, _ -> + | Some decl, _ -> + let kind = showModifiers "user-defined type" decl.Modifiers |> toUpperFirst let name = decl.QualifiedName.Name.Value |> withNewLine - let ns = sprintf "Namespace: %s" decl.QualifiedName.Namespace.Value |> withNewLine + let ns = sprintf "Namespace: %s" decl.QualifiedName.Namespace.Value |> withNewLine let info = sprintf "Underlying type: %s" (TypeName decl.Type) let doc = PrintSummary decl.Documentation markdown - sprintf "User defined type %s%s%s%s" name ns info doc + sprintf "%s %s%s%s%s" kind name ns info doc | None, Some sym -> sprintf "Type %s" sym.Value | _ -> "?" let typeParamName onUnknown (sym : QsSymbol) = @@ -331,10 +342,10 @@ let public TypeInfo (symbolTable : NamespaceManager) (currentNS, source) (qsType | _ -> sprintf "Built-in type %s%s" (typeName qsType.Type) doc |> NonNullable.New -let private printCallableKind capitalize = function - | QsCallableKind.Function -> if capitalize then "Function" else "function" - | QsCallableKind.Operation -> if capitalize then "Operation" else "operation" - | QsCallableKind.TypeConstructor -> if capitalize then "Type constructor" else "type constructor" +let private printCallableKind = function + | QsCallableKind.Function -> "function" + | QsCallableKind.Operation -> "operation" + | QsCallableKind.TypeConstructor -> "type constructor" [] let public PrintArgumentTuple item = @@ -343,8 +354,14 @@ let public PrintArgumentTuple item = [] let public PrintSignature (header : CallableDeclarationHeader) = let callable = - QsCallable.New header.Kind (header.SourceFile, Null) - (header.QualifiedName, header.Attributes, header.ArgumentTuple, header.Signature, ImmutableArray.Empty, ImmutableArray.Empty, QsComments.Empty); + QsCallable.New header.Kind (header.SourceFile, Null) (header.QualifiedName, + header.Attributes, + header.Modifiers, + header.ArgumentTuple, + header.Signature, + ImmutableArray.Empty, + ImmutableArray.Empty, + QsComments.Empty) let signature = SyntaxTreeToQsharp.DeclarationSignature (callable, new Func<_,_>(TypeName)) let annotation = CharacteristicsAnnotation (header.Signature.Information.Characteristics, sprintf "%s%s" newLine) sprintf "%s%s" signature annotation @@ -352,11 +369,12 @@ let public PrintSignature (header : CallableDeclarationHeader) = [] let public VariableInfo (symbolTable : NamespaceManager) (locals : LocalDeclarations) (currentNS, source) (qsSym : QsSymbol) markdown = match qsSym |> globalCallableResolution symbolTable (currentNS, source) with - | Some decl, _ -> - let name = sprintf "%s %s" (printCallableKind true decl.Kind) (PrintSignature decl) |> withNewLine + | Some decl, _ -> + let kind = showModifiers (printCallableKind decl.Kind) decl.Modifiers |> toUpperFirst + let nameAndSignature = PrintSignature decl |> withNewLine let ns = sprintf "Namespace: %s" decl.QualifiedName.Namespace.Value let doc = PrintSummary decl.Documentation markdown - sprintf "%s%s%s" name ns doc + sprintf "%s %s%s%s" kind nameAndSignature ns doc | None, Some sym -> let localVars = locals.AsVariableLookup() if localVars.ContainsKey sym then @@ -383,25 +401,27 @@ let public DeclarationInfo symbolTable (locals : LocalDeclarations) (currentNS, sprintf "Declaration of %s variable %s%s" kind name info | false, _ -> match qsSym |> globalTypeResolution symbolTable (currentNS, source) with // needs to be before querying callables - | Some decl, _ -> + | Some decl, _ -> + let kind = showModifiers "user-defined type" decl.Modifiers let name = decl.QualifiedName.Name.Value |> withNewLine let ns = sprintf "Namespace: %s" decl.QualifiedName.Namespace.Value |> withNewLine let info = sprintf "Underlying type: %s" (decl.Type |> TypeName) let doc = PrintSummary decl.Documentation markdown - sprintf "Declaration of user defined type %s%s%s%s" name ns info doc + sprintf "Declaration of %s %s%s%s%s" kind name ns info doc | None, _ -> match qsSym |> globalCallableResolution symbolTable (currentNS, source) with | Some decl, _ -> - let functorSupport characteristics = - let charEx = SyntaxTreeToQsharp.CharacteristicsExpression characteristics - if String.IsNullOrWhiteSpace charEx then "(None)" else charEx - let name = sprintf "%s %s" (printCallableKind false decl.Kind) decl.QualifiedName.Name.Value |> withNewLine + let kind = showModifiers (printCallableKind decl.Kind) decl.Modifiers + let name = decl.QualifiedName.Name.Value |> withNewLine let ns = sprintf "Namespace: %s" decl.QualifiedName.Namespace.Value |> withNewLine let input = sprintf "Input type: %s" (decl.Signature.ArgumentType |> TypeName) |> withNewLine let output = sprintf "Output type: %s" (decl.Signature.ReturnType |> TypeName) |> withNewLine + let functorSupport characteristics = + let charEx = SyntaxTreeToQsharp.CharacteristicsExpression characteristics + if String.IsNullOrWhiteSpace charEx then "(None)" else charEx let fs = sprintf "Supported functors: %s" (decl.Signature.Information.Characteristics |> functorSupport) let doc = PrintSummary decl.Documentation markdown - sprintf "Declaration of %s%s%s%s%s%s" name ns input output fs doc + sprintf "Declaration of %s %s%s%s%s%s%s" kind name ns input output fs doc | None, _ -> match symbolTable.Documentation().TryGetValue name with | true, docs -> sprintf "Declaration of a partial namespace %s%s" name.Value (namespaceDocumentation (docs, markdown)) @@ -477,5 +497,3 @@ let public SymbolDeclaration (symbolTable : NamespaceManager) (locals : LocalDec | Some decl, _ -> decl.Location |> QsNullable<_>.Map (fun loc -> decl.SourceFile, loc.Offset, loc.Range) | _ -> LocalVariable locals qsSym |> QsNullable<_>.Map (fun (_, pos, range) -> source, pos, range) | _ -> Null - - diff --git a/src/QsCompiler/SyntaxProcessor/TreeVerification.fs b/src/QsCompiler/SyntaxProcessor/TreeVerification.fs index e12df1aaa8..e3660028d8 100644 --- a/src/QsCompiler/SyntaxProcessor/TreeVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/TreeVerification.fs @@ -153,7 +153,8 @@ let CheckDefinedTypesForCycles (definitions : ImmutableArray [it; ot] | QsTypeKind.TupleType vtypeList -> vtypeList |> Seq.toList - | QsTypeKind.UserDefinedType udt -> updateContainedReferences rootIndex (location, QsQualifiedName.New(udt.Namespace, udt.Name)) + | QsTypeKind.UserDefinedType udt -> + updateContainedReferences rootIndex (location, QsQualifiedName.New(udt.Namespace, udt.Name)) | _ -> [] let walk_udts () = // builds up containedTypes and containedIn diff --git a/src/QsCompiler/TestProjects/TestProjects.sln b/src/QsCompiler/TestProjects/TestProjects.sln index 2394ad35c5..a2e0503b96 100644 --- a/src/QsCompiler/TestProjects/TestProjects.sln +++ b/src/QsCompiler/TestProjects/TestProjects.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28016.0 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29905.134 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test1", "test1\test1.csproj", "{836644B8-DEB3-4E4F-A1B9-54388E23D8AF}" EndProject @@ -21,6 +21,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test8", "test8\test8.csproj EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test9", "test9\test9.csproj", "{4C489C80-CEC0-401C-8EB0-951433EB4CB4}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test10", "test10\test10.csproj", "{B96BC26B-B357-445E-93AD-96A3CA60C39C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test11", "test11\test11.csproj", "{BA4E3734-1A61-4790-9C33-D24F13ECB9C6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test12", "test12\test12.csproj", "{96333D50-173E-420A-8084-EF2E10B5324C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test13", "test13\test13.csproj", "{3EDCBFE8-4EAD-4B2A-A899-10511A5721D3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +71,22 @@ Global {4C489C80-CEC0-401C-8EB0-951433EB4CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU {4C489C80-CEC0-401C-8EB0-951433EB4CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU {4C489C80-CEC0-401C-8EB0-951433EB4CB4}.Release|Any CPU.Build.0 = Release|Any CPU + {B96BC26B-B357-445E-93AD-96A3CA60C39C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B96BC26B-B357-445E-93AD-96A3CA60C39C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B96BC26B-B357-445E-93AD-96A3CA60C39C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B96BC26B-B357-445E-93AD-96A3CA60C39C}.Release|Any CPU.Build.0 = Release|Any CPU + {BA4E3734-1A61-4790-9C33-D24F13ECB9C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA4E3734-1A61-4790-9C33-D24F13ECB9C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA4E3734-1A61-4790-9C33-D24F13ECB9C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA4E3734-1A61-4790-9C33-D24F13ECB9C6}.Release|Any CPU.Build.0 = Release|Any CPU + {96333D50-173E-420A-8084-EF2E10B5324C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96333D50-173E-420A-8084-EF2E10B5324C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96333D50-173E-420A-8084-EF2E10B5324C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96333D50-173E-420A-8084-EF2E10B5324C}.Release|Any CPU.Build.0 = Release|Any CPU + {3EDCBFE8-4EAD-4B2A-A899-10511A5721D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3EDCBFE8-4EAD-4B2A-A899-10511A5721D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3EDCBFE8-4EAD-4B2A-A899-10511A5721D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3EDCBFE8-4EAD-4B2A-A899-10511A5721D3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/QsCompiler/TestTargets/Libraries/Library1/AccessModifiers.qs b/src/QsCompiler/TestTargets/Libraries/Library1/AccessModifiers.qs new file mode 100644 index 0000000000..ac831ac137 --- /dev/null +++ b/src/QsCompiler/TestTargets/Libraries/Library1/AccessModifiers.qs @@ -0,0 +1,15 @@ +/// This file contains redefinitions of types and callables declared in Tests.Compiler\TestCases\AccessModifiers.qs. It +/// is used as an assembly reference to test support for re-using names of inaccessible declarations in references. +namespace Microsoft.Quantum.Testing.AccessModifiers { + internal newtype InternalType = Unit; + + internal function InternalFunction () : Unit {} +} + +/// This namespace contains additional definitions of types and callables meant to be used by the +/// Microsoft.Quantum.Testing.AccessModifiers namespace. +namespace Microsoft.Quantum.Testing.AccessModifiers.C { + internal newtype InternalTypeC = Unit; + + internal function InternalFunctionC () : Unit {} +} diff --git a/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs b/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs new file mode 100644 index 0000000000..72e8ddd8c9 --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.QsCompiler.Testing + +open System.Collections.Generic +open System.IO +open Microsoft.Quantum.QsCompiler.DataTypes +open Microsoft.Quantum.QsCompiler.Diagnostics +open Microsoft.Quantum.QsCompiler.SyntaxExtensions +open Microsoft.Quantum.QsCompiler.SyntaxTree +open Xunit + + +type AccessModifierTests (output) = + inherit CompilerTests + (CompilerTests.Compile "TestCases" ["AccessModifiers.qs"] [File.ReadAllLines("ReferenceTargets.txt").[1]], + output) + + member private this.Expect name (diagnostics : IEnumerable) = + let ns = "Microsoft.Quantum.Testing.AccessModifiers" |> NonNullable<_>.New + let name = name |> NonNullable<_>.New + this.Verify (QsQualifiedName.New (ns, name), diagnostics) + + [] + member this.``Callables`` () = + this.Expect "CallableUseOK" [] + this.Expect "CallableReferenceInternalInaccessible" [Error ErrorCode.InaccessibleCallable] + + [] + member this.``Types`` () = + this.Expect "TypeUseOK" [] + this.Expect "TypeReferenceInternalInaccessible" [Error ErrorCode.InaccessibleType] + this.Expect "TypeConstructorReferenceInternalInaccessible" [Error ErrorCode.InaccessibleCallable] + + [] + member this.``Callable signatures`` () = + this.Expect "CallableLeaksInternalTypeIn1" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "CallableLeaksInternalTypeIn2" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "CallableLeaksInternalTypeIn3" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "CallableLeaksInternalTypeOut1" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "CallableLeaksInternalTypeOut2" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "CallableLeaksInternalTypeOut3" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "InternalCallableInternalTypeOK" [] + + [] + member this.``Underlying types`` () = + this.Expect "PublicTypeLeaksInternalType1" [Error ErrorCode.TypeLessAccessibleThanParentType] + this.Expect "PublicTypeLeaksInternalType2" [Error ErrorCode.TypeLessAccessibleThanParentType] + this.Expect "PublicTypeLeaksInternalType3" [Error ErrorCode.TypeLessAccessibleThanParentType] + this.Expect "InternalTypeInternalTypeOK" [] diff --git a/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs b/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs index f3a1806ecc..2ae27951e9 100644 --- a/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs +++ b/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs @@ -13,7 +13,7 @@ open Xunit.Abstractions type FunctorAutoGenTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "FunctorGeneration.qs"], output) + inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "FunctorGeneration.qs"] [], output) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.FunctorGeneration" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.Compiler/ClassicalControlTests.fs b/src/QsCompiler/Tests.Compiler/ClassicalControlTests.fs index d5f6ec5a25..454ff162a2 100644 --- a/src/QsCompiler/Tests.Compiler/ClassicalControlTests.fs +++ b/src/QsCompiler/Tests.Compiler/ClassicalControlTests.fs @@ -23,12 +23,8 @@ type ClassicalControlTests () = 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 + let sourceInput = Path.Combine ("TestCases", fileName) |> File.ReadAllText sourceInput.Split ([|"==="|], StringSplitOptions.RemoveEmptyEntries) let BuildContent content = @@ -69,7 +65,7 @@ type ClassicalControlTests () = |> writer.Statements.OnScope |> ignore - writer.SharedState.StatementOutputHandle + writer.SharedState.StatementOutputHandle |> Seq.filter (not << String.IsNullOrWhiteSpace) |> Seq.toArray @@ -121,7 +117,7 @@ type ClassicalControlTests () = 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 ExpandBuiltInQualifiedSymbol (i, (builtin : BuiltIn)) = (i, builtin.FullName.Namespace.Value, builtin.FullName.Name.Value) let IdentifyGeneratedByCalls generatedCallables calls = let mutable callables = generatedCallables |> Seq.map (fun x -> x, x |> (GetBodyFromCallable >> GetLinesFromSpecialization)) @@ -163,13 +159,13 @@ type ClassicalControlTests () = |> (fun x -> x.Value) let ApplyIfElseTest compilation = - + 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, targs, args) = CheckIfLineIsCall BuiltIn.ApplyIfElseR.Namespace.Value BuiltIn.ApplyIfElseR.Name.Value lines.[1] + let (success, targs, args) = CheckIfLineIsCall BuiltIn.ApplyIfElseR.FullName.Namespace.Value BuiltIn.ApplyIfElseR.FullName.Name.Value lines.[1] Assert.True(success, sprintf "Callable %O(%A) did not have expected content" original.Parent original.Kind) targs, args @@ -212,8 +208,8 @@ type ClassicalControlTests () = Assert.True(DoesCallSupportFunctors expectedFunctors call, sprintf "Callable %O did not support the expected functors" call.FullName) [] - [] - member this.``Basic Hoist`` () = + [] + member this.``Basic Lift`` () = let result = CompileClassicalControlTest 1 let generated = GetCallablesWithSuffix result Signatures.ClassicalControlNs "_Foo" @@ -227,30 +223,30 @@ type ClassicalControlTests () = |> AssertSpecializationHasCalls generated [] - [] - member this.``Hoist Loops`` () = + [] + member this.``Lift Loops`` () = CompileClassicalControlTest 2 |> ignore [] - [] - member this.``Don't Hoist Single Call`` () = - // Single calls should not be hoisted into their own operation + [] + member this.``Don't Lift Single Call`` () = + // Single calls should not be lifted 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 + [] + member this.``Lift Single Non-Call`` () = + // Single expressions that are not calls should be lifted into their own operation CompileClassicalControlTest 4 |> ignore [] - [] - member this.``Don't Hoist Return Statements`` () = + [] + member this.``Don't Lift Return Statements`` () = CompileClassicalControlTest 5 |> ignore [] - [] - member this.``All-Or-None Hoisting`` () = + [] + member this.``All-Or-None Lifting`` () = CompileClassicalControlTest 6 |> ignore [] @@ -271,10 +267,10 @@ type ClassicalControlTests () = [] member this.``Apply If Zero Else One`` () = let (targs, args) = CompileClassicalControlTest 8 |> ApplyIfElseTest - + let Bar = {Namespace = NonNullable<_>.New Signatures.ClassicalControlNs; Name = NonNullable<_>.New "Bar"} let SubOp1 = {Namespace = NonNullable<_>.New "SubOps"; Name = NonNullable<_>.New "SubOp1"} - + IsApplyIfElseArgsMatch args "r" Bar SubOp1 |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyIfElse did not have the correct arguments")) @@ -304,7 +300,7 @@ type ClassicalControlTests () = let elseOp = {Namespace = NonNullable<_>.New "SubOps"; Name = NonNullable<_>.New "SubOp3"} let errorMsg = "ApplyIfElse did not have the correct arguments" - let (success, _, _, _, subArgs) = IsApplyIfElseArgsMatch args "r" ifOp { Namespace = BuiltIn.ApplyIfElseR.Namespace; Name = BuiltIn.ApplyIfElseR.Name } + let (success, _, _, _, subArgs) = IsApplyIfElseArgsMatch args "r" ifOp BuiltIn.ApplyIfElseR.FullName Assert.True(success, errorMsg) IsApplyIfElseArgsMatch subArgs "r" elseOp elifOp // elif and else are swapped because second condition is against One |> (fun (x, _, _, _, _) -> Assert.True(x, errorMsg)) @@ -318,7 +314,7 @@ type ClassicalControlTests () = let elseOp = {Namespace = NonNullable<_>.New "SubOps"; Name = NonNullable<_>.New "SubOp2"} let errorMsg = "ApplyIfElse did not have the correct arguments" - let (success, _, subArgs, _, _) = IsApplyIfElseArgsMatch args "r" { Namespace = BuiltIn.ApplyIfElseRCA.Namespace; Name = BuiltIn.ApplyIfElseR.Name } elseOp + let (success, _, subArgs, _, _) = IsApplyIfElseArgsMatch args "r" BuiltIn.ApplyIfElseR.FullName 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)) @@ -332,24 +328,24 @@ type ClassicalControlTests () = let elseOp = {Namespace = NonNullable<_>.New "SubOps"; Name = NonNullable<_>.New "SubOp2"} let errorMsg = "ApplyIfElse did not have the correct arguments" - let (success, _, _, _, subArgs) = IsApplyIfElseArgsMatch args "r" ifOp { Namespace = BuiltIn.ApplyIfElseR.Namespace; Name = BuiltIn.ApplyIfElseR.Name } + let (success, _, _, _, subArgs) = IsApplyIfElseArgsMatch args "r" ifOp BuiltIn.ApplyIfElseR.FullName 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`` () = + [] + member this.``Don't Lift Functions`` () = CompileClassicalControlTest 13 |> ignore [] - [] - member this.``Hoist Self-Contained Mutable`` () = + [] + member this.``Lift Self-Contained Mutable`` () = CompileClassicalControlTest 14 |> ignore [] - [] - member this.``Don't Hoist General Mutable`` () = + [] + member this.``Don't Lift General Mutable`` () = CompileClassicalControlTest 15 |> ignore [] @@ -385,7 +381,7 @@ type ClassicalControlTests () = // 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] + let (success, _, args) = CheckIfLineIsCall BuiltIn.ApplyIfZero.FullName.Namespace.Value BuiltIn.ApplyIfZero.FullName.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 @@ -1179,7 +1175,7 @@ type ClassicalControlTests () = AssertCallSupportsFunctors [QsFunctor.Adjoint] outerOp let lines = GetLinesFromSpecialization original - let (success, _, args) = CheckIfLineIsCall BuiltIn.ApplyIfZeroA.Namespace.Value BuiltIn.ApplyIfZeroA.Name.Value lines.[2] + let (success, _, args) = CheckIfLineIsCall BuiltIn.ApplyIfZeroA.FullName.Namespace.Value BuiltIn.ApplyIfZeroA.FullName.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 @@ -1193,7 +1189,7 @@ type ClassicalControlTests () = 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] + let (success, _, args) = CheckIfLineIsCall BuiltIn.ApplyIfZero.FullName.Namespace.Value BuiltIn.ApplyIfZero.FullName.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"} @@ -1202,27 +1198,27 @@ type ClassicalControlTests () = Assert.True((typeArgs = "Int, Double"), "Bar did not have the correct type arguments") [] - [] - member this.``Hoist Functor Application`` () = + [] + member this.``Lift Functor Application`` () = CompileClassicalControlTest 25 |> ignore [] - [] - member this.``Hoist Partial Application`` () = + [] + member this.``Lift Partial Application`` () = CompileClassicalControlTest 26 |> ignore [] - [] - member this.``Hoist Array Item Call`` () = + [] + member this.``Lift Array Item Call`` () = CompileClassicalControlTest 27 |> ignore [] - [] - member this.``Hoist One Not Both`` () = - // If hoisting is not needed on one of the blocks, it should not - // prevent the other blocks from being hoisted, as it would in + [] + member this.``Lift One Not Both`` () = + // If lifting is not needed on one of the blocks, it should not + // prevent the other blocks from being lifted, as it would in // the All-Or-Nothing test where a block is *invalid* for - // hoisting due to a set statement or return statement. + // lifting due to a set statement or return statement. CompileClassicalControlTest 28 |> ignore [] @@ -1235,12 +1231,12 @@ type ClassicalControlTests () = Assert.True(3 = Seq.length lines, sprintf "Callable %O(%A) did not have the expected number of statements" original.Parent original.Kind) - let (success, targs, args) = CheckIfLineIsCall BuiltIn.ApplyConditionally.Namespace.Value BuiltIn.ApplyConditionally.Name.Value lines.[2] + let (success, targs, args) = CheckIfLineIsCall BuiltIn.ApplyConditionally.FullName.Namespace.Value BuiltIn.ApplyConditionally.FullName.Name.Value lines.[2] Assert.True(success, sprintf "Callable %O(%A) did not have expected content" original.Parent original.Kind) let Bar = {Namespace = NonNullable<_>.New Signatures.ClassicalControlNs; Name = NonNullable<_>.New "Bar"} let SubOp1 = {Namespace = NonNullable<_>.New "SubOps"; Name = NonNullable<_>.New "SubOp1"} - + IsApplyIfElseArgsMatch args "[r1], [r2]" Bar SubOp1 |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyConditionally did not have the correct arguments")) @@ -1256,12 +1252,12 @@ type ClassicalControlTests () = Assert.True(3 = Seq.length lines, sprintf "Callable %O(%A) did not have the expected number of statements" original.Parent original.Kind) - let (success, targs, args) = CheckIfLineIsCall BuiltIn.ApplyConditionally.Namespace.Value BuiltIn.ApplyConditionally.Name.Value lines.[2] + let (success, targs, args) = CheckIfLineIsCall BuiltIn.ApplyConditionally.FullName.Namespace.Value BuiltIn.ApplyConditionally.FullName.Name.Value lines.[2] Assert.True(success, sprintf "Callable %O(%A) did not have expected content" original.Parent original.Kind) let Bar = {Namespace = NonNullable<_>.New Signatures.ClassicalControlNs; Name = NonNullable<_>.New "Bar"} let NoOp = {Namespace = NonNullable<_>.New "Microsoft.Quantum.Canon"; Name = NonNullable<_>.New "NoOp"} - + IsApplyIfElseArgsMatch args "[r1], [r2]" Bar NoOp |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyConditionally did not have the correct arguments")) @@ -1271,18 +1267,18 @@ type ClassicalControlTests () = [] member this.``Inequality with ApplyConditionally`` () = let result = CompileClassicalControlTest 31 - + let original = GetCallableWithName result Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable let lines = original |> GetLinesFromSpecialization Assert.True(3 = Seq.length lines, sprintf "Callable %O(%A) did not have the expected number of statements" original.Parent original.Kind) - let (success, targs, args) = CheckIfLineIsCall BuiltIn.ApplyConditionally.Namespace.Value BuiltIn.ApplyConditionally.Name.Value lines.[2] + let (success, targs, args) = CheckIfLineIsCall BuiltIn.ApplyConditionally.FullName.Namespace.Value BuiltIn.ApplyConditionally.FullName.Name.Value lines.[2] Assert.True(success, sprintf "Callable %O(%A) did not have expected content" original.Parent original.Kind) let Bar = {Namespace = NonNullable<_>.New Signatures.ClassicalControlNs; Name = NonNullable<_>.New "Bar"} let SubOp1 = {Namespace = NonNullable<_>.New "SubOps"; Name = NonNullable<_>.New "SubOp1"} - + IsApplyIfElseArgsMatch args "[r1], [r2]" SubOp1 Bar |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyConditionally did not have the correct arguments")) @@ -1292,23 +1288,23 @@ type ClassicalControlTests () = [] member this.``Inequality with Apply If One Else Zero`` () = let (targs, args) = CompileClassicalControlTest 32 |> ApplyIfElseTest - + let Bar = {Namespace = NonNullable<_>.New Signatures.ClassicalControlNs; Name = NonNullable<_>.New "Bar"} let SubOp1 = {Namespace = NonNullable<_>.New "SubOps"; Name = NonNullable<_>.New "SubOp1"} - + IsApplyIfElseArgsMatch args "r" SubOp1 Bar |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyIfElse did not have the correct arguments")) - + Assert.True(IsTypeArgsMatch targs "Unit, Result", "ApplyIfElse did not have the correct type arguments") [] [] member this.``Inequality with Apply If Zero Else One`` () = let (targs, args) = CompileClassicalControlTest 33 |> ApplyIfElseTest - + let Bar = {Namespace = NonNullable<_>.New Signatures.ClassicalControlNs; Name = NonNullable<_>.New "Bar"} let SubOp1 = {Namespace = NonNullable<_>.New "SubOps"; Name = NonNullable<_>.New "SubOp1"} - + IsApplyIfElseArgsMatch args "r" Bar SubOp1 |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyIfElse did not have the correct arguments")) @@ -1318,7 +1314,7 @@ type ClassicalControlTests () = [] member this.``Inequality with ApplyIfOne`` () = let result = CompileClassicalControlTest 34 - + let originalOp = GetCallableWithName result Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable [ (1, BuiltIn.ApplyIfOne) ] @@ -1329,7 +1325,7 @@ type ClassicalControlTests () = [] member this.``Inequality with ApplyIfZero`` () = let result = CompileClassicalControlTest 35 - + let originalOp = GetCallableWithName result Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable [ (1, BuiltIn.ApplyIfZero) ] diff --git a/src/QsCompiler/Tests.Compiler/CommandLineTests.fs b/src/QsCompiler/Tests.Compiler/CommandLineTests.fs index a880acafb0..4a13ca8592 100644 --- a/src/QsCompiler/Tests.Compiler/CommandLineTests.fs +++ b/src/QsCompiler/Tests.Compiler/CommandLineTests.fs @@ -155,6 +155,31 @@ let ``diagnose outputs`` () = Assert.Equal(ReturnCode.INVALID_ARGUMENTS, result) +[] +let ``options from response files`` () = + let configFile = ("TestCases", "qsc-config.txt") |> Path.Combine + let configArgs = + [| + "-i" + ("TestCases","LinkingTests","Core.qs") |> Path.Combine + ("TestCases","LinkingTests","Diagnostics.qs") |> Path.Combine + |] + File.WriteAllText(configFile, String.Join (" ", configArgs)) + let commandLineArgs = + [| + "build" + "-v" + "Detailed" + "--format" + "MsBuild" + "--response-files" + configFile + |] + + let result = Program.Main commandLineArgs + Assert.Equal(ReturnCode.SUCCESS, result) + + [] let ``generate docs`` () = let docsFolder = ("TestCases", "docs.Out") |> Path.Combine diff --git a/src/QsCompiler/Tests.Compiler/CompletionParsingTests.fs b/src/QsCompiler/Tests.Compiler/CompletionParsingTests.fs index beba2f499a..d8c181fc6e 100644 --- a/src/QsCompiler/Tests.Compiler/CompletionParsingTests.fs +++ b/src/QsCompiler/Tests.Compiler/CompletionParsingTests.fs @@ -94,7 +94,7 @@ let private operationTopLevelStatement = Keyword "controlled" ] -let testElifElse scope previous = +let private testElifElse scope previous = let statement = match scope with | Operation | OperationTopLevel -> operationStatement @@ -109,6 +109,12 @@ let testElifElse scope previous = ("else ", []) ] +let private testWithModifiers tests = + List.concat [ + tests + List.map (fun (input, result) -> ("internal " + input, result)) tests + ] |> List.iter (matches NamespaceTopLevel Null) + [] let ``Top-level parser tests`` () = List.iter (matches TopLevel Null) [ @@ -128,6 +134,7 @@ let ``Namespace top-level parser tests`` () = Keyword "function" Keyword "operation" Keyword "newtype" + Keyword "internal" Keyword "open" ] List.iter (matches NamespaceTopLevel Null) [ @@ -143,11 +150,15 @@ let ``Namespace top-level parser tests`` () = ("newt", keywords) ("newtype", keywords) ("open", keywords) + ("pri", keywords) + ("int", keywords) + ("internal", keywords) + ("internal ", [Keyword "function"; Keyword "operation"; Keyword "newtype"]) ] [] let ``Function declaration parser tests`` () = - List.iter (matches NamespaceTopLevel Null) [ + testWithModifiers [ ("function ", [Declaration]) ("function Foo", [Declaration]) ("function Foo ", []) @@ -200,7 +211,7 @@ let ``Operation declaration parser tests`` () = Keyword "Adj" Keyword "Ctl" ] - List.iter (matches NamespaceTopLevel Null) [ + testWithModifiers [ ("operation ", [Declaration]) ("operation Foo", [Declaration]) ("operation Foo ", []) @@ -281,7 +292,7 @@ let ``Operation declaration parser tests`` () = [] let ``Type declaration parser tests`` () = - List.iter (matches NamespaceTopLevel Null) [ + testWithModifiers [ ("newtype ", [Declaration]) ("newtype MyType", [Declaration]) ("newtype MyType ", []) diff --git a/src/QsCompiler/Tests.Compiler/ExecutionTests.fs b/src/QsCompiler/Tests.Compiler/ExecutionTests.fs index a4a4db38cd..3e222a6ffc 100644 --- a/src/QsCompiler/Tests.Compiler/ExecutionTests.fs +++ b/src/QsCompiler/Tests.Compiler/ExecutionTests.fs @@ -25,8 +25,8 @@ type ExecutionTests (output:ITestOutputHelper) = let ExecuteOnQuantumSimulator cName = let exitCode, ex = ref -101, ref null let out, err = ref (new StringBuilder()), ref (new StringBuilder()) - let exe = File.ReadAllLines("ExecutionTarget.txt").Single() - let args = sprintf "%s %s.%s" exe "Microsoft.Quantum.Testing.ExecutionTests" cName + let exe = File.ReadAllLines("ReferenceTargets.txt").First() + let args = sprintf "\"%s\" %s.%s" exe "Microsoft.Quantum.Testing.ExecutionTests" cName let ranToEnd = ProcessRunner.Run ("dotnet", args, out, err, exitCode, ex, timeout = 10000) Assert.False(String.IsNullOrWhiteSpace exe) Assert.True(ranToEnd) diff --git a/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs b/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs index 73b1c26f7a..384b25b378 100644 --- a/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs @@ -13,7 +13,7 @@ open Xunit.Abstractions type GlobalVerificationTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs";"GlobalVerification.qs";"Types.qs";System.IO.Path.Join("LinkingTests", "Core.qs")], output) + inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "GlobalVerification.qs"; "Types.qs"; System.IO.Path.Join("LinkingTests", "Core.qs")] [], output) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.GlobalVerification" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.Compiler/LinkingTests.fs b/src/QsCompiler/Tests.Compiler/LinkingTests.fs index e97dd4f3ce..56235e36ab 100644 --- a/src/QsCompiler/Tests.Compiler/LinkingTests.fs +++ b/src/QsCompiler/Tests.Compiler/LinkingTests.fs @@ -5,6 +5,7 @@ namespace Microsoft.Quantum.QsCompiler.Testing open System open System.Collections.Generic +open System.Collections.Immutable open System.IO open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.CompilationBuilder @@ -15,19 +16,54 @@ open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.QsCompiler.Transformations.IntrinsicResolution open Microsoft.Quantum.QsCompiler.Transformations.Monomorphization open Microsoft.Quantum.QsCompiler.Transformations.Monomorphization.Validation +open Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace +open Microsoft.VisualStudio.LanguageServer.Protocol open Xunit open Xunit.Abstractions type LinkingTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile (Path.Combine ("TestCases", "LinkingTests" )) ["Core.qs"; "InvalidEntryPoints.qs"], output) + inherit CompilerTests(CompilerTests.Compile (Path.Combine ("TestCases", "LinkingTests" )) ["Core.qs"; "InvalidEntryPoints.qs"] [], output) let compilationManager = new CompilationUnitManager(new Action (fun ex -> failwith ex.Message)) - let getTempFile () = new Uri(Path.GetFullPath(Path.GetRandomFileName())) + let getTempFile () = + // The file name needs to end in ".qs" so that it isn't ignored by the References.Headers class during the + // internal renaming tests. + Path.GetRandomFileName () + ".qs" |> Path.GetFullPath |> Uri + 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 + let defaultOffset = + { + Offset = DiagnosticTools.AsTuple (Position (0, 0)) + Range = QsCompilerDiagnostic.DefaultRange + } + + let qualifiedName ns name = + { + Namespace = NonNullable<_>.New ns + Name = NonNullable<_>.New name + } + + let createReferences : seq> -> References = + Seq.map (fun (source, namespaces) -> + KeyValuePair.Create(NonNullable<_>.New source, References.Headers (NonNullable<_>.New source, namespaces))) + >> ImmutableDictionary.CreateRange + >> References + + /// Counts the number of references to the qualified name in all of the namespaces, including the declaration. + let countReferences namespaces (name : QsQualifiedName) = + let references = IdentifierReferences (name, defaultOffset) + Seq.iter (references.Namespaces.OnNamespace >> ignore) namespaces + let declaration = if obj.ReferenceEquals (references.SharedState.DeclarationLocation, null) then 0 else 1 + references.SharedState.Locations.Count + declaration + + do + let addOrUpdateSourceFile filePath = + getManager (new Uri(filePath)) (File.ReadAllText filePath) + |> compilationManager.AddOrUpdateSourceFileAsync + |> ignore Path.Combine ("TestCases", "LinkingTests", "Core.qs") |> Path.GetFullPath |> addOrUpdateSourceFile static member private ReadAndChunkSourceFile fileName = @@ -53,19 +89,23 @@ type LinkingTests (output:ITestOutputHelper) = for callable in built.Callables.Values |> Seq.filter inFile do tests.Verify (callable.FullName, diag) - member private this.BuildContent content = + member private this.BuildContent (source, ?references) = + let fileId = getTempFile () + let file = getManager fileId source - let fileId = getTempFile() - let file = getManager fileId content + match references with + | Some references -> compilationManager.UpdateReferencesAsync references |> ignore + | None -> () + compilationManager.AddOrUpdateSourceFileAsync file |> ignore - compilationManager.AddOrUpdateSourceFileAsync(file) |> ignore - let compilationDataStructures = compilationManager.Build() - compilationManager.TryRemoveSourceFileAsync(fileId, false) |> ignore + let compilation = compilationManager.Build () + compilationManager.TryRemoveSourceFileAsync (fileId, false) |> ignore + compilationManager.UpdateReferencesAsync (References ImmutableDictionary<_, _>.Empty) |> ignore - compilationDataStructures.Diagnostics() |> Seq.exists (fun d -> d.IsError()) |> Assert.False - Assert.NotNull compilationDataStructures.BuiltCompilation + compilation.Diagnostics () |> Seq.exists (fun d -> d.IsError ()) |> Assert.False + Assert.NotNull compilation.BuiltCompilation - compilationDataStructures + compilation member private this.CompileMonomorphization input = @@ -111,6 +151,31 @@ type LinkingTests (output:ITestOutputHelper) = |> Assert.True) |> ignore + /// Runs the nth internal renaming test, asserting that declarations with the given name and references to them have + /// been renamed across the compilation unit. + member private this.RunInternalRenamingTest num renamed notRenamed = + let chunks = LinkingTests.ReadAndChunkSourceFile "InternalRenaming.qs" + let sourceCompilation = this.BuildContent chunks.[num - 1] + + let namespaces = + sourceCompilation.BuiltCompilation.Namespaces + |> Seq.filter (fun ns -> ns.Name.Value.StartsWith Signatures.InternalRenamingNs) + let references = createReferences ["InternalRenaming.dll", namespaces] + let referenceCompilation = this.BuildContent ("", references) + + let countAll namespaces names = + names |> Seq.map (countReferences namespaces) |> Seq.sum + + let beforeCount = countAll sourceCompilation.BuiltCompilation.Namespaces (Seq.concat [renamed; notRenamed]) + let afterCountOriginal = countAll referenceCompilation.BuiltCompilation.Namespaces renamed + + let newNames = renamed |> Seq.map (fun name -> CompilationUnit.ReferenceDecorator.Decorate (name, 0)) + let afterCount = countAll referenceCompilation.BuiltCompilation.Namespaces (Seq.concat [newNames; notRenamed]) + + Assert.NotEqual (0, beforeCount) + Assert.Equal (0, afterCountOriginal) + Assert.Equal (beforeCount, afterCount) + [] member this.``Monomorphization`` () = @@ -245,3 +310,76 @@ type LinkingTests (output:ITestOutputHelper) = this.Expect "InvalidEntryPoint35" [Error ErrorCode.CallableTypeInEntryPointSignature; Error ErrorCode.UserDefinedTypeInEntryPointSignature] this.Expect "InvalidEntryPoint36" [Error ErrorCode.CallableTypeInEntryPointSignature; Error ErrorCode.UserDefinedTypeInEntryPointSignature] + [] + member this.``Rename internal operation call references`` () = + this.RunInternalRenamingTest 1 + [qualifiedName Signatures.InternalRenamingNs "Foo"] + [qualifiedName Signatures.InternalRenamingNs "Bar"] + + [] + member this.``Rename internal function call references`` () = + this.RunInternalRenamingTest 2 + [qualifiedName Signatures.InternalRenamingNs "Foo"] + [qualifiedName Signatures.InternalRenamingNs "Bar"] + + [] + member this.``Rename internal type references`` () = + this.RunInternalRenamingTest 3 + [ + qualifiedName Signatures.InternalRenamingNs "Foo" + qualifiedName Signatures.InternalRenamingNs "Bar" + qualifiedName Signatures.InternalRenamingNs "Baz" + ] + [] + + [] + member this.``Rename internal references across namespaces`` () = + this.RunInternalRenamingTest 4 + [ + qualifiedName Signatures.InternalRenamingNs "Foo" + qualifiedName Signatures.InternalRenamingNs "Bar" + qualifiedName (Signatures.InternalRenamingNs + ".Extra") "Qux" + ] + [qualifiedName (Signatures.InternalRenamingNs + ".Extra") "Baz"] + + [] + member this.``Rename internal qualified references`` () = + this.RunInternalRenamingTest 5 + [ + qualifiedName Signatures.InternalRenamingNs "Foo" + qualifiedName Signatures.InternalRenamingNs "Bar" + qualifiedName (Signatures.InternalRenamingNs + ".Extra") "Qux" + ] + [qualifiedName (Signatures.InternalRenamingNs + ".Extra") "Baz"] + + [] + member this.``Rename internal attribute references`` () = + this.RunInternalRenamingTest 6 + [qualifiedName Signatures.InternalRenamingNs "Foo"] + [qualifiedName Signatures.InternalRenamingNs "Bar"] + + [] + member this.``Rename specializations for internal operations`` () = + this.RunInternalRenamingTest 7 + [qualifiedName Signatures.InternalRenamingNs "Foo"] + [qualifiedName Signatures.InternalRenamingNs "Bar"] + + [] + member this.``Group internal specializations by source file`` () = + let chunks = LinkingTests.ReadAndChunkSourceFile "InternalRenaming.qs" + let sourceCompilation = this.BuildContent chunks.[7] + let namespaces = + sourceCompilation.BuiltCompilation.Namespaces + |> Seq.filter (fun ns -> ns.Name.Value.StartsWith Signatures.InternalRenamingNs) + + let references = createReferences ["InternalRenaming1.dll", namespaces + "InternalRenaming2.dll", namespaces] + let referenceCompilation = this.BuildContent ("", references) + let callables = GlobalCallableResolutions referenceCompilation.BuiltCompilation.Namespaces + + for i in 0 .. references.Declarations.Count - 1 do + let name = + CompilationUnit.ReferenceDecorator.Decorate (qualifiedName Signatures.InternalRenamingNs "Foo", i) + let specializations = callables.[name].Specializations + Assert.Equal (4, specializations.Length) + Assert.True (specializations |> Seq.forall (fun s -> s.SourceFile = callables.[name].SourceFile)) diff --git a/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs b/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs index 2570fd7295..1d68db9683 100644 --- a/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs @@ -14,7 +14,11 @@ open Xunit.Abstractions type LocalVerificationTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "LocalVerification.qs"; "Types.qs"; Path.Combine ("LinkingTests", "Core.qs")], output) + inherit CompilerTests( + CompilerTests.Compile "TestCases" [ + "General.qs"; "LocalVerification.qs"; "Types.qs"; + Path.Combine ("LinkingTests", "Core.qs"); Path.Combine ("LinkingTests", "Diagnostics.qs") + ] [], output) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.LocalVerification" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.Compiler/SerializationTests.fs b/src/QsCompiler/Tests.Compiler/SerializationTests.fs index 354d580967..c48e8f233d 100644 --- a/src/QsCompiler/Tests.Compiler/SerializationTests.fs +++ b/src/QsCompiler/Tests.Compiler/SerializationTests.fs @@ -148,6 +148,7 @@ module SerializationTests = Kind = QsCallableKind.TypeConstructor QualifiedName = qualifiedName "Microsoft.Quantum" "Pair" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (2,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 9}, {Line = 1; Column = 13}) |> DeclarationHeader.Range.Defined @@ -161,6 +162,7 @@ module SerializationTests = Kind = QsCallableKind.Function QualifiedName = qualifiedName "Microsoft.Quantum" "emptyFunction" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (4,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 10}, {Line = 1; Column = 23}) |> DeclarationHeader.Range.Defined @@ -174,6 +176,7 @@ module SerializationTests = Kind = QsCallableKind.Operation QualifiedName = qualifiedName "Microsoft.Quantum" "emptyOperation" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (5,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 11}, {Line = 1; Column = 25}) |> DeclarationHeader.Range.Defined @@ -187,6 +190,7 @@ module SerializationTests = Kind = QsCallableKind.TypeConstructor QualifiedName = qualifiedName "Microsoft.Quantum" "Unused" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (3,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 9}, {Line = 1; Column = 15}) |> DeclarationHeader.Range.Defined @@ -207,6 +211,7 @@ module SerializationTests = { QualifiedName = qualifiedName "Microsoft.Quantum" "Pair" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (2,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 9}, {Line = 1; Column = 13}) |> DeclarationHeader.Range.Defined @@ -219,6 +224,7 @@ module SerializationTests = { QualifiedName = qualifiedName "Microsoft.Quantum" "Unused" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (3,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 9}, {Line = 1; Column = 15}) |> DeclarationHeader.Range.Defined @@ -229,15 +235,15 @@ module SerializationTests = |> testOne [] - let CALLABLE_1 = "{\"Kind\":{\"Case\":\"TypeConstructor\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":2,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":9},\"Item2\":{\"Line\":1,\"Column\":13}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"__Item1__\"]},\"Type\":{\"Case\":\"Int\"},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":1}}}]},{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"__Item2__\"]},\"Type\":{\"Case\":\"Int\"},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":1}}}]}]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"TupleType\",\"Fields\":[[{\"Case\":\"Int\"},{\"Case\":\"Int\"}]]},\"ReturnType\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":true}}},\"Documentation\":[\"type constructor for user defined type\"]}" + let CALLABLE_1 = "{\"Kind\":{\"Case\":\"TypeConstructor\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\"},\"Attributes\":[],\"Modifiers\":{\"Access\":{\"Case\":\"DefaultAccess\"}},\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":2,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":9},\"Item2\":{\"Line\":1,\"Column\":13}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"__Item1__\"]},\"Type\":{\"Case\":\"Int\"},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":1}}}]},{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"__Item2__\"]},\"Type\":{\"Case\":\"Int\"},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":1}}}]}]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"TupleType\",\"Fields\":[[{\"Case\":\"Int\"},{\"Case\":\"Int\"}]]},\"ReturnType\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":true}}},\"Documentation\":[\"type constructor for user defined type\"]}" [] - let TYPE_1 = "{\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":2,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":9},\"Item2\":{\"Line\":1,\"Column\":13}},\"Type\":{\"Case\":\"TupleType\",\"Fields\":[[{\"Case\":\"Int\"},{\"Case\":\"Int\"}]]},\"TypeItems\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"Case\":\"Anonymous\",\"Fields\":[{\"Case\":\"Int\"}]}]},{\"Case\":\"QsTupleItem\",\"Fields\":[{\"Case\":\"Anonymous\",\"Fields\":[{\"Case\":\"Int\"}]}]}]]},\"Documentation\":[]}" + let TYPE_1 = "{\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\"},\"Attributes\":[],\"Modifiers\":{\"Access\":{\"Case\":\"DefaultAccess\"}},\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":2,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":9},\"Item2\":{\"Line\":1,\"Column\":13}},\"Type\":{\"Case\":\"TupleType\",\"Fields\":[[{\"Case\":\"Int\"},{\"Case\":\"Int\"}]]},\"TypeItems\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"Case\":\"Anonymous\",\"Fields\":[{\"Case\":\"Int\"}]}]},{\"Case\":\"QsTupleItem\",\"Fields\":[{\"Case\":\"Anonymous\",\"Fields\":[{\"Case\":\"Int\"}]}]}]]},\"Documentation\":[]}" [] - let CALLABLE_2 = "{\"Kind\":{\"Case\":\"Function\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyFunction\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":4,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":10},\"Item2\":{\"Line\":1,\"Column\":23}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"p\"]},\"Type\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":25},\"Item2\":{\"Line\":1,\"Column\":26}}}]}]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"ReturnType\":{\"Case\":\"UnitType\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}}},\"Documentation\":[]}" + let CALLABLE_2 = "{\"Kind\":{\"Case\":\"Function\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyFunction\"},\"Attributes\":[],\"Modifiers\":{\"Access\":{\"Case\":\"DefaultAccess\"}},\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":4,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":10},\"Item2\":{\"Line\":1,\"Column\":23}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"p\"]},\"Type\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":25},\"Item2\":{\"Line\":1,\"Column\":26}}}]}]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"ReturnType\":{\"Case\":\"UnitType\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}}},\"Documentation\":[]}" [] let SPECIALIZATION_1 = "{\"Kind\":{\"Case\":\"QsBody\"},\"TypeArguments\":{\"Case\":\"Null\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}},\"Parent\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyFunction\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":4,\"Item2\":43},\"HeaderRange\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":5}},\"Documentation\":[]}" [] - let CALLABLE_3 = "{\"Kind\":{\"Case\":\"Operation\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyOperation\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":5,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":11},\"Item2\":{\"Line\":1,\"Column\":25}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"UnitType\"},\"ReturnType\":{\"Case\":\"UnitType\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}}},\"Documentation\":[]}" + let CALLABLE_3 = "{\"Kind\":{\"Case\":\"Operation\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyOperation\"},\"Attributes\":[],\"Modifiers\":{\"Access\":{\"Case\":\"DefaultAccess\"}},\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":5,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":11},\"Item2\":{\"Line\":1,\"Column\":25}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"UnitType\"},\"ReturnType\":{\"Case\":\"UnitType\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}}},\"Documentation\":[]}" [] let SPECIALIZATION_3 = "{\"Kind\":{\"Case\":\"QsBody\"},\"TypeArguments\":{\"Case\":\"Null\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}},\"Parent\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyOperation\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":5,\"Item2\":39},\"HeaderRange\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":5}},\"Documentation\":[]}" diff --git a/src/QsCompiler/Tests.Compiler/TestCases/AccessModifiers.qs b/src/QsCompiler/Tests.Compiler/TestCases/AccessModifiers.qs new file mode 100644 index 0000000000..4292d5d09b --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/TestCases/AccessModifiers.qs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/// This namespace contains test cases for access modifiers. +namespace Microsoft.Quantum.Testing.AccessModifiers { + open Microsoft.Quantum.Testing.AccessModifiers.A; + open Microsoft.Quantum.Testing.AccessModifiers.B as B; + open Microsoft.Quantum.Testing.AccessModifiers.C; + + // Redefine inaccessible references (see TestTargets\Libraries\Library1\AccessModifiers.qs) + + internal newtype InternalType = Unit; + + internal function InternalFunction () : Unit {} + + // Callables + + function CallableUseOK () : Unit { + InternalFunction(); + InternalFunctionA(); + B.InternalFunctionB(); + } + + function CallableReferenceInternalInaccessible () : Unit { + InternalFunctionC(); + } + + // Types + + function TypeUseOK () : Unit { + let it = InternalType(); + let its = new InternalType[1]; + let ita = InternalTypeA(); + let itas = new InternalTypeA[1]; + let itb = B.InternalTypeB(); + let itbs = new B.InternalTypeB[1]; + } + + function TypeReferenceInternalInaccessible () : Unit { + let itcs = new InternalTypeC[1]; + } + + function TypeConstructorReferenceInternalInaccessible () : Unit { + let itc = InternalTypeC(); + } + + // Callable signatures + + function CallableLeaksInternalTypeIn1 (x : InternalType) : Unit {} + + function CallableLeaksInternalTypeIn2 (x : (Int, InternalType)) : Unit {} + + function CallableLeaksInternalTypeIn3 (x : (Int, (InternalType, Bool))) : Unit {} + + function CallableLeaksInternalTypeOut1 () : InternalType { + return InternalType(); + } + + function CallableLeaksInternalTypeOut2 () : (Int, InternalType) { + return (0, InternalType()); + } + + function CallableLeaksInternalTypeOut3 () : (Int, (InternalType, Bool)) { + return (0, (InternalType(), false)); + } + + internal function InternalCallableInternalTypeOK (x : InternalType) : InternalType { + return InternalType(); + } + + // Underlying types + + newtype PublicTypeLeaksInternalType1 = InternalType; + + newtype PublicTypeLeaksInternalType2 = (Int, InternalType); + + newtype PublicTypeLeaksInternalType3 = (Int, (InternalType, Bool)); + + internal newtype InternalTypeInternalTypeOK = InternalType; +} + +/// This namespace contains additional definitions of types and callables meant to be used by the +/// Microsoft.Quantum.Testing.AccessModifiers namespace. +namespace Microsoft.Quantum.Testing.AccessModifiers.A { + internal function InternalFunctionA () : Unit {} + + internal newtype InternalTypeA = Unit; +} + +/// This namespace contains additional definitions of types and callables meant to be used by the +/// Microsoft.Quantum.Testing.AccessModifiers namespace. +namespace Microsoft.Quantum.Testing.AccessModifiers.B { + internal function InternalFunctionB () : Unit {} + + internal newtype InternalTypeB = Unit; +} diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/ClassicalControl.qs b/src/QsCompiler/Tests.Compiler/TestCases/ClassicalControl.qs similarity index 85% rename from src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/ClassicalControl.qs rename to src/QsCompiler/Tests.Compiler/TestCases/ClassicalControl.qs index fcfcfd5db8..f7f581b485 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/ClassicalControl.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/ClassicalControl.qs @@ -1,1018 +1,1040 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - - -namespace SubOps { - operation SubOp1() : Unit { } - operation SubOp2() : Unit { } - operation SubOp3() : Unit { } - - operation SubOpCA1() : Unit is Ctl + Adj { } - operation SubOpCA2() : Unit is Ctl + Adj { } - operation SubOpCA3() : Unit is Ctl + Adj { } -} - -// ================================= - -// 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(); - } - - let temp = 0; - - if (r == One) { - SubOp2(); - } - } -} - -// ================================= - -// Apply If Zero Else One -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Bar(r : Result) : Unit { } - - operation Foo() : Unit { - let r = Zero; - - if (r == Zero) { - Bar(r); - } else { - SubOp1(); - } - } -} - -// ================================= - -// Apply If One Else Zero -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Bar(r : Result) : Unit { } - - operation Foo() : Unit { - let r = One; - - if (r == One) { - Bar(r); - } else { - SubOp1(); - } - } -} - -// ================================= - -// If Elif -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Foo() : Unit { - let r = Zero; - - if (r == Zero) { - SubOp1(); - } elif (r == One) { - SubOp2(); - } else { - SubOp3(); - } - } -} - -// ================================= - -// And Condition -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Foo() : Unit { - let r = Zero; - - if (r == Zero and r == One) { - SubOp1(); - } else { - SubOp2(); - } - } -} - -// ================================= - -// Or Condition -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Foo() : Unit { - let r = Zero; - - if (r == Zero or r == One) { - SubOp1(); - } else { - SubOp2(); - } - } -} - -// ================================= - -// 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) { - SubOpCA1(); - SubOpCA2(); - } - } - - adjoint (...) { - let w = One; - - if (w == One) { - SubOpCA2(); - SubOpCA3(); - } - } - } - - operation Self() : Unit is Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - adjoint self; - } - - operation Invert() : Unit is Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - adjoint invert; - } -} - -// ================================= - -// Controlled Support -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Provided() : Unit is Ctl { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled (ctl, ...) { - let w = One; - - if (w == One) { - SubOpCA2(); - SubOpCA3(); - } - } - } - - operation Distribute() : Unit is Ctl { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - 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) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled adjoint (ctl, ...) { - let y = One; - - if (y == One) { - SubOpCA2(); - SubOpCA3(); - } - } - } - - operation ProvidedAdjoint() : Unit is Ctl + Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - adjoint (...) { - let w = One; - - if (w == One) { - SubOpCA3(); - SubOpCA1(); - } - } - - controlled adjoint (ctl, ...) { - let y = One; - - if (y == One) { - SubOpCA2(); - SubOpCA3(); - } - } - } - - operation ProvidedControlled() : Unit is Ctl + Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled (ctl, ...) { - let w = One; - - if (w == One) { - SubOpCA3(); - SubOpCA1(); - } - } - - controlled adjoint (ctl, ...) { - let y = One; - - if (y == One) { - SubOpCA2(); - SubOpCA3(); - } - } - } - - operation ProvidedAll() : Unit is Ctl + Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled (ctl, ...) { - let w = One; - - if (w == One) { - SubOpCA3(); - SubOpCA1(); - } - } - - adjoint (...) { - let y = One; - - if (y == One) { - SubOpCA2(); - SubOpCA3(); - } - } - - controlled adjoint (ctl, ...) { - let b = One; - - if (b == One) { - let temp1 = 0; - let temp2 = 0; - SubOpCA3(); - } - } - } -} - -// ================================= - -// Controlled Adjoint Support - Distribute -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation DistributeBody() : Unit is Ctl + Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled adjoint distribute; - } - - operation DistributeAdjoint() : Unit is Ctl + Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - adjoint (...) { - let w = One; - - if (w == One) { - SubOpCA3(); - SubOpCA1(); - } - } - - controlled adjoint distribute; - } - - operation DistributeControlled() : Unit is Ctl + Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled (ctl, ...) { - let w = One; - - if (w == One) { - SubOpCA3(); - SubOpCA1(); - } - } - - controlled adjoint distribute; - } - - operation DistributeAll() : Unit is Ctl + Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled (ctl, ...) { - let w = One; - - if (w == One) { - SubOpCA3(); - SubOpCA1(); - } - } - - adjoint (...) { - let y = One; - - if (y == One) { - SubOpCA2(); - SubOpCA3(); - } - } - - 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) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled adjoint invert; - } - - operation InvertAdjoint() : Unit is Ctl + Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - adjoint (...) { - let w = One; - - if (w == One) { - SubOpCA3(); - SubOpCA1(); - } - } - - controlled adjoint invert; - } - - operation InvertControlled() : Unit is Ctl + Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled (ctl, ...) { - let w = One; - - if (w == One) { - SubOpCA3(); - SubOpCA1(); - } - } - - controlled adjoint invert; - } - - operation InvertAll() : Unit is Ctl + Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled (ctl, ...) { - let w = One; - - if (w == One) { - SubOpCA3(); - SubOpCA1(); - } - } - - adjoint (...) { - let y = One; - - if (y == One) { - SubOpCA2(); - SubOpCA3(); - } - } - - 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) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled adjoint self; - } - - operation SelfControlled() : Unit is Ctl + Adj { - body (...) { - let r = Zero; - - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } - - controlled (ctl, ...) { - let w = One; - - if (w == One) { - SubOpCA3(); - SubOpCA1(); - } - } - - controlled adjoint self; - } -} - -// ================================= - -// Within Block Support -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Foo() : Unit { - let r = One; - within { - if (r == Zero) { - SubOpCA1(); - SubOpCA2(); - } - } apply { - if (r == One) { - SubOpCA2(); - SubOpCA3(); - } - } - } -} - -// ================================= - -// 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 SubOpCA1(); - } - } -} - -// ================================= - -// 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](); - } - } -} - -// ================================= - -// Hoist One Not Both -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Foo() : Unit { - let r = Zero; - if (r == Zero) { - SubOp1(); - SubOp2(); - } - else { - SubOp3(); - } - } -} - -// ================================= - -// Apply Conditionally -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Bar(r : Result) : Unit { } - - operation Foo() : Unit { - let r1 = Zero; - let r2 = Zero; - - if (r1 == r2) { - Bar(r1); - } - else { - SubOp1(); - } - } -} - -// ================================= - -// Apply Conditionally With NoOp -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Bar(r : Result) : Unit { } - - operation Foo() : Unit { - let r1 = Zero; - let r2 = Zero; - - if (r1 == r2) { - Bar(r1); - } - } -} - -// ================================= - -// Inequality with ApplyConditionally -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Bar(r : Result) : Unit { } - - operation Foo() : Unit { - let r1 = Zero; - let r2 = Zero; - - if (r1 != r2) { - Bar(r1); - } - else { - SubOp1(); - } - } -} - -// ================================= - -// Inequality with Apply If One Else Zero -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Bar(r : Result) : Unit { } - - operation Foo() : Unit { - let r = Zero; - - if (r != Zero) { - Bar(r); - } - else { - SubOp1(); - } - } -} - -// ================================= - -// Inequality with Apply If Zero Else One -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Bar(r : Result) : Unit { } - - operation Foo() : Unit { - let r = Zero; - - if (r != One) { - Bar(r); - } - else { - SubOp1(); - } - } -} - -// ================================= - -// Inequality with ApplyIfOne -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Foo() : Unit { - let r = Zero; - - if (r != Zero) { - SubOp1(); - } - } -} - -// ================================= - -// Inequality with ApplyIfZero -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Foo() : Unit { - let r = Zero; - - if (r != One) { - SubOp1(); - } - } -} - -// ================================= - -// Literal on the Left -namespace Microsoft.Quantum.Testing.ClassicalControl { - open SubOps; - - operation Foo() : Unit { - let r = Zero; - - if (Zero == r) { - SubOp1(); - } - } +// 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 { } + + operation ApplyConditionally<'T,'U>(measurementResults : Result[], resultsValues : Result[], (onEqualOp : ('T => Unit), equalArg : 'T) , (onNonEqualOp : ('U => Unit), nonEqualArg : 'U)) : Unit { } + operation ApplyConditionallyA<'T,'U>(measurementResults : Result[], resultsValues : Result[], (onEqualOp : ('T => Unit is Adj), equalArg : 'T) , (onNonEqualOp : ('U => Unit is Adj), nonEqualArg : 'U)) : Unit is Adj { } + operation ApplyConditionallyC<'T,'U>(measurementResults : Result[], resultsValues : Result[], (onEqualOp : ('T => Unit is Ctl), equalArg : 'T) , (onNonEqualOp : ('U => Unit is Ctl), nonEqualArg : 'U)) : Unit is Ctl { } + operation ApplyConditionallyCA<'T,'U>(measurementResults : Result[], resultsValues : Result[], (onEqualOp : ('T => Unit is Ctl + Adj), equalArg : 'T) , (onNonEqualOp : ('U => Unit is Ctl + Adj), nonEqualArg : 'U)) : Unit is Ctl + Adj { } +} + +namespace SubOps { + operation SubOp1() : Unit { } + operation SubOp2() : Unit { } + operation SubOp3() : Unit { } + + operation SubOpCA1() : Unit is Ctl + Adj { } + operation SubOpCA2() : Unit is Ctl + Adj { } + operation SubOpCA3() : Unit is Ctl + Adj { } +} + +// ================================= + +// Basic Lift +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; + } + } + } +} + +// ================================= + +// Lift 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 Lift Single Call +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + if (r == Zero) { + SubOp1(); + } + } +} + +// ================================= + +// Lift Single Non-Call +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero) { + let temp = 2; + } + } +} + +// ================================= + +// Don't Lift Return Statements +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + if (r == Zero) { + SubOp1(); + return (); + } + } +} + +// ================================= + +// All-Or-None Lifting +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(); + } + + let temp = 0; + + if (r == One) { + SubOp2(); + } + } +} + +// ================================= + +// Apply If Zero Else One +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Bar(r : Result) : Unit { } + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero) { + Bar(r); + } else { + SubOp1(); + } + } +} + +// ================================= + +// Apply If One Else Zero +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Bar(r : Result) : Unit { } + + operation Foo() : Unit { + let r = One; + + if (r == One) { + Bar(r); + } else { + SubOp1(); + } + } +} + +// ================================= + +// If Elif +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero) { + SubOp1(); + } elif (r == One) { + SubOp2(); + } else { + SubOp3(); + } + } +} + +// ================================= + +// And Condition +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero and r == One) { + SubOp1(); + } else { + SubOp2(); + } + } +} + +// ================================= + +// Or Condition +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r == Zero or r == One) { + SubOp1(); + } else { + SubOp2(); + } + } +} + +// ================================= + +// Don't Lift 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 { } +} + +// ================================= + +// Lift 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 Lift 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) { + SubOpCA1(); + SubOpCA2(); + } + } + + adjoint (...) { + let w = One; + + if (w == One) { + SubOpCA2(); + SubOpCA3(); + } + } + } + + operation Self() : Unit is Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + adjoint self; + } + + operation Invert() : Unit is Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + adjoint invert; + } +} + +// ================================= + +// Controlled Support +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Provided() : Unit is Ctl { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOpCA2(); + SubOpCA3(); + } + } + } + + operation Distribute() : Unit is Ctl { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + 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) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled adjoint (ctl, ...) { + let y = One; + + if (y == One) { + SubOpCA2(); + SubOpCA3(); + } + } + } + + operation ProvidedAdjoint() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + adjoint (...) { + let w = One; + + if (w == One) { + SubOpCA3(); + SubOpCA1(); + } + } + + controlled adjoint (ctl, ...) { + let y = One; + + if (y == One) { + SubOpCA2(); + SubOpCA3(); + } + } + } + + operation ProvidedControlled() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOpCA3(); + SubOpCA1(); + } + } + + controlled adjoint (ctl, ...) { + let y = One; + + if (y == One) { + SubOpCA2(); + SubOpCA3(); + } + } + } + + operation ProvidedAll() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOpCA3(); + SubOpCA1(); + } + } + + adjoint (...) { + let y = One; + + if (y == One) { + SubOpCA2(); + SubOpCA3(); + } + } + + controlled adjoint (ctl, ...) { + let b = One; + + if (b == One) { + let temp1 = 0; + let temp2 = 0; + SubOpCA3(); + } + } + } +} + +// ================================= + +// Controlled Adjoint Support - Distribute +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation DistributeBody() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled adjoint distribute; + } + + operation DistributeAdjoint() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + adjoint (...) { + let w = One; + + if (w == One) { + SubOpCA3(); + SubOpCA1(); + } + } + + controlled adjoint distribute; + } + + operation DistributeControlled() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOpCA3(); + SubOpCA1(); + } + } + + controlled adjoint distribute; + } + + operation DistributeAll() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOpCA3(); + SubOpCA1(); + } + } + + adjoint (...) { + let y = One; + + if (y == One) { + SubOpCA2(); + SubOpCA3(); + } + } + + 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) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled adjoint invert; + } + + operation InvertAdjoint() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + adjoint (...) { + let w = One; + + if (w == One) { + SubOpCA3(); + SubOpCA1(); + } + } + + controlled adjoint invert; + } + + operation InvertControlled() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOpCA3(); + SubOpCA1(); + } + } + + controlled adjoint invert; + } + + operation InvertAll() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOpCA3(); + SubOpCA1(); + } + } + + adjoint (...) { + let y = One; + + if (y == One) { + SubOpCA2(); + SubOpCA3(); + } + } + + 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) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled adjoint self; + } + + operation SelfControlled() : Unit is Ctl + Adj { + body (...) { + let r = Zero; + + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } + + controlled (ctl, ...) { + let w = One; + + if (w == One) { + SubOpCA3(); + SubOpCA1(); + } + } + + controlled adjoint self; + } +} + +// ================================= + +// Within Block Support +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = One; + within { + if (r == Zero) { + SubOpCA1(); + SubOpCA2(); + } + } apply { + if (r == One) { + SubOpCA2(); + SubOpCA3(); + } + } + } +} + +// ================================= + +// 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); + } + } +} + +// ================================= + +// Lift Functor Application +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + if (r == Zero) { + Adjoint SubOpCA1(); + } + } +} + +// ================================= + +// Lift 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); + } + } +} + +// ================================= + +// Lift Array Item Call +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let f = [SubOp1]; + let r = Zero; + if (r == Zero) { + f[0](); + } + } +} + +// ================================= + +// Lift One Not Both +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + if (r == Zero) { + SubOp1(); + SubOp2(); + } + else { + SubOp3(); + } + } +} + +// ================================= + +// Apply Conditionally +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Bar(r : Result) : Unit { } + + operation Foo() : Unit { + let r1 = Zero; + let r2 = Zero; + + if (r1 == r2) { + Bar(r1); + } + else { + SubOp1(); + } + } +} + +// ================================= + +// Apply Conditionally With NoOp +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Bar(r : Result) : Unit { } + + operation Foo() : Unit { + let r1 = Zero; + let r2 = Zero; + + if (r1 == r2) { + Bar(r1); + } + } +} + +// ================================= + +// Inequality with ApplyConditionally +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Bar(r : Result) : Unit { } + + operation Foo() : Unit { + let r1 = Zero; + let r2 = Zero; + + if (r1 != r2) { + Bar(r1); + } + else { + SubOp1(); + } + } +} + +// ================================= + +// Inequality with Apply If One Else Zero +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Bar(r : Result) : Unit { } + + operation Foo() : Unit { + let r = Zero; + + if (r != Zero) { + Bar(r); + } + else { + SubOp1(); + } + } +} + +// ================================= + +// Inequality with Apply If Zero Else One +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Bar(r : Result) : Unit { } + + operation Foo() : Unit { + let r = Zero; + + if (r != One) { + Bar(r); + } + else { + SubOp1(); + } + } +} + +// ================================= + +// Inequality with ApplyIfOne +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r != Zero) { + SubOp1(); + } + } +} + +// ================================= + +// Inequality with ApplyIfZero +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r != One) { + SubOp1(); + } + } +} + +// ================================= + +// Literal on the Left +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (Zero == r) { + SubOp1(); + } + } } \ No newline at end of file diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/Core.qs b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/Core.qs index 66fe60a6f3..64005ce6d0 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/Core.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/Core.qs @@ -19,9 +19,3 @@ namespace Microsoft.Quantum.Core { } } -namespace Microsoft.Quantum.Diagnostics { - - // needs to be available for testing - @ Attribute() - newtype Test = String; -} \ No newline at end of file diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/Diagnostics.qs b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/Diagnostics.qs new file mode 100644 index 0000000000..6a691e0946 --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/Diagnostics.qs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Diagnostics { + + // needs to be available for testing + @ Attribute() + newtype Test = String; +} + diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/InternalRenaming.qs b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/InternalRenaming.qs new file mode 100644 index 0000000000..a28e639c74 --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/InternalRenaming.qs @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Test 1: Rename internal operation call references + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal operation Foo () : Unit { + } + + operation Bar () : Unit { + Foo(); + } +} + +// ================================= +// Test 2: Rename internal function call references + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal function Foo () : Int { + return 42; + } + + function Bar () : Unit { + let x = Foo(); + let y = 1 + (Foo() - 5); + let z = (Foo(), 9); + } +} + +// ================================= +// Test 3: Rename internal type references + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal newtype Foo = Unit; + + internal newtype Bar = (Int, Foo); + + internal function Baz (x : Foo) : Foo { + return Foo(); + } +} + +// ================================= +// Test 4: Rename internal references across namespaces + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal newtype Foo = Unit; + + internal function Bar () : Unit { + } +} + +namespace Microsoft.Quantum.Testing.InternalRenaming.Extra { + open Microsoft.Quantum.Testing.InternalRenaming; + + function Baz () : Unit { + return Bar(); + } + + internal function Qux (x : Foo) : Foo { + return x; + } +} + +// ================================= +// Test 5: Rename internal qualified references + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal newtype Foo = Unit; + + internal function Bar () : Unit { + } +} + +namespace Microsoft.Quantum.Testing.InternalRenaming.Extra { + function Baz () : Unit { + return Microsoft.Quantum.Testing.InternalRenaming.Bar(); + } + + internal function Qux (x : Microsoft.Quantum.Testing.InternalRenaming.Foo) + : Microsoft.Quantum.Testing.InternalRenaming.Foo { + return x; + } +} + +// ================================= +// Test 6: Rename internal attribute references + +namespace Microsoft.Quantum.Testing.InternalRenaming { + @Attribute() + internal newtype Foo = Unit; + + @Foo() + function Bar () : Unit { + } +} + +// ================================= +// Test 7: Rename specializations for internal operations + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal operation Foo () : Unit is Adj { + body { + } + + adjoint { + } + } + + operation Bar () : Unit { + Foo(); + Adjoint Foo(); + } +} + +// ================================= +// Test 8: Group internal specializations by source file + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal operation Foo () : Unit is Adj + Ctl { + body { + } + + adjoint { + } + + controlled (cs, ...) { + } + + controlled adjoint (cs, ...) { + } + } +} diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/QuantumProcessorExtensions.qs b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/QuantumProcessorExtensions.qs deleted file mode 100644 index d125169a19..0000000000 --- a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/QuantumProcessorExtensions.qs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 { } - - operation ApplyConditionally<'T,'U>(measurementResults : Result[], resultsValues : Result[], (onEqualOp : ('T => Unit), equalArg : 'T) , (onNonEqualOp : ('U => Unit), nonEqualArg : 'U)) : Unit { } - operation ApplyConditionallyA<'T,'U>(measurementResults : Result[], resultsValues : Result[], (onEqualOp : ('T => Unit is Adj), equalArg : 'T) , (onNonEqualOp : ('U => Unit is Adj), nonEqualArg : 'U)) : Unit is Adj { } - operation ApplyConditionallyC<'T,'U>(measurementResults : Result[], resultsValues : Result[], (onEqualOp : ('T => Unit is Ctl), equalArg : 'T) , (onNonEqualOp : ('U => Unit is Ctl), nonEqualArg : 'U)) : Unit is Ctl { } - operation ApplyConditionallyCA<'T,'U>(measurementResults : Result[], resultsValues : Result[], (onEqualOp : ('T => Unit is Ctl + Adj), equalArg : 'T) , (onNonEqualOp : ('U => Unit is Ctl + Adj), nonEqualArg : 'U)) : Unit is Ctl + Adj { } -} \ No newline at end of file diff --git a/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs b/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs index 764104c323..eb03d7079a 100644 --- a/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs @@ -90,13 +90,13 @@ type CompilerTests (compilation : CompilationUnitManager.Compilation, output:ITe if other.Any() then NotImplementedException "unknown diagnostics item to verify" |> raise - static member Compile srcFolder files = + static member Compile srcFolder files references = let compileFiles (files : IEnumerable<_>) = let mgr = new CompilationUnitManager(fun ex -> failwith ex.Message) - files.ToImmutableDictionary(Path.GetFullPath >> Uri, File.ReadAllText) + files.ToImmutableDictionary(Path.GetFullPath >> Uri, File.ReadAllText) |> CompilationUnitManager.InitializeFileManagers - |> mgr.AddOrUpdateSourceFilesAsync + |> mgr.AddOrUpdateSourceFilesAsync |> ignore - mgr.Build() - files |> Seq.map (fun file -> Path.Combine (srcFolder, file)) |> compileFiles - + mgr.UpdateReferencesAsync(new References(ProjectManager.LoadReferencedAssemblies(references))) |> ignore + mgr.Build() + files |> Seq.map (fun file -> Path.Combine (srcFolder, file)) |> compileFiles diff --git a/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs b/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs index c4d7f6efed..fafd1a95c8 100644 --- a/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs +++ b/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs @@ -113,6 +113,7 @@ 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" +let public InternalRenamingNs = "Microsoft.Quantum.Testing.InternalRenaming" /// Expected callable signatures to be found when running Monomorphization tests let public MonomorphizationSignatures = @@ -216,25 +217,25 @@ let private _DefaultWithOperation = _MakeTypeMap [| /// Expected callable signatures to be found when running Classical Control tests let public ClassicalControlSignatures = [| - (_DefaultTypes, [| // Basic Hoist + (_DefaultTypes, [| // Basic Lift ClassicalControlNs, "Foo", [||], "Unit"; // The original operation ClassicalControlNs, "_Foo", [|"Result"|], "Unit"; // The generated operation |]) - (_DefaultTypes, [| // Hoist Loops + (_DefaultTypes, [| // Lift Loops ClassicalControlNs, "Foo", [||], "Unit" ClassicalControlNs, "_Foo", [|"Result"|], "Unit" |]) - (_DefaultTypes, [| // Don't Hoist Single Call + (_DefaultTypes, [| // Don't Lift Single Call ClassicalControlNs, "Foo", [||], "Unit" |]) - (_DefaultTypes, [| // Hoist Single Non-Call + (_DefaultTypes, [| // Lift Single Non-Call ClassicalControlNs, "Foo", [||], "Unit" ClassicalControlNs, "_Foo", [|"Result"|], "Unit" |]) - (_DefaultTypes, [| // Don't Hoist Return Statements + (_DefaultTypes, [| // Don't Lift Return Statements ClassicalControlNs, "Foo", [||], "Unit" |]) - (_DefaultTypes, [| // All-Or-None Hoisting + (_DefaultTypes, [| // All-Or-None Lifting ClassicalControlNs, "IfInvalid", [||], "Unit" ClassicalControlNs, "ElseInvalid", [||], "Unit" ClassicalControlNs, "BothInvalid", [||], "Unit" @@ -259,17 +260,17 @@ let public ClassicalControlSignatures = (_DefaultTypes, [| // Or Condition ClassicalControlNs, "Foo", [||], "Unit" |]) - (_DefaultTypes, [| // Don't Hoist Functions + (_DefaultTypes, [| // Don't Lift Functions ClassicalControlNs, "Foo", [||], "Unit" ClassicalControlNs, "SubFunc1", [||], "Unit" ClassicalControlNs, "SubFunc2", [||], "Unit" ClassicalControlNs, "SubFunc3", [||], "Unit" |]) - (_DefaultTypes, [| // Hoist Self-Contained Mutable + (_DefaultTypes, [| // Lift Self-Contained Mutable ClassicalControlNs, "Foo", [||], "Unit" ClassicalControlNs, "_Foo", [|"Result"|], "Unit" |]) - (_DefaultTypes, [| // Don't Hoist General Mutable + (_DefaultTypes, [| // Don't Lift General Mutable ClassicalControlNs, "Foo", [||], "Unit" |]) (_DefaultTypes, [| // Generics Support @@ -368,20 +369,20 @@ let public ClassicalControlSignatures = ClassicalControlNs, "Bar", [|"Bar.Q";"Bar.W"|], "Unit" ClassicalControlNs, "Foo", [||], "Unit" |]) - (_DefaultTypes, [| // Hoist Functor Application + (_DefaultTypes, [| // Lift Functor Application ClassicalControlNs, "Foo", [||], "Unit" ClassicalControlNs, "_Foo", [|"Result"|], "Unit" |]) - (_DefaultTypes, [| // Hoist Partial Application + (_DefaultTypes, [| // Lift Partial Application ClassicalControlNs, "Bar", [|"Int";"Double"|], "Unit" ClassicalControlNs, "Foo", [||], "Unit" ClassicalControlNs, "_Foo", [|"Result"|], "Unit" |]) - (_DefaultWithOperation, [| // Hoist Array Item Call + (_DefaultWithOperation, [| // Lift Array Item Call ClassicalControlNs, "Foo", [||], "Unit" ClassicalControlNs, "_Foo", [|"SubOp1Type[]";"Result"|], "Unit" |]) - (_DefaultTypes, [| // Hoist One Not Both + (_DefaultTypes, [| // Lift One Not Both ClassicalControlNs, "Foo", [||], "Unit" ClassicalControlNs, "_Foo", [|"Result"|], "Unit" |]) diff --git a/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj b/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj index 996cf6a79c..879f089a5d 100644 --- a/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj +++ b/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj @@ -23,7 +23,7 @@ PreserveNewest - + PreserveNewest @@ -44,8 +44,8 @@ PreserveNewest - - PreserveNewest + + PreserveNewest PreserveNewest @@ -126,6 +126,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + @@ -143,6 +149,7 @@ + @@ -164,14 +171,17 @@ - + - + - "$(MSBuildThisFileDirectory)..\TestTargets\Simulation\Example\bin\$(Configuration)\netcoreapp3.1\Example.dll" + $(MSBuildThisFileDirectory)..\TestTargets\Simulation\Example\bin\$(Configuration)\netcoreapp3.1\Example.dll + $(MSBuildThisFileDirectory)..\TestTargets\Libraries\Library1\bin\$(Configuration)\netcoreapp3.1\Library1.dll - + diff --git a/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs b/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs index 02d13a18ce..cdd8592016 100644 --- a/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs +++ b/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs @@ -13,7 +13,7 @@ open Xunit.Abstractions type TypeCheckingTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "TypeChecking.qs"; "Types.qs"], output) + inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "TypeChecking.qs"; "Types.qs"] [], output) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.TypeChecking" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs b/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs index a4e8d649a1..dad33df6a6 100644 --- a/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs +++ b/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs @@ -10,7 +10,7 @@ using System.IO; using System.Linq; using Xunit; - +using static Microsoft.Quantum.QsCompiler.Documentation.Testing.Utils; namespace Microsoft.Quantum.QsCompiler.Documentation.Testing { @@ -20,17 +20,6 @@ namespace Microsoft.Quantum.QsCompiler.Documentation.Testing public class DocParsingTests { - private readonly Tuple EmptyRange = - new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero); - private readonly QsNullable ZeroLocation = - QsNullable.NewValue(new QsLocation(new Tuple(0, 0), new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero))); - private readonly NonNullable CanonName = NonNullable.New("Microsoft.Quantum.Canon"); - - private QsQualifiedName MakeFullName(string name) - { - return new QsQualifiedName(CanonName, NonNullable.New(name)); - } - [Fact] public void ParseDocComments() { @@ -158,6 +147,7 @@ element indexes the subsystem on which the generator acts on. var generatorIndexType = new QsCustomType(MakeFullName("GeneratorIndex"), ImmutableArray.Empty, + new Modifiers(AccessModifier.DefaultAccess), NonNullable.New("GeneratorRepresentation.qs"), ZeroLocation, baseType, @@ -339,6 +329,7 @@ of the generator represented by $U$. var qsCallable = new QsCallable(QsCallableKind.Operation, MakeFullName("AdiabaticStateEnergyUnitary"), ImmutableArray.Empty, + new Modifiers(AccessModifier.DefaultAccess), NonNullable.New("Techniques.qs"), ZeroLocation, signature, diff --git a/src/QsCompiler/Tests.DocGenerator/DocWritingTests.cs b/src/QsCompiler/Tests.DocGenerator/DocWritingTests.cs new file mode 100644 index 0000000000..8cb04e860b --- /dev/null +++ b/src/QsCompiler/Tests.DocGenerator/DocWritingTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; +using Microsoft.Quantum.QsCompiler.SyntaxTree; +using System; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text; +using Xunit; +using static Microsoft.Quantum.QsCompiler.Documentation.Testing.Utils; + +namespace Microsoft.Quantum.QsCompiler.Documentation.Testing +{ + using ArgDeclType = LocalVariableDeclaration; + using QsType = QsTypeKind; + + public class DocWritingTests + { + /// + /// Tests that internal and private items are skipped when generating documentation for a namespace. + /// + [Fact] + public void ExcludeInaccessible() + { + var elements = + new[] { AccessModifier.DefaultAccess, AccessModifier.Internal } + .SelectMany(access => + { + var source = NonNullable.New("Tests.qs"); + var unit = ResolvedType.New(QsType.UnitType); + + var signature = new ResolvedSignature(Array.Empty().ToImmutableArray(), + unit, + unit, + CallableInformation.NoInformation); + var argumentTuple = QsTuple.NewQsTuple(ImmutableArray.Create>()); + var callable = new QsCallable(kind: QsCallableKind.Operation, + fullName: MakeFullName(access + "Operation"), + attributes: ImmutableArray.Empty, + modifiers: new Modifiers(access), + sourceFile: source, + location: ZeroLocation, + signature: signature, + argumentTuple: argumentTuple, + specializations: ImmutableArray.Create(), + documentation: ImmutableArray.Create(), + comments: QsComments.Empty); + + var typeItems = QsTuple.NewQsTuple( + ImmutableArray.Create(QsTuple.NewQsTupleItem(QsTypeItem.NewAnonymous(unit)))); + var type = new QsCustomType(fullName: MakeFullName(access + "Type"), + attributes: ImmutableArray.Empty, + modifiers: new Modifiers(access), + sourceFile: source, + location: ZeroLocation, + type: unit, + typeItems: typeItems, + documentation: ImmutableArray.Create(), + comments: QsComments.Empty); + return new[] + { + QsNamespaceElement.NewQsCallable(callable), + QsNamespaceElement.NewQsCustomType(type) + }; + }); + var emptyLookup = Array.Empty>().ToLookup(x => NonNullable.New("")); + var ns = new QsNamespace(CanonName, elements.ToImmutableArray(), emptyLookup); + var docNs = new DocNamespace(ns); + var stream = new MemoryStream(); + docNs.WriteToStream(stream, null); + + var expected = @"### YamlMime:QSharpNamespace +uid: microsoft.quantum.canon +name: Microsoft.Quantum.Canon +operations: +- uid: microsoft.quantum.canon.defaultaccessoperation + summary: '' +newtypes: +- uid: microsoft.quantum.canon.defaultaccesstype + summary: '' +... +"; + var actual = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal(expected, actual); + } + } +} diff --git a/src/QsCompiler/Tests.DocGenerator/Utils.cs b/src/QsCompiler/Tests.DocGenerator/Utils.cs new file mode 100644 index 0000000000..c9badf778a --- /dev/null +++ b/src/QsCompiler/Tests.DocGenerator/Utils.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SyntaxTree; +using System; + +namespace Microsoft.Quantum.QsCompiler.Documentation.Testing +{ + internal static class Utils + { + internal static readonly Tuple EmptyRange = + new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero); + internal static readonly QsNullable ZeroLocation = + QsNullable.NewValue( + new QsLocation(new Tuple(0, 0), + new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero))); + internal static readonly NonNullable CanonName = NonNullable.New("Microsoft.Quantum.Canon"); + + internal static QsQualifiedName MakeFullName(string name) + { + return new QsQualifiedName(CanonName, NonNullable.New(name)); + } + } +} diff --git a/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs b/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs index 47de9f2635..c4e98fd1e0 100644 --- a/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs +++ b/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs @@ -241,7 +241,7 @@ public void LoadQsharpConsoleApps() } [TestMethod] - public void LoadQsharpUnittest() + public void LoadQsharpUnitTest() { var (projectFile, context) = Context("test5"); var projDir = Path.GetDirectoryName(projectFile); @@ -293,7 +293,7 @@ internal static class CompilationContext { internal static ProjectInformation Load(Uri projectFile) { - void LogOutput(string msg, MessageType level) => + static void LogOutput(string msg, MessageType level) => Console.WriteLine($"[{level}]: {msg}"); return new EditorState(new ProjectLoader(LogOutput), null, null, null, null) .QsProjectLoader(projectFile, out var loaded) ? loaded : null; diff --git a/src/QsCompiler/TextProcessor/CodeCompletion/FragmentParsing.fs b/src/QsCompiler/TextProcessor/CodeCompletion/FragmentParsing.fs index f58fc1e20f..051dd94c48 100644 --- a/src/QsCompiler/TextProcessor/CodeCompletion/FragmentParsing.fs +++ b/src/QsCompiler/TextProcessor/CodeCompletion/FragmentParsing.fs @@ -22,6 +22,10 @@ open Microsoft.Quantum.QsCompiler.TextProcessing.CodeCompletion.ParsingPrimitive #nowarn "40" +/// Parses a declaration modifier list. +let private modifiers = + expectedKeyword qsInternal + /// Parses a callable signature. let private callableSignature = let name = expectedId Declaration (term symbol) @@ -33,11 +37,11 @@ let private callableSignature = /// Parses a function declaration. let private functionDeclaration = - expectedKeyword fctDeclHeader ?>> callableSignature + optR modifiers @>> expectedKeyword fctDeclHeader ?>> callableSignature /// Parses an operation declaration. let private operationDeclaration = - expectedKeyword opDeclHeader ?>> callableSignature ?>> characteristicsAnnotation + optR modifiers @>> expectedKeyword opDeclHeader ?>> callableSignature ?>> characteristicsAnnotation /// Parses a user-defined type declaration. let private udtDeclaration = @@ -46,7 +50,7 @@ let private udtDeclaration = let namedItem = name ?>> expected colon ?>> qsType return! qsType <|>@ tuple1 (namedItem <|>@ udt) } - expectedKeyword typeDeclHeader ?>> name ?>> expected equal ?>> udt + optR modifiers @>> expectedKeyword typeDeclHeader ?>> name ?>> expected equal ?>> udt /// Parses an open directive. let private openDirective = diff --git a/src/QsCompiler/TextProcessor/CodeCompletion/ParsingPrimitives.fs b/src/QsCompiler/TextProcessor/CodeCompletion/ParsingPrimitives.fs index 17690806e3..fc6e6b8851 100644 --- a/src/QsCompiler/TextProcessor/CodeCompletion/ParsingPrimitives.fs +++ b/src/QsCompiler/TextProcessor/CodeCompletion/ParsingPrimitives.fs @@ -110,6 +110,11 @@ let expectedQualifiedSymbol kind = attempt (term qualifiedSymbol |>> fst .>> previousCharSatisfiesNot Char.IsWhiteSpace .>> optional eot) <|> (term qualifiedSymbol >>% []) +/// Optionally parses `p`, backtracking if it consumes EOT so another parser can try, too. Best if used with `@>>`, +/// e.g., `optR foo @>> bar`. +let optR p = + attempt (p .>> previousCharSatisfies ((<>) '\u0004')) <|> lookAhead p <|>% [] + /// `manyR p shouldBacktrack` is like `many p` but is reentrant on the last item if /// 1. The last item is followed by EOT; and /// 2. `shouldBacktrack` succeeds at the beginning of the last item, or the last item consists of only EOT. diff --git a/src/QsCompiler/TextProcessor/QsFragmentParsing.fs b/src/QsCompiler/TextProcessor/QsFragmentParsing.fs index 28dd79d042..c4241ab761 100644 --- a/src/QsCompiler/TextProcessor/QsFragmentParsing.fs +++ b/src/QsCompiler/TextProcessor/QsFragmentParsing.fs @@ -113,6 +113,11 @@ let private allocationScope = let initializerTuple = buildTupleItem validInitializer buildInitializerTuple ErrorCode.InvalidInitializerExpression ErrorCode.MissingInitializerExpression invalidInitializer isTupleContinuation optTupleBrackets (initializerTuple |> symbolBinding equal ErrorCode.ExpectingAssignment) |>> fst +/// Parses keywords that modify the visibility or behavior of a declaration. +let private modifiers = + let accessModifier = (qsInternal.parse >>% Internal) <|>% DefaultAccess + accessModifier |>> fun access -> {Access = access} + /// Parses a Q# operation or function signature. /// Expects type annotations for each symbol in the argument tuple, and raises Missing- or InvalidTypeAnnotation errors otherwise. /// Raises Missing- or InvalidArumentDeclaration error for entirely missing or invalid arguments, @@ -184,60 +189,23 @@ let private functorGenDirective = // parsing all possible directives for all fun // Q# fragments -// making this recursive so any new fragment only needs to be added here (defining the necessary keywords in the Language module) -let rec private getFragments() = // Note: this needs to be a function! - [ - (qsImmutableBinding , letStatement ) - (qsMutableBinding , mutableStatement ) - (qsValueUpdate , setStatement ) - (qsReturn , returnStatement ) - (qsFail , failStatement ) - (qsIf , ifClause ) - (qsElif , elifClause ) - (qsElse , elseClause ) - (qsFor , forHeader ) - (qsWhile , whileHeader ) - (qsRepeat , repeatHeader ) - (qsUntil , untilSuccess ) - (qsWithin , withinHeader ) - (qsApply , applyHeader ) - (qsUsing , usingHeader ) - (qsBorrowing , borrowingHeader ) - (namespaceDeclHeader , namespaceDeclaration ) - (typeDeclHeader , udtDeclaration ) - (opDeclHeader , operationDeclaration ) - (fctDeclHeader , functionDeclaration ) - (ctrlAdjDeclHeader , controlledAdjointDeclaration) // needs to be before adjointDeclaration and controlledDeclaration! - (adjDeclHeader , adjointDeclaration ) - (ctrlDeclHeader , controlledDeclaration ) - (bodyDeclHeader , bodyDeclaration ) - (importDirectiveHeader, openDirective ) - ] - -and private headerCheck = // DO NOT REMOVE - the check is executed once at the beginning, just as it should be - let implementedHeaders = (getFragments() |> List.map (fun x -> (fst x).id)).ToImmutableHashSet() - let existingHeaders = Keywords.FragmentHeaders.ToImmutableHashSet() - if (implementedHeaders.SymmetricExcept existingHeaders).Count <> 0 then - System.NotImplementedException "mismatch between existing Q# fragments and implemented Q# fragments" |> raise - - // namespace and attribute parsing /// Uses buildFragment to parse a Q# OpenDirective as QsFragment. -and private openDirective = +let private openDirective = let invalid = OpenDirective (invalidSymbol, Null) let nsNameAndAlias = let aliasOption = (importedAs.parse >>. expectedNamespaceName eof |>> Value) <|>% Null expectedNamespaceName importedAs.parse .>>. aliasOption - buildFragment importDirectiveHeader.parse nsNameAndAlias invalid OpenDirective eof + buildFragment importDirectiveHeader.parse nsNameAndAlias invalid (fun _ -> OpenDirective) eof /// Uses buildFragment to parse a Q# NamespaceDeclaration as QsFragment. -and private namespaceDeclaration = +let private namespaceDeclaration = let invalid = NamespaceDeclaration invalidSymbol - buildFragment namespaceDeclHeader.parse (expectedNamespaceName eof) invalid NamespaceDeclaration eof + buildFragment namespaceDeclHeader.parse (expectedNamespaceName eof) invalid (fun _ -> NamespaceDeclaration) eof /// Uses buildFragment to parse a Q# DeclarationAttribute as QsFragment. -and private attributeDeclaration = +let private attributeDeclaration = let invalid = DeclarationAttribute (invalidSymbol, unknownExpr) let attributeId = multiSegmentSymbol ErrorCode.InvalidIdentifierName |>> asQualifiedSymbol let expectedArgs = @@ -248,44 +216,54 @@ and private attributeDeclaration = let invalidAttribute = (invalidSymbol, unknownExpr) let invalidErr, missingErr = ErrorCode.InvalidAttributeIdentifier, ErrorCode.MissingAttributeIdentifier expected (attributeId .>>. expectedArgs) invalidErr missingErr invalidAttribute attributeIntro - buildFragment attributeIntro expectedAttribute invalid DeclarationAttribute attributeIntro + buildFragment attributeIntro expectedAttribute invalid (fun _ -> DeclarationAttribute) attributeIntro // operation and function parsing /// Uses buildFragment to parse a Q# BodyDeclaration as QsFragment. -and private bodyDeclaration = +let private bodyDeclaration = let invalid = BodyDeclaration unknownGenerator - buildFragment bodyDeclHeader.parse functorGenDirective invalid BodyDeclaration eof + buildFragment bodyDeclHeader.parse functorGenDirective invalid (fun _ -> BodyDeclaration) eof /// Uses buildFragment to parse a Q# AdjointDeclaration as QsFragment. -and private adjointDeclaration = +let private adjointDeclaration = let invalid = AdjointDeclaration unknownGenerator - buildFragment adjDeclHeader.parse functorGenDirective invalid AdjointDeclaration eof + buildFragment adjDeclHeader.parse functorGenDirective invalid (fun _ -> AdjointDeclaration) eof /// Uses buildFragment to parse a Q# ControlledDeclaration as QsFragment. -and private controlledDeclaration = +let private controlledDeclaration = let invalid = ControlledDeclaration unknownGenerator - buildFragment ctrlDeclHeader.parse functorGenDirective invalid ControlledDeclaration eof + buildFragment ctrlDeclHeader.parse functorGenDirective invalid (fun _ -> ControlledDeclaration) eof /// Uses buildFragment to parse a Q# ControlledAdjointDeclaration as QsFragment. -and private controlledAdjointDeclaration = +let private controlledAdjointDeclaration = let invalid = ControlledAdjointDeclaration unknownGenerator - buildFragment (attempt ctrlAdjDeclHeader.parse) functorGenDirective invalid ControlledAdjointDeclaration eof + buildFragment (attempt ctrlAdjDeclHeader.parse) functorGenDirective invalid (fun _ -> ControlledAdjointDeclaration) eof /// Uses buildFragment to parse a Q# OperationDeclaration as QsFragment. -and private operationDeclaration = - let invalid = OperationDeclaration (invalidSymbol, CallableSignature.Invalid) - buildFragment opDeclHeader.parse signature invalid OperationDeclaration eof - +let private operationDeclaration = + let invalid = OperationDeclaration ({Access = DefaultAccess}, invalidSymbol, CallableSignature.Invalid) + buildFragment + (modifiers .>> opDeclHeader.parse |> attempt) + signature + invalid + (fun mods (symbol, signature) -> OperationDeclaration (mods, symbol, signature)) + eof + /// Uses buildFragment to parse a Q# FunctionDeclaration as QsFragment. -and private functionDeclaration = - let invalid = FunctionDeclaration (invalidSymbol, CallableSignature.Invalid) - buildFragment fctDeclHeader.parse signature invalid FunctionDeclaration eof +let private functionDeclaration = + let invalid = FunctionDeclaration ({Access = DefaultAccess}, invalidSymbol, CallableSignature.Invalid) + buildFragment + (modifiers .>> fctDeclHeader.parse |> attempt) + signature + invalid + (fun mods (symbol, signature) -> FunctionDeclaration (mods, symbol, signature)) + eof /// Uses buildFragment to parse a Q# TypeDefinition as QsFragment. -and private udtDeclaration = - let invalid = TypeDefinition (invalidSymbol, invalidArgTupleItem) +let private udtDeclaration = + let invalid = TypeDefinition ({Access = DefaultAccess}, invalidSymbol, invalidArgTupleItem) let udtTuple = // not unified with the argument tuple for callable declarations, since the error handling needs to be different let asAnonymousItem t = QsTupleItem ((MissingSymbol, Null) |> QsSymbol.New, t) let namedItem = @@ -297,37 +275,52 @@ and private udtDeclaration = let tupleItem = attempt namedItem <|> (typeParser typeTuple |>> asAnonymousItem) // namedItem needs to be first, and we can't be permissive for tuple types! buildTupleItem tupleItem (fst >> QsTuple) ErrorCode.InvalidUdtItemDeclaration ErrorCode.MissingUdtItemDeclaration invalidArgTupleItem eof let invalidNamedSingle = followedBy namedItem >>. optTupleBrackets namedItem |>> fst - invalidNamedSingle <|> udtTupleItem // require parenthesis for a single named item + invalidNamedSingle <|> udtTupleItem // require parenthesis for a single named item let declBody = expectedIdentifierDeclaration equal .>> equal .>>. udtTuple - buildFragment typeDeclHeader.parse declBody invalid TypeDefinition eof + buildFragment + (modifiers .>> typeDeclHeader.parse |> attempt) + declBody + invalid + (fun mods (symbol, underlyingType) -> TypeDefinition (mods, symbol, underlyingType)) + eof // statement parsing /// Uses buildFragment to parse a Q# return-statement as QsFragment. -and private returnStatement = +let private returnStatement = let invalid = ReturnStatement unknownExpr - buildFragment qsReturn.parse (expectedExpr eof) invalid ReturnStatement eof + buildFragment qsReturn.parse (expectedExpr eof) invalid (fun _ -> ReturnStatement) eof /// Uses buildFragment to parse a Q# fail-statement as QsFragment. -and private failStatement = +let private failStatement = let invalid = FailStatement unknownExpr - buildFragment qsFail.parse (expectedExpr eof) invalid FailStatement eof + buildFragment qsFail.parse (expectedExpr eof) invalid (fun _ -> FailStatement) eof /// Uses buildFragment to parse a Q# immutable binding (i.e. let-statement) as QsFragment. -and private letStatement = +let private letStatement = let invalid = ImmutableBinding (invalidSymbol, unknownExpr) - buildFragment qsImmutableBinding.parse (expectedExpr eof |> symbolBinding equal ErrorCode.ExpectingAssignment) invalid ImmutableBinding eof + buildFragment + qsImmutableBinding.parse + (expectedExpr eof |> symbolBinding equal ErrorCode.ExpectingAssignment) + invalid + (fun _ -> ImmutableBinding) + eof /// Uses buildFragment to parse a Q# mutable binding (i.e. mutable-statement) as QsFragment. -and private mutableStatement = +let private mutableStatement = let invalid = MutableBinding (invalidSymbol, unknownExpr) - buildFragment qsMutableBinding.parse (expectedExpr eof |> symbolBinding equal ErrorCode.ExpectingAssignment) invalid MutableBinding eof + buildFragment + qsMutableBinding.parse + (expectedExpr eof |> symbolBinding equal ErrorCode.ExpectingAssignment) + invalid + (fun _ -> MutableBinding) + eof /// Uses buildFragment to parse a Q# value update (i.e. set-statement) as QsFragment. -and private setStatement = +let private setStatement = let applyAndReassignOp = let updateAndReassign id = let update = pstring qsCopyAndUpdateOp.cont |> term @@ -376,72 +369,115 @@ and private setStatement = let expectedEqual = expected equal ErrorCode.ExpectingAssignment ErrorCode.ExpectingAssignment "" (preturn ()) symbolTuple .>> expectedEqual .>>. expectedExpr eof let invalid = ValueUpdate (unknownExpr, unknownExpr) - buildFragment qsValueUpdate.parse (attempt applyAndReassign <|> symbolUpdate) invalid ValueUpdate eof + buildFragment qsValueUpdate.parse (attempt applyAndReassign <|> symbolUpdate) invalid (fun _ -> ValueUpdate) eof /// Uses buildFragment to parse a Q# if clause as QsFragment. -and private ifClause = +let private ifClause = let invalid = IfClause unknownExpr - buildFragment qsIf.parse (expectedCondition eof) invalid IfClause eof + buildFragment qsIf.parse (expectedCondition eof) invalid (fun _ -> IfClause) eof /// Uses buildFragment to parse a Q# elif clause as QsFragment. -and private elifClause = +let private elifClause = let invalid = ElifClause unknownExpr - buildFragment qsElif.parse (expectedCondition eof) invalid ElifClause eof + buildFragment qsElif.parse (expectedCondition eof) invalid (fun _ -> ElifClause) eof /// Uses buildFragment to parse a Q# else clause as QsFragment. -and private elseClause = - let valid = fun _ -> ElseClause - buildFragment qsElse.parse (preturn "") ElseClause valid eof +let private elseClause = + buildFragment qsElse.parse (preturn "") ElseClause (fun _ _ -> ElseClause) eof /// Uses buildFragment to parse a Q# for-statement intro (for-statement without the body) as QsFragment. -and private forHeader = +let private forHeader = let invalid = ForLoopIntro (invalidSymbol, unknownExpr) let loopVariableBinding = expectedExpr rTuple |> symbolBinding qsRangeIter.parse ErrorCode.ExpectingIteratorItemAssignment let forBody = optTupleBrackets loopVariableBinding |>> fst - buildFragment qsFor.parse forBody invalid ForLoopIntro eof + buildFragment qsFor.parse forBody invalid (fun _ -> ForLoopIntro) eof /// Uses buildFragment to parse a Q# while-statement intro (while-statement without the body) as QsFragment. -and private whileHeader = +let private whileHeader = let invalid = WhileLoopIntro unknownExpr let whileBody = optTupleBrackets (expectedExpr isTupleContinuation) |>> fst - buildFragment qsWhile.parse whileBody invalid WhileLoopIntro eof + buildFragment qsWhile.parse whileBody invalid (fun _ -> WhileLoopIntro) eof /// Uses buildFragment to parse a Q# repeat intro as QsFragment. -and private repeatHeader = - let valid = fun _ -> RepeatIntro - buildFragment qsRepeat.parse (preturn "") RepeatIntro valid eof +let private repeatHeader = + buildFragment qsRepeat.parse (preturn "") RepeatIntro (fun _ _ -> RepeatIntro) eof /// Uses buildFragment to parse a Q# until success clause as QsFragment. -and private untilSuccess = +let private untilSuccess = let invalid = UntilSuccess (unknownExpr, false) let optionalFixup = qsRUSfixup.parse >>% true <|> preturn false - buildFragment qsUntil.parse (expectedCondition qsRUSfixup.parse .>>. optionalFixup) invalid UntilSuccess eof + buildFragment qsUntil.parse (expectedCondition qsRUSfixup.parse .>>. optionalFixup) invalid (fun _ -> UntilSuccess) eof /// Uses buildFragment to parse a Q# within-block intro as QsFragment. -and private withinHeader = - let valid = fun _ -> WithinBlockIntro - buildFragment qsWithin.parse (preturn "") WithinBlockIntro valid eof +let private withinHeader = + buildFragment qsWithin.parse (preturn "") WithinBlockIntro (fun _ _ -> WithinBlockIntro) eof /// Uses buildFragment to parse a Q# apply block intro as QsFragment. -and private applyHeader = - let valid = fun _ -> ApplyBlockIntro - buildFragment qsApply.parse (preturn "") ApplyBlockIntro valid eof +let private applyHeader = + buildFragment qsApply.parse (preturn "") ApplyBlockIntro (fun _ _ -> ApplyBlockIntro) eof /// Uses buildFragment to parse a Q# using block intro as QsFragment. -and private usingHeader = +let private usingHeader = let invalid = UsingBlockIntro (invalidSymbol, invalidInitializer) - buildFragment qsUsing.parse allocationScope invalid UsingBlockIntro eof + buildFragment qsUsing.parse allocationScope invalid (fun _ -> UsingBlockIntro) eof /// Uses buildFragment to parse a Q# borrowing block intro as QsFragment. -and private borrowingHeader = +let private borrowingHeader = let invalid = BorrowingBlockIntro (invalidSymbol, invalidInitializer) - buildFragment qsBorrowing.parse allocationScope invalid BorrowingBlockIntro eof + buildFragment qsBorrowing.parse allocationScope invalid (fun _ -> BorrowingBlockIntro) eof + +/// Always builds an invalid fragment after parsing the given fragment header. +let private buildInvalidFragment header = + buildFragment header (fail "invalid syntax") InvalidFragment (fun _ _ -> InvalidFragment) attributeIntro + +/// Fragment header keywords and their corresponding fragment parsers. +let private fragments = + [ + (qsImmutableBinding, letStatement) + (qsMutableBinding, mutableStatement) + (qsValueUpdate, setStatement) + (qsReturn, returnStatement) + (qsFail, failStatement) + (qsIf, ifClause) + (qsElif, elifClause) + (qsElse, elseClause) + (qsFor, forHeader) + (qsWhile, whileHeader) + (qsRepeat, repeatHeader) + (qsUntil, untilSuccess) + (qsWithin, withinHeader) + (qsApply, applyHeader) + (qsUsing, usingHeader) + (qsBorrowing, borrowingHeader) + (namespaceDeclHeader, namespaceDeclaration) + (typeDeclHeader, udtDeclaration) + (opDeclHeader, operationDeclaration) + (fctDeclHeader, functionDeclaration) + (ctrlAdjDeclHeader, controlledAdjointDeclaration) // needs to be before adjointDeclaration and controlledDeclaration! + (adjDeclHeader, adjointDeclaration) + (ctrlDeclHeader, controlledDeclaration) + (bodyDeclHeader, bodyDeclaration) + (importDirectiveHeader, openDirective) + + // This fragment header does not have its own fragment kind. Instead, it is only parsed as part of another + // fragment kind. If this header occurs by itself, without the other header it's a part of, an invalid fragment + // should be created. Since it's at the end of the list, we know that all of the other fragment kinds have been + // tried first. + (qsInternal, buildInvalidFragment qsInternal.parse) + ] + +do + // Make sure all of the fragment header keywords are listed above. + let implementedHeaders = (List.map (fun (keyword, _) -> keyword.id) fragments).ToImmutableHashSet() + let existingHeaders = Keywords.FragmentHeaders.ToImmutableHashSet() + if (implementedHeaders.SymmetricExcept existingHeaders).Count <> 0 then + System.NotImplementedException "mismatch between existing Q# fragments and implemented Q# fragments" |> raise /// Uses buildFragment to parse a Q# expression statement as QsFragment. @@ -455,8 +491,7 @@ let private expressionStatement = errRange |> QsCompilerDiagnostic.Error (ErrorCode.NonCallExprAsStatement, []) |> preturn >>= pushDiagnostic let anyExpr = getPosition .>>. expr >>= errOnNonCallLike >>% InvalidFragment // keeping this as unknown fragment so no further type checking is done attempt callLikeExpr |>> ExpressionStatement <|> anyExpr - buildFragment (lookAhead valid) valid invalid id eof // let's limit this to call like expressions - + buildFragment (lookAhead valid) valid invalid (fun _ -> id) eof // let's limit this to call like expressions // externally called routines @@ -464,12 +499,7 @@ let private expressionStatement = /// Raises a suitable error and returns UnknownStatement as QsFragment if the parsing fails. let internal codeFragment = let validFragment = - choice (getFragments() |> List.map snd) + choice (fragments |> List.map snd) <|> attributeDeclaration - <|> expressionStatement// the expressionStatement needs to be last - let invalidFragment = - let valid = fun _ -> InvalidFragment - buildFragment (preturn ()) (fail "invalid syntax") InvalidFragment valid attributeIntro - attempt validFragment <|> invalidFragment - - + <|> expressionStatement // the expressionStatement needs to be last + attempt validFragment <|> buildInvalidFragment (preturn ()) diff --git a/src/QsCompiler/TextProcessor/QsKeywords.fs b/src/QsCompiler/TextProcessor/QsKeywords.fs index ed0dc00bde..b873770526 100644 --- a/src/QsCompiler/TextProcessor/QsKeywords.fs +++ b/src/QsCompiler/TextProcessor/QsKeywords.fs @@ -200,6 +200,9 @@ let typeDeclHeader = addFragmentHeader Declarations.Type /// keyword for a Q# declaration (QsFragmentHeader) let namespaceDeclHeader = addFragmentHeader Declarations.Namespace +/// keyword for a Q# declaration modifier (QsFragmentHeader) +let qsInternal = addFragmentHeader Declarations.Internal + // directives /// keyword for a Q# directive (QsFragmentHeader) diff --git a/src/QsCompiler/TextProcessor/SyntaxBuilder.fs b/src/QsCompiler/TextProcessor/SyntaxBuilder.fs index 04bf21a1d4..67fcb67e11 100644 --- a/src/QsCompiler/TextProcessor/SyntaxBuilder.fs +++ b/src/QsCompiler/TextProcessor/SyntaxBuilder.fs @@ -424,14 +424,17 @@ let private filterAndAdapt (diagnostics : QsCompilerDiagnostic list) endPos = |> List.map (fun diagnostic -> diagnostic.WithRange(rangeWithinFragment diagnostic.Range)) /// Constructs a QsCodeFragment. -/// If the given header succeeds, attempts the given body parser to obtain the argument to construct the QsFragment using the given -/// fragmentKind constructor, or defaults to the given invalid fragment. -/// Generates a suitable error if the body parser fails. -/// If the body parser succeeds, advances until the next fragment header or until the given continuation succeeds, -/// generating an ExcessContinuation error for any non-whitespace code. -/// Determines the Range and Text for the fragment and attaches all current diagnostics saved in the user state to the QsFragment. -/// Upon fragment construction, clears all diagnostics currently stored in the UserState. -let internal buildFragment header body (invalid : QsFragmentKind) (fragmentKind : 'a -> QsFragmentKind) continuation = +/// +/// If the given header parser succeeds, attempts the given body parser. +/// +/// If the body parser succeeds, gives the results from both the header and body to the given fragmentKind constructor, +/// and advances until the next fragment header or until the given continuation succeeds, generating an +/// ExcessContinuation error for any non-whitespace code. Otherwise, if the body parser fails, defaults to the given +/// invalid fragment and generates a diagnostic. +/// +/// Determines the Range and Text for the fragment and attaches all current diagnostics saved in the user state to the +/// QsFragment. Upon fragment construction, clears all diagnostics currently stored in the UserState. +let internal buildFragment header body (invalid : QsFragmentKind) fragmentKind continuation = let build (kind, (startPos, (text, endPos))) = getUserState .>> clearDiagnostics |>> fun diagnostics -> @@ -449,18 +452,13 @@ let internal buildFragment header body (invalid : QsFragmentKind) (fragmentKind (getPosition .>>. fragmentEnd) |> runOnSubstream state let continuation = (continuation >>% ()) <|> (qsFragmentHeader >>% ()) - let validBody state = + let validBody state headerResult = let processExcessCode = buildError (skipInvalidUntil continuation) ErrorCode.ExcessContinuation >>% () (body .>>? followedBy (continuation <|> eof)) <|> (body .>> processExcessCode) - |>> fragmentKind .>>. delimiters state + |>> fragmentKind headerResult .>>. delimiters state let invalidBody state = getPosition .>> (advanceTo continuation) .>>. delimiters state >>= buildDiagnostic getCharStreamState >>= fun state -> - header >>. (attempt (validBody state) <|> invalidBody state) >>= build - - - - - - + header >>= fun headerResult -> + (attempt (validBody state headerResult) <|> invalidBody state) >>= build diff --git a/src/QsCompiler/Transformations/ClassicallyControlled.cs b/src/QsCompiler/Transformations/ClassicallyControlled.cs index b8d47bc3d1..c39d2838b7 100644 --- a/src/QsCompiler/Transformations/ClassicallyControlled.cs +++ b/src/QsCompiler/Transformations/ClassicallyControlled.cs @@ -9,7 +9,6 @@ using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.Transformations.Core; -using Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace; namespace Microsoft.Quantum.QsCompiler.Transformations.ClassicallyControlled @@ -20,7 +19,7 @@ namespace Microsoft.Quantum.QsCompiler.Transformations.ClassicallyControlled /// /// This transformation works in two passes. - /// 1st Pass: Hoist the contents of conditional statements into separate operations, where possible. + /// 1st Pass: Lift 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 interface calls, where possible. @@ -30,7 +29,7 @@ public static class ReplaceClassicalControl { public static QsCompilation Apply(QsCompilation compilation) { - compilation = HoistTransformation.Apply(compilation); + compilation = LiftConditionBlocks.Apply(compilation); return ConvertConditions.Apply(compilation); } @@ -73,6 +72,29 @@ private class StatementTransformation : StatementTransformation parent) : base(parent) { } + /// + /// Get the combined type resolutions for a pair of nested resolutions, + /// resolving references in the inner resolutions to the outer resolutions. + /// + private TypeArgsResolution GetCombinedTypeResolution(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(); + } + /// /// Checks if the scope is valid for conversion to an operation call from the conditional control API. /// It is valid if there is exactly one statement in it and that statement is a call like expression statement. @@ -95,7 +117,7 @@ public StatementTransformation(SyntaxTreeTransformation par var callTypeArguments = expr.Item.TypeArguments; var idTypeArguments = call.Item1.TypeArguments; - var combinedTypeArguments = Utils.GetCombinedTypeResolution(callTypeArguments, idTypeArguments); + var combinedTypeArguments = GetCombinedTypeResolution(callTypeArguments, idTypeArguments); // This relies on anything having type parameters must be a global callable. var newCallIdentifier = call.Item1; @@ -133,6 +155,56 @@ public StatementTransformation(SyntaxTreeTransformation par return (false, null, null); } + /// + /// Gets an identifier and argument tuple for the built-in operation NoOp. + /// + private (TypedExpression, TypedExpression) GetNoOp() + { + var opInfo = BuiltIn.NoOp; + + var properties = new[] { OpProperty.Adjointable, OpProperty.Controllable }; + var characteristics = new CallableInformation( + ResolvedCharacteristics.FromProperties(properties), + new InferredCallableInformation(((BuiltInKind.Operation)opInfo.Kind).IsSelfAdjoint, false)); + + var unitType = ResolvedType.New(ResolvedTypeKind.UnitType); + var operationType = ResolvedType.New(ResolvedTypeKind.NewOperation( + Tuple.Create(unitType, unitType), + characteristics)); + + var args = new TypedExpression( + ExpressionKind.UnitValue, + TypeArgsResolution.Empty, + unitType, + new InferredExpressionInformation(false, false), + QsNullable>.Null); + var typeArgs = ImmutableArray.Create(unitType); + + var identifier = new TypedExpression( + ExpressionKind.NewIdentifier( + Identifier.NewGlobalCallable(opInfo.FullName), + QsNullable>.NewValue(typeArgs)), + typeArgs + .Zip(((BuiltInKind.Operation)opInfo.Kind).TypeParameters, (type, param) => Tuple.Create(opInfo.FullName, param, type)) + .ToImmutableArray(), + operationType, + new InferredExpressionInformation(false, false), + QsNullable>.Null); + + return (identifier, args); + } + + /// + /// Creates a value tuple expression containing the given expressions. + /// + private 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, expressions.Any(exp => exp.InferredInformation.HasLocalQuantumDependency)), + QsNullable>.Null); + #region Condition Converting Logic /// @@ -141,13 +213,28 @@ public StatementTransformation(SyntaxTreeTransformation par /// private TypedExpression CreateControlCall(BuiltIn opInfo, IEnumerable properties, TypedExpression args, IEnumerable typeArgs) { + var characteristics = new CallableInformation( + ResolvedCharacteristics.FromProperties(properties), + new InferredCallableInformation(((BuiltInKind.Operation)opInfo.Kind).IsSelfAdjoint, false)); + + var unitType = ResolvedType.New(ResolvedTypeKind.UnitType); + var operationType = ResolvedType.New(ResolvedTypeKind.NewOperation( + Tuple.Create(args.ResolvedType, unitType), + characteristics)); + // Build the surrounding control call - var identifier = Utils.CreateIdentifierExpression( - Identifier.NewGlobalCallable(new QsQualifiedName(opInfo.Namespace, opInfo.Name)), + var identifier = new TypedExpression( + ExpressionKind.NewIdentifier( + Identifier.NewGlobalCallable(opInfo.FullName), + typeArgs.Any() + ? QsNullable>.NewValue(typeArgs.ToImmutableArray()) + : QsNullable>.Null), typeArgs - .Zip(opInfo.TypeParameters, (type, param) => Tuple.Create(new QsQualifiedName(opInfo.Namespace, opInfo.Name), param, type)) + .Zip(((BuiltInKind.Operation)opInfo.Kind).TypeParameters, (type, param) => Tuple.Create(opInfo.FullName, param, type)) .ToImmutableArray(), - Utils.GetOperationType(properties, args.ResolvedType)); + operationType, + new InferredExpressionInformation(false, false), + QsNullable>.Null); // Creates type resolutions for the call expression var opTypeArgResolutions = typeArgs @@ -165,7 +252,12 @@ x.Resolution is ResolvedTypeKind.TupleType tup }) .ToImmutableArray(); - return Utils.CreateCallLikeExpression(identifier, args, opTypeArgResolutions); + return new TypedExpression( + ExpressionKind.NewCallLikeExpression(identifier, args), + opTypeArgResolutions, + unitType, + new InferredExpressionInformation(false, true), + QsNullable>.Null); } /// @@ -191,11 +283,11 @@ private TypedExpression CreateApplyConditionallyExpression(TypedExpression condi if (equalityScope == null) { - (equalityId, equalityArgs) = Utils.GetNoOp(); + (equalityId, equalityArgs) = GetNoOp(); } else if (inequalityScope == null) { - (inequalityId, inequalityArgs) = Utils.GetNoOp(); + (inequalityId, inequalityArgs) = GetNoOp(); } // Get characteristic properties from global id @@ -228,11 +320,25 @@ private TypedExpression CreateApplyConditionallyExpression(TypedExpression condi controlOpInfo = BuiltIn.ApplyConditionally; } - var equality = Utils.CreateValueTupleExpression(equalityId, equalityArgs); - var inequality = Utils.CreateValueTupleExpression(inequalityId, inequalityArgs); - var controlArgs = Utils.CreateValueTupleExpression(Utils.CreateValueArray(conditionExpr1), Utils.CreateValueArray(conditionExpr2), equality, inequality); + // Takes a single TypedExpression of type Result and puts in into a + // value array expression with the given expression as its only item. + TypedExpression BoxResultInArray(TypedExpression expression) => + new TypedExpression( + ExpressionKind.NewValueArray(ImmutableArray.Create(expression)), + TypeArgsResolution.Empty, + ResolvedType.New(ResolvedTypeKind.NewArrayType(ResolvedType.New(ResolvedTypeKind.Result))), + new InferredExpressionInformation(false, expression.InferredInformation.HasLocalQuantumDependency), + QsNullable>.Null); + + var equality = CreateValueTupleExpression(equalityId, equalityArgs); + var inequality = CreateValueTupleExpression(inequalityId, inequalityArgs); + var controlArgs = CreateValueTupleExpression( + BoxResultInArray(conditionExpr1), + BoxResultInArray(conditionExpr2), + equality, + inequality); var targetArgsTypes = ImmutableArray.Create(equalityArgs.ResolvedType, inequalityArgs.ResolvedType); - + return CreateControlCall(controlOpInfo, props, controlArgs, targetArgsTypes); } @@ -285,10 +391,10 @@ private TypedExpression CreateApplyIfExpression(QsResult result, TypedExpression (TypedExpression, ImmutableArray) GetArgs(TypedExpression zeroId, TypedExpression zeroArgs, TypedExpression oneId, TypedExpression oneArgs) => ( - Utils.CreateValueTupleExpression( + CreateValueTupleExpression( conditionExpression, - Utils.CreateValueTupleExpression(zeroId, zeroArgs), - Utils.CreateValueTupleExpression(oneId, oneArgs)), + CreateValueTupleExpression(zeroId, zeroArgs), + CreateValueTupleExpression(oneId, oneArgs)), ImmutableArray.Create(zeroArgs.ResolvedType, oneArgs.ResolvedType) ); @@ -324,9 +430,9 @@ private TypedExpression CreateApplyIfExpression(QsResult result, TypedExpression : BuiltIn.ApplyIfOne; } - controlArgs = Utils.CreateValueTupleExpression( + controlArgs = CreateValueTupleExpression( conditionExpression, - Utils.CreateValueTupleExpression(conditionId, conditionArgs)); + CreateValueTupleExpression(conditionId, conditionArgs)); targetArgsTypes = ImmutableArray.Create(conditionArgs.ResolvedType); } @@ -387,7 +493,7 @@ private QsStatement ConvertConditionalToControlCall(QsStatement statement) // The scope arguments are reversed to account for the negation of the NEQ return CreateControlStatement(statement, CreateApplyConditionallyExpression(lhsConditionExpression, rhsConditionExpression, defaultScope, conditionScope)); } - + // ToDo: Diagnostic message return statement; // The condition does not fit a supported format. } @@ -519,15 +625,11 @@ private bool IsConditionedOnResultInequalityExpression(TypedExpression expressio var subCondition = new QsConditionalStatement(conditionStatment.ConditionalBlocks.RemoveAt(0), conditionStatment.Default); var secondConditionBlock = conditionStatment.ConditionalBlocks[1].Item2; - - var subIfStatment = new QsStatement - ( + var subIfStatment = new QsStatement( QsStatementKind.NewQsConditionalStatement(subCondition), LocalDeclarations.Empty, secondConditionBlock.Location, - secondConditionBlock.Comments - ); - + secondConditionBlock.Comments); var newDefault = QsNullable.NewValue(new QsPositionedBlock( new QsScope(ImmutableArray.Create(subIfStatment), secondConditionBlock.Body.KnownSymbols), secondConditionBlock.Location, @@ -550,13 +652,11 @@ private bool IsConditionedOnResultInequalityExpression(TypedExpression expressio if (condition.Expression is ExpressionKind.OR orCondition) { var subCondition = new QsConditionalStatement(ImmutableArray.Create(Tuple.Create(orCondition.Item2, block)), conditionStatment.Default); - var subIfStatment = new QsStatement - ( + var subIfStatment = new QsStatement( QsStatementKind.NewQsConditionalStatement(subCondition), LocalDeclarations.Empty, block.Location, - QsComments.Empty - ); + QsComments.Empty); var newDefault = QsNullable.NewValue(new QsPositionedBlock( new QsScope(ImmutableArray.Create(subIfStatment), block.Body.KnownSymbols), block.Location, @@ -584,13 +684,11 @@ private bool IsConditionedOnResultInequalityExpression(TypedExpression expressio if (condition.Expression is ExpressionKind.AND andCondition) { var subCondition = new QsConditionalStatement(ImmutableArray.Create(Tuple.Create(andCondition.Item2, block)), conditionStatment.Default); - var subIfStatment = new QsStatement - ( + var subIfStatment = new QsStatement( QsStatementKind.NewQsConditionalStatement(subCondition), LocalDeclarations.Empty, block.Location, - QsComments.Empty - ); + QsComments.Empty); var newBlock = new QsPositionedBlock( new QsScope(ImmutableArray.Create(subIfStatment), block.Body.KnownSymbols), block.Location, @@ -621,13 +719,11 @@ private QsStatement ReshapeConditional(QsStatement statement) (wasAndProcessed, stm) = ProcessAND(stm); } while (wasOrProcessed || wasAndProcessed); - return new QsStatement - ( + return new QsStatement( QsStatementKind.NewQsConditionalStatement(stm), statement.SymbolDeclarations, statement.Location, - statement.Comments - ); + statement.Comments); } return statement; } @@ -661,394 +757,56 @@ public override QsScope OnScope(QsScope scope) } } - - /// - /// 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. - /// - internal static class HoistTransformation // this class can be made public once its functionality is no longer tied to the classically-controlled transformation + internal static class LiftConditionBlocks { - internal static QsCompilation Apply(QsCompilation compilation) => HoistContents.Apply(compilation); - - private class HoistContents : SyntaxTreeTransformation + public static QsCompilation Apply(QsCompilation compilation) { - public static QsCompilation Apply(QsCompilation compilation) - { - var filter = new HoistContents(); - - return new QsCompilation(compilation.Namespaces.Select(ns => filter.Namespaces.OnNamespace(ns)).ToImmutableArray(), compilation.EntryPoints); - } - - public class CallableDetails - { - public QsCallable Callable; - public QsSpecialization Adjoint; - public QsSpecialization Controlled; - public QsSpecialization ControlledAdjoint; - public QsNullable> TypeParamTypes; + var filter = new LiftContent(); - 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; - } - } + return new QsCompilation(compilation.Namespaces.Select(ns => filter.Namespaces.OnNamespace(ns)).ToImmutableArray(), compilation.EntryPoints); + } - public class TransformationState + private class LiftContent : ContentLifting.LiftContent + { + internal class TransformationState : ContentLifting.LiftContent.TransformationState { - public bool IsValidScope = true; - public List ControlOperations = null; - public ImmutableArray>> CurrentHoistParams = - ImmutableArray>>.Empty; - public bool ContainsHoistParamRef = false; - - public CallableDetails CurrentCallable = null; - public bool InBody = false; - public bool InAdjoint = false; - public bool InControlled = false; - public bool InWithinBlock = false; - - 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); - } - - public (QsCallable, ResolvedType) GenerateOperation(QsScope contents) - { - var newName = UniqueVariableNames.PrependGuid(CurrentCallable.Callable.FullName); - - 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 = UpdateGeneratedOp.Apply(controlCallable, knownVariables, CurrentCallable.Callable.FullName, newName); - - return (updatedCallable, signature.ArgumentType); - } + internal bool IsConditionLiftable = false; } - private HoistContents() : base(new TransformationState()) + public LiftContent() : base(new TransformationState()) { - this.Namespaces = new NamespaceTransformation(this); this.StatementKinds = new StatementKindTransformation(this); - this.Expressions = new ExpressionTransformation(this); - this.ExpressionKinds = new ExpressionKindTransformation(this); - this.Types = new TypeTransformation(this, TransformationOptions.Disabled); - } - - private class NamespaceTransformation : NamespaceTransformation - { - public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) { } - - public override QsCallable OnCallableDeclaration(QsCallable c) - { - SharedState.CurrentCallable = new CallableDetails(c); - return base.OnCallableDeclaration(c); - } - - public override QsSpecialization OnBodySpecialization(QsSpecialization spec) - { - SharedState.InBody = true; - var rtrn = base.OnBodySpecialization(spec); - SharedState.InBody = false; - return rtrn; - } - - public override QsSpecialization OnAdjointSpecialization(QsSpecialization spec) - { - SharedState.InAdjoint = true; - var rtrn = base.OnAdjointSpecialization(spec); - SharedState.InAdjoint = false; - return rtrn; - } - - public override QsSpecialization OnControlledSpecialization(QsSpecialization spec) - { - SharedState.InControlled = true; - var rtrn = base.OnControlledSpecialization(spec); - SharedState.InControlled = false; - return rtrn; - } - - public override QsCallable OnFunction(QsCallable c) => c; // Prevent anything in functions from being hoisted - - public override QsNamespace OnNamespace(QsNamespace ns) - { - // Control operations list will be populated in the transform - SharedState.ControlOperations = new List(); - return base.OnNamespace(ns) - .WithElements(elems => elems.AddRange(SharedState.ControlOperations.Select(op => QsNamespaceElement.NewQsCallable(op)))); - } } - private class StatementKindTransformation : StatementKindTransformation + private new class StatementKindTransformation : ContentLifting.LiftContent.StatementKindTransformation { public StatementKindTransformation(SyntaxTreeTransformation parent) : base(parent) { } - private (QsCallable, QsStatement) HoistBody(QsScope body) - { - var (targetOp, originalArgumentType) = SharedState.GenerateOperation(body); - var targetOpType = ResolvedType.New(ResolvedTypeKind.NewOperation( - Tuple.Create( - originalArgumentType, - ResolvedType.New(ResolvedTypeKind.UnitType)), - targetOp.Signature.Information)); - - var targetTypeArgTypes = SharedState.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 = body.KnownSymbols.Variables; - - TypedExpression targetArgs = null; - if (knownSymbols.Any()) - { - targetArgs = Utils.CreateValueTupleExpression(knownSymbols.Select(var => Utils.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(SharedState.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)); - } - - // ToDo: This logic should be externalized at some point to make the Hoisting more general 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 = SharedState.InWithinBlock; - SharedState.InWithinBlock = true; - var (_, outer) = this.OnPositionedBlock(QsNullable.Null, stm.OuterTransformation); - SharedState.InWithinBlock = superInWithinBlock; - - var (_, inner) = this.OnPositionedBlock(QsNullable.Null, stm.InnerTransformation); - - return QsStatementKind.NewQsConjugation(new QsConjugation(outer, inner)); - } - - public override QsStatementKind OnReturnStatement(TypedExpression ex) - { - SharedState.IsValidScope = false; - return base.OnReturnStatement(ex); - } - - public override QsStatementKind OnValueUpdate(QsValueUpdate stm) - { - // If lhs contains an identifier found in the scope's known variables (variables from the super-scope), the scope is not valid - var lhs = this.Expressions.OnTypedExpression(stm.Lhs); - - if (SharedState.ContainsHoistParamRef) - { - SharedState.IsValidScope = false; - } - - var rhs = this.Expressions.OnTypedExpression(stm.Rhs); - return QsStatementKind.NewQsValueUpdate(new QsValueUpdate(lhs, rhs)); + && call.Item1.ResolvedType.Resolution.IsOperation + && call.Item1.Expression is ExpressionKind.Identifier + && !TypedExpression.IsPartialApplication(expr.Item.Expression); } public override QsStatementKind OnConditionalStatement(QsConditionalStatement stm) { - var contextValidScope = SharedState.IsValidScope; - var contextHoistParams = SharedState.CurrentHoistParams; - - var isHoistValid = true; + var contextIsConditionLiftable = SharedState.IsConditionLiftable; + SharedState.IsConditionLiftable = true; var newConditionBlocks = new List>(); var generatedOperations = new List(); foreach (var conditionBlock in stm.ConditionalBlocks) { + var contextValidScope = SharedState.IsValidScope; + var contextParams = SharedState.GeneratedOpParams; + SharedState.IsValidScope = true; - SharedState.CurrentHoistParams = conditionBlock.Item2.Body.KnownSymbols.IsEmpty - ? ImmutableArray>>.Empty - : conditionBlock.Item2.Body.KnownSymbols.Variables; + SharedState.GeneratedOpParams = conditionBlock.Item2.Body.KnownSymbols.Variables; var (expr, block) = this.OnPositionedBlock(QsNullable.NewValue(conditionBlock.Item1), conditionBlock.Item2); @@ -1056,12 +814,16 @@ public override QsStatementKind OnConditionalStatement(QsConditionalStatement st // the condition logic for the conversion and using that condition here //var (isExprCondition, _, _) = IsConditionedOnResultLiteralExpression(expr.Item); - if (SharedState.IsValidScope) // if sub-scope is valid, hoist content + if (IsScopeSingleCall(block.Body)) + { + newConditionBlocks.Add(Tuple.Create(expr.Item, block)); + } + // ToDo: We may want to prevent empty blocks from getting lifted + else //if(block.Body.Statements.Length > 0) { - if (/*isExprCondition &&*/ !IsScopeSingleCall(block.Body)) + // Lift the scope to its own operation + if (SharedState.LiftBody(block.Body, out var callable, out var call)) { - // Hoist the scope to its own operation - var (callable, call) = HoistBody(block.Body); block = new QsPositionedBlock( new QsScope(ImmutableArray.Create(call), block.Body.KnownSymbols), block.Location, @@ -1069,33 +831,39 @@ public override QsStatementKind OnConditionalStatement(QsConditionalStatement st newConditionBlocks.Add(Tuple.Create(expr.Item, block)); generatedOperations.Add(callable); } - else if(block.Body.Statements.Length > 0) + else { - newConditionBlocks.Add(Tuple.Create(expr.Item, block)); + SharedState.IsConditionLiftable = false; } } - else - { - isHoistValid = false; - break; - } + + SharedState.GeneratedOpParams = contextParams; + SharedState.IsValidScope = contextValidScope; + + if (!SharedState.IsConditionLiftable) break; } var newDefault = QsNullable.Null; - if (isHoistValid && stm.Default.IsValue) + if (SharedState.IsConditionLiftable && stm.Default.IsValue) { + var contextValidScope = SharedState.IsValidScope; + var contextParams = SharedState.GeneratedOpParams; + SharedState.IsValidScope = true; - SharedState.CurrentHoistParams = stm.Default.Item.Body.KnownSymbols.IsEmpty - ? ImmutableArray>>.Empty - : stm.Default.Item.Body.KnownSymbols.Variables; + SharedState.GeneratedOpParams = stm.Default.Item.Body.KnownSymbols.Variables; var (_, block) = this.OnPositionedBlock(QsNullable.Null, stm.Default.Item); - if (SharedState.IsValidScope) + + if (IsScopeSingleCall(block.Body)) + { + newDefault = QsNullable.NewValue(block); + } + // ToDo: We may want to prevent empty blocks from getting lifted + else //if(block.Body.Statements.Length > 0) { - if (!IsScopeSingleCall(block.Body)) // if sub-scope is valid, hoist content + // Lift the scope to its own operation + if (SharedState.LiftBody(block.Body, out var callable, out var call)) { - // Hoist the scope to its own operation - var (callable, call) = HoistBody(block.Body); block = new QsPositionedBlock( new QsScope(ImmutableArray.Create(call), block.Body.KnownSymbols), block.Location, @@ -1103,184 +871,32 @@ public override QsStatementKind OnConditionalStatement(QsConditionalStatement st newDefault = QsNullable.NewValue(block); generatedOperations.Add(callable); } - else if(block.Body.Statements.Length > 0) + else { - newDefault = QsNullable.NewValue(block); + SharedState.IsConditionLiftable = false; } } - else - { - isHoistValid = false; - } + + SharedState.GeneratedOpParams = contextParams; + SharedState.IsValidScope = contextValidScope; } - if (isHoistValid) + if (SharedState.IsConditionLiftable) { - SharedState.ControlOperations.AddRange(generatedOperations); + SharedState.GeneratedOperations.AddRange(generatedOperations); } - SharedState.CurrentHoistParams = contextHoistParams; - SharedState.IsValidScope = contextValidScope; - - return isHoistValid + var rtrn = SharedState.IsConditionLiftable ? QsStatementKind.NewQsConditionalStatement( new QsConditionalStatement(newConditionBlocks.ToImmutableArray(), newDefault)) : QsStatementKind.NewQsConditionalStatement( new QsConditionalStatement(stm.ConditionalBlocks, stm.Default)); - } - public override QsStatementKind OnStatementKind(QsStatementKind kind) - { - SharedState.ContainsHoistParamRef = false; // Every statement kind starts off false - return base.OnStatementKind(kind); - } - } + SharedState.IsConditionLiftable = contextIsConditionLiftable; - private class ExpressionTransformation : ExpressionTransformation - { - public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent) { } - - public override TypedExpression OnTypedExpression(TypedExpression ex) - { - var contextContainsHoistParamRef = SharedState.ContainsHoistParamRef; - SharedState.ContainsHoistParamRef = false; - var rtrn = base.OnTypedExpression(ex); - - // If the sub context contains a reference, then the super context contains a reference, - // otherwise return the super context to its original value - if (!SharedState.ContainsHoistParamRef) - { - SharedState.ContainsHoistParamRef = contextContainsHoistParamRef; - } - - return rtrn; - } - } - - private class ExpressionKindTransformation : ExpressionKindTransformation - { - public ExpressionKindTransformation(SyntaxTreeTransformation parent) : base(parent) { } - - public override ExpressionKind OnIdentifier(Identifier sym, QsNullable> tArgs) - { - if (sym is Identifier.LocalVariable local && - SharedState.CurrentHoistParams.Any(param => param.VariableName.Equals(local.Item))) - { - SharedState.ContainsHoistParamRef = true; - } - return base.OnIdentifier(sym, tArgs); - } - } - } - - /// - /// 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 UpdateGeneratedOp : SyntaxTreeTransformation - { - public static QsCallable Apply(QsCallable qsCallable, ImmutableArray>> parameters, QsQualifiedName oldName, QsQualifiedName newName) - { - var filter = new UpdateGeneratedOp(parameters, oldName, newName); - - return filter.Namespaces.OnCallableDeclaration(qsCallable); - } - - public class TransformationState - { - public bool IsRecursiveIdentifier = false; - public readonly ImmutableArray>> Parameters; - public readonly QsQualifiedName OldName; - public readonly QsQualifiedName NewName; - - public TransformationState(ImmutableArray>> parameters, QsQualifiedName oldName, QsQualifiedName newName) - { - Parameters = parameters; - OldName = oldName; - NewName = newName; - } - } - - private UpdateGeneratedOp(ImmutableArray>> parameters, QsQualifiedName oldName, QsQualifiedName newName) - : base(new TransformationState(parameters, oldName, newName)) - { - this.Expressions = new ExpressionTransformation(this); - this.ExpressionKinds = new ExpressionKindTransformation(this); - this.Types = new TypeTransformation(this); - } - - private class ExpressionTransformation : ExpressionTransformation - { - public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent) { } - - public override ImmutableDictionary>, ResolvedType> OnTypeParamResolutions(ImmutableDictionary>, ResolvedType> typeParams) - { - // Prevent keys from having their names updated - return typeParams.ToImmutableDictionary(kvp => kvp.Key, kvp => this.Types.OnType(kvp.Value)); - } - - public override TypedExpression OnTypedExpression(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 && - SharedState.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 = SharedState.IsRecursiveIdentifier; - var rtrn = base.OnTypedExpression(ex); - SharedState.IsRecursiveIdentifier = isRecursiveIdentifier; - return rtrn; - } - } - - private class ExpressionKindTransformation : ExpressionKindTransformation - { - public ExpressionKindTransformation(SyntaxTreeTransformation parent) : base(parent) { } - - 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 && SharedState.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. - SharedState.IsRecursiveIdentifier = true; - } return rtrn; } } - - private class TypeTransformation : TypeTransformation - { - public TypeTransformation(SyntaxTreeTransformation parent) : base(parent) { } - - public override ResolvedTypeKind OnTypeParameter(QsTypeParameter tp) - { - // Reroute a type parameter's origin to the newly generated operation - if (!SharedState.IsRecursiveIdentifier && SharedState.OldName.Equals(tp.Origin)) - { - tp = new QsTypeParameter(SharedState.NewName, tp.TypeName, tp.Range); - } - - return base.OnTypeParameter(tp); - } - } } } } diff --git a/src/QsCompiler/Transformations/ClassicallyControlledUtils.cs b/src/QsCompiler/Transformations/ClassicallyControlledUtils.cs deleted file mode 100644 index 64fc309581..0000000000 --- a/src/QsCompiler/Transformations/ClassicallyControlledUtils.cs +++ /dev/null @@ -1,143 +0,0 @@ -// 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.ClassicallyControlled -{ - using ExpressionKind = QsExpressionKind; - using ResolvedTypeKind = QsTypeKind; - using TypeArgsResolution = ImmutableArray, ResolvedType>>; - - /// - /// These tools are specific to the classically-controlled transformation and are not intended for wider use in their current state. - /// They rely on the specific context in which they are invoked during that transformation and are not general purpuse tools. - /// - internal static class Utils - { - internal 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 - ); - - internal 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 - ); - - internal static TypedExpression CreateCallLikeExpression(TypedExpression id, TypedExpression args, TypeArgsResolution typeRes) => - new TypedExpression - ( - ExpressionKind.NewCallLikeExpression(id, args), - typeRes, - ResolvedType.New(ResolvedTypeKind.UnitType), - new InferredExpressionInformation(false, true), - QsNullable>.Null - ); - - /// - /// Creates an array literal with the given items, setting the range information to Null. - /// If no items are given, creates an empty array of type Unit[]. - /// The resolved types for all of the given expressions must match, - /// none of the given expressions should have a local quantum dependency, - /// and all range information should be stripped from each given expression. - /// - internal static TypedExpression CreateValueArray(params TypedExpression[] expressions) - { - ResolvedType type = null; - if (expressions.Any()) - { - type = expressions.First().ResolvedType; - QsCompilerError.Verify(expressions.All(expr => expr.ResolvedType.Equals(type))); - } - else - { - type = ResolvedType.New(ResolvedTypeKind.UnitType); - } - - return new TypedExpression - ( - ExpressionKind.NewValueArray(expressions.ToImmutableArray()), - TypeArgsResolution.Empty, - ResolvedType.New(ResolvedTypeKind.NewArrayType(type)), - new InferredExpressionInformation(false, false), - QsNullable>.Null - ); - } - - internal static ResolvedType GetOperationType(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)); - } - - internal static TypeArgsResolution GetCombinedTypeResolution(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(); - } - - internal static (TypedExpression, TypedExpression) GetNoOp() - { - var identifier = Utils.CreateIdentifierExpression( - Identifier.NewGlobalCallable(new QsQualifiedName(BuiltIn.NoOp.Namespace, BuiltIn.NoOp.Name)), - BuiltIn.NoOp.TypeParameters - .Select(param => Tuple.Create(new QsQualifiedName(BuiltIn.NoOp.Namespace, BuiltIn.NoOp.Name), param, ResolvedType.New(ResolvedTypeKind.UnitType))) - .ToImmutableArray(), - Utils.GetOperationType(new[] { OpProperty.Adjointable, OpProperty.Controllable }, ResolvedType.New(ResolvedTypeKind.UnitType))); - - var args = new TypedExpression - ( - ExpressionKind.UnitValue, - TypeArgsResolution.Empty, - ResolvedType.New(ResolvedTypeKind.UnitType), - new InferredExpressionInformation(false, false), - QsNullable>.Null - ); - - return (identifier, args); - } - } -} diff --git a/src/QsCompiler/Transformations/ContentLifting.cs b/src/QsCompiler/Transformations/ContentLifting.cs new file mode 100644 index 0000000000..c44f3cde4a --- /dev/null +++ b/src/QsCompiler/Transformations/ContentLifting.cs @@ -0,0 +1,583 @@ +// 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; +using Microsoft.Quantum.QsCompiler.Transformations.Core; +using Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace; + + +namespace Microsoft.Quantum.QsCompiler.Transformations.ContentLifting +{ + using ExpressionKind = QsExpressionKind; + using ResolvedTypeKind = QsTypeKind; + using TypeArgsResolution = ImmutableArray, ResolvedType>>; + + /// + /// Static class to accumulate all type parameter independent subclasses used by LiftContent. + /// + public static class LiftContent + { + internal class CallableDetails + { + internal readonly QsCallable Callable; + internal readonly QsSpecialization Adjoint; + internal readonly QsSpecialization Controlled; + internal readonly QsSpecialization ControlledAdjoint; + internal readonly QsNullable> TypeParameters; + + internal CallableDetails(QsCallable callable) + { + Callable = callable; + // ToDo: this may need to be adapted once we support type specializations + Adjoint = callable.Specializations.FirstOrDefault(spec => spec.Kind == QsSpecializationKind.QsAdjoint); + Controlled = callable.Specializations.FirstOrDefault(spec => spec.Kind == QsSpecializationKind.QsControlled); + ControlledAdjoint = callable.Specializations.FirstOrDefault(spec => spec.Kind == QsSpecializationKind.QsControlledAdjoint); + // ToDo: this may need to be per-specialization + TypeParameters = 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; + } + } + + public class TransformationState + { + // ToDo: It should be possible to make these three properties private, + // if we absorb the corresponding logic into LiftBody. + public bool IsValidScope = true; + internal bool ContainsParamRef = false; + internal ImmutableArray>> GeneratedOpParams = + ImmutableArray>>.Empty; + + internal CallableDetails CurrentCallable = null; + + protected internal bool InBody = false; + protected internal bool InAdjoint = false; + protected internal bool InControlled = false; + protected internal bool InControlledAdjoint = false; + protected internal bool InWithinBlock = false; + + protected internal List GeneratedOperations = null; + + private (ResolvedSignature, IEnumerable) MakeSpecializations( + QsQualifiedName callableName, ResolvedType paramsType, 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; + bool isSelfAdjoint = false; + + if (InWithinBlock) + { + addAdjoint = true; + addControlled = false; + } + else if (InBody) + { + if (adj != null && adj.Implementation is SpecializationImplementation.Generated adjGen) + { + addAdjoint = adjGen.Item.IsInvert; + isSelfAdjoint = adjGen.Item.IsSelfInverse; + } + if (ctl != null && ctl.Implementation is SpecializationImplementation.Generated ctlGen) addControlled = ctlGen.Item.IsDistribute; + if (ctlAdj != null && ctlAdj.Implementation is SpecializationImplementation.Generated ctlAdjGen) + { + addAdjoint = addAdjoint || ctlAdjGen.Item.IsInvert && ctl.Implementation.IsGenerated; + addControlled = addControlled || ctlAdjGen.Item.IsDistribute && adj.Implementation.IsGenerated; + isSelfAdjoint = isSelfAdjoint || ctlAdjGen.Item.IsSelfInverse; + } + } + else if (ctlAdj != null && ctlAdj.Implementation is SpecializationImplementation.Generated gen) + { + addControlled = InAdjoint && gen.Item.IsDistribute; + addAdjoint = InControlled && gen.Item.IsInvert; + isSelfAdjoint = gen.Item.IsSelfInverse; + } + + var props = new List(); + if (addAdjoint) props.Add(OpProperty.Adjointable); + if (addControlled) props.Add(OpProperty.Controllable); + var newSig = new ResolvedSignature( + CurrentCallable.Callable.Signature.TypeParameters, + paramsType, + ResolvedType.New(ResolvedTypeKind.UnitType), + new CallableInformation(ResolvedCharacteristics.FromProperties(props), new InferredCallableInformation(isSelfAdjoint, false))); + + 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 = UniqueVariableNames.PrependGuid(CurrentCallable.Callable.FullName); + + var knownVariables = contents.KnownSymbols.Variables; + + var parameters = QsTuple>.NewQsTuple(knownVariables + .Select(var => QsTuple>.NewQsTupleItem(new LocalVariableDeclaration( + QsLocalSymbol.NewValidName(var.VariableName), + var.Type, + new InferredExpressionInformation(false, false), + 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 generatedCallable = new QsCallable( + QsCallableKind.Operation, + newName, + ImmutableArray.Empty, + new Modifiers(AccessModifier.Internal), + CurrentCallable.Callable.SourceFile, + QsNullable.Null, + signature, + parameters, + specializations.ToImmutableArray(), + ImmutableArray.Empty, + QsComments.Empty); + + // Change the origin of all type parameter references to use the new name and make all variables immutable + generatedCallable = UpdateGeneratedOp.Apply(generatedCallable, knownVariables, CurrentCallable.Callable.FullName, newName); + + return (generatedCallable, signature.ArgumentType); + } + + /// + /// Generates a new operation with the body's contents. All the known variables at the + /// start of the block will become parameters to the new operation, and the operation + /// will have all the valid type parameters of the calling context as type parameters. + /// The generated operation is returned, along with a call to the new operation is + /// also returned with all the type parameters and known variables being forwarded to + /// the new operation as arguments. + /// + /// The given body should be validated with the SharedState.IsValidScope before using this function. + /// + public bool LiftBody(QsScope body, out QsCallable callable, out QsStatement callStatement) + { + if (!IsValidScope) + { + callable = null; + callStatement = null; + return false; + } + + var (generatedOp, originalArgumentType) = GenerateOperation(body); + var generatedOpType = ResolvedType.New(ResolvedTypeKind.NewOperation( + Tuple.Create( + originalArgumentType, + ResolvedType.New(ResolvedTypeKind.UnitType)), + generatedOp.Signature.Information)); + + // Forward the type parameters of the parent callable to the type arguments of the call to the generated operation. + var typeArguments = CurrentCallable.TypeParameters; + var generatedOpId = new TypedExpression( + ExpressionKind.NewIdentifier( + Identifier.NewGlobalCallable(generatedOp.FullName), + typeArguments), + typeArguments.IsNull + ? TypeArgsResolution.Empty + : typeArguments.Item + .Select(type => Tuple.Create(generatedOp.FullName, ((ResolvedTypeKind.TypeParameter)type.Resolution).Item.TypeName, type)) + .ToImmutableArray(), + generatedOpType, + new InferredExpressionInformation(false, false), + QsNullable>.Null); + + var knownSymbols = body.KnownSymbols.Variables; + TypedExpression arguments = null; + if (knownSymbols.Any()) + { + var argumentArray = knownSymbols + .Select(var => new TypedExpression( + ExpressionKind.NewIdentifier( + Identifier.NewLocalVariable(var.VariableName), + QsNullable>.Null), + TypeArgsResolution.Empty, + var.Type, + var.InferredInformation, + QsNullable>.Null)) + .ToImmutableArray(); + + arguments = new TypedExpression( + ExpressionKind.NewValueTuple(argumentArray), + TypeArgsResolution.Empty, + ResolvedType.New(ResolvedTypeKind.NewTupleType(argumentArray.Select(expr => expr.ResolvedType).ToImmutableArray())), + new InferredExpressionInformation(false, argumentArray.Any(exp => exp.InferredInformation.HasLocalQuantumDependency)), + QsNullable>.Null); + } + else + { + arguments = new TypedExpression( + ExpressionKind.UnitValue, + TypeArgsResolution.Empty, + ResolvedType.New(ResolvedTypeKind.UnitType), + new InferredExpressionInformation(false, false), + QsNullable>.Null); + } + + var call = new TypedExpression( + ExpressionKind.NewCallLikeExpression(generatedOpId, arguments), + typeArguments.IsNull + ? TypeArgsResolution.Empty + : typeArguments.Item + .Select(type => Tuple.Create(CurrentCallable.Callable.FullName, ((ResolvedTypeKind.TypeParameter)type.Resolution).Item.TypeName, type)) + .ToImmutableArray(), + ResolvedType.New(ResolvedTypeKind.UnitType), + new InferredExpressionInformation(false, true), + QsNullable>.Null); + + // set output parameters + callable = generatedOp; + callStatement = new QsStatement( + QsStatementKind.NewQsExpressionStatement(call), + LocalDeclarations.Empty, + QsNullable.Null, + QsComments.Empty); + + return true; + } + } + + /// + /// 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 and HasLocalQuantumDependency info on parameter references to be false + /// + private class UpdateGeneratedOp : SyntaxTreeTransformation + { + public static QsCallable Apply(QsCallable qsCallable, ImmutableArray>> parameters, QsQualifiedName oldName, QsQualifiedName newName) + { + var filter = new UpdateGeneratedOp(parameters, oldName, newName); + + return filter.Namespaces.OnCallableDeclaration(qsCallable); + } + + public class TransformationState + { + public bool IsRecursiveIdentifier = false; + public readonly ImmutableArray>> Parameters; + public readonly QsQualifiedName OldName; + public readonly QsQualifiedName NewName; + + public TransformationState(ImmutableArray>> parameters, QsQualifiedName oldName, QsQualifiedName newName) + { + Parameters = parameters; + OldName = oldName; + NewName = newName; + } + } + + private UpdateGeneratedOp(ImmutableArray>> parameters, QsQualifiedName oldName, QsQualifiedName newName) + : base(new TransformationState(parameters, oldName, newName)) + { + this.Expressions = new ExpressionTransformation(this); + this.ExpressionKinds = new ExpressionKindTransformation(this); + this.Types = new TypeTransformation(this); + } + + private class ExpressionTransformation : ExpressionTransformation + { + public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent) { } + + public override ImmutableDictionary>, ResolvedType> OnTypeParamResolutions(ImmutableDictionary>, ResolvedType> typeParams) + { + // Prevent keys from having their names updated + return typeParams.ToImmutableDictionary(kvp => kvp.Key, kvp => this.Types.OnType(kvp.Value)); + } + + public override TypedExpression OnTypedExpression(TypedExpression ex) + { + // Checks if expression is mutable identifier that is in parameter list + if ((ex.InferredInformation.IsMutable || ex.InferredInformation.HasLocalQuantumDependency) + && ex.Expression is ExpressionKind.Identifier id + && id.Item1 is Identifier.LocalVariable variable + && SharedState.Parameters.Any(x => x.VariableName.Equals(variable))) + { + // Set the mutability to false + ex = new TypedExpression( + ex.Expression, + ex.TypeArguments, + ex.ResolvedType, + new InferredExpressionInformation(false, false), // parameter references cannot be mutable or have local quantum dependency + ex.Range); + } + + // Prevent IsRecursiveIdentifier from propagating beyond the typed expression it is referring to + var isRecursiveIdentifier = SharedState.IsRecursiveIdentifier; + var rtrn = base.OnTypedExpression(ex); + SharedState.IsRecursiveIdentifier = isRecursiveIdentifier; + return rtrn; + } + } + + private class ExpressionKindTransformation : ExpressionKindTransformation + { + public ExpressionKindTransformation(SyntaxTreeTransformation parent) : base(parent) { } + + public override ExpressionKind OnIdentifier(Identifier sym, QsNullable> tArgs) + { + var rtrn = base.OnIdentifier(sym, tArgs); + + // 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 && SharedState.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. + SharedState.IsRecursiveIdentifier = true; + } + return rtrn; + } + } + + private class TypeTransformation : TypeTransformation + { + public TypeTransformation(SyntaxTreeTransformation parent) : base(parent) { } + + public override ResolvedTypeKind OnTypeParameter(QsTypeParameter tp) + { + // Reroute a type parameter's origin to the newly generated operation + if (!SharedState.IsRecursiveIdentifier && SharedState.OldName.Equals(tp.Origin)) + { + tp = new QsTypeParameter(SharedState.NewName, tp.TypeName, tp.Range); + } + + return base.OnTypeParameter(tp); + } + } + } + } + + /// + /// This transformation handles the task of lifting the contents of code blocks into generated operations. + /// The transformation provides validation to see if any given block can safely be lifted into its 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. + /// A block can be checked by setting the SharedState.IsValidScope to true before traversing the scope, + /// then checking the SharedState.IsValidScope after traversal. Blocks should be validated before calling + /// the SharedState.LiftBody function, which will generate a new operation with the block's contents. + /// All the known variables at the start of the block will become parameters to the new operation, and + /// the operation will have all the valid type parameters of the calling context as type parameters. + /// A call to the new operation is also returned with all the valid type parameters and known variables + /// being forwarded to the new operation as arguments. + /// + /// ToDo: This transformation currently does not support lifting inside of functions. + /// + public class LiftContent : SyntaxTreeTransformation + where T : LiftContent.TransformationState + { + protected LiftContent(T state) : base(state) + { + this.Namespaces = new NamespaceTransformation(this); + this.StatementKinds = new StatementKindTransformation(this); + this.Expressions = new ExpressionTransformation(this); + this.ExpressionKinds = new ExpressionKindTransformation(this); + this.Types = new TypeTransformation(this, TransformationOptions.Disabled); + } + + protected class NamespaceTransformation : NamespaceTransformation + { + public NamespaceTransformation(SyntaxTreeTransformation parent) : base(parent) { } + + public override QsCallable OnCallableDeclaration(QsCallable c) + { + SharedState.CurrentCallable = new LiftContent.CallableDetails(c); + return base.OnCallableDeclaration(c); + } + + public override QsSpecialization OnBodySpecialization(QsSpecialization spec) + { + SharedState.InBody = true; + var rtrn = base.OnBodySpecialization(spec); + SharedState.InBody = false; + return rtrn; + } + + public override QsSpecialization OnAdjointSpecialization(QsSpecialization spec) + { + SharedState.InAdjoint = true; + var rtrn = base.OnAdjointSpecialization(spec); + SharedState.InAdjoint = false; + return rtrn; + } + + public override QsSpecialization OnControlledSpecialization(QsSpecialization spec) + { + SharedState.InControlled = true; + var rtrn = base.OnControlledSpecialization(spec); + SharedState.InControlled = false; + return rtrn; + } + + public override QsSpecialization OnControlledAdjointSpecialization(QsSpecialization spec) + { + SharedState.InControlledAdjoint = true; + var rtrn = base.OnControlledAdjointSpecialization(spec); + SharedState.InControlledAdjoint = false; + return rtrn; + } + + // ToDo: We will want to support lifting of functions in the future + public override QsCallable OnFunction(QsCallable c) => c; // Prevent anything in functions from being lifted + + public override QsNamespace OnNamespace(QsNamespace ns) + { + // Generated operations list will be populated in the transform + SharedState.GeneratedOperations = new List(); + return base.OnNamespace(ns) + .WithElements(elems => elems.AddRange(SharedState.GeneratedOperations.Select(op => QsNamespaceElement.NewQsCallable(op)))); + } + } + + protected class StatementKindTransformation : StatementKindTransformation + { + public StatementKindTransformation(SyntaxTreeTransformation parent) : base(parent) { } + + public override QsStatementKind OnConjugation(QsConjugation stm) + { + var superInWithinBlock = SharedState.InWithinBlock; + SharedState.InWithinBlock = true; + var (_, outer) = this.OnPositionedBlock(QsNullable.Null, stm.OuterTransformation); + SharedState.InWithinBlock = superInWithinBlock; + + var (_, inner) = this.OnPositionedBlock(QsNullable.Null, stm.InnerTransformation); + + return QsStatementKind.NewQsConjugation(new QsConjugation(outer, inner)); + } + + public override QsStatementKind OnReturnStatement(TypedExpression ex) + { + SharedState.IsValidScope = false; + return base.OnReturnStatement(ex); + } + + public override QsStatementKind OnValueUpdate(QsValueUpdate stm) + { + // If lhs contains an identifier found in the scope's known variables (variables from the super-scope), the scope is not valid + var lhs = this.Expressions.OnTypedExpression(stm.Lhs); + + if (SharedState.ContainsParamRef) + { + SharedState.IsValidScope = false; + } + + var rhs = this.Expressions.OnTypedExpression(stm.Rhs); + return QsStatementKind.NewQsValueUpdate(new QsValueUpdate(lhs, rhs)); + } + + public override QsStatementKind OnStatementKind(QsStatementKind kind) + { + SharedState.ContainsParamRef = false; // Every statement kind starts off false + return base.OnStatementKind(kind); + } + } + + protected class ExpressionTransformation : ExpressionTransformation + { + public ExpressionTransformation(SyntaxTreeTransformation parent) : base(parent) { } + + public override TypedExpression OnTypedExpression(TypedExpression ex) + { + var contextContainsParamRef = SharedState.ContainsParamRef; + SharedState.ContainsParamRef = false; + var rtrn = base.OnTypedExpression(ex); + + // If the sub context contains a reference, then the super context contains a reference, + // otherwise return the super context to its original value + SharedState.ContainsParamRef |= contextContainsParamRef; + + return rtrn; + } + } + + protected class ExpressionKindTransformation : ExpressionKindTransformation + { + public ExpressionKindTransformation(SyntaxTreeTransformation parent) : base(parent) { } + + public override ExpressionKind OnIdentifier(Identifier sym, QsNullable> tArgs) + { + if (sym is Identifier.LocalVariable local && + SharedState.GeneratedOpParams.Any(param => param.VariableName.Equals(local.Item))) + { + SharedState.ContainsParamRef = true; + } + return base.OnIdentifier(sym, tArgs); + } + } + } +} diff --git a/src/QsCompiler/Transformations/SearchAndReplace.cs b/src/QsCompiler/Transformations/SearchAndReplace.cs index e12ed5dd81..7541d512a6 100644 --- a/src/QsCompiler/Transformations/SearchAndReplace.cs +++ b/src/QsCompiler/Transformations/SearchAndReplace.cs @@ -363,6 +363,59 @@ public override QsStatementKind OnValueUpdate(QsValueUpdate stm) // routines for replacing symbols/identifiers + /// + /// Provides simple name decoration (or name mangling) by prefixing names with a label and number. + /// + public class NameDecorator + { + private const string original = "original"; + + private readonly string label; + + private readonly Regex pattern; + + /// + /// Creates a new name decorator using the label. + /// + /// The label to use as the prefix for decorated names. + public NameDecorator(string label) + { + this.label = label; + pattern = new Regex($"^__{Regex.Escape(label)}[0-9]*__(?<{original}>.*)__$"); + } + + /// + /// Decorates the name with the label of this name decorator and the given number. + /// + /// The name to decorate. + /// The number to use along with the label to decorate the name. + /// The decorated name. + public string Decorate(string name, int number) => $"__{label}{number}__{name}__"; + + /// + /// Decorates the name of the qualified name with the label of this name decorator and the given number. + /// + /// The qualified name to decorate. + /// The number to use along with the label to decorate the qualified name. + /// The decorated qualified name. + public QsQualifiedName Decorate(QsQualifiedName name, int number) => + new QsQualifiedName(name.Namespace, NonNullable.New(Decorate(name.Name.Value, number))); + + /// + /// Reverses decoration previously done to the name using the same label as this name decorator. + /// + /// The decorated name to undecorate. + /// + /// The original name before decoration, if the decorated name uses the same label as this name decorator; + /// otherwise, null. + /// + public string Undecorate(string name) + { + var match = pattern.Match(name).Groups[original]; + return match.Success ? match.Value : null; + } + } + /// /// Upon transformation, assigns each defined variable a unique name, independent on the scope, and replaces all references to it accordingly. /// The original variable name can be recovered by using the static method StripUniqueName. @@ -371,6 +424,8 @@ public override QsStatementKind OnValueUpdate(QsValueUpdate stm) public class UniqueVariableNames : SyntaxTreeTransformation { + private static readonly NameDecorator decorator = new NameDecorator("qsVar"); + public class TransformationState { private int VariableNr = 0; @@ -387,18 +442,14 @@ internal QsExpressionKind AdaptIdentifier(Identifier sym, QsNullable internal NonNullable GenerateUniqueName(NonNullable varName) { - var unique = NonNullable.New($"__{Prefix}{this.VariableNr++}__{varName.Value}__"); + var unique = NonNullable.New(decorator.Decorate(varName.Value, VariableNr++)); this.UniqueNames[varName] = unique; return unique; } } - private const string Prefix = "qsVar"; - private const string OrigVarName = "origVarName"; - private static readonly Regex WrappedVarName = new Regex($"^__{Prefix}[0-9]*__(?<{OrigVarName}>.*)__$"); - - public UniqueVariableNames() + public UniqueVariableNames() : base(new TransformationState()) { this.StatementKinds = new StatementKindTransformation(this); @@ -410,13 +461,12 @@ public UniqueVariableNames() // static methods for convenience internal static QsQualifiedName PrependGuid(QsQualifiedName original) => - new QsQualifiedName(original.Namespace, NonNullable.New("_" + Guid.NewGuid().ToString("N") + "_" + original.Name.Value)); + new QsQualifiedName( + original.Namespace, + NonNullable.New("_" + Guid.NewGuid().ToString("N") + "_" + original.Name.Value)); - public static NonNullable StripUniqueName(NonNullable uniqueName) - { - var matched = WrappedVarName.Match(uniqueName.Value).Groups[OrigVarName]; - return matched.Success ? NonNullable.New(matched.Value) : uniqueName; - } + public static NonNullable StripUniqueName(NonNullable uniqueName) => + NonNullable.New(decorator.Undecorate(uniqueName.Value) ?? uniqueName.Value); // helper classes @@ -446,6 +496,183 @@ public override QsExpressionKind OnIdentifier(Identifier sym, QsNullable + /// A transformation that renames all references to each given qualified name. + /// + public class RenameReferences : SyntaxTreeTransformation + { + private class TransformationState + { + private readonly IImmutableDictionary names; + + internal TransformationState(IImmutableDictionary names) => + this.names = names; + + /// + /// Gets the renamed version of the qualified name if one exists; otherwise, returns the original name. + /// + /// The qualified name to rename. + /// + /// The renamed version of the qualified name if one exists; otherwise, returns the original name. + /// + internal QsQualifiedName GetNewName(QsQualifiedName name) => names.GetValueOrDefault(name) ?? name; + + /// + /// Gets the renamed version of the user-defined type if one exists; otherwise, returns the original one. + /// + /// + /// The renamed version of the user-defined type if one exists; otherwise, returns the original one. + /// + internal UserDefinedType RenameUdt(UserDefinedType udt) + { + var newName = GetNewName(new QsQualifiedName(udt.Namespace, udt.Name)); + return new UserDefinedType(newName.Namespace, newName.Name, udt.Range); + } + } + + + private readonly TransformationState State; + + /// + /// Creates a new rename references transformation. + /// + /// The mapping from existing names to new names. + public RenameReferences(IImmutableDictionary names) + { + State = new TransformationState(names); + Types = new TypeTransformation(this); + ExpressionKinds = new ExpressionKindTransformation(this); + Namespaces = new NamespaceTransformation(this); + } + + + // methods for transformations on headers + + /// + /// Renames references in the callable declaration header, including the name of the callable itself. + /// + /// The callable declaration header in which to rename references. + /// The callable declaration header with renamed references. + public CallableDeclarationHeader OnCallableDeclarationHeader(CallableDeclarationHeader callable) => + new CallableDeclarationHeader( + kind: callable.Kind, + qualifiedName: State.GetNewName(callable.QualifiedName), + attributes: callable.Attributes.Select(Namespaces.OnAttribute).ToImmutableArray(), + modifiers: callable.Modifiers, + sourceFile: callable.SourceFile, + position: callable.Position, + symbolRange: callable.SymbolRange, + argumentTuple: Namespaces.OnArgumentTuple(callable.ArgumentTuple), + signature: Namespaces.OnSignature(callable.Signature), + documentation: Namespaces.OnDocumentation(callable.Documentation)); + + /// + /// Renames references in the specialization declaration header, including the name of the specialization + /// itself. + /// + /// The specialization declaration header in which to rename references. + /// The specialization declaration header with renamed references. + public SpecializationDeclarationHeader OnSpecializationDeclarationHeader( + SpecializationDeclarationHeader specialization) + { + var typeArguments = + specialization.TypeArguments.IsValue + ? QsNullable>.NewValue( + specialization.TypeArguments.Item.Select(Types.OnType).ToImmutableArray()) + : QsNullable>.Null; + return new SpecializationDeclarationHeader( + kind: specialization.Kind, + typeArguments: typeArguments, + information: specialization.Information, + parent: State.GetNewName(specialization.Parent), + attributes: specialization.Attributes.Select(Namespaces.OnAttribute).ToImmutableArray(), + sourceFile: specialization.SourceFile, + position: specialization.Position, + headerRange: specialization.HeaderRange, + documentation: Namespaces.OnDocumentation(specialization.Documentation)); + } + + /// + /// Renames references in the type declaration header, including the name of the type itself. + /// + /// The type declaration header in which to rename references. + /// The type declaration header with renamed references. + public TypeDeclarationHeader OnTypeDeclarationHeader(TypeDeclarationHeader type) + { + return new TypeDeclarationHeader( + qualifiedName: State.GetNewName(type.QualifiedName), + attributes: type.Attributes.Select(Namespaces.OnAttribute).ToImmutableArray(), + modifiers: type.Modifiers, + sourceFile: type.SourceFile, + position: type.Position, + symbolRange: type.SymbolRange, + type: Types.OnType(type.Type), + typeItems: Namespaces.OnTypeItems(type.TypeItems), + documentation: Namespaces.OnDocumentation(type.Documentation)); + } + + + // private helper classes + + private class TypeTransformation : Core.TypeTransformation + { + private readonly TransformationState State; + + public TypeTransformation(RenameReferences parent) : base(parent) => + this.State = parent.State; + + public override QsTypeKind OnUserDefinedType(UserDefinedType udt) => + base.OnUserDefinedType(State.RenameUdt(udt)); + + public override QsTypeKind OnTypeParameter(QsTypeParameter tp) => + base.OnTypeParameter(new QsTypeParameter(State.GetNewName(tp.Origin), tp.TypeName, tp.Range)); + } + + private class ExpressionKindTransformation : Core.ExpressionKindTransformation + { + private readonly TransformationState State; + + public ExpressionKindTransformation(RenameReferences parent) : base(parent) => + this.State = parent.State; + + public override QsExpressionKind OnIdentifier(Identifier id, QsNullable> typeArgs) + { + if (id is Identifier.GlobalCallable global) + { + id = Identifier.NewGlobalCallable(State.GetNewName(global.Item)); + } + return base.OnIdentifier(id, typeArgs); + } + } + + private class NamespaceTransformation : Core.NamespaceTransformation + { + private readonly TransformationState State; + + public NamespaceTransformation(RenameReferences parent) : base(parent) => + this.State = parent.State; + + public override QsDeclarationAttribute OnAttribute(QsDeclarationAttribute attribute) + { + var argument = Transformation.Expressions.OnTypedExpression(attribute.Argument); + var typeId = attribute.TypeId.IsValue + ? QsNullable.NewValue(State.RenameUdt(attribute.TypeId.Item)) + : attribute.TypeId; + return base.OnAttribute( + new QsDeclarationAttribute(typeId, argument, attribute.Offset, attribute.Comments)); + } + + public override QsCallable OnCallableDeclaration(QsCallable callable) => + base.OnCallableDeclaration(callable.WithFullName(State.GetNewName)); + + public override QsCustomType OnTypeDeclaration(QsCustomType type) => + base.OnTypeDeclaration(type.WithFullName(State.GetNewName)); + + public override QsSpecialization OnSpecializationDeclaration(QsSpecialization spec) => + base.OnSpecializationDeclaration(spec.WithParent(State.GetNewName)); + } + } + // general purpose helpers diff --git a/src/QuantumSdk/DefaultItems/DefaultItems.props.v.template b/src/QuantumSdk/DefaultItems/DefaultItems.props.v.template index 98c796f32d..ea3594c422 100644 --- a/src/QuantumSdk/DefaultItems/DefaultItems.props.v.template +++ b/src/QuantumSdk/DefaultItems/DefaultItems.props.v.template @@ -33,6 +33,7 @@ true true false + false dotnet "$(MSBuildThisFileDirectory)../tools/utils/Microsoft.Quantum.Sdk.BuildConfiguration.dll" $(DefaultQscBuildConfigExe) dotnet "$(MSBuildThisFileDirectory)../tools/qsc/qsc.dll" diff --git a/src/QuantumSdk/DefaultItems/DefaultItems.targets b/src/QuantumSdk/DefaultItems/DefaultItems.targets index 764107d873..caf136ac33 100644 --- a/src/QuantumSdk/DefaultItems/DefaultItems.targets +++ b/src/QuantumSdk/DefaultItems/DefaultItems.targets @@ -9,15 +9,15 @@ - - + + - + QsharpLibrary QsharpExe @@ -25,18 +25,34 @@ Possible values are 'Exe', or 'Library'. - + - QuantumProcessorBackend - SimulatorBackend + HoneywellProcessor + IonQProcessor + QCIProcessor Unspecified - - Possible values are 'QuantumSimulator', 'ToffoliSimulator', 'ResourcesEstimator', or 'QuantumProcessor'. - The execution target for a Q# library needs to be 'Any'. - true + + Possible values are 'Honeywell', 'IonQ', 'QCI', or 'Any'. + The execution target for a Q# library needs to be 'Any'. - + + + OpenQASM + ExtendedQASM + OpenQASM + + + + + + QPGen1 + QPGen0 + QPGen1 + + + + $([System.String]::Copy('$(AssemblyName)').Replace(' ','')) @@ -55,7 +71,28 @@ - + + + + <_TargetPackageReference Include="@(PackageReference)" Condition="@(PackageReference->Count()) > 0 And %(PackageReference.IsTargetPackage)" /> + <_TargetPackageReferencePathProperty Include="@(_TargetPackageReference->'Pkg$([System.String]::Copy('%(_TargetPackageReference.Identity)').Replace('.','_'))')" /> + <_ResolvedTargetPackageReferences Include="$(%(_TargetPackageReferencePathProperty.Identity))" /> + + + + + %(_ResolvedTargetPackageReferences.Identity) + + + + + diff --git a/src/QuantumSdk/Sdk/Sdk.props b/src/QuantumSdk/Sdk/Sdk.props index ba31d6536e..f44ea733eb 100644 --- a/src/QuantumSdk/Sdk/Sdk.props +++ b/src/QuantumSdk/Sdk/Sdk.props @@ -31,11 +31,13 @@ - + + - + + @@ -64,7 +64,7 @@ - + @@ -73,9 +73,11 @@ <_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 + <_QscCommandTrimFlag Condition="'$(ResolvedQuantumProcessor)' == 'QPGen1'">--trim 2 + <_QscCommandTargetPackageFlag Condition="'$(ResolvedTargetPackage)' != ''">--target-package "$(ResolvedTargetPackage)" + <_QscCommandTestNamesFlag Condition="$(ExposeReferencesViaTestNames)">--load-test-names <_QscPackageLoadFallbackFoldersFlag Condition="@(ResolvedPackageLoadFallbackFolders->Count()) > 0">--package-load-fallback-folders "@(ResolvedPackageLoadFallbackFolders,'" "')" - <_QscCommandArgs>--proj "$(PathCompatibleAssemblyName)" $(_QscCommandDocsFlag) $(_QscCommandInputFlag) $(_QscCommandOutputFlag) $(_QscCommandReferencesFlag) $(_QscCommandLoadFlag) $(_QscCommandTrimFlag) $(_QscPackageLoadFallbackFoldersFlag) $(AdditionalQscArguments) + <_QscCommandArgs>--proj "$(PathCompatibleAssemblyName)" $(_QscCommandDocsFlag) $(_QscCommandInputFlag) $(_QscCommandOutputFlag) $(_QscCommandReferencesFlag) $(_QscCommandLoadFlag) $(_QscCommandTrimFlag) $(_QscCommandTargetPackageFlag) $(_QscPackageLoadFallbackFoldersFlag) $(_QscCommandTestNamesFlag) $(AdditionalQscArguments) <_QscCommandArgsFile>$(QscBuildConfigOutputPath)qsc.rsp $(QscExe) build --format MsBuild $(_VerbosityFlag) --response-files $(_QscCommandArgsFile) diff --git a/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json.v.template b/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json.v.template index 10a844f2a7..4dbc1671fd 100644 --- a/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json.v.template +++ b/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json.v.template @@ -60,7 +60,7 @@ { "comment": "C# reserved words which can't be used in Q#, E-L", "name": "invalid.illegal.el.qsharp", - "match": "\\b(enum|event|explicit|extern|finally|fixed|float|foreach|goto|implicit|int|interface|internal|lock|long)\\b" + "match": "\\b(enum|event|explicit|extern|finally|fixed|float|foreach|goto|implicit|int|interface|lock|long)\\b" }, { "comment": "C# reserved words which can't be used in Q#, N-S", @@ -78,7 +78,7 @@ "patterns": [ { "name": "keyword.other.qsharp", - "match": "\\b(namespace|open|as|newtype|operation|function|body|(a|A)djoint|(c|C)ontrolled|self|auto|distribute|invert|intrinsic)\\b" + "match": "\\b(namespace|open|as|internal|newtype|operation|function|body|(a|A)djoint|(c|C)ontrolled|self|auto|distribute|invert|intrinsic)\\b" } ] },