From 3ffd626b249f836369054cc1aa9e5d2f8aadfe0b Mon Sep 17 00:00:00 2001 From: Chris Granade Date: Fri, 12 Mar 2021 12:34:33 -0800 Subject: [PATCH 1/8] Fix #885 (#886) * [WIP] Started updating VS LSP client. * A few more nullability fixes. * Use explicit 0:0 range instead of relying on constructor. * Propagate nullability metadata to F# callers. * Avoid calling unsafe constructors. * Avoid deprecated Assert.Equals. * Set DocumentRangeFormattingProvider to false. * Fix to AssertCapability. * Assertions should work more reliabily when capabilities are null. * Disable nullable reference checking when copying unsound types. * Check if Range is null, since nullability metadata can be violated. Co-authored-by: Ricardo Espinoza --- src/QsCompiler/CommandLineTool/Logging.cs | 12 ++-- .../CompilationManager.csproj | 2 +- .../CompilationManager/DiagnosticTools.cs | 46 ++++++++----- .../CompilationManager/Diagnostics.cs | 48 +++++++++++--- .../EditorSupport/CodeCompletion.cs | 4 +- .../EditorSupport/EditorCommands.cs | 4 +- .../EditorSupport/SymbolInformation.cs | 2 +- .../CompilationManager/ProjectManager.cs | 22 ++++++- src/QsCompiler/CompilationManager/Utils.cs | 6 +- src/QsCompiler/Compiler/LoadedStep.cs | 4 +- src/QsCompiler/Compiler/Logging.cs | 18 +++-- .../LanguageServer/Communication.cs | 24 ++++--- .../LanguageServer/LanguageServer.cs | 19 ++++-- .../LanguageServer/LanguageServer.csproj | 2 +- .../Tests.Compiler/CallGraphTests.fs | 2 +- .../Tests.Compiler/CompilationLoaderTests.fs | 2 +- src/QsCompiler/Tests.Compiler/LinkingTests.fs | 2 +- .../TestUtils/SetupVerificationTests.fs | 6 +- .../Tests.LanguageServer/TestSetup.cs | 2 +- .../Tests.LanguageServer.csproj | 2 +- src/QsCompiler/Tests.LanguageServer/Tests.cs | 66 +++++++++++++++---- 21 files changed, 214 insertions(+), 81 deletions(-) diff --git a/src/QsCompiler/CommandLineTool/Logging.cs b/src/QsCompiler/CommandLineTool/Logging.cs index 4101b2a036..a1173b9359 100644 --- a/src/QsCompiler/CommandLineTool/Logging.cs +++ b/src/QsCompiler/CommandLineTool/Logging.cs @@ -41,12 +41,14 @@ public ConsoleLogger( /// Prints the given message to the Console. /// Errors and Warnings are printed to the error stream. /// - private static void PrintToConsole(DiagnosticSeverity severity, string message) + private static void PrintToConsole(DiagnosticSeverity? severity, string message) { - var (stream, color) = - severity == DiagnosticSeverity.Error ? (Console.Error, ConsoleColor.Red) : - severity == DiagnosticSeverity.Warning ? (Console.Error, ConsoleColor.Yellow) : - (Console.Out, Console.ForegroundColor); + var (stream, color) = severity switch + { + DiagnosticSeverity.Error => (Console.Error, ConsoleColor.Red), + DiagnosticSeverity.Warning => (Console.Error, ConsoleColor.Yellow), + _ => (Console.Out, Console.ForegroundColor) + }; var consoleColor = Console.ForegroundColor; Console.ForegroundColor = color; diff --git a/src/QsCompiler/CompilationManager/CompilationManager.csproj b/src/QsCompiler/CompilationManager/CompilationManager.csproj index 5212b69da0..753a0cad2b 100644 --- a/src/QsCompiler/CompilationManager/CompilationManager.csproj +++ b/src/QsCompiler/CompilationManager/CompilationManager.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/QsCompiler/CompilationManager/DiagnosticTools.cs b/src/QsCompiler/CompilationManager/DiagnosticTools.cs index db7157cb4a..512312624d 100644 --- a/src/QsCompiler/CompilationManager/DiagnosticTools.cs +++ b/src/QsCompiler/CompilationManager/DiagnosticTools.cs @@ -37,28 +37,35 @@ public static Position SymbolPosition(QsLocation rootLocation, QsNullable - position is null ? null : new Lsp.Position(position.Line, position.Character); - - Lsp.Range? CopyRange(Lsp.Range? range) => - range is null - ? null - : new Lsp.Range - { - Start = CopyPosition(range.Start), - End = CopyPosition(range.End) - }; + Lsp.Position CopyPosition(Lsp.Position position) => + new Lsp.Position(position.Line, position.Character); + Lsp.Range CopyRange(Lsp.Range range) => + new Lsp.Range + { + Start = CopyPosition(range.Start), + End = CopyPosition(range.End) + }; + + // NB: The nullability metadata on Diagnostic.Range is incorrect, + // such that some Diagnostic values may have nullable ranges. + // We cannot assign that to a new Diagnostic without + // contradicting nullability metadata, however, so we need to + // explicitly disable nullable references for the following + // statement. Once the upstream bug in the LSP client package + // is fixed, we can remove the nullable disable here. + #nullable disable return message is null ? null : new Diagnostic { - Range = CopyRange(message.Range), + Range = message.Range == null ? null : CopyRange(message.Range), Severity = message.Severity, Code = message.Code, Source = message.Source, Message = message.Message }; + #nullable restore } /// @@ -70,12 +77,17 @@ range is null public static Diagnostic WithLineNumOffset(this Diagnostic diagnostic, int offset) { var copy = diagnostic.Copy(); - copy.Range.Start.Line += offset; - copy.Range.End.Line += offset; - if (copy.Range.Start.Line < 0 || copy.Range.End.Line < 0) + // NB: Despite the nullability metadata, Range may be null here. + // We thus need to guard accordingly. + if (copy.Range != null) { - throw new ArgumentOutOfRangeException( - nameof(offset), "Translated diagnostic has negative line numbers."); + copy.Range.Start.Line += offset; + copy.Range.End.Line += offset; + if (copy.Range.Start.Line < 0 || copy.Range.End.Line < 0) + { + throw new ArgumentOutOfRangeException( + nameof(offset), "Translated diagnostic has negative line numbers."); + } } return copy; } diff --git a/src/QsCompiler/CompilationManager/Diagnostics.cs b/src/QsCompiler/CompilationManager/Diagnostics.cs index 3602ed3566..0c13106ec5 100644 --- a/src/QsCompiler/CompilationManager/Diagnostics.cs +++ b/src/QsCompiler/CompilationManager/Diagnostics.cs @@ -121,7 +121,7 @@ public static Diagnostic LoadWarning(WarningCode code, IEnumerable args, Code = Code(code), Source = source, Message = DiagnosticItem.Message(code, args ?? Enumerable.Empty()), - Range = null + Range = new Lsp.Range { Start = new Lsp.Position(0, 0), End = new Lsp.Position(0, 0) } }; // warnings 20** @@ -134,7 +134,7 @@ internal static Diagnostic EmptyStatementWarning(string filename, Position pos) Code = WarningCode.ExcessSemicolon.Code(), Source = filename, Message = DiagnosticItem.Message(WarningCode.ExcessSemicolon, Enumerable.Empty()), - Range = pos == null ? null : new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } + Range = new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } }; } } @@ -156,32 +156,42 @@ public static Diagnostic LoadError(ErrorCode code, IEnumerable args, str Code = Code(code), Source = source, Message = DiagnosticItem.Message(code, args ?? Enumerable.Empty()), - Range = null + Range = new Lsp.Range { Start = new Lsp.Position(0, 0), End = new Lsp.Position(0, 0) } }; // errors 20** internal static Diagnostic InvalidFragmentEnding(string filename, ErrorCode code, Position pos) { + if (pos == null) + { + throw new ArgumentNullException(nameof(pos)); + } + return new Diagnostic { Severity = DiagnosticSeverity.Error, Code = Code(code), Source = filename, Message = DiagnosticItem.Message(code, Enumerable.Empty()), - Range = pos == null ? null : new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } + Range = new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } }; } internal static Diagnostic MisplacedOpeningBracketError(string filename, Position pos) { + if (pos == null) + { + throw new ArgumentNullException(nameof(pos)); + } + return new Diagnostic { Severity = DiagnosticSeverity.Error, Code = ErrorCode.MisplacedOpeningBracket.Code(), Source = filename, Message = DiagnosticItem.Message(ErrorCode.MisplacedOpeningBracket, Enumerable.Empty()), - Range = pos == null ? null : new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } + Range = new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } }; } @@ -189,49 +199,69 @@ internal static Diagnostic MisplacedOpeningBracketError(string filename, Positio internal static Diagnostic ExcessBracketError(string filename, Position pos) { + if (pos == null) + { + throw new ArgumentNullException(nameof(pos)); + } + return new Diagnostic { Severity = DiagnosticSeverity.Error, Code = ErrorCode.ExcessBracketError.Code(), Source = filename, Message = DiagnosticItem.Message(ErrorCode.ExcessBracketError, Enumerable.Empty()), - Range = pos == null ? null : new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } + Range = new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } }; } internal static Diagnostic MissingClosingBracketError(string filename, Position pos) { + if (pos == null) + { + throw new ArgumentNullException(nameof(pos)); + } + return new Diagnostic { Severity = DiagnosticSeverity.Error, Code = ErrorCode.MissingBracketError.Code(), Source = filename, Message = DiagnosticItem.Message(ErrorCode.MissingBracketError, Enumerable.Empty()), - Range = pos == null ? null : new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } + Range = new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } }; } internal static Diagnostic MissingStringDelimiterError(string filename, Position pos) { + if (pos == null) + { + throw new ArgumentNullException(nameof(pos)); + } + return new Diagnostic { Severity = DiagnosticSeverity.Error, Code = ErrorCode.MissingStringDelimiterError.Code(), Source = filename, Message = DiagnosticItem.Message(ErrorCode.MissingStringDelimiterError, Enumerable.Empty()), - Range = pos == null ? null : new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } + Range = new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } }; } internal static Diagnostic InvalidCharacterInInterpolatedArgument(string filename, Position pos, char invalidCharacter) { + if (pos == null) + { + throw new ArgumentNullException(nameof(pos)); + } + return new Diagnostic { Severity = DiagnosticSeverity.Error, Code = ErrorCode.InvalidCharacterInInterpolatedArgument.Code(), Source = filename, Message = DiagnosticItem.Message(ErrorCode.InvalidCharacterInInterpolatedArgument, new[] { invalidCharacter.ToString() }), - Range = pos == null ? null : new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } + Range = new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() } }; } } diff --git a/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs b/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs index c6a2f8555e..74e76ff73c 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs @@ -430,7 +430,7 @@ private static IEnumerable GetNamespaceAliasCompletions( /// completion item data is missing properties. /// private static string? TryGetDocumentation( - CompilationUnit compilation, CompletionItemData data, CompletionItemKind kind, bool useMarkdown) + CompilationUnit compilation, CompletionItemData data, CompletionItemKind? kind, bool useMarkdown) { if (data.QualifiedName == null || data.SourceFile == null @@ -619,7 +619,7 @@ private static CompletionList ToCompletionList(this IEnumerable new CompletionList { IsIncomplete = isIncomplete, - Items = items?.ToArray() + Items = items?.ToArray() ?? new CompletionItem[] { } }; /// diff --git a/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs b/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs index af82d0b54d..245b4abe90 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs @@ -203,7 +203,7 @@ DocumentHighlight AsHighlight(Lsp.Range range) => Hover? GetHover(string? info) => info == null ? null : new Hover { Contents = new MarkupContent { Kind = format, Value = info }, - Range = new Lsp.Range { Start = position?.ToLsp(), End = position?.ToLsp() } + Range = new Lsp.Range { Start = position.ToLsp(), End = position.ToLsp() } }; var markdown = format == MarkupKind.Markdown; @@ -393,7 +393,7 @@ List FunctorApplications(ref QsExpression ex) MarkupContent AsMarkupContent(string str) => new MarkupContent { Kind = format, Value = str }; ParameterInformation AsParameterInfo(string? paramName) => new ParameterInformation { - Label = paramName, + Label = paramName ?? "", Documentation = AsMarkupContent(documentation.ParameterDescription(paramName)) }; diff --git a/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs b/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs index 6ecefa439b..cae3308baf 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs @@ -28,7 +28,7 @@ internal static class SymbolInfo internal static Location AsLocation(string source, Position offset, Range relRange) => new Location { - Uri = CompilationUnitManager.TryGetUri(source, out var uri) ? uri : null, + Uri = CompilationUnitManager.TryGetUri(source, out var uri) ? uri : throw new Exception($"Source location {source} could not be converted to a valid URI."), Range = (offset + relRange).ToLsp() }; diff --git a/src/QsCompiler/CompilationManager/ProjectManager.cs b/src/QsCompiler/CompilationManager/ProjectManager.cs index 92aaeb1ce1..6da389f204 100644 --- a/src/QsCompiler/CompilationManager/ProjectManager.cs +++ b/src/QsCompiler/CompilationManager/ProjectManager.cs @@ -929,10 +929,30 @@ public Task ManagerTaskAsync(Uri file, Action exec try { + // NB: As of version 16.9.1180 of the LSP client, document + // changes are presented as the sum type + // TextDocumentEdit[] | (TextDocumentEdit | CreateFile | RenameFile | DeleteFile)[]. + // Thus, to collect them with a SelectMany call, we need + // to ensure that the first case (TextDocumentEdit[]) is + // first wrapped in a cast to the sum type + // TextDocumentEdit | CreateFile | RenameFile | DeleteFile. + // Note that the SumType struct is defined in the LSP client, + // and works by defining explicit cast operators for each case. + IEnumerable> CastToSumType(SumType[]>? editCollection) => + editCollection switch + { + { } edits => edits.Match( + simpleEdits => simpleEdits.Cast>(), + complexEdits => complexEdits), + null => ImmutableList>.Empty + }; + // if a file belongs to several compilation units, then this will fail var changes = edits.SelectMany(edit => edit.Changes) .ToDictionary(pair => pair.Key, pair => pair.Value); - var documentChanges = edits.SelectMany(edit => edit.DocumentChanges).ToArray(); + var documentChanges = edits + .SelectMany(edits => CastToSumType(edits.DocumentChanges).ToArray()) + .ToArray(); return new WorkspaceEdit { Changes = changes, DocumentChanges = documentChanges }; } catch diff --git a/src/QsCompiler/CompilationManager/Utils.cs b/src/QsCompiler/CompilationManager/Utils.cs index 4c0874539a..20d7cc4fd2 100644 --- a/src/QsCompiler/CompilationManager/Utils.cs +++ b/src/QsCompiler/CompilationManager/Utils.cs @@ -168,8 +168,10 @@ public static Position ToQSharp(this Lsp.Position position) => /// /// Converts the Q# compiler position into a language server protocol position. /// - public static Lsp.Position ToLsp(this Position position) => - new Lsp.Position(position.Line, position.Column); + public static Lsp.Position ToLsp(this Position? position) => + position == null + ? new Lsp.Position() + : new Lsp.Position(position.Line, position.Column); /// /// Converts the language server protocol range into a Q# compiler range. diff --git a/src/QsCompiler/Compiler/LoadedStep.cs b/src/QsCompiler/Compiler/LoadedStep.cs index 6e533cf18f..86288d5417 100644 --- a/src/QsCompiler/Compiler/LoadedStep.cs +++ b/src/QsCompiler/Compiler/LoadedStep.cs @@ -123,7 +123,9 @@ internal static Diagnostic ConvertDiagnostic(IRewriteStep.Diagnostic diagnostic, Severity = severity, Message = $"{stageAnnotation}{diagnostic.Message}", Source = diagnostic.Source, - Range = diagnostic.Source is null ? null : diagnostic.Range?.ToLsp() + Range = diagnostic.Source is null || diagnostic.Range is null + ? new VisualStudio.LanguageServer.Protocol.Range() + : diagnostic.Range.ToLsp() }; } diff --git a/src/QsCompiler/Compiler/Logging.cs b/src/QsCompiler/Compiler/Logging.cs index 68fb6471c0..7ad998ede2 100644 --- a/src/QsCompiler/Compiler/Logging.cs +++ b/src/QsCompiler/Compiler/Logging.cs @@ -69,6 +69,13 @@ protected internal virtual void OnException(Exception ex) => Message = $"{Environment.NewLine}{ex}{Environment.NewLine}" }); + // NB: Calling the LSP.Range constructor results in an object with + // non-nullable fields set to null values, confusing other places + // where we use nullable reference type metadata. To address this, + // we explicitly construct an empty range that runs from 0:0 to 0:0 + // that we can use when there is no reasonable range to provide. + private static readonly LSP.Range EmptyRange = new LSP.Range { Start = new Position(0, 0), End = new Position(0, 0) }; + // routines for convenience /// @@ -82,7 +89,7 @@ public void Log(ErrorCode code, IEnumerable args, string? source = null, Code = Errors.Code(code), Source = source, Message = DiagnosticItem.Message(code, args ?? Enumerable.Empty()), - Range = range + Range = range ?? EmptyRange }); /// @@ -96,7 +103,7 @@ public void Log(WarningCode code, IEnumerable args, string? source = nul Code = Warnings.Code(code), Source = source, Message = DiagnosticItem.Message(code, args ?? Enumerable.Empty()), - Range = range + Range = range ?? EmptyRange }); /// @@ -111,7 +118,7 @@ public void Log(InformationCode code, IEnumerable args, string? source = Code = null, // do not show a code for infos Source = source, Message = $"{DiagnosticItem.Message(code, args ?? Enumerable.Empty())}{Environment.NewLine}{string.Join(Environment.NewLine, messageParam)}", - Range = range + Range = range ?? EmptyRange }); /// @@ -157,6 +164,7 @@ private void Output(Diagnostic? msg) public void Log(Diagnostic m) { if (m.Severity == DiagnosticSeverity.Warning && + m.Code != null && CompilationBuilder.Diagnostics.TryGetCode(m.Code, out int code) && this.noWarn.Contains(code)) { @@ -172,7 +180,9 @@ public void Log(Diagnostic m) ++this.NrWarningsLogged; } - var msg = m.Range == null ? m : m.WithLineNumOffset(this.lineNrOffset); + // We only want to print line number offsets if at least one of the + // start and end ranges are not both empty. + var msg = m.Range == EmptyRange ? m : m.WithLineNumOffset(this.lineNrOffset); this.Output(msg); } diff --git a/src/QsCompiler/LanguageServer/Communication.cs b/src/QsCompiler/LanguageServer/Communication.cs index 293d226141..80db08a720 100644 --- a/src/QsCompiler/LanguageServer/Communication.cs +++ b/src/QsCompiler/LanguageServer/Communication.cs @@ -64,13 +64,21 @@ public class CodeActionParams [DataMember(Name = "context")] public CodeActionContext? Context { get; set; } - public VisualStudio.LanguageServer.Protocol.CodeActionParams ToCodeActionParams() => - new VisualStudio.LanguageServer.Protocol.CodeActionParams - { - TextDocument = this.TextDocument, - Range = this.Range, - Context = this.Context?.ToCodeActionContext() - }; + public VisualStudio.LanguageServer.Protocol.CodeActionParams? ToCodeActionParams() => + this.TextDocument == null + ? null + : new VisualStudio.LanguageServer.Protocol.CodeActionParams + { + TextDocument = this.TextDocument, + Range = this.Range ?? new Range(), + Context = this.Context?.ToCodeActionContext() ?? + // Make a blank context if we're missing a code action + // context. + new VisualStudio.LanguageServer.Protocol.CodeActionContext + { + Diagnostics = new Diagnostic[] { } + } + }; } /// @@ -87,7 +95,7 @@ public class CodeActionContext public VisualStudio.LanguageServer.Protocol.CodeActionContext ToCodeActionContext() => new VisualStudio.LanguageServer.Protocol.CodeActionContext { - Diagnostics = this.Diagnostics, + Diagnostics = this.Diagnostics ?? new Diagnostic[] { }, Only = null }; } diff --git a/src/QsCompiler/LanguageServer/LanguageServer.cs b/src/QsCompiler/LanguageServer/LanguageServer.cs index 5e63ee8474..6543ba39ce 100644 --- a/src/QsCompiler/LanguageServer/LanguageServer.cs +++ b/src/QsCompiler/LanguageServer/LanguageServer.cs @@ -273,6 +273,7 @@ public object Initialize(JToken arg) CompletionProvider = supportsCompletion ? new CompletionOptions() : null, SignatureHelpProvider = new SignatureHelpOptions(), ExecuteCommandProvider = new ExecuteCommandOptions(), + DocumentRangeFormattingProvider = false }; capabilities.TextDocumentSync.Change = TextDocumentSyncKind.Incremental; capabilities.TextDocumentSync.OpenClose = true; @@ -353,7 +354,11 @@ public Task OnTextDocumentDidSaveAsync(JToken arg) return Task.CompletedTask; } var param = Utils.TryJTokenAs(arg); - return this.editorState.SaveFileAsync(param.TextDocument, param.Text); + // NB: if param.Text is null, then there's nothing to actually + // do here. + return param.Text == null + ? Task.CompletedTask + : this.editorState.SaveFileAsync(param.TextDocument, param.Text); } [JsonRpcMethod(Methods.TextDocumentDidChangeName)] @@ -398,10 +403,8 @@ public object OnTextDocumentDefinition(JToken arg) var param = Utils.TryJTokenAs(arg); var defaultLocation = new Location { - Uri = param?.TextDocument?.Uri, - Range = param?.Position != null - ? new VisualStudio.LanguageServer.Protocol.Range { Start = param.Position, End = param.Position } - : null + Uri = param.TextDocument.Uri, + Range = new VisualStudio.LanguageServer.Protocol.Range { Start = param.Position, End = param.Position } }; try { @@ -595,6 +598,12 @@ CodeAction CreateAction(string title, WorkspaceEdit edit) return ProtocolError.AwaitingInitialization; } var param = Utils.TryJTokenAs(arg).ToCodeActionParams(); + if (param == null) + { + this.LogToWindow("No code action parameters found; skipping code actions.", MessageType.Warning); + return Array.Empty(); + } + try { return diff --git a/src/QsCompiler/LanguageServer/LanguageServer.csproj b/src/QsCompiler/LanguageServer/LanguageServer.csproj index 5e0c4656cf..b66bd62bc7 100644 --- a/src/QsCompiler/LanguageServer/LanguageServer.csproj +++ b/src/QsCompiler/LanguageServer/LanguageServer.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/QsCompiler/Tests.Compiler/CallGraphTests.fs b/src/QsCompiler/Tests.Compiler/CallGraphTests.fs index dd260aa419..9555e36865 100644 --- a/src/QsCompiler/Tests.Compiler/CallGraphTests.fs +++ b/src/QsCompiler/Tests.Compiler/CallGraphTests.fs @@ -93,7 +93,7 @@ type CallGraphTests(output: ITestOutputHelper) = let got = compilationDataStructures.Diagnostics() - |> Seq.filter (fun d -> d.Severity = DiagnosticSeverity.Error) + |> Seq.filter (fun d -> d.Severity = Nullable DiagnosticSeverity.Error) |> Seq.choose (fun d -> match Diagnostics.TryGetCode d.Code with | true, code -> Some code diff --git a/src/QsCompiler/Tests.Compiler/CompilationLoaderTests.fs b/src/QsCompiler/Tests.Compiler/CompilationLoaderTests.fs index ce126675d2..09c4ec8124 100644 --- a/src/QsCompiler/Tests.Compiler/CompilationLoaderTests.fs +++ b/src/QsCompiler/Tests.Compiler/CompilationLoaderTests.fs @@ -53,7 +53,7 @@ type CompilationLoaderTests(output: ITestOutputHelper) = let errors = compilation.Diagnostics() - |> Seq.filter (fun diagnostic -> diagnostic.Severity = DiagnosticSeverity.Error) + |> Seq.filter (fun diagnostic -> diagnostic.Severity = Nullable DiagnosticSeverity.Error) Assert.Empty errors compilation.BuiltCompilation diff --git a/src/QsCompiler/Tests.Compiler/LinkingTests.fs b/src/QsCompiler/Tests.Compiler/LinkingTests.fs index 9409c49cf5..4c0860cd1e 100644 --- a/src/QsCompiler/Tests.Compiler/LinkingTests.fs +++ b/src/QsCompiler/Tests.Compiler/LinkingTests.fs @@ -118,7 +118,7 @@ type LinkingTests(output: ITestOutputHelper) = member private this.BuildReference(source: string, content) = let comp = this.BuildContent(new CompilationUnitManager(), content) - Assert.Empty(comp.Diagnostics() |> Seq.filter (fun d -> d.Severity = DiagnosticSeverity.Error)) + Assert.Empty(comp.Diagnostics() |> Seq.filter (fun d -> d.Severity = Nullable DiagnosticSeverity.Error)) struct (source, comp.BuiltCompilation.Namespaces) member private this.CompileMonomorphization input = diff --git a/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs b/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs index 9e89b7fc68..b1e62c98c6 100644 --- a/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs @@ -90,15 +90,15 @@ type CompilerTests(compilation: CompilationUnitManager.Compilation) = member this.Verify(name, expected: IEnumerable) = let expected = expected.Select(fun code -> int code) - VerifyDiagnosticsOfSeverity DiagnosticSeverity.Error name expected + VerifyDiagnosticsOfSeverity (Nullable DiagnosticSeverity.Error) name expected member this.Verify(name, expected: IEnumerable) = let expected = expected.Select(fun code -> int code) - VerifyDiagnosticsOfSeverity DiagnosticSeverity.Warning name expected + VerifyDiagnosticsOfSeverity (Nullable DiagnosticSeverity.Warning) name expected member this.Verify(name, expected: IEnumerable) = let expected = expected.Select(fun code -> int code) - VerifyDiagnosticsOfSeverity DiagnosticSeverity.Information name expected + VerifyDiagnosticsOfSeverity (Nullable DiagnosticSeverity.Information) name expected member this.VerifyDiagnostics(name, expected: IEnumerable) = let errs = diff --git a/src/QsCompiler/Tests.LanguageServer/TestSetup.cs b/src/QsCompiler/Tests.LanguageServer/TestSetup.cs index e31eda192f..b501c986f7 100644 --- a/src/QsCompiler/Tests.LanguageServer/TestSetup.cs +++ b/src/QsCompiler/Tests.LanguageServer/TestSetup.cs @@ -31,7 +31,7 @@ public Task GetFileContentInMemoryAsync(string filename) => public Task GetFileDiagnosticsAsync(string? filename = null) => this.rpc.InvokeWithParameterObjectAsync( Methods.WorkspaceExecuteCommand.Name, - TestUtils.ServerCommand(CommandIds.FileDiagnostics, filename == null ? new TextDocumentIdentifier { Uri = null } : TestUtils.GetTextDocumentIdentifier(filename))); + TestUtils.ServerCommand(CommandIds.FileDiagnostics, filename == null ? new TextDocumentIdentifier { Uri = new Uri("file://unknown") } : TestUtils.GetTextDocumentIdentifier(filename))); public Task SetupAsync() { diff --git a/src/QsCompiler/Tests.LanguageServer/Tests.LanguageServer.csproj b/src/QsCompiler/Tests.LanguageServer/Tests.LanguageServer.csproj index ba88b76c19..1aed3f1489 100644 --- a/src/QsCompiler/Tests.LanguageServer/Tests.LanguageServer.csproj +++ b/src/QsCompiler/Tests.LanguageServer/Tests.LanguageServer.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/QsCompiler/Tests.LanguageServer/Tests.cs b/src/QsCompiler/Tests.LanguageServer/Tests.cs index b7bfe4f3a5..b32a22b180 100644 --- a/src/QsCompiler/Tests.LanguageServer/Tests.cs +++ b/src/QsCompiler/Tests.LanguageServer/Tests.cs @@ -12,6 +12,39 @@ namespace Microsoft.Quantum.QsLanguageServer.Testing { + internal static class Extensions + { + internal static void AssertCapability(this SumType? capability, bool shouldHave = true, Func? condition = null) + { + if (shouldHave) + { + Assert.IsTrue(capability.HasValue, "Expected capability to have value, but was null."); + } + + if (capability.HasValue) + { + capability!.Value.Match( + flag => + { + Assert.AreEqual(flag, shouldHave); + return true; + }, + options => + { + if (condition != null) + { + Assert.IsTrue(condition(options)); + } + else + { + Assert.IsNotNull(options); + } + return true; + }); + } + } + } + [TestClass] public partial class BasicFunctionality { @@ -68,16 +101,19 @@ public async Task ServerCapabilitiesAsync() { // NOTE: these assertions need to be adapted when the server capabilities are changed var initParams = TestUtils.GetInitializeParams(); - initParams.Capabilities.Workspace.ApplyEdit = true; + Assert.IsNotNull(initParams.Capabilities.Workspace); + // We use the null-forgiving operator, since we check that Workspace + // is not null above. + initParams.Capabilities.Workspace!.ApplyEdit = true; var initReply = await this.rpc.InvokeWithParameterObjectAsync(Methods.Initialize.Name, initParams); Assert.IsNotNull(initReply); Assert.IsNotNull(initReply.Capabilities); Assert.IsNotNull(initReply.Capabilities.TextDocumentSync); - Assert.IsNotNull(initReply.Capabilities.TextDocumentSync.Save); + Assert.IsNotNull(initReply.Capabilities.TextDocumentSync!.Save); Assert.IsNull(initReply.Capabilities.CodeLensProvider); Assert.IsNotNull(initReply.Capabilities.CompletionProvider); - Assert.IsTrue(initReply.Capabilities.CompletionProvider.ResolveProvider); + Assert.IsTrue(initReply.Capabilities.CompletionProvider!.ResolveProvider); Assert.IsNotNull(initReply.Capabilities.CompletionProvider.TriggerCharacters); Assert.IsTrue(initReply.Capabilities.CompletionProvider.TriggerCharacters.SequenceEqual(new[] { ".", "(" })); Assert.IsNotNull(initReply.Capabilities.SignatureHelpProvider?.TriggerCharacters); @@ -85,18 +121,20 @@ public async Task ServerCapabilitiesAsync() Assert.IsNotNull(initReply.Capabilities.ExecuteCommandProvider?.Commands); Assert.IsTrue(initReply.Capabilities.ExecuteCommandProvider!.Commands.Contains(CommandIds.ApplyEdit)); Assert.IsTrue(initReply.Capabilities.TextDocumentSync.OpenClose); - Assert.IsTrue(initReply.Capabilities.TextDocumentSync.Save.IncludeText); + initReply.Capabilities.TextDocumentSync.Save.AssertCapability( + true, + options => options.IncludeText); Assert.AreEqual(TextDocumentSyncKind.Incremental, initReply.Capabilities.TextDocumentSync.Change); - Assert.IsTrue(initReply.Capabilities.DefinitionProvider); - Assert.IsTrue(initReply.Capabilities.ReferencesProvider); - Assert.IsTrue(initReply.Capabilities.DocumentHighlightProvider); - Assert.IsTrue(initReply.Capabilities.DocumentSymbolProvider); - Assert.IsFalse(initReply.Capabilities.WorkspaceSymbolProvider); - Assert.IsTrue(initReply.Capabilities.RenameProvider.Value is bool supported && supported); - Assert.IsTrue(initReply.Capabilities.HoverProvider); - Assert.IsFalse(initReply.Capabilities.DocumentFormattingProvider); - Assert.IsFalse(initReply.Capabilities.DocumentRangeFormattingProvider); - Assert.IsTrue(initReply.Capabilities.CodeActionProvider); + initReply.Capabilities.DefinitionProvider.AssertCapability(); + initReply.Capabilities.ReferencesProvider.AssertCapability(); + initReply.Capabilities.DocumentHighlightProvider.AssertCapability(); + initReply.Capabilities.DocumentSymbolProvider.AssertCapability(); + initReply.Capabilities.WorkspaceSymbolProvider.AssertCapability(shouldHave: false); + initReply.Capabilities.RenameProvider.AssertCapability(); + initReply.Capabilities.HoverProvider.AssertCapability(); + initReply.Capabilities.DocumentFormattingProvider.AssertCapability(shouldHave: false); + initReply.Capabilities.DocumentRangeFormattingProvider.AssertCapability(shouldHave: false); + initReply.Capabilities.CodeActionProvider.AssertCapability(); } [TestMethod] From 15c3318b7850e1cedf71c1efaa2ab587bfa60f04 Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Fri, 12 Mar 2021 18:41:21 -0800 Subject: [PATCH 2/8] readme --- QIR-README.md | 210 +++++++++++--------------------------------------- 1 file changed, 43 insertions(+), 167 deletions(-) diff --git a/QIR-README.md b/QIR-README.md index 6bf43b9fc6..68343a752b 100644 --- a/QIR-README.md +++ b/QIR-README.md @@ -1,188 +1,64 @@ -# QIR Pre-Release Instructions +# QIR Emission - Preview Feature -This file contains directions for using the pre-release version of the QIR -code generation. -This code is still in flux and will change frequently. -In particular, some features are still missing. +This file contains directions for using the preview feature integrated into the Q# compiler to emit QIR. +QIR is a convention for how to represent quantum programs in LLVM. Its specification can be found [here](https://github.com/microsoft/qsharp-language/tree/main/Specifications/QIR#quantum-intermediate-representation-qir). +We aim to ultimately move the Q# compiler to be fully LLVM-based. While the emission is supported starting with the March 2021 release, it is as of this time not yet connected to the runtime. The QIR runtime and instructions for how to execute the emitted QIR can be found [here](https://github.com/microsoft/qsharp-runtime/tree/main/src/QirRuntime#the-native-qir-runtime). We are working on a full integration in the future. -See [this list, below](#to-dos) for some specific open issues and work items. +## Using the Q# Compiler to Emit QIR -## Important Notes - -- The [Llvm.NET ](https://github.com/UbiquityDotNET/Llvm.NET) library does not currently - work on Linux or Mac. Currently QIR must be generated on a Windows machine. This will be - fixed before release. - -## Branch Structure - -QIR development is on the `feature/qir` branch. - -## Using the Q# Compiler to Generate QIR - -Build the QsCompiler solution locally. - -From the repository's root directory, you can run the following: - -```bash -dotnet run --project src/QsCompiler/CommandLineTool/ build --qir QIR s --input examples/QIR/QirCore.qs examples/QIR/QirTarget.qs --proj +QIR can be emitted for any Q# project as long as its output type is an executable. +To enable QIR emission, open the project file in a text editor and add the following project property: ``` - -Note that if the Q# file you're compiling contains an operation with the `@EntryPoint` attribute, -you will need to add a `--build-exe` switch to the command: - -```bash -dotnet run --project src/QsCompiler/CommandLineTool/ build --qir QIR s --build-exe --input examples/QIR/QirCore.qs examples/QIR/QirTarget.qs --proj +true ``` - -Alternatively, you can go to the build output directory and run the Q# compiler executable -from there. -The build output directory is `src/QsCompiler/CommandLineTool/bin/debug/netcoreapp3.1`, -and the Q# compiler is `qsc.exe`. -In this case, the command line is just: - -```bash -./qsc.exe build --qir QIR s --input /examples/QIR/QirCore.qs /examples/QIR/QirTarget.qs --proj +If the project builds successfully, the .ll file containing QIR can be found in `qir` folder in the project folder. Alternatively, the folder path can be specified via the `QirOutputPath` project property. The project file should look similar to this: ``` + + + Exe + netcoreapp3.1 + $(MSBuildThisFileDirectory)/qir + Detailed + + +``` +For more information about project properties and other Sdk capabilities, see [here](https://github.com/microsoft/qsharp-compiler/tree/main/src/QuantumSdk#the-microsoftquantumsdk-nuget-package). -Again, you will need the `--build-exe` switch if you have an entry point defined. - -This will create two files: an LLVM file with a `.ll` extension, -and a text file containing any issues with a `.log` extension. -Both will have the base name set from the `--proj` command line argument. +## Limitations -Assuming everything went OK, the log file will contain the single line "No errors." -If there were errors during the Q# compilation, they will be listed in the log file, one per line. -If the Q# compilation succeeded but the QIR/LLVM generation threw an exception, -the exception and stack trace will appear in the LLVM file. -If the Q# compilation and LLVM generation succeeded, but the generated LLVM did not validate, -then the LLVM validation status will appear in the log file. +Please be aware that as of this time, it is not possible to both QIR and C#. +Open issues related to QIR emission can be found [here](https://github.com/microsoft/qsharp-compiler/issues?q=is%3Aopen+is%3Aissue+label%3A%22area%3A+QIR%22). +The emitted QIR does currently not contain any debug information. We are planning to add better support in the future. If you are interested in contributing, please indicate your interest on [this issue](https://github.com/microsoft/qsharp-compiler/issues/637). ### Entry Points -If you have a Q# operation with the `@EntryPoint` attribute, the QIR generator -will create an additional C-callable wrapper function for the entry point. +For Q# operation decorated with the `@EntryPoint` attribute, the QIR generation +will create an additional C-callable wrapper function. The name of this wrapper is the same as the full, namespace-qualified name -of the entry point, with periods replaced by underscores. - -The entry point wrapper performs some translation between QIR types and standard -C types. -In particular, a QIR array parameter will be replaced in the input signature by -two parameters, an i64 array count and a pointer to the element type. - -The entry point wrapper function gets tagged with an LLVM "EntryPoint" attribute. -Note that this is a custom attribute, rather than metadata, so that passes should -not drop it. +of the entry point, with periods replaced by double underscores. +The entry point wrapper function gets tagged with an custom LLVM "EntryPoint" attribute. + +The entry point wrapper performs some translation between standard +C types and QIR types. + +| QIR type | C-compatible LLVM type | +| --- | --- | +| `%Tuple*` | a pointer to the LLVM struct that corresponds to the fully typed Q# tuple | +| `%Array*` | `{i64, i8*}*` | +| `%BigInt*` | `{i64, i8*}*` | +| `%String*` | `i8*` | +| `%Result*` | `i8` | +| `%Range*` | `{i64, i64, i64}*` | +| `%Int` | `i64` | +| `%Double` | `double` | +| `%Bool` | `i8` | +| `%Pauli` | `i8` | The QIR generator does not currently create a `main(argc, argv)` that translates string values to QIR types that would allow QIR compiled to executable through clang to be executed from the command line. -## Q# Core - -Usually the Q# compiler reads in various built-ins from a Core.qs file that is included by -default in project builds. -When used as above to build individual files, though, this file is not included. - -A version of the Core.qs file suitable for use with the QIR tool is included as -`examples/QIR/QirCore.qs`. -As described above, this file should be included in all QIR generation builds. - -## Target Definitions - -The Q# compiler and LLVM generator do not have a built-in quantum instruction set. -Instead, they rely on two Q# segments that serve three purposes: - -- One segment defines the target-level instruction set; that is, the set of LLVM functions that - should be called to perform quantum operations. -- A second segment defines the user-level instruction set; that is, the set of Q# operations that - user code should use in algorithms. Usually this would match the microsoft.quantum.intrinsic - namespace, and indeed would be specified in that namespace. -- The second segment also specifies the mapping from user-level instructions to target-level - instructions. This mapping is written in Q#, and can be arbitrarily complex; there's no - assumption that there's a simple 1-1 map from user-level to target-level instructions. - -These two segments can be in the same file as the application Q# code, or in a separate file, -or in two separate files, one per segment. - -A sample Q# file suitable that contains both segments suitable for use with the QIR tool is included as -`examples/QIR/QirTarget.qs`. -As described above, this file should be included in all QIR generation builds. - -The sample file does not define intrinsics for the full set of Q# primitives. -You may need to extend this file if it is missing intrinsics you require. - -### Target-level instruction set - -The target-level instructions can all be defined in a single namespace, or in many namespaces. -Both samples define them in the `Microsoft.Quantum.Instructions` namespace, but that can be -changed if desired. -The actual namespace name is not significant. - -Target-level instruction definitions follow this pattern: - -```qsharp - @Intrinsic("x") - operation PhysX (qb : Qubit) : Unit - { - body intrinsic; - } -``` - -- The `Intrinsic` attribute is required. The string argument, with a `quantum.qis.` prefix, - becomes the name of the global LLVM function that implements this instruction. -- The signature of the LLVM function is derived directly from the Q# operation's signature. -- Target-level operations should only have `body` specializations, not `adjoint` or `controlled`. -- The name of the target-level instructions can be arbitrary. By keeping it different from the - name of the user-level instruction, we remove the need to explicitly qualify the name with the - namespace when we refer to it in the user-level instruction definition. -- At the moment, the intrinsic can only have a `body` specialization. This may change in the - future, but also might not. - -### User-level instruction set - -Generally, user-level instructions should be defined in the `Microsoft.Quantum.Intrinsic` -namespace to match standard Q# usage. -Similarly, the instruction names should match the names in the standard Q# library in that -namespace, so that other existing Q# code will find the instructions correctly. - -User-level instruction definitions follow this pattern: - -```qsharp - @Inline() - operation X(qb : Qubit) : Unit - is Adj { - body - { - PhysX(qb); - } - adjoint self; - } -``` - -- The `Inline` attribute is optional. If it appears, the QIR generator will in-line the - implementation of the user-level instruction, so that the generated QIR directly calls the - target-level instruction. Otherwise the generated QIR will call into the LLVM function that - implements the user-level instruction, which will contain a call into the target-level - instruction. -- The implementation of the user-level instruction can contain arbitrary Q# code. For instance, - both sample files define the adjoint of a rotation as the rotation by the negative of the angle. - Once I add support for controlled, the controlled `X` implementation will have Q# code that will - start by checking the number of control qubits and either calling a target-level `CNOT` if - there's one, executing one of the various Toffoli decompositions if there are two, and using - multiple Toffolis with an ancilla qubit if there are more than two. -- User-level instructions will often have both `adjoint` and `controlled` specializations. Because - target-level instructions don't have such specializations, the only directive that can be used - to define these specializations is `self` to mark self-adjoint instructions. - ## QIR Specification The QIR specification is on the [Q# language repository](https://github.com/microsoft/qsharp-language/tree/main/Specifications/QIR). -## To-Dos - -- [ ] Add debug information to QIR, issue #637. -- [ ] Generate a `main` function for an entrypoint, issue #638 -- [ ] Handle structure/tuple mapping for entrypoints, issue #639 -- [ ] Add metadata to QIR from Q# attributes, issue #640 -- [ ] Code clean-up, issue #641 -- [ ] Port to Linux and MacOS, issue #642 From 35bf92c4749268a3106e7631788ca1a70d78a1d5 Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Fri, 12 Mar 2021 18:42:06 -0800 Subject: [PATCH 3/8] moving readme to different folder --- QIR-README.md => src/QsCompiler/QirGeneration/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename QIR-README.md => src/QsCompiler/QirGeneration/README.md (100%) diff --git a/QIR-README.md b/src/QsCompiler/QirGeneration/README.md similarity index 100% rename from QIR-README.md rename to src/QsCompiler/QirGeneration/README.md From 5b4c85fa3f9e37f6f9ca94a361ed5116af72fab0 Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Fri, 12 Mar 2021 18:53:24 -0800 Subject: [PATCH 4/8] disable C# generation when QIR emission is enabled --- src/QuantumSdk/DefaultItems/DefaultItems.targets | 1 + 1 file changed, 1 insertion(+) diff --git a/src/QuantumSdk/DefaultItems/DefaultItems.targets b/src/QuantumSdk/DefaultItems/DefaultItems.targets index f139e7dc2f..75bb7d6cb7 100644 --- a/src/QuantumSdk/DefaultItems/DefaultItems.targets +++ b/src/QuantumSdk/DefaultItems/DefaultItems.targets @@ -59,6 +59,7 @@ true + false true true From bedff8fe6a28a6f0545a23b93dc8efe293138880 Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Fri, 12 Mar 2021 19:41:20 -0800 Subject: [PATCH 5/8] setting up automatic C# entry point generation if necessary --- .../DefaultItems/DefaultItems.targets | 1 + src/QuantumSdk/QuantumSdk.nuspec | 1 + src/QuantumSdk/Sdk/Sdk.targets | 5 ++++ .../Tools/BuildConfiguration/Program.cs | 2 +- .../DefaultEntryPoint.csproj | 12 ++++++++ .../Tools/DefaultEntryPoint/Program.cs | 29 +++++++++++++++++++ src/QuantumSdk/Tools/Tools.sln | 6 ++++ 7 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/QuantumSdk/Tools/DefaultEntryPoint/DefaultEntryPoint.csproj create mode 100644 src/QuantumSdk/Tools/DefaultEntryPoint/Program.cs diff --git a/src/QuantumSdk/DefaultItems/DefaultItems.targets b/src/QuantumSdk/DefaultItems/DefaultItems.targets index 75bb7d6cb7..5461c5f43c 100644 --- a/src/QuantumSdk/DefaultItems/DefaultItems.targets +++ b/src/QuantumSdk/DefaultItems/DefaultItems.targets @@ -57,6 +57,7 @@ + dotnet "$(MSBuildThisFileDirectory)../tools/utils/Microsoft.Quantum.Sdk.DefaultEntryPoint.Generation.dll" true false diff --git a/src/QuantumSdk/QuantumSdk.nuspec b/src/QuantumSdk/QuantumSdk.nuspec index ad4c7f11a7..55601c7d3d 100644 --- a/src/QuantumSdk/QuantumSdk.nuspec +++ b/src/QuantumSdk/QuantumSdk.nuspec @@ -23,6 +23,7 @@ + diff --git a/src/QuantumSdk/Sdk/Sdk.targets b/src/QuantumSdk/Sdk/Sdk.targets index 740e4fb357..ed729e5fe7 100644 --- a/src/QuantumSdk/Sdk/Sdk.targets +++ b/src/QuantumSdk/Sdk/Sdk.targets @@ -150,9 +150,14 @@ + + $(GeneratedFilesOutputPath)Main.cs + + + Parser.Default .ParseArguments(args) .MapResult( - (Options opts) => (int)BuildConfiguration.Generate(opts), + (Options opts) => (int)Generate(opts), errs => (int)ReturnCode.INVALID_ARGUMENTS); } } diff --git a/src/QuantumSdk/Tools/DefaultEntryPoint/DefaultEntryPoint.csproj b/src/QuantumSdk/Tools/DefaultEntryPoint/DefaultEntryPoint.csproj new file mode 100644 index 0000000000..a523ee9268 --- /dev/null +++ b/src/QuantumSdk/Tools/DefaultEntryPoint/DefaultEntryPoint.csproj @@ -0,0 +1,12 @@ + + + + + Exe + netcoreapp3.1 + Microsoft.Quantum.Sdk.DefaultEntryPoint.Generation + Build tool to generate a minimal C# entry point when no C# files are included in the compilation. + + + + diff --git a/src/QuantumSdk/Tools/DefaultEntryPoint/Program.cs b/src/QuantumSdk/Tools/DefaultEntryPoint/Program.cs new file mode 100644 index 0000000000..cff09f9a22 --- /dev/null +++ b/src/QuantumSdk/Tools/DefaultEntryPoint/Program.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Sdk.Tools +{ + public static class DefaultEntryPoint + { + private const string CSharpEntryPoint = +@"using System; + +namespace Microsoft.Quantum.Sdk.Tools +{ + public static class DefaultEntryPoint + { + private static void Main(string[] args) => + Console.WriteLine( + ""Full support for executing projects compiled to QIR is not yet integrated into the Sdk. "" + + ""The generated QIR can be executed by manually linking the QIR runtime. "" + + ""See https://github.com/microsoft/qsharp-runtime/tree/main/src/QirRuntime for further instructions""); + } +} +"; + + private static void Main(string[] args) + { + System.IO.File.WriteAllText(args[0], CSharpEntryPoint); + } + } +} diff --git a/src/QuantumSdk/Tools/Tools.sln b/src/QuantumSdk/Tools/Tools.sln index 133e1cad9d..e295379546 100644 --- a/src/QuantumSdk/Tools/Tools.sln +++ b/src/QuantumSdk/Tools/Tools.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.29519.181 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildConfiguration", "BuildConfiguration\BuildConfiguration.csproj", "{6FF10429-1DE1-425B-AB72-2EE91A402A9A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DefaultEntryPoint", "DefaultEntryPoint\DefaultEntryPoint.csproj", "{8771D3DE-E919-493F-BA9A-00CF91BD9469}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {6FF10429-1DE1-425B-AB72-2EE91A402A9A}.Debug|Any CPU.Build.0 = Debug|Any CPU {6FF10429-1DE1-425B-AB72-2EE91A402A9A}.Release|Any CPU.ActiveCfg = Release|Any CPU {6FF10429-1DE1-425B-AB72-2EE91A402A9A}.Release|Any CPU.Build.0 = Release|Any CPU + {8771D3DE-E919-493F-BA9A-00CF91BD9469}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8771D3DE-E919-493F-BA9A-00CF91BD9469}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8771D3DE-E919-493F-BA9A-00CF91BD9469}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8771D3DE-E919-493F-BA9A-00CF91BD9469}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 5660e0d2a0c6fe7d49797d790fb8c9ec91d1f605 Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Fri, 12 Mar 2021 20:16:39 -0800 Subject: [PATCH 6/8] updating build scripts --- build/manifest.ps1 | 7 +++---- build/pack.ps1 | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/manifest.ps1 b/build/manifest.ps1 index 46834a06c6..1255885ae9 100644 --- a/build/manifest.ps1 +++ b/build/manifest.ps1 @@ -48,11 +48,10 @@ $artifacts = @{ ".\src\QsCompiler\Optimizations\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.QsOptimizations.dll", ".\src\QsCompiler\SyntaxProcessor\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.QsSyntaxProcessor.dll", ".\src\QsCompiler\TextProcessor\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.QsTextProcessor.dll", - ".\src\QsCompiler\Transformations\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.QsTransformations.dll" - + ".\src\QsCompiler\Transformations\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.QsTransformations.dll", ".\src\QsCompiler\CommandLineTool\bin\$Env:BUILD_CONFIGURATION\netcoreapp3.1\qsc.dll", - - ".\src\QuantumSdk\Tools\BuildConfiguration\bin\$Env:BUILD_CONFIGURATION\netcoreapp3.1\Microsoft.Quantum.Sdk.BuildConfiguration.dll" + ".\src\QuantumSdk\Tools\BuildConfiguration\bin\$Env:BUILD_CONFIGURATION\netcoreapp3.1\Microsoft.Quantum.Sdk.BuildConfiguration.dll", + ".\src\QuantumSdk\Tools\DefaultEntryPoint\bin\$Env:BUILD_CONFIGURATION\netcoreapp3.1\Microsoft.Quantum.Sdk.DefaultEntryPoint.Generation.dll" ) | ForEach-Object { Join-Path $PSScriptRoot (Join-Path ".." $_) }; } diff --git a/build/pack.ps1 b/build/pack.ps1 index 7beecd3a29..9ec07039e2 100644 --- a/build/pack.ps1 +++ b/build/pack.ps1 @@ -87,6 +87,7 @@ $all_ok = $True Publish-One '../src/QsCompiler/CommandLineTool/CommandLineTool.csproj' Publish-One '../src/QuantumSdk/Tools/BuildConfiguration/BuildConfiguration.csproj' +Publish-One '../src/QuantumSdk/Tools/DefaultEntryPoint/DefaultEntryPoint.csproj' Pack-One '../src/QsCompiler/Compiler/Compiler.csproj' '-IncludeReferencedProjects' Pack-Dotnet '../src/Documentation/DocumentationGenerator/DocumentationGenerator.csproj' From 427f9c7692dd9fac783b6d7db9d16553ec9e2934 Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Sat, 13 Mar 2021 10:21:02 -0800 Subject: [PATCH 7/8] check explicitly that QirGeneration is true --- src/QuantumSdk/Sdk/Sdk.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QuantumSdk/Sdk/Sdk.targets b/src/QuantumSdk/Sdk/Sdk.targets index ed729e5fe7..1c8800f5ab 100644 --- a/src/QuantumSdk/Sdk/Sdk.targets +++ b/src/QuantumSdk/Sdk/Sdk.targets @@ -151,7 +151,7 @@ - $(GeneratedFilesOutputPath)Main.cs + $(GeneratedFilesOutputPath)Main.cs From 4c6c4f75b10047a7be87f984ae97a1f06c88eb80 Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Sat, 13 Mar 2021 10:32:16 -0800 Subject: [PATCH 8/8] adding an additional link --- src/QuantumSdk/Tools/DefaultEntryPoint/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/QuantumSdk/Tools/DefaultEntryPoint/Program.cs b/src/QuantumSdk/Tools/DefaultEntryPoint/Program.cs index cff09f9a22..4c518253b7 100644 --- a/src/QuantumSdk/Tools/DefaultEntryPoint/Program.cs +++ b/src/QuantumSdk/Tools/DefaultEntryPoint/Program.cs @@ -15,8 +15,9 @@ public static class DefaultEntryPoint private static void Main(string[] args) => Console.WriteLine( ""Full support for executing projects compiled to QIR is not yet integrated into the Sdk. "" + + ""For more information about the feature, see https://github.com/microsoft/qsharp-compiler/tree/main/src/QsCompiler/QirGeneration. "" + ""The generated QIR can be executed by manually linking the QIR runtime. "" + - ""See https://github.com/microsoft/qsharp-runtime/tree/main/src/QirRuntime for further instructions""); + ""See https://github.com/microsoft/qsharp-runtime/tree/main/src/QirRuntime for further instructions.""); } } ";