diff --git a/src/QsCompiler/CompilationManager/CompilationUnit.cs b/src/QsCompiler/CompilationManager/CompilationUnit.cs index 9aaf1e6ea5..d21769578e 100644 --- a/src/QsCompiler/CompilationManager/CompilationUnit.cs +++ b/src/QsCompiler/CompilationManager/CompilationUnit.cs @@ -5,9 +5,7 @@ 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; @@ -18,6 +16,7 @@ using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace; using Microsoft.VisualStudio.LanguageServer.Protocol; +using static Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants; namespace Microsoft.Quantum.QsCompiler.CompilationBuilder @@ -221,11 +220,16 @@ public class CompilationUnit : IReaderWriterLock, IDisposable internal References Externals { get; private set; } internal NamespaceManager GlobalSymbols { get; private set; } + private readonly Dictionary CompiledCallables; private readonly Dictionary CompiledTypes; + private readonly ReaderWriterLockSlim SyncRoot; private readonly HashSet DependentLocks; + internal readonly RuntimeCapabilities RuntimeCapabilities; + internal readonly bool IsExecutable; + public void Dispose() { this.SyncRoot.Dispose(); } @@ -234,13 +238,18 @@ public void Dispose() /// with the given sequence of locks registered as dependent locks if the sequence is not null. /// Throws an ArgumentNullException if any of the given locks is. /// - internal CompilationUnit(References externals = null, IEnumerable dependentLocks = null) + internal CompilationUnit(RuntimeCapabilities capabilities, bool isExecutable, + References externals = null, IEnumerable dependentLocks = null) { this.SyncRoot = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); this.DependentLocks = dependentLocks == null ? new HashSet() : new HashSet(dependentLocks); if (dependentLocks?.Contains(null) ?? false) throw new ArgumentNullException(nameof(dependentLocks), "one or more of the given locks is null"); + + this.RuntimeCapabilities = capabilities; + this.IsExecutable = isExecutable; + this.CompiledCallables = new Dictionary(); this.CompiledTypes = new Dictionary(); this.UpdateReferences(externals ?? References.Empty); @@ -260,7 +269,8 @@ internal void UpdateReferences(References externals) this.GlobalSymbols = new NamespaceManager(this, this.Externals.Declarations.Values.SelectMany(h => h.Callables), this.Externals.Declarations.Values.SelectMany(h => h.Specializations.Select(t => new Tuple(t.Item1, t.Item2))), - this.Externals.Declarations.Values.SelectMany(h => h.Types)); + this.Externals.Declarations.Values.SelectMany(h => h.Types), + this.RuntimeCapabilities, this.IsExecutable); } finally { this.ExitWriteLock(); } } diff --git a/src/QsCompiler/CompilationManager/CompilationUnitManager.cs b/src/QsCompiler/CompilationManager/CompilationUnitManager.cs index d25e0b5693..757e094801 100644 --- a/src/QsCompiler/CompilationManager/CompilationUnitManager.cs +++ b/src/QsCompiler/CompilationManager/CompilationUnitManager.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures; using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.ReservedKeywords; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; @@ -58,14 +59,16 @@ public class CompilationUnitManager : IDisposable protected readonly ProcessingQueue Processing; /// - /// Initializes a CompilationUnitManager instance. - /// If an for publishing diagnostics is given and is not null, + /// Initializes a CompilationUnitManager instance for a project with the given properties. + /// If an for publishing diagnostics is given and is not null, /// that action is called whenever diagnostics within a file have changed and are ready for publishing. /// - public CompilationUnitManager(Action exceptionLogger = null, Action publishDiagnostics = null, bool syntaxCheckOnly = false) + public CompilationUnitManager( + Action exceptionLogger = null, Action publishDiagnostics = null, bool syntaxCheckOnly = false, + AssemblyConstants.RuntimeCapabilities capabilities = AssemblyConstants.RuntimeCapabilities.Unknown, bool isExecutable = false) { this.EnableVerification = !syntaxCheckOnly; - this.CompilationUnit = new CompilationUnit(); + this.CompilationUnit = new CompilationUnit(capabilities, isExecutable); this.FileContentManagers = new ConcurrentDictionary, FileContentManager>(); this.ChangedFiles = new ManagedHashSet>(new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion)); this.PublishDiagnostics = publishDiagnostics ?? (_ => { }); @@ -437,7 +440,7 @@ private Task SpawnGlobalTypeCheckingAsync(bool runSynchronously = false) // work with a separate compilation unit instance such that processing of all further edits can go on in parallel var sourceFiles = this.FileContentManagers.Values.OrderBy(m => m.FileName); this.ChangedFiles.RemoveAll(f => sourceFiles.Any(m => m.FileName.Value == f.Value)); - var compilation = new CompilationUnit(this.CompilationUnit.Externals, sourceFiles.Select(file => file.SyncRoot)); + var compilation = new CompilationUnit(this.CompilationUnit.RuntimeCapabilities, this.CompilationUnit.IsExecutable, this.CompilationUnit.Externals, sourceFiles.Select(file => file.SyncRoot)); var content = compilation.UpdateGlobalSymbolsFor(sourceFiles); foreach (var file in sourceFiles) this.PublishDiagnostics(file.Diagnostics()); diff --git a/src/QsCompiler/CompilationManager/ProjectManager.cs b/src/QsCompiler/CompilationManager/ProjectManager.cs index d42cb675f0..564e489e63 100644 --- a/src/QsCompiler/CompilationManager/ProjectManager.cs +++ b/src/QsCompiler/CompilationManager/ProjectManager.cs @@ -11,31 +11,49 @@ using System.Threading.Tasks; using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.Diagnostics; +using Microsoft.Quantum.QsCompiler.ReservedKeywords; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { - public class ProjectInformation + internal class ProjectProperties { - public delegate bool Loader(Uri projectFile, out ProjectInformation projectInfo); - public readonly string Version; public readonly string OutputPath; + public readonly AssemblyConstants.RuntimeCapabilities RuntimeCapabilities; + public readonly bool IsExecutable; public readonly bool ExposeReferencesViaTestNames; + + internal static ProjectProperties Default => + new ProjectProperties("Latest", "", AssemblyConstants.RuntimeCapabilities.Unknown, false, false); + + public ProjectProperties(string version, string outputPath, AssemblyConstants.RuntimeCapabilities runtimeCapabilities, bool isExecutable, bool loadTestNames) + { + this.Version = version ?? ""; + this.OutputPath = outputPath ?? throw new ArgumentNullException(nameof(outputPath)); + this.RuntimeCapabilities = runtimeCapabilities; + this.IsExecutable = isExecutable; + this.ExposeReferencesViaTestNames = loadTestNames; + } + } + + public class ProjectInformation + { + public delegate bool Loader(Uri projectFile, out ProjectInformation projectInfo); + + internal readonly ProjectProperties Properties; public readonly ImmutableArray SourceFiles; public readonly ImmutableArray ProjectReferences; public readonly ImmutableArray References; - internal static ProjectInformation Empty(string version, string outputPath) => - new ProjectInformation(version, outputPath, false, Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()); + internal static ProjectInformation Empty(string version, string outputPath, AssemblyConstants.RuntimeCapabilities runtimeCapabilities) => + new ProjectInformation(version, outputPath, runtimeCapabilities, false, false, Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()); - public ProjectInformation(string version, string outputPath, bool loadTestNames, + public ProjectInformation(string version, string outputPath, AssemblyConstants.RuntimeCapabilities runtimeCapabilities, bool isExecutable, bool loadTestNames, IEnumerable sourceFiles, IEnumerable projectReferences, IEnumerable references) { - this.Version = version ?? ""; - this.OutputPath = outputPath ?? throw new ArgumentNullException(nameof(outputPath)); - this.ExposeReferencesViaTestNames = loadTestNames; + this.Properties = new ProjectProperties(version, outputPath, runtimeCapabilities, isExecutable, 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)); @@ -48,7 +66,7 @@ private class Project : IDisposable { public readonly Uri ProjectFile; public Uri OutputPath { get; private set; } - private bool ExposeReferencesViaTestNames; + public ProjectProperties Properties { get; private set; } private bool IsLoaded; /// @@ -120,14 +138,16 @@ internal Project(Uri projectFile, ProjectInformation projectInfo, this.ProjectFile = projectFile ?? throw new ArgumentNullException(nameof(projectFile)); this.SetProjectInformation(projectInfo ?? throw new ArgumentNullException(nameof(projectInfo))); - var version = Version.TryParse(projectInfo.Version, out Version v) ? v : null; - if (projectInfo.Version.Equals("Latest", StringComparison.InvariantCultureIgnoreCase)) version = new Version(0, 3); + var version = Version.TryParse(projectInfo.Properties.Version, out Version v) ? v : null; + if (projectInfo.Properties.Version.Equals("Latest", StringComparison.InvariantCultureIgnoreCase)) version = new Version(0, 3); var ignore = version == null || version < new Version(0, 3) ? true : false; // We track the file contents for unsupported projects in case the files are migrated to newer projects while editing, // but we don't do any semantic verification, and we don't publish diagnostics for them. this.Processing = new ProcessingQueue(onException); - this.Manager = new CompilationUnitManager(onException, ignore ? null : publishDiagnostics, syntaxCheckOnly: ignore); + this.Manager = new CompilationUnitManager( + onException, ignore ? null : publishDiagnostics, syntaxCheckOnly: ignore, + this.Properties.RuntimeCapabilities, this.Properties.IsExecutable); this.Log = log ?? ((msg, severity) => Console.WriteLine($"{severity}: {msg}")); this.LoadedSourceFiles = ImmutableHashSet.Empty; @@ -144,10 +164,10 @@ internal Project(Uri projectFile, ProjectInformation projectInfo, private void SetProjectInformation(ProjectInformation projectInfo) { if (projectInfo == null) throw new ArgumentNullException(nameof(projectInfo)); - this.ExposeReferencesViaTestNames = projectInfo.ExposeReferencesViaTestNames; + this.Properties = projectInfo.Properties; this.IsLoaded = false; - var outputPath = projectInfo.OutputPath; + var outputPath = projectInfo.Properties.OutputPath; try { outputPath = Path.GetFullPath(outputPath); } catch { outputPath = null; } var outputUri = Uri.TryCreate(outputPath, UriKind.Absolute, out Uri uri) ? uri : null; @@ -261,7 +281,7 @@ private Task LoadProjectReferencesAsync( projectReferences, GetProjectOutputPath(projectOutputPaths, diagnostics), diagnostics.Add, this.Manager.LogException); - this.LoadedProjectReferences = new References(loadedHeaders); + this.LoadedProjectReferences = new References(loadedHeaders, this.Properties.ExposeReferencesViaTestNames); var importedDeclarations = this.LoadedReferences.CombineWith(this.LoadedProjectReferences, (code,args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); this.ProjectReferenceDiagnostics = diagnostics.ToImmutableArray(); @@ -288,7 +308,7 @@ 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); + var loaded = new References(loadedHeaders, this.Properties.ExposeReferencesViaTestNames); QsCompilerError.Verify(!loaded.Declarations.Any() || (loaded.Declarations.Count == 1 && loaded.Declarations.First().Key.Value == projRefId.Value), @@ -320,7 +340,7 @@ private Task LoadReferencedAssembliesAsync(IEnumerable references, bool var loadedHeaders = ProjectManager.LoadReferencedAssemblies(references, diagnostics.Add, this.Manager.LogException); - this.LoadedReferences = new References(loadedHeaders); + this.LoadedReferences = new References(loadedHeaders, this.Properties.ExposeReferencesViaTestNames); var importedDeclarations = this.LoadedReferences.CombineWith(this.LoadedProjectReferences, (code, args) => diagnostics.Add(Errors.LoadError(code, args, MessageSource(this.ProjectFile)))); this.ReferenceDiagnostics = diagnostics.ToImmutableArray(); @@ -342,7 +362,7 @@ 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); + var loaded = new References(loadedHeaders, this.Properties.ExposeReferencesViaTestNames); QsCompilerError.Verify(!loaded.Declarations.Any() || (loaded.Declarations.Count == 1 && loaded.Declarations.First().Key.Value == refId.Value), @@ -687,7 +707,7 @@ public Task ProjectChangedOnDiskAsync(Uri projectFile, ProjectInformation.Loader if (!loaded) { existing?.LoadProjectAsync(ImmutableDictionary.Empty, null, MigrateToDefaultManager(openInEditor), - ProjectInformation.Empty("Latest", existing.OutputPath.LocalPath))?.Wait(); // does need to block, or the call to the DefaultManager in ManagerTaskAsync needs to be adapted + ProjectInformation.Empty("Latest", existing.OutputPath.LocalPath, AssemblyConstants.RuntimeCapabilities.Unknown))?.Wait(); // does need to block, or the call to the DefaultManager in ManagerTaskAsync needs to be adapted if (existing != null) this.ProjectReferenceChangedOnDiskChangeAsync(projectFile); return; } diff --git a/src/QsCompiler/CompilationManager/Properties/AssemblyInfo.cs b/src/QsCompiler/CompilationManager/Properties/AssemblyInfo.cs index 8413ca8700..08ea392bdb 100644 --- a/src/QsCompiler/CompilationManager/Properties/AssemblyInfo.cs +++ b/src/QsCompiler/CompilationManager/Properties/AssemblyInfo.cs @@ -6,3 +6,4 @@ // Allow the test assembly to use our internal methods [assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsCompiler" + SigningConstants.PUBLIC_KEY)] +[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.QsLanguageServer" + SigningConstants.PUBLIC_KEY)] diff --git a/src/QsCompiler/Compiler/CompilationLoader.cs b/src/QsCompiler/Compiler/CompilationLoader.cs index 2c53d9179f..b430dbfa36 100644 --- a/src/QsCompiler/Compiler/CompilationLoader.cs +++ b/src/QsCompiler/Compiler/CompilationLoader.cs @@ -23,7 +23,7 @@ using MetadataReference = Microsoft.CodeAnalysis.MetadataReference; using OptimizationLevel = Microsoft.CodeAnalysis.OptimizationLevel; -using RuntimeCapabilities = Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants.RuntimeCapabilities; +using static Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants; namespace Microsoft.Quantum.QsCompiler @@ -435,7 +435,7 @@ public CompilationLoader(SourceLoader loadSources, ReferenceLoader loadReference RaiseCompilationTaskStart("OverallCompilation", "Build"); this.CompilationStatus.Validation = Status.Succeeded; var files = CompilationUnitManager.InitializeFileManagers(sourceFiles, null, this.OnCompilerException); // do *not* live track (i.e. use publishing) here! - var compilationManager = new CompilationUnitManager(this.OnCompilerException); + var compilationManager = new CompilationUnitManager(this.OnCompilerException, capabilities: this.Config.RuntimeCapabilities, isExecutable: this.Config.IsExecutable); compilationManager.UpdateReferencesAsync(references); compilationManager.AddOrUpdateSourceFilesAsync(files); this.VerifiedCompilation = compilationManager.Build(); @@ -450,9 +450,6 @@ public CompilationLoader(SourceLoader loadSources, ReferenceLoader loadReference this.Logger?.Log(WarningCode.MissingEntryPoint, Array.Empty()); } - // TODO: - // give warnings and ignore entry points in libraries, - // and check additional restriction on the return type for execution on quantum processors. // executing the specified rewrite steps diff --git a/src/QsCompiler/Core/SymbolTable.fs b/src/QsCompiler/Core/SymbolTable.fs index dcbdcc8551..b5bfb42124 100644 --- a/src/QsCompiler/Core/SymbolTable.fs +++ b/src/QsCompiler/Core/SymbolTable.fs @@ -741,7 +741,8 @@ and NamespaceManager (syncRoot : IReaderWriterLock, callablesInRefs : IEnumerable, specializationsInRefs : IEnumerable, - typesInRefs : IEnumerable) = + typesInRefs : IEnumerable, + runtimeCapabilites, isExecutable) = // 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 @@ -958,9 +959,18 @@ and NamespaceManager let signatureErrs = inErrs.Concat outErrs errs.AddRange signatureErrs + // currently, only return values of type Result, Result[], and tuples thereof are supported on quantum processors + if runtimeCapabilites = AssemblyConstants.RuntimeCapabilities.QPRGen0 || runtimeCapabilites = AssemblyConstants.RuntimeCapabilities.QPRGen1 then + let invalid = signature.ReturnType.ExtractAll (fun t -> t.Type |> function + | Result | ArrayType _ | TupleType _ | InvalidType -> Seq.empty + | _ -> Seq.singleton t) + if invalid.Any() then errs.Add (decl.Position, signature.ReturnType.Range |> orDefault |> QsCompilerDiagnostic.Warning (WarningCode.NonResultTypeReturnedInEntryPoint, [])) + // validate entry point argument names let asCommandLineArg (str : string) = str.ToLowerInvariant() |> String.filter((<>)'_') - let reservedCommandLineArgs = CommandLineArguments.ReservedArguments |> Seq.map asCommandLineArg |> Seq.toArray + let reservedCommandLineArgs = + CommandLineArguments.ReservedArguments.Concat CommandLineArguments.ReservedArgumentAbbreviations + |> Seq.map asCommandLineArg |> Seq.toArray let nameAndRange (sym : QsSymbol) = sym.Symbol |> function | Symbol name -> Some (asCommandLineArg name.Value, sym.Range) | _ -> None @@ -968,14 +978,17 @@ and NamespaceManager let verifyArgument i (arg, range : QsNullable<_>) = if i > 0 && simplifiedArgNames.[..i-1] |> Seq.map fst |> Seq.contains arg then errs.Add (decl.Position, range.ValueOr decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.DuplicateEntryPointArgumentName, [])) - elif reservedCommandLineArgs.Contains arg || (arg.Length = 1 && CommandLineArguments.ReservedArgumentAbbreviations.Contains arg.[0]) + elif reservedCommandLineArgs.Contains arg then errs.Add (decl.Position, range.ValueOr decl.Range |> QsCompilerDiagnostic.Warning (WarningCode.ReservedEntryPointArgumentName, [])) simplifiedArgNames |> List.iteri verifyArgument - // check that there is no more than one entry point + // check that there is no more than one entry point, and no entry point if the project is not executable if signatureErrs.Any() then false, errs + elif not isExecutable then + errs.Add (offset, range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.EntryPointInLibrary, [])) + false, errs else GetEntryPoints() |> Seq.tryHead |> function - | None -> true, errs + | None -> isExecutable, errs | Some (epName, epSource) -> let msgArgs = [sprintf "%s.%s" epName.Namespace.Value epName.Name.Value; epSource.Value] errs.Add (offset, range |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.MultipleEntryPoints, msgArgs)) diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index 9c2df007e8..a975b60474 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -239,11 +239,12 @@ type ErrorCode = | MultipleEntryPoints = 6236 | InvalidEntryPointSpecialization = 6237 | DuplicateEntryPointArgumentName = 6238 - | InvalidTestAttributePlacement = 6239 - | InvalidExecutionTargetForTest = 6240 - | ExpectingFullNameAsAttributeArgument = 6241 - | AttributeInvalidOnSpecialization = 6242 - | AttributeInvalidOnCallable = 6243 + | EntryPointInLibrary = 6239 + | InvalidTestAttributePlacement = 6240 + | InvalidExecutionTargetForTest = 6241 + | ExpectingFullNameAsAttributeArgument = 6242 + | AttributeInvalidOnSpecialization = 6243 + | AttributeInvalidOnCallable = 6244 | TypeMismatchInReturn = 6301 | TypeMismatchInValueUpdate = 6302 @@ -328,6 +329,7 @@ type WarningCode = | MissingEntryPoint = 6202 | IgnoredEntryPoint = 6203 | ReservedEntryPointArgumentName = 6204 + | NonResultTypeReturnedInEntryPoint = 6205 | GeneratorDirectiveWillBeIgnored = 6301 | UnreachableCode = 6302 @@ -612,6 +614,7 @@ type DiagnosticItem = | ErrorCode.MultipleEntryPoints -> "Invalid entry point. An entry point {0} already exists in {1}." | ErrorCode.InvalidEntryPointSpecialization -> "Entry points cannot have any other specializations besides the default body." | ErrorCode.DuplicateEntryPointArgumentName -> "Invalid name for entry point argument. A similar argument name is already in use." + | ErrorCode.EntryPointInLibrary -> "Invalid entry point. Only executable Q# projects can have entry points." | 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." @@ -701,9 +704,10 @@ type DiagnosticItem = | WarningCode.NamespaceAliasIsAlreadyDefined -> "A short name for this namespace is already defined." | WarningCode.MissingBodyDeclaration -> "A body specification for this callable is missing. The callable is assumed to be intrinsic." | WarningCode.DuplicateAttribute -> "The attribute {0} is a duplication and will be ignored." - | WarningCode.MissingEntryPoint -> "The project is a Q# command line application but no entry point has been found. The project should be a library, and any C# driver code should be defined in a separate project." + | WarningCode.MissingEntryPoint -> "The project is an executable Q# project but no entry point has been found. The project should be a library, and any C# driver code should be defined in a separate project." | WarningCode.IgnoredEntryPoint -> "Entry point will be ignored. The project is a Q# library and cannot have any entry points." | WarningCode.ReservedEntryPointArgumentName -> "The argument name conflicts with a default argument for a Q# command line application." + | WarningCode.NonResultTypeReturnedInEntryPoint -> "Only values of type Result, Result[], and tuples thereof can be returned when executing on a quantum processor." | WarningCode.GeneratorDirectiveWillBeIgnored -> "Generation directive ignored. A specialization of this callable has been declared as intrinsic." | WarningCode.UnreachableCode -> "This statement will never be executed." diff --git a/src/QsCompiler/DataStructures/ReservedKeywords.fs b/src/QsCompiler/DataStructures/ReservedKeywords.fs index b636ebd09e..0709434b72 100644 --- a/src/QsCompiler/DataStructures/ReservedKeywords.fs +++ b/src/QsCompiler/DataStructures/ReservedKeywords.fs @@ -250,11 +250,11 @@ module AssemblyConstants = | QPRGen1 = 2 -/// contains reserved names for command line arguments +/// contains reserved names for command line arguments of Q# projects module CommandLineArguments = - let SimulatorOption = "simulator" - let ReservedArguments = ImmutableArray.Create SimulatorOption - let ReservedArgumentAbbreviations = ImmutableArray.Create SimulatorOption.[0] + let SimulatorOption = ("simulator", "s") + let ReservedArguments = ImmutableArray.Create (fst SimulatorOption) + let ReservedArgumentAbbreviations = ImmutableArray.Create (snd SimulatorOption) let BuiltInSimulators = [AssemblyConstants.QuantumSimulator; AssemblyConstants.ToffoliSimulator; AssemblyConstants.ResourcesEstimator] diff --git a/src/QsCompiler/LanguageServer/EditorState.cs b/src/QsCompiler/LanguageServer/EditorState.cs index 3a7e9decf6..e214fd4512 100644 --- a/src/QsCompiler/LanguageServer/EditorState.cs +++ b/src/QsCompiler/LanguageServer/EditorState.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.Build.Execution; using Microsoft.Quantum.QsCompiler.CompilationBuilder; +using Microsoft.Quantum.QsCompiler.ReservedKeywords; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -104,15 +105,22 @@ internal bool QsProjectLoader(Uri projectFile, out ProjectInformation info) var targetFile = projectInstance.GetPropertyValue("TargetFileName"); var outputPath = Path.Combine(projectInstance.Directory, outputDir, targetFile); + var resRuntimeCapability = projectInstance.GetPropertyValue("ResolvedRuntimeCapabilities"); + var runtimeCapabilities = Enum.TryParse(resRuntimeCapability, out AssemblyConstants.RuntimeCapabilities capability) + ? capability + : AssemblyConstants.RuntimeCapabilities.Unknown; + var sourceFiles = GetItemsByType(projectInstance, "QsharpCompile"); var projectReferences = GetItemsByType(projectInstance, "ProjectReference"); var references = GetItemsByType(projectInstance, "Reference"); + var version = projectInstance.GetPropertyValue("QsharpLangVersion"); + var isExecutable = "QsharpExe".Equals(projectInstance.GetPropertyValue("ResolvedQsharpOutputType"), StringComparison.InvariantCultureIgnoreCase); 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, loadTestNames, sourceFiles, projectReferences, references); + info = new ProjectInformation(version, outputPath, runtimeCapabilities, isExecutable, loadTestNames, sourceFiles, projectReferences, references); return true; } diff --git a/src/QsCompiler/Tests.Compiler/LinkingTests.fs b/src/QsCompiler/Tests.Compiler/LinkingTests.fs index 191b4891f7..af92541db6 100644 --- a/src/QsCompiler/Tests.Compiler/LinkingTests.fs +++ b/src/QsCompiler/Tests.Compiler/LinkingTests.fs @@ -7,11 +7,11 @@ open System open System.Collections.Generic open System.Collections.Immutable open System.IO -open System.Linq open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.CompilationBuilder open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.Diagnostics +open Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants open Microsoft.Quantum.QsCompiler.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.QsCompiler.Transformations.IntrinsicResolution @@ -26,7 +26,7 @@ open Xunit.Abstractions type LinkingTests (output:ITestOutputHelper) = 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 compilationManager = new CompilationUnitManager(new Action (fun ex -> failwith ex.Message), isExecutable = true) // The file name needs to end in ".qs" so that it isn't ignored by the References.Headers class during the internal renaming tests. let getTempFile () = Path.GetRandomFileName () + ".qs" |> Path.GetFullPath |> Uri @@ -67,7 +67,7 @@ type LinkingTests (output:ITestOutputHelper) = let name = name |> NonNullable<_>.New this.Verify (QsQualifiedName.New (ns, name), diag) - member private this.CompileAndVerify input (diag : DiagnosticItem seq) = + member private this.CompileAndVerify (compilationManager : CompilationUnitManager) input (diag : DiagnosticItem seq) = let fileId = getTempFile() let file = getManager fileId input @@ -227,33 +227,34 @@ type LinkingTests (output:ITestOutputHelper) = let fileId = getTempFile() let file = getManager fileId entryPoints.[0] compilationManager.AddOrUpdateSourceFileAsync(file) |> ignore - this.CompileAndVerify entryPoints.[1] [Error ErrorCode.MultipleEntryPoints] + this.CompileAndVerify compilationManager entryPoints.[1] [Error ErrorCode.MultipleEntryPoints] compilationManager.TryRemoveSourceFileAsync(fileId, false) |> ignore - [] - member this.``Entry point specialization verification`` () = - - for entryPoint in LinkingTests.ReadAndChunkSourceFile "EntryPointSpecializations.qs" do - this.CompileAndVerify entryPoint [Error ErrorCode.InvalidEntryPointSpecialization] - - [] member this.``Entry point validation`` () = for entryPoint in LinkingTests.ReadAndChunkSourceFile "ValidEntryPoints.qs" do - this.CompileAndVerify entryPoint [] + this.CompileAndVerify compilationManager entryPoint [] + this.Expect "EntryPointInLibrary" [Error ErrorCode.EntryPointInLibrary] [] member this.``Entry point argument name verification`` () = let tests = LinkingTests.ReadAndChunkSourceFile "EntryPointDiagnostics.qs" - this.CompileAndVerify tests.[0] [Error ErrorCode.DuplicateEntryPointArgumentName] - this.CompileAndVerify tests.[1] [Error ErrorCode.DuplicateEntryPointArgumentName] - this.CompileAndVerify tests.[2] [Error ErrorCode.DuplicateEntryPointArgumentName] - this.CompileAndVerify tests.[3] [Warning WarningCode.ReservedEntryPointArgumentName] - this.CompileAndVerify tests.[4] [Warning WarningCode.ReservedEntryPointArgumentName] + this.CompileAndVerify compilationManager tests.[0] [Error ErrorCode.DuplicateEntryPointArgumentName] + this.CompileAndVerify compilationManager tests.[1] [Error ErrorCode.DuplicateEntryPointArgumentName] + this.CompileAndVerify compilationManager tests.[2] [Error ErrorCode.DuplicateEntryPointArgumentName] + this.CompileAndVerify compilationManager tests.[3] [Warning WarningCode.ReservedEntryPointArgumentName] + this.CompileAndVerify compilationManager tests.[4] [Warning WarningCode.ReservedEntryPointArgumentName] + + + [] + member this.``Entry point specialization verification`` () = + + for entryPoint in LinkingTests.ReadAndChunkSourceFile "EntryPointSpecializations.qs" do + this.CompileAndVerify compilationManager entryPoint [Error ErrorCode.InvalidEntryPointSpecialization] [] @@ -271,6 +272,24 @@ type LinkingTests (output:ITestOutputHelper) = this.Expect "InvalidEntryPointPlacement7" [Error ErrorCode.MisplacedDeclarationAttribute] + [] + member this.``Entry point return type restriction for quantum processors`` () = + + let tests = LinkingTests.ReadAndChunkSourceFile "EntryPointDiagnostics.qs" + let compilationManager = new CompilationUnitManager(new Action (fun ex -> failwith ex.Message), isExecutable = true, capabilities = RuntimeCapabilities.QPRGen0) + let addOrUpdateSourceFile filePath = getManager (new Uri(filePath)) (File.ReadAllText filePath) |> compilationManager.AddOrUpdateSourceFileAsync |> ignore + Path.Combine ("TestCases", "LinkingTests", "Core.qs") |> Path.GetFullPath |> addOrUpdateSourceFile + + this.CompileAndVerify compilationManager tests.[5] [] + this.CompileAndVerify compilationManager tests.[6] [] + this.CompileAndVerify compilationManager tests.[7] [] + this.CompileAndVerify compilationManager tests.[8] [] + this.CompileAndVerify compilationManager tests.[9] [Warning WarningCode.NonResultTypeReturnedInEntryPoint] + this.CompileAndVerify compilationManager tests.[10] [Warning WarningCode.NonResultTypeReturnedInEntryPoint] + this.CompileAndVerify compilationManager tests.[11] [Warning WarningCode.NonResultTypeReturnedInEntryPoint] + this.CompileAndVerify compilationManager tests.[12] [Warning WarningCode.NonResultTypeReturnedInEntryPoint] + + [] member this.``Entry point argument and return type verification`` () = diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/EntryPointDiagnostics.qs b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/EntryPointDiagnostics.qs index 7d712670a0..57f7b28c6e 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/EntryPointDiagnostics.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/EntryPointDiagnostics.qs @@ -41,3 +41,82 @@ namespace Microsoft.Quantum.Testing.EntryPoints { @ EntryPoint() operation InvalidEntryPoint45(s : Int) : Unit {} } + +// ================================= + +namespace Microsoft.Quantum.Testing.EntryPoints { + + @ EntryPoint() + operation EntryPoint1() : Result { + return Zero; + } +} + +// ================================= + +namespace Microsoft.Quantum.Testing.EntryPoints { + + @ EntryPoint() + operation EntryPoint2() : Result[] { + return [Zero]; + } +} + +// ================================= + +namespace Microsoft.Quantum.Testing.EntryPoints { + + @ EntryPoint() + operation EntryPoint3() : (Result, Result[]) { + return (Zero, [Zero]); + } +} + +// ================================= + +namespace Microsoft.Quantum.Testing.EntryPoints { + + @ EntryPoint() + operation EntryPoint4() : ((Result, Result), Result[]) { + return ((Zero, Zero), [Zero]); + } +} + +// ================================= + +namespace Microsoft.Quantum.Testing.EntryPoints { + + @ EntryPoint() + operation EntryPoint5() : Unit {} +} + +// ================================= + +namespace Microsoft.Quantum.Testing.EntryPoints { + + @ EntryPoint() + operation EntryPoint6() : (Int, Result) { + return (0, Zero); + } +} + +// ================================= + +namespace Microsoft.Quantum.Testing.EntryPoints { + + @ EntryPoint() + operation EntryPoint7() : String[] { + return [""]; + } +} + +// ================================= + +namespace Microsoft.Quantum.Testing.EntryPoints { + + @ EntryPoint() + operation EntryPoint8() : ((Result[], (Result, Int)[]), Result) { + return (([Zero], [(Zero, 0)]), Zero); + } +} + diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/InvalidEntryPoints.qs b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/InvalidEntryPoints.qs index a7198c1741..8dfbb83f54 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/InvalidEntryPoints.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/InvalidEntryPoints.qs @@ -8,6 +8,9 @@ namespace Microsoft.Quantum.Testing.EntryPoints { // tests related to entry point placement verification + @ EntryPoint() + operation EntryPointInLibrary() : Unit { } + @ EntryPoint() newtype InvalidEntryPointPlacement1 = Int; diff --git a/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs b/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs index c4e98fd1e0..8ffae6b26a 100644 --- a/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs +++ b/src/QsCompiler/Tests.LanguageServer/ProjectLoaderTests.cs @@ -113,8 +113,8 @@ public void LoadOutdatedQsharpProject() var (projectFile, context) = Context("test9"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); - Assert.AreEqual("test9.dll", Path.GetFileName(context.OutputPath)); - Assert.IsTrue(Path.GetDirectoryName(context.OutputPath).StartsWith(projDir)); + Assert.AreEqual("test9.dll", Path.GetFileName(context.Properties.OutputPath)); + Assert.IsTrue(Path.GetDirectoryName(context.Properties.OutputPath).StartsWith(projDir)); var qsFiles = new string[] { @@ -133,8 +133,8 @@ public void LoadQsharpCoreLibraries() var (projectFile, context) = Context("test3"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); - Assert.AreEqual("test3.dll", Path.GetFileName(context.OutputPath)); - Assert.IsTrue(Path.GetDirectoryName(context.OutputPath).StartsWith(projDir)); + Assert.AreEqual("test3.dll", Path.GetFileName(context.Properties.OutputPath)); + Assert.IsTrue(Path.GetDirectoryName(context.Properties.OutputPath).StartsWith(projDir)); var qsFiles = new string[] { @@ -152,8 +152,8 @@ public void LoadQsharpCoreLibraries() (projectFile, context) = Context("test12"); projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); - Assert.AreEqual("test12.dll", Path.GetFileName(context.OutputPath)); - Assert.IsTrue(Path.GetDirectoryName(context.OutputPath).StartsWith(projDir)); + Assert.AreEqual("test12.dll", Path.GetFileName(context.Properties.OutputPath)); + Assert.IsTrue(Path.GetDirectoryName(context.Properties.OutputPath).StartsWith(projDir)); qsFiles = new string[] { @@ -175,8 +175,8 @@ public void LoadQsharpFrameworkLibrary() var (projectFile, context) = Context("test7"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); - Assert.AreEqual("test7.dll", Path.GetFileName(context.OutputPath)); - Assert.IsTrue(Path.GetDirectoryName(context.OutputPath).StartsWith(projDir)); + Assert.AreEqual("test7.dll", Path.GetFileName(context.Properties.OutputPath)); + Assert.IsTrue(Path.GetDirectoryName(context.Properties.OutputPath).StartsWith(projDir)); var qsFiles = new string[] { @@ -195,8 +195,8 @@ public void LoadQsharpConsoleApps() var (projectFile, context) = Context("test4"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); - Assert.AreEqual("test4.dll", Path.GetFileName(context.OutputPath)); - Assert.IsTrue(Path.GetDirectoryName(context.OutputPath).StartsWith(projDir)); + Assert.AreEqual("test4.dll", Path.GetFileName(context.Properties.OutputPath)); + Assert.IsTrue(Path.GetDirectoryName(context.Properties.OutputPath).StartsWith(projDir)); var qsFiles = new string[] { @@ -212,8 +212,8 @@ public void LoadQsharpConsoleApps() (projectFile, context) = Context("test10"); projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); - Assert.AreEqual("test10.dll", Path.GetFileName(context.OutputPath)); - Assert.IsTrue(Path.GetDirectoryName(context.OutputPath).StartsWith(projDir)); + Assert.AreEqual("test10.dll", Path.GetFileName(context.Properties.OutputPath)); + Assert.IsTrue(Path.GetDirectoryName(context.Properties.OutputPath).StartsWith(projDir)); qsFiles = new string[] { @@ -227,8 +227,8 @@ public void LoadQsharpConsoleApps() (projectFile, context) = Context("test11"); projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); - Assert.AreEqual("test11.dll", Path.GetFileName(context.OutputPath)); - Assert.IsTrue(Path.GetDirectoryName(context.OutputPath).StartsWith(projDir)); + Assert.AreEqual("test11.dll", Path.GetFileName(context.Properties.OutputPath)); + Assert.IsTrue(Path.GetDirectoryName(context.Properties.OutputPath).StartsWith(projDir)); qsFiles = new string[] { @@ -246,8 +246,8 @@ public void LoadQsharpUnitTest() var (projectFile, context) = Context("test5"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); - Assert.AreEqual("test5.dll", Path.GetFileName(context.OutputPath)); - Assert.IsTrue(Path.GetDirectoryName(context.OutputPath).StartsWith(projDir)); + Assert.AreEqual("test5.dll", Path.GetFileName(context.Properties.OutputPath)); + Assert.IsTrue(Path.GetDirectoryName(context.Properties.OutputPath).StartsWith(projDir)); var qsFiles = new string[] { @@ -270,8 +270,8 @@ public void LoadQsharpMultiFrameworkLibrary() var (projectFile, context) = Context("test6"); var projDir = Path.GetDirectoryName(projectFile); Assert.IsNotNull(context); - Assert.AreEqual("test6.dll", Path.GetFileName(context.OutputPath)); - Assert.IsTrue(Path.GetDirectoryName(context.OutputPath).StartsWith(projDir)); + Assert.AreEqual("test6.dll", Path.GetFileName(context.Properties.OutputPath)); + Assert.IsTrue(Path.GetDirectoryName(context.Properties.OutputPath).StartsWith(projDir)); var qsFiles = new string[] { diff --git a/src/QuantumSdk/DefaultItems/DefaultItems.targets b/src/QuantumSdk/DefaultItems/DefaultItems.targets index 4f8243ac80..aaed1d27ab 100644 --- a/src/QuantumSdk/DefaultItems/DefaultItems.targets +++ b/src/QuantumSdk/DefaultItems/DefaultItems.targets @@ -46,10 +46,10 @@ - QPRGen1 - QPRGen0 - QPRGen1 - Unknown + QPRGen1 + QPRGen0 + QPRGen1 + Unknown diff --git a/src/QuantumSdk/Sdk/Sdk.props b/src/QuantumSdk/Sdk/Sdk.props index 7f9bd490b9..9c0384e810 100644 --- a/src/QuantumSdk/Sdk/Sdk.props +++ b/src/QuantumSdk/Sdk/Sdk.props @@ -30,6 +30,7 @@ +