diff --git a/src/QsFmt/Formatter.Tests/Examples.fs b/src/QsFmt/Formatter.Tests/Examples.fs index 56b136dbf1..3056117530 100644 --- a/src/QsFmt/Formatter.Tests/Examples.fs +++ b/src/QsFmt/Formatter.Tests/Examples.fs @@ -245,6 +245,50 @@ let ``Removes For-Loop Parens`` = } }""" +[] +let ``Update Specialization Declaration`` = + """namespace Foo { + operation Bar() : Unit is Ctl + Adj { + body () { + } + adjoint () { + } + controlled (q) { + } + controlled adjoint (q) { + } + } +}""", + + """namespace Foo { + operation Bar() : Unit is Ctl + Adj { + body (...) { + } + adjoint (...) { + } + controlled (q, ...) { + } + controlled adjoint (q, ...) { + } + } +}""" + +[] +let ``Update Specialization Declaration No Parens`` = + """namespace Foo { + operation Bar() : Unit is Ctl + Adj { + body {} + adjoint {} + } +}""", + + """namespace Foo { + operation Bar() : Unit is Ctl + Adj { + body (...) {} + adjoint (...) {} + } +}""" + [] let ``Allows size as an Identifier`` = """namespace Foo { diff --git a/src/QsFmt/Formatter/Formatter.fs b/src/QsFmt/Formatter/Formatter.fs index c8c86a8e38..9d678cfa16 100644 --- a/src/QsFmt/Formatter/Formatter.fs +++ b/src/QsFmt/Formatter/Formatter.fs @@ -84,6 +84,7 @@ let versionToUpdateRules (version: Version option) = simpleRule qubitBindingUpdate simpleRule unitUpdate simpleRule forParensUpdate + simpleRule specializationUpdate simpleRule arraySyntaxUpdate simpleRule booleanOperatorUpdate ] diff --git a/src/QsFmt/Formatter/ParseTree/Namespace.fs b/src/QsFmt/Formatter/ParseTree/Namespace.fs index 6074e419b0..ed8fbb3fc7 100644 --- a/src/QsFmt/Formatter/ParseTree/Namespace.fs +++ b/src/QsFmt/Formatter/ParseTree/Namespace.fs @@ -33,8 +33,18 @@ type SpecializationGeneratorVisitor(tokens) = toBuiltIn context.intrinsic context.semicolon override _.VisitProvidedGenerator context = + let tospecializationParameters tokens (context: QSharpParser.SpecializationParameterTupleContext) = + let parameters = context._parameters |> Seq.map (Node.toUnknown tokens) + let commas = context._commas |> Seq.map (Node.toTerminal tokens) + + { + OpenParen = context.openParen |> Node.toTerminal tokens + Items = Node.tupleItems parameters commas + CloseParen = context.closeParen |> Node.toTerminal tokens + } + Provided( - parameters = (Option.ofObj context.provided.parameters |> Option.map (Node.toUnknown tokens)), + parameters = (Option.ofObj context.provided.parameters |> Option.map (tospecializationParameters tokens)), statements = { OpenBrace = context.provided.block.openBrace |> Node.toTerminal tokens diff --git a/src/QsFmt/Formatter/Rules.fs b/src/QsFmt/Formatter/Rules.fs index eb19f1479a..961feb5cb7 100644 --- a/src/QsFmt/Formatter/Rules.fs +++ b/src/QsFmt/Formatter/Rules.fs @@ -160,6 +160,61 @@ let forParensUpdate = } } +/// +/// Make sure that a contains a comma. +/// +let ensureComma (item: 'a SequenceItem) = + match item.Comma with + | None -> { Item = item.Item; Comma = { Prefix = []; Text = "," } |> Some } + | Some _ -> item + +/// +/// Prepends the with an ellipsis item if it does not already contain one. +/// +let ensureEllipsis (parameters: Terminal Tuple) = + let ellipsis nspace = + { Prefix = [ spaces nspace ]; Text = "..." } + + let ellipsisItem nspace = + { Item = ellipsis nspace |> Some; Comma = None } + + { parameters with + Items = + match parameters.Items with + // Replace, e.g., `body ()` with `body (...)` + | [] -> [ ellipsisItem 0 ] + // Replace, e.g., `controlled (q)` with `controlled (q, ...)` + | [ x ] -> + match Option.get(x.Item).Text with + | "..." -> [ x ] + | _ -> [ ensureComma x; ellipsisItem 1 ] + | _ -> parameters.Items + } + +let specializationUpdate = + { new Rewriter<_>() with + override _.SpecializationGenerator((), generator) = + let emptyTuple = + { + OpenParen = { Prefix = [ spaces 1 ]; Text = "(" } + Items = [] + CloseParen = { Prefix = []; Text = ")" } + } + + match generator with + | Provided (parameters, statements) -> + Provided( + parameters = + (match parameters with + // Replace, e.g., `body` with `body (...)` + | None -> ensureEllipsis emptyTuple |> Some + // Replace, e.g., `body ()` with `body (...)` + | Some par -> ensureEllipsis par |> Some), + statements = statements + ) + | _ -> generator + } + let arraySyntaxUpdate = let getBuiltInDefault builtIn = diff --git a/src/QsFmt/Formatter/Rules.fsi b/src/QsFmt/Formatter/Rules.fsi index 6faa03eca9..c260c5b15b 100644 --- a/src/QsFmt/Formatter/Rules.fsi +++ b/src/QsFmt/Formatter/Rules.fsi @@ -29,11 +29,15 @@ val unitUpdate : unit Rewriter /// Updates for-loops to remove deprecated parentheses. val forParensUpdate: unit Rewriter +/// Updates deprecated specialization declarations to add a `...` parameter. +val specializationUpdate: unit Rewriter + /// Updates the `new [n]` array syntax to the new `[val, size = n]` array syntax. val arraySyntaxUpdate: unit Rewriter /// Provides warnings for deprecated array syntax still in the syntax tree. val checkArraySyntax: string -> Document -> string list -/// Replaces `&&`, `||`, and `!` with `and`, `or`, and `not`, respectively. +/// Replaces deprecated use of boolean operators `&&`, `||`, and `!` with their keyword +/// equivalence `and`, `or`, and `not` respectively. val booleanOperatorUpdate : unit Rewriter diff --git a/src/QsFmt/Formatter/SyntaxTree/Namespace.fs b/src/QsFmt/Formatter/SyntaxTree/Namespace.fs index 5f5f1e1339..acf5013000 100644 --- a/src/QsFmt/Formatter/SyntaxTree/Namespace.fs +++ b/src/QsFmt/Formatter/SyntaxTree/Namespace.fs @@ -14,7 +14,7 @@ type TypeParameterBinding = type SpecializationGenerator = | BuiltIn of name: Terminal * semicolon: Terminal - | Provided of parameters: Terminal option * statements: Statement Block + | Provided of parameters: Terminal Tuple option * statements: Statement Block type Specialization = { Names: Terminal list; Generator: SpecializationGenerator } diff --git a/src/QsFmt/Formatter/SyntaxTree/Namespace.fsi b/src/QsFmt/Formatter/SyntaxTree/Namespace.fsi index 51e673d71d..6dfe50b94d 100644 --- a/src/QsFmt/Formatter/SyntaxTree/Namespace.fsi +++ b/src/QsFmt/Formatter/SyntaxTree/Namespace.fsi @@ -32,7 +32,7 @@ type internal SpecializationGenerator = | BuiltIn of name: Terminal * semicolon: Terminal /// A provided specialization. - | Provided of parameters: Terminal option * statements: Statement Block + | Provided of parameters: Terminal Tuple option * statements: Statement Block /// A specialization. type internal Specialization = diff --git a/src/QsFmt/Formatter/SyntaxTree/Reducer.fs b/src/QsFmt/Formatter/SyntaxTree/Reducer.fs index 87a1569125..076d020c1e 100644 --- a/src/QsFmt/Formatter/SyntaxTree/Reducer.fs +++ b/src/QsFmt/Formatter/SyntaxTree/Reducer.fs @@ -152,7 +152,7 @@ type internal 'result Reducer() as reducer = match generator with | BuiltIn (name, semicolon) -> [ reducer.Terminal name; reducer.Terminal semicolon ] |> reduce | Provided (parameters, statements) -> - (parameters |> Option.map reducer.Terminal |> Option.toList) + (parameters |> Option.map (curry reducer.Tuple reducer.Terminal) |> Option.toList) @ [ reducer.Block(reducer.Statement, statements) ] |> reduce diff --git a/src/QsFmt/Formatter/SyntaxTree/Rewriter.fs b/src/QsFmt/Formatter/SyntaxTree/Rewriter.fs index 996dfc6fbf..8203931054 100644 --- a/src/QsFmt/Formatter/SyntaxTree/Rewriter.fs +++ b/src/QsFmt/Formatter/SyntaxTree/Rewriter.fs @@ -151,7 +151,7 @@ type 'context Rewriter() = BuiltIn(name = rewriter.Terminal(context, name), semicolon = rewriter.Terminal(context, semicolon)) | Provided (parameters, statements) -> Provided( - parameters = (parameters |> Option.map (curry rewriter.Terminal context)), + parameters = (parameters |> Option.map (curry3 rewriter.Tuple context rewriter.Terminal)), statements = rewriter.Block(context, rewriter.Statement, statements) ) diff --git a/src/QsFmt/Parser/QSharpParser.g4 b/src/QsFmt/Parser/QSharpParser.g4 index a33690167d..3064013b9b 100644 --- a/src/QsFmt/Parser/QSharpParser.g4 +++ b/src/QsFmt/Parser/QSharpParser.g4 @@ -109,7 +109,8 @@ specializationGenerator providedSpecialization : parameters=specializationParameterTuple? block=scope; -specializationParameterTuple : '(' (specializationParameter (',' specializationParameter)*)? ')'; +specializationParameterTuple + : openParen='(' (parameters+=specializationParameter (commas+=',' parameters+=specializationParameter)*)? closeParen=')'; specializationParameter : Identifier diff --git a/src/QsFmt/README.md b/src/QsFmt/README.md index 91a7c7ac20..9cd6d8ce56 100644 --- a/src/QsFmt/README.md +++ b/src/QsFmt/README.md @@ -15,29 +15,29 @@ dotnet build ./App/App.fsproj ## Usage -Updates the source code in input files: -    `qsfmt update --input Path\To\My\File1.qs Path\To\My\File2.qs` -Updates the source code in project: +Updates the source code in input files: +    `qsfmt update --input Path\To\My\File1.qs Path\To\My\File2.qs` +Updates the source code in project:     `qsfmt update --project Path\To\My\Project.csproj` -Command Line Options: -    `-i`, `--input`: Required. Files or folders to update. -    `-p`, `--project`: Required. The project file for the project to update. -    `-b`, `--backup`: Option to create backup files of input files. -    `-r`, `--recurse`: Option to process input folders recursively. -    `--qsharp-version`: Option to provide a Q# version to the tool. -    `--help`: Display this help screen. +Command Line Options: +    `-i`, `--input`: Required. Files or folders to update. +    `-p`, `--project`: Required. The project file for the project to update. +    `-b`, `--backup`: Option to create backup files of input files. +    `-r`, `--recurse`: Option to process input folders recursively. +    `--qsharp-version`: Option to provide a Q# version to the tool. +    `--help`: Display this help screen.     `--version`: Display version information. -Either the `--input` option or the `--project` must be used to specify the input files to the tool. +Either the `--input` option or the `--project` must be used to specify the input files to the tool. The `--recurse` and `--qsharp-version` options can only be used with the `--input` option. ## Input and Output -Input to the formatter can be specified in one of two ways. +Input to the formatter can be specified in one of two ways. Individual files or folders can be specified with the `--input` command-line argument. Multiple files and folders can be specified after the argument, but at least one is expected. .qs extension directly under the folder will be processed. If the `--recurse` option is -specified, subfolders will be processed recursively for Q# files. +specified, subfolders will be processed recursively for Q# files. The other method of providing input to the formatter is by specifying a Q# project file with the `--project` command-line argument. When this method is used, exactly one project file is expected after the argument, and the tool will use MSBuild to determine all applicable Q# source @@ -57,6 +57,7 @@ Updating rules currently in use: - Array Syntax - [proposal](https://github.com/microsoft/qsharp-language/blob/main/Approved/2-enhanced-array-literals.md) - Using and Borrowing Syntax - [proposal](https://github.com/microsoft/qsharp-language/blob/main/Approved/1-implicitly-scoped-qubit-allocation.md) - Parentheses in For Loop Syntax - Removes deprecated parentheses around for-loop range expressions. + - Specialization Declaration Syntax - Add `...` to deprecated forms of the parameter list in specialization declarations. - Unit Syntax - Replaces deprecated unit syntax `()` for `Unit`. - Boolean Operator Syntax - Replaces deprecated use of boolean operators `&&`, `||`, and `!` with their keyword equivalence `and`, `or`, and `not` respectively.