From 9517469e7a14d3ed2544f3ebafe4be01cd3a0233 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Sat, 18 Nov 2023 15:15:00 -0500 Subject: [PATCH 01/11] =?UTF-8?q?Handle=20argument=20patterns=20in=20?= =?UTF-8?q?=CE=BBs=20correctly=20(again=3F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Compiler/Service/ServiceAnalysis.fs | 22 +++++- .../RemoveUnnecessaryParenthesesTests.fs | 72 +++++++++++++++---- 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs index faaf0ed3c65..bf990952490 100644 --- a/src/Compiler/Service/ServiceAnalysis.fs +++ b/src/Compiler/Service/ServiceAnalysis.fs @@ -1424,6 +1424,7 @@ module UnnecessaryParentheses = | _ -> ValueNone module SynPat = + /// Matches if any pattern in the given list is a SynPat.Typed. [] let (|AnyTyped|_|) pats = if @@ -1436,6 +1437,21 @@ module UnnecessaryParentheses = else ValueNone + /// Matches if the given pattern is atomic. + [] + let (|Atomic|_|) pat = + match pat with + | SynPat.Named _ + | SynPat.Wild _ + | SynPat.Paren _ + | SynPat.Tuple(isStruct = true) + | SynPat.Record _ + | SynPat.ArrayOrList _ + | SynPat.Const _ + | SynPat.LongIdent(argPats = SynArgPats.Pats []) + | SynPat.Null _ -> ValueSome Atomic + | _ -> ValueNone + /// If the given pattern is a parenthesized pattern and the parentheses /// are unnecessary in the given context, returns the unnecessary parentheses' range. let unnecessaryParentheses pat path = @@ -1477,10 +1493,13 @@ module UnnecessaryParentheses = // let! () = … // and! () = … // use! () = … + // fun () -> … + // function () -> … // match … with () -> … | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynBinding _ :: _ | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynExpr (SynExpr.ForEach _) :: _ | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ + | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynExpr (SynExpr.Lambda _) :: _ | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynMatchClause _ :: _ -> ValueNone // (()) is required when overriding a generic member @@ -1516,8 +1535,7 @@ module UnnecessaryParentheses = | SynPat.Paren (_, range), SyntaxNode.SynExpr (SynExpr.ForEach _) :: _ | SynPat.Paren (_, range), SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ | SynPat.Paren (_, range), SyntaxNode.SynMatchClause _ :: _ - | SynPat.Paren (_, range), - SyntaxNode.SynExpr (SynExpr.Lambda(args = SynSimplePats.SimplePats(pats = [ SynSimplePat.Id _ ]))) :: _ -> ValueSome range + | SynPat.Paren (Atomic, range), SyntaxNode.SynExpr (SynExpr.Lambda(parsedData = Some _)) :: _ -> ValueSome range // Nested patterns. | SynPat.Paren (inner, range), SyntaxNode.SynPat outer :: _ -> diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index ca780c76db7..47df6e40bb4 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -1581,13 +1581,33 @@ match Unchecked.defaultof<_> with "fun (_) -> ()", "fun _ -> ()" "fun (x) -> x", "fun x -> x" "fun (x: int) -> x", "fun (x: int) -> x" - "fun x (y: int) -> x", "fun x (y: int) -> x" - "fun x (y, z) -> x", "fun x (y, z) -> x" "fun x (y) -> x", "fun x y -> x" - "fun x -> fun (y) -> x", "fun x -> fun y -> x" "fun (Lazy x) -> x", "fun (Lazy x) -> x" + "fun (Failure _ | _) -> ()", "fun (Failure _ | _) -> ()" + "fun (x & y) -> ()", "fun (x & y) -> ()" + "fun (x as y) -> ()", "fun (x as y) -> ()" + "fun (x :: xs) -> ()", "fun (x :: xs) -> ()" "fun (x, y) -> x, y", "fun (x, y) -> x, y" "fun (struct (x, y)) -> x, y", "fun struct (x, y) -> x, y" + "fun ([x]) -> x", "fun [x] -> x" + "fun ([|x|]) -> x", "fun [|x|] -> x" + "fun ({ contents = x }) -> x", "fun { contents = x } -> x" + + "fun _ () -> ()", "fun _ () -> ()" + "fun _ (_) -> ()", "fun _ _ -> ()" + "fun _ (x) -> x", "fun _ x -> x" + "fun _ (x: int) -> x", "fun _ (x: int) -> x" + "fun _ x (y) -> x", "fun _ x y -> x" + "fun _ (Lazy x) -> x", "fun _ (Lazy x) -> x" + "fun _ (Failure _ | _) -> ()", "fun _ (Failure _ | _) -> ()" + "fun _ (x & y) -> ()", "fun _ (x & y) -> ()" + "fun _ (x as y) -> ()", "fun _ (x as y) -> ()" + "fun _ (x :: xs) -> ()", "fun _ (x :: xs) -> ()" + "fun _ (x, y) -> x, y", "fun _ (x, y) -> x, y" + "fun _ (struct (x, y)) -> x, y", "fun _ struct (x, y) -> x, y" + "fun _ ([x]) -> x", "fun _ [x] -> x" + "fun _ ([|x|]) -> x", "fun _ [|x|] -> x" + "fun _ ({ contents = x }) -> x", "fun _ { contents = x } -> x" // MatchLambda "function () -> ()", "function () -> ()" @@ -1639,14 +1659,34 @@ match Unchecked.defaultof<_> with "let (x as y) = z in ()", "let x as y = z in ()" "let (Lazy x) = y in ()", "let (Lazy x) = y in ()" "let (Lazy _ | _) = z in ()", "let Lazy _ | _ = z in ()" - "let f () = () in ()", "let f () = () in ()" - "let f (_) = () in ()", "let f _ = () in ()" - "let f (x) = x in ()", "let f x = x in ()" - "let f (x: int) = x in ()", "let f (x: int) = x in ()" - "let f x (y) = x in ()", "let f x y = x in ()" - "let f (Lazy x) = x in ()", "let f (Lazy x) = x in ()" - "let f (x, y) = x, y in ()", "let f (x, y) = x, y in ()" - "let f (struct (x, y)) = x, y in ()", "let f struct (x, y) = x, y in ()" + + "let f () = ()", "let f () = ()" + "let f (_) = ()", "let f _ = ()" + "let f (x) = x", "let f x = x" + "let f (x: int) = x", "let f (x: int) = x" + "let inline f ([] g) = g ()", "let inline f ([] g) = g ()" + "let f x (y) = x", "let f x y = x" + "let f (Lazy x) = x", "let f (Lazy x) = x" + "let f (Failure _ | _) = ()", "let f (Failure _ | _) = ()" + "let f (x & y) = ()", "let f (x & y) = ()" + "let f (x as y) = ()", "let f (x as y) = ()" + "let f (x :: xs) = ()", "let f (x :: xs) = ()" + "let f (x, y) = x, y", "let f (x, y) = x, y" + "let f (struct (x, y)) = x, y", "let f struct (x, y) = x, y" + + "let f _ () = ()", "let f _ () = ()" + "let f _ (_) = ()", "let f _ _ = ()" + "let f _ (x) = x", "let f _ x = x" + "let f _ (x: int) = x", "let f _ (x: int) = x" + "let inline f _ ([] g) = g ()", "let inline f _ ([] g) = g ()" + "let f _ x (y) = x", "let f _ x y = x" + "let f _ (Lazy x) = x", "let f _ (Lazy x) = x" + "let f _ (Failure _ | _) = ()", "let f _ (Failure _ | _) = ()" + "let f _ (x & y) = ()", "let f _ (x & y) = ()" + "let f _ (x as y) = ()", "let f _ (x as y) = ()" + "let f _ (x :: xs) = ()", "let f _ (x :: xs) = ()" + "let f _ (x, y) = x, y", "let f _ (x, y) = x, y" + "let f _ (struct (x, y)) = x, y", "let f _ struct (x, y) = x, y" // TryWith "try raise null with () -> ()", "try raise null with () -> ()" @@ -1706,7 +1746,7 @@ match Unchecked.defaultof<_> with "type T = static member M(_) = ()", "type T = static member M _ = ()" "type T = static member M(x) = x", "type T = static member M x = x" "type T = static member M(x: int) = x", "type T = static member M(x: int) = x" - "type T = static member inline M([] f) = ()", "type T = static member inline M([] f) = ()" + "type T = static member inline M([] f) = f ()", "type T = static member inline M([] f) = f ()" "type T = static member M x (y) = x", "type T = static member M x y = x" "type T = static member M(Lazy x) = x", "type T = static member M(Lazy x) = x" "type T = static member M(Failure _ | _) = ()", "type T = static member M(Failure _ | _) = ()" @@ -1717,12 +1757,15 @@ match Unchecked.defaultof<_> with "type T = static member M(struct (x, y)) = x, y", "type T = static member M struct (x, y) = x, y" "type T = static member M(?x) = ()", "type T = static member M ?x = ()" "type T = static member M(?x: int) = ()", "type T = static member M(?x: int) = ()" + "type T = static member M([x]) = x", "type T = static member M [x] = x" + "type T = static member M([|x|]) = x", "type T = static member M [|x|] = x" + "type T = static member M({ contents = x }) = x", "type T = static member M { contents = x } = x" "type T = member _.M() = ()", "type T = member _.M() = ()" "type T = member _.M(_) = ()", "type T = member _.M _ = ()" "type T = member _.M(x) = x", "type T = member _.M x = x" "type T = member _.M(x: int) = x", "type T = member _.M(x: int) = x" - "type T = member inline _.M([] f) = ()", "type T = member inline _.M([] f) = ()" + "type T = member inline _.M([] f) = f ()", "type T = member inline _.M([] f) = f ()" "type T = member _.M x (y) = x", "type T = member _.M x y = x" "type T = member _.M(Lazy x) = x", "type T = member _.M(Lazy x) = x" "type T = member _.M(Failure _ | _) = ()", "type T = member _.M(Failure _ | _) = ()" @@ -1733,6 +1776,9 @@ match Unchecked.defaultof<_> with "type T = member _.M(struct (x, y)) = x, y", "type T = member _.M struct (x, y) = x, y" "type T = member _.M(?x) = ()", "type T = member _.M ?x = ()" "type T = member _.M(?x: int) = ()", "type T = member _.M(?x: int) = ()" + "type T = member _.M([x]) = x", "type T = member _.M [x] = x" + "type T = member _.M([|x|]) = x", "type T = member _.M [|x|] = x" + "type T = member _.M({ contents = x }) = x", "type T = member _.M { contents = x } = x" // See https://github.com/dotnet/fsharp/issues/16254. " From 41be4040332bf7400155be0a96b792557e957f0a Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Mon, 20 Nov 2023 14:57:50 -0500 Subject: [PATCH 02/11] Parens: more comprehensive pattern tests --- src/Compiler/Service/ServiceAnalysis.fs | 108 ++- .../RemoveUnnecessaryParenthesesTests.fs | 888 +++++++++++------- 2 files changed, 608 insertions(+), 388 deletions(-) diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs index bf990952490..f19c29fc1de 100644 --- a/src/Compiler/Service/ServiceAnalysis.fs +++ b/src/Compiler/Service/ServiceAnalysis.fs @@ -1424,6 +1424,8 @@ module UnnecessaryParentheses = | _ -> ValueNone module SynPat = + let (|Last|) = List.last + /// Matches if any pattern in the given list is a SynPat.Typed. [] let (|AnyTyped|_|) pats = @@ -1437,6 +1439,33 @@ module UnnecessaryParentheses = else ValueNone + /// Matches if any member in the given list is an inherit + /// or implementation of an interface with generic type args. + [] + let (|AnyGenericInheritOrInterfaceImpl|_|) members = + if + members + |> List.exists (function + | SynMemberDefn.ImplicitInherit(inheritType = SynType.App(typeArgs = _ :: _)) + | SynMemberDefn.ImplicitInherit(inheritType = SynType.LongIdentApp(typeArgs = _ :: _)) + | SynMemberDefn.Interface(interfaceType = SynType.App(typeArgs = _ :: _)) + | SynMemberDefn.Interface(interfaceType = SynType.LongIdentApp(typeArgs = _ :: _)) -> true + | _ -> false) + then + ValueSome AnyGenericInheritOrInterfaceImpl + else + ValueNone + + /// Matches the rightmost potentially dangling nested pattern. + let rec (|Rightmost|) pat = + match pat with + | SynPat.Or(rhsPat = Rightmost pat) + | SynPat.ListCons(rhsPat = Rightmost pat) + | SynPat.As(rhsPat = Rightmost pat) + | SynPat.Ands(pats = Last (Rightmost pat)) + | SynPat.Tuple (isStruct = false; elementPats = Last (Rightmost pat)) -> pat + | pat -> pat + /// Matches if the given pattern is atomic. [] let (|Atomic|_|) pat = @@ -1449,14 +1478,13 @@ module UnnecessaryParentheses = | SynPat.ArrayOrList _ | SynPat.Const _ | SynPat.LongIdent(argPats = SynArgPats.Pats []) - | SynPat.Null _ -> ValueSome Atomic + | SynPat.Null _ + | SynPat.QuoteExpr _-> ValueSome Atomic | _ -> ValueNone /// If the given pattern is a parenthesized pattern and the parentheses /// are unnecessary in the given context, returns the unnecessary parentheses' range. let unnecessaryParentheses pat path = - let (|Last|) = List.last - match pat, path with // Parens are needed in: // @@ -1473,34 +1501,28 @@ module UnnecessaryParentheses = // fun (x, y, …) -> … // fun (x: …) -> … // fun (Pattern …) -> … + | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynPat (Rightmost rightmost) :: SyntaxNode.SynMatchClause _ :: _ when + obj.ReferenceEquals + ( + pat, + rightmost + ) + -> + ValueNone + | SynPat.Paren (Rightmost (SynPat.Typed _), _), SyntaxNode.SynMatchClause _ :: _ | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ - | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynPat (SynPat.Tuple _) :: SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ + | SynPat.Paren (SynPat.Typed _, _), + SyntaxNode.SynPat (SynPat.Tuple(isStruct = false)) :: SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ | SynPat.Paren (SynPat.Tuple (isStruct = false; elementPats = AnyTyped), _), SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ - | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynMatchClause _ :: _ - | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynPat (SynPat.Tuple _) :: SyntaxNode.SynMatchClause _ :: _ - | SynPat.Paren (SynPat.Tuple (isStruct = false; elementPats = Last (SynPat.Typed _)), _), SyntaxNode.SynMatchClause _ :: _ - | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynPat (SynPat.Tuple _) :: SyntaxNode.SynBinding _ :: _ + | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynPat (SynPat.Tuple(isStruct = false)) :: SyntaxNode.SynBinding _ :: _ | SynPat.Paren (SynPat.Tuple (isStruct = false; elementPats = AnyTyped), _), SyntaxNode.SynBinding _ :: _ - | SynPat.Paren (SynPat.LongIdent _, _), SyntaxNode.SynBinding _ :: _ - | SynPat.Paren (SynPat.LongIdent _, _), SyntaxNode.SynExpr (SynExpr.Lambda _) :: _ + | SynPat.Paren (SynPat.LongIdent(argPats = SynArgPats.Pats (_ :: _)), _), SyntaxNode.SynBinding _ :: _ + | SynPat.Paren (SynPat.LongIdent(argPats = SynArgPats.Pats (_ :: _)), _), SyntaxNode.SynExpr (SynExpr.Lambda _) :: _ | SynPat.Paren (SynPat.Tuple(isStruct = false), _), SyntaxNode.SynExpr (SynExpr.Lambda(parsedData = Some _)) :: _ | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynExpr (SynExpr.Lambda(parsedData = Some _)) :: _ -> ValueNone - // () is parsed as this in certain cases… - // - // let () = … - // for () in … do … - // let! () = … - // and! () = … - // use! () = … - // fun () -> … - // function () -> … - // match … with () -> … - | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynBinding _ :: _ - | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynExpr (SynExpr.ForEach _) :: _ - | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ - | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynExpr (SynExpr.Lambda _) :: _ - | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynMatchClause _ :: _ -> ValueNone + // () is parsed as this. + | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), _ -> ValueNone // (()) is required when overriding a generic member // where unit is the generic type argument: @@ -1508,17 +1530,23 @@ module UnnecessaryParentheses = // type C<'T> = abstract M : 'T -> unit // let _ = { new C with override _.M (()) = () } | SynPat.Paren (SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), _), - SyntaxNode.SynPat (SynPat.LongIdent _) :: SyntaxNode.SynBinding _ :: _ - | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), - SyntaxNode.SynPat (SynPat.Paren _) :: SyntaxNode.SynPat (SynPat.LongIdent _) :: SyntaxNode.SynBinding _ :: _ -> ValueNone + SyntaxNode.SynPat (SynPat.LongIdent _) :: SyntaxNode.SynBinding _ :: SyntaxNode.SynExpr (SynExpr.ObjExpr(objType = SynType.App(typeArgs = _ :: _) | SynType.LongIdentApp(typeArgs = _ :: _))) :: _ + | SynPat.Paren (SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), _), + SyntaxNode.SynPat (SynPat.LongIdent _) :: SyntaxNode.SynBinding _ :: SyntaxNode.SynMemberDefn _ :: SyntaxNode.SynTypeDefn (SynTypeDefn(typeRepr = SynTypeDefnRepr.ObjectModel(members = AnyGenericInheritOrInterfaceImpl))) :: _ -> + ValueNone - // Parens are required for the first of multiple additional constructors. - // We simply require them always. + // Parens are required around the atomic argument of + // any additional `new` constructor that is not the last. // // type T … = // new (x) = … // new (x, y) = … - | SynPat.Paren _, SyntaxNode.SynPat (SynPat.LongIdent(longDotId = SynLongIdent(id = [ Ident "new" ]))) :: _ -> ValueNone + | SynPat.Paren (Atomic, range), + SyntaxNode.SynPat (SynPat.LongIdent(longDotId = SynLongIdent(id = [ Ident "new" ]))) :: SyntaxNode.SynBinding _ :: SyntaxNode.SynMemberDefn _ :: SyntaxNode.SynTypeDefn (SynTypeDefn(typeRepr = SynTypeDefnRepr.ObjectModel(members = Last (SynMemberDefn.Member(memberDefn = SynBinding(headPat = SynPat.LongIdent(argPats = SynArgPats.Pats [ arg ]))))))) :: _ -> + if obj.ReferenceEquals(pat, arg) then + ValueSome range + else + ValueNone // Parens are otherwise never needed in these cases: // @@ -1557,7 +1585,10 @@ module UnnecessaryParentheses = // (x as y) :: xs | SynPat.ListCons _, SynPat.Or _ | SynPat.ListCons _, SynPat.Ands _ - | SynPat.ListCons _, SynPat.As _ + | SynPat.ListCons _, SynPat.As _ -> ValueNone + + // Pattern (x = (…)) + | SynPat.LongIdent(argPats = SynArgPats.NamePatPairs _), _ -> ValueSome range // Pattern (x : int) // Pattern ([] x) @@ -1593,17 +1624,12 @@ module UnnecessaryParentheses = // A, (B | C) // A & (B | C) | SynPat.Tuple _, SynPat.Or _ - | SynPat.Ands _, SynPat.Or _ + | SynPat.Ands _, SynPat.Or _ -> ValueNone - // (x : int) | x // (x : int) & y - | SynPat.Or _, SynPat.Typed _ - | SynPat.Ands _, SynPat.Typed _ - - // let () = … - // member _.M() = … - | SynPat.Paren _, SynPat.Const (SynConst.Unit, _) - | SynPat.LongIdent _, SynPat.Const (SynConst.Unit, _) -> ValueNone + // x & (y : int) & z + | SynPat.Ands (Last (SynPat.Paren(pat = Is inner)), _), SynPat.Typed _ -> ValueSome range + | SynPat.Ands _, SynPat.Typed _ -> ValueNone | _, SynPat.Const _ | _, SynPat.Wild _ diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index 47df6e40bb4..79667d6aac1 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -2,6 +2,7 @@ module FSharp.Editor.Tests.CodeFixes.RemoveUnnecessaryParenthesesTests +open System.Text open FSharp.Compiler.Text open Microsoft.CodeAnalysis.Text open Microsoft.VisualStudio.FSharp.Editor @@ -1469,317 +1470,524 @@ let _ = (2 + 2) { return 5 } let ``Infix operators with leading and trailing chars`` expr expected = expectFix expr expected module Patterns = - let attributedPatterns = - memberData { - "let inline f ([] g) = g ()", "let inline f ([] g) = g ()" - "let inline f ([] g) = g ()", "let inline f ([] g) = g ()" // Not currently removing parens in attributes, but we could. - "let inline f ([] (g)) = g ()", "let inline f ([] g) = g ()" - "type T = member inline _.M([] g) = g ()", "type T = member inline _.M([] g) = g ()" - "type T = member inline _.M([] g) = g ()", "type T = member inline _.M([] g) = g ()" // Not currently removing parens in attributes, but we could. - "type T = member inline _.M([] (g)) = g ()", "type T = member inline _.M([] g) = g ()" - } - - [] - let ``Attributed patterns`` original expected = expectFix original expected - - /// match … with pat -> … - let expectFix pat expected = - let code = - $" -let (|A|_|) _ = None -let (|B|_|) _ = None -let (|C|_|) _ = None -let (|D|_|) _ = None -let (|P|_|) _ _ = None -match Unchecked.defaultof<_> with -| %s{pat} -> () -| _ -> () -" - - let expected = - $" -let (|A|_|) _ = None -let (|B|_|) _ = None -let (|C|_|) _ = None -let (|D|_|) _ = None -let (|P|_|) _ _ = None -match Unchecked.defaultof<_> with -| %s{expected} -> () -| _ -> () -" + type SynPat = + | Const of string + | Wild + | Named of string + | Typed of SynPat * string + | Attrib of string * SynPat + | Or of SynPat * SynPat + | ListCons of SynPat * SynPat + | Ands of SynPat list + | As of SynPat * SynPat + | LongIdent of string + | LongIdentWithArgs of string * SynPat + | LongIdentWithNamedArgs of string * (string * SynPat) list + | Tuple of SynPat list + | StructTuple of SynPat list + | Paren of SynPat + | List of SynPat list + | Array of SynPat list + | Record of (string * SynPat) list + | Null + | OptionalVal of SynPat + | IsInst of string + | QuoteExpr of string + + module SynPat = + let (|DanglingTyped|_|) pat = + let (|Last|) = List.last + + let rec loop pat = + match pat with + | Typed _ -> Some DanglingTyped + | Or (_, pat) + | ListCons (_, pat) + | As (_, pat) + | Ands (Last pat) + | Tuple (Last pat) -> loop pat + | _ -> None + + loop pat + + let (|Atomic|NonAtomic|) pat = + match pat with + | Const _ + | Wild + | Named _ + | Null + | LongIdent _ + | StructTuple _ + | List _ + | Array _ + | Paren _ + | Record _ + | QuoteExpr _ -> Atomic + | Typed _ + | Attrib _ + | Or _ + | ListCons _ + | Ands _ + | As _ + | LongIdentWithArgs _ + | LongIdentWithNamedArgs _ + | Tuple _ + | OptionalVal _ + | IsInst _ -> NonAtomic + + let fmt (sb: StringBuilder) pat = + let rec loop (sb: StringBuilder) (cont: StringBuilder -> StringBuilder) = + function + | Const c -> cont (sb.Append c) + | Wild -> cont (sb.Append "_") + | Named name -> cont (sb.Append name) + | Typed (pat, ty) -> loop sb (cont << fun sb -> sb.Append(" : ").Append ty) pat + | Attrib (attrib, pat) -> loop (sb.Append("[<").Append(attrib).Append(">] ")) cont pat + | Or (l, r) -> loop sb (fun sb -> loop (sb.Append " | ") cont r) l + | ListCons (h, t) -> loop sb (fun sb -> loop (sb.Append " :: ") cont t) h + | Ands pats -> separateBy " & " sb cont pats + | As (l, r) -> loop sb (fun sb -> loop (sb.Append " as ") cont r) l + | LongIdent id -> cont (sb.Append id) + | LongIdentWithArgs (id, arg) -> loop (sb.Append(id).Append ' ') cont arg + | LongIdentWithNamedArgs (id, args) -> + fmtFields (sb.Append(id).Append " (") (cont << fun (sb: StringBuilder) -> sb.Append ')') args + | Tuple pats -> separateBy ", " sb cont pats + | StructTuple pats -> separateBy ", " (sb.Append "struct (") (cont << fun sb -> sb.Append ')') pats + | Paren pat -> loop (sb.Append '(') (cont << fun sb -> sb.Append ')') pat + | List pats -> separateBy "; " (sb.Append '[') (cont << fun sb -> sb.Append ']') pats + | Array pats -> + separateBy + "; " + (sb.Append "[|") + (cont + << fun sb -> (if sb.Chars(sb.Length - 1) = '>' then sb.Append ' ' else sb).Append "|]") + pats + | Record fields -> fmtFields (sb.Append "{ ") (cont << fun (sb: StringBuilder) -> sb.Append " }") fields + | Null -> cont (sb.Append "null") + | OptionalVal pat -> loop (sb.Append '?') cont pat + | IsInst ty -> cont (sb.Append(":? ").Append ty) + | QuoteExpr expr -> cont (sb.Append("<@ ").Append(expr).Append " @>") + + and separateBy separator sb cont = + function + | [] -> cont sb + | pat :: [] -> loop sb cont pat + | pat :: pats -> loop sb (fun sb -> separateBy separator (sb.Append separator) cont pats) pat + + and fmtFields sb cont = + function + | [] -> cont sb + | (field, pat) :: [] -> loop (sb.Append(field).Append " = ") cont pat + | (field, pat) :: pats -> loop (sb.Append(field).Append " = ") (fun sb -> fmtFields (sb.Append "; ") cont pats) pat + + loop sb id pat + + let parenthesize inner outer = + let impl paren inner outer = + let Paren = paren - expectFix code expected + [ + match outer, inner with + | _, Attrib _ -> () + + | LongIdentWithArgs (name, _), Atomic -> LongIdentWithArgs(name, Paren inner), LongIdentWithArgs(name, inner) + | LongIdentWithArgs (name, _), NonAtomic -> LongIdentWithArgs(name, Paren inner), LongIdentWithArgs(name, Paren inner) + + | LongIdentWithNamedArgs (name, args), _ -> + for i, (field, _) in Seq.indexed args do + LongIdentWithNamedArgs(name, args |> List.updateAt i (field, Paren inner)), + LongIdentWithNamedArgs(name, args |> List.updateAt i (field, inner)) + + | _, QuoteExpr _ -> () + + | Record fields, Atomic -> + for i, (field, _) in Seq.indexed fields do + Record(fields |> List.updateAt i (field, Paren inner)), Record(fields |> List.updateAt i (field, inner)) + + | Typed (_, ty), (Atomic | Typed _ | IsInst _) -> Typed(Paren inner, ty), Typed(inner, ty) + | Typed (_, ty), NonAtomic -> Typed(Paren inner, ty), Typed(Paren inner, ty) + | Attrib (attrib, _), Atomic -> Attrib(attrib, Paren inner), Attrib(attrib, inner) + | OptionalVal _, _ -> () + + | ListCons _, Tuple _ -> ListCons(Paren inner, Wild), ListCons(Paren inner, Wild) + | ListCons _, ListCons _ -> + ListCons(Paren inner, Wild), ListCons(Paren inner, Wild) + ListCons(Wild, Paren inner), ListCons(Wild, inner) + | ListCons _, (Or _ | Ands _ | As _) -> + ListCons(Paren inner, Wild), ListCons(Paren inner, Wild) + ListCons(Wild, Paren inner), ListCons(Wild, Paren inner) + | ListCons _, _ -> + ListCons(Paren inner, Wild), ListCons(inner, Wild) + ListCons(Wild, Paren inner), ListCons(Wild, inner) + + | As _, (Or _ | Ands _ | Tuple _ | ListCons _) -> + As(Paren inner, Wild), As(inner, Wild) + As(Wild, Paren inner), As(Wild, Paren inner) + | As _, _ -> + As(Paren inner, Wild), As(inner, Wild) + As(Wild, Paren inner), As(Wild, inner) + + | Or _, As _ -> + Or(Paren inner, Wild), Or(Paren inner, Wild) + Or(Wild, Paren inner), Or(Wild, Paren inner) + | Or _, _ -> + Or(Paren inner, Wild), Or(inner, Wild) + Or(Wild, Paren inner), Or(Wild, inner) + + | Ands pats, Typed _ -> + let last = pats.Length - 1 + + for i, _ in Seq.indexed pats do + if i = last then + Ands(pats |> List.updateAt i (Paren inner)), Ands(pats |> List.updateAt i inner) + else + Ands(pats |> List.updateAt i (Paren inner)), Ands(pats |> List.updateAt i (Paren inner)) + | Ands pats, (Or _ | As _) -> + for i, _ in Seq.indexed pats do + Ands(pats |> List.updateAt i (Paren inner)), Ands(pats |> List.updateAt i (Paren inner)) + | Ands pats, _ -> + for i, _ in Seq.indexed pats do + Ands(pats |> List.updateAt i (Paren inner)), Ands(pats |> List.updateAt i inner) + + | Tuple pats, (Or _ | As _ | Tuple _) -> + for i, _ in Seq.indexed pats do + Tuple(pats |> List.updateAt i (Paren inner)), Tuple(pats |> List.updateAt i (Paren inner)) + | Tuple pats, _ -> + for i, _ in Seq.indexed pats do + Tuple(pats |> List.updateAt i (Paren inner)), Tuple(pats |> List.updateAt i inner) + + | StructTuple pats, (Or _ | As _ | Tuple _) -> + for i, _ in Seq.indexed pats do + StructTuple(pats |> List.updateAt i (Paren inner)), StructTuple(pats |> List.updateAt i (Paren inner)) + | StructTuple pats, _ -> + for i, _ in Seq.indexed pats do + StructTuple(pats |> List.updateAt i (Paren inner)), StructTuple(pats |> List.updateAt i inner) + + | List pats, _ -> + for i, _ in Seq.indexed pats do + List(pats |> List.updateAt i (Paren inner)), List(pats |> List.updateAt i inner) + + | Array pats, _ -> + for i, _ in Seq.indexed pats do + Array(pats |> List.updateAt i (Paren inner)), Array(pats |> List.updateAt i inner) + + | _ -> () + ] - let nestedPatterns = + // We want to test both single () and double (()) + // in every context. + match inner with + | Const "()" -> [ yield! impl id inner outer; yield! impl Paren inner outer ] + | _ -> impl Paren inner outer + + let atomicOrNullaryPatterns = + [ + Const "()" + Const "3" + Wild + Named "x" + LongIdent "C" + StructTuple [ Wild; Wild ] + List [] + Array [] + Record [ "Z", Wild ] + Null + QuoteExpr "3" + IsInst(nameof obj) + ] + + let outerPatterns = + [ + Typed(Wild, nameof obj) + Attrib("OptionalArgument", Wild) + Or(Wild, Wild) + ListCons(Wild, Wild) + Ands [ Wild; Wild ] + As(Wild, Wild) + LongIdentWithArgs("A", Wild) + LongIdentWithNamedArgs("B", [ "x", Wild ]) + Tuple [ Wild; Wild ] + StructTuple [ Wild; Wild ] + Paren Wild + List [ Wild; Wild ] + Array [ Wild; Wild ] + Record [ "X", Wild; "Y", Wild ] + OptionalVal(Named "y") + ] + + let patterns = atomicOrNullaryPatterns @ outerPatterns |> List.distinct + + let bareAtomics = + [ + for pat in + patterns + |> Seq.filter (function + | SynPat.QuoteExpr _ + | SynPat.NonAtomic -> false + | _ -> true) do + match pat with + | Const "()" -> + pat, pat + Paren pat, pat + | _ -> Paren pat, pat + ] + + let nestedAtomics = + [ + for outer, inner in (outerPatterns, atomicOrNullaryPatterns) ||> Seq.allPairs do + yield! SynPat.parenthesize inner outer + ] + + let nestedOuters = + [ + for outer, inner in (outerPatterns, nestedAtomics |> Seq.map snd) ||> Seq.allPairs |> Seq.distinct do + yield! SynPat.parenthesize inner outer + ] + + let fmtAllAsMemberData pairs = memberData { - // Typed - "((3) : int)", "(3 : int)" - - // Attrib - // Or - "(A) | B", "A | B" - "A | (B)", "A | B" - - // ListCons - "(3) :: []", "3 :: []" - "3 :: ([])", "3 :: []" - - // Ands - "(A) & B", "A & B" - "A & (B)", "A & B" - "A & (B) & C", "A & B & C" - "A & B & (C)", "A & B & C" - - // As - "_ as (A)", "_ as A" - - // LongIdent - "Lazy (3)", "Lazy 3" - "Some (3)", "Some 3" - "Some(3)", "Some 3" - - // Tuple - "(1), 2", "1, 2" - "1, (2)", "1, 2" - - // Paren - "()", "()" - "(())", "()" - "((3))", "(3)" - - // ArrayOrList - "[(3)]", "[3]" - "[(3); 4]", "[3; 4]" - "[3; (4)]", "[3; 4]" - "[|(3)|]", "[|3|]" - "[|(3); 4|]", "[|3; 4|]" - "[|3; (4)|]", "[|3; 4|]" - - // Record - "{ A = (3) }", "{ A = 3 }" - "{ A =(3) }", "{ A = 3 }" - "{ A=(3) }", "{ A=3 }" - "{A=(3)}", "{A=3}" - - // QuoteExpr - "P <@ (3) @>", "P <@ 3 @>" + let sb = StringBuilder() + + for original, expected in pairs -> + (let original = string (SynPat.fmt sb original) in + ignore <| sb.Clear() + original), + (let expected = string (SynPat.fmt sb expected) in + ignore <| sb.Clear() + expected) } - // This is mainly to verify that all pattern kinds are traversed. - // It is _not_ an exhaustive test of all possible pattern nestings. - [] - let ``Nested patterns`` original expected = expectFix original expected - - let patternsInExprs = - memberData { - // ForEach - "for (x) in [] do ()", "for x in [] do ()" - "for (Lazy x) in [] do ()", "for Lazy x in [] do ()" - - // Lambda - "fun () -> ()", "fun () -> ()" - "fun (_) -> ()", "fun _ -> ()" - "fun (x) -> x", "fun x -> x" - "fun (x: int) -> x", "fun (x: int) -> x" - "fun x (y) -> x", "fun x y -> x" - "fun (Lazy x) -> x", "fun (Lazy x) -> x" - "fun (Failure _ | _) -> ()", "fun (Failure _ | _) -> ()" - "fun (x & y) -> ()", "fun (x & y) -> ()" - "fun (x as y) -> ()", "fun (x as y) -> ()" - "fun (x :: xs) -> ()", "fun (x :: xs) -> ()" - "fun (x, y) -> x, y", "fun (x, y) -> x, y" - "fun (struct (x, y)) -> x, y", "fun struct (x, y) -> x, y" - "fun ([x]) -> x", "fun [x] -> x" - "fun ([|x|]) -> x", "fun [|x|] -> x" - "fun ({ contents = x }) -> x", "fun { contents = x } -> x" - - "fun _ () -> ()", "fun _ () -> ()" - "fun _ (_) -> ()", "fun _ _ -> ()" - "fun _ (x) -> x", "fun _ x -> x" - "fun _ (x: int) -> x", "fun _ (x: int) -> x" - "fun _ x (y) -> x", "fun _ x y -> x" - "fun _ (Lazy x) -> x", "fun _ (Lazy x) -> x" - "fun _ (Failure _ | _) -> ()", "fun _ (Failure _ | _) -> ()" - "fun _ (x & y) -> ()", "fun _ (x & y) -> ()" - "fun _ (x as y) -> ()", "fun _ (x as y) -> ()" - "fun _ (x :: xs) -> ()", "fun _ (x :: xs) -> ()" - "fun _ (x, y) -> x, y", "fun _ (x, y) -> x, y" - "fun _ (struct (x, y)) -> x, y", "fun _ struct (x, y) -> x, y" - "fun _ ([x]) -> x", "fun _ [x] -> x" - "fun _ ([|x|]) -> x", "fun _ [|x|] -> x" - "fun _ ({ contents = x }) -> x", "fun _ { contents = x } -> x" - - // MatchLambda - "function () -> ()", "function () -> ()" - "function (_) -> ()", "function _ -> ()" - "function (x) -> x", "function x -> x" - "function (x: int) -> x", "function (x: int) -> x" - "function (x: int, y) -> x", "function x: int, y -> x" - "function (x, y: int) -> x", "function (x, y: int) -> x" - "function (Lazy x) -> x", "function Lazy x -> x" - "function (1 | 2) -> () | _ -> ()", "function 1 | 2 -> () | _ -> ()" - "function (x & y) -> x, y", "function x & y -> x, y" - "function (x as y) -> x, y", "function x as y -> x, y" - "function (x :: xs) -> ()", "function x :: xs -> ()" - "function (x, y) -> x, y", "function x, y -> x, y" - "function (struct (x, y)) -> x, y", "function struct (x, y) -> x, y" - "function x when (true) -> x | y -> y", "function x when true -> x | y -> y" - "function x when (match x with _ -> true) -> x | y -> y", "function x when (match x with _ -> true) -> x | y -> y" - - "function x when (let x = 3 in match x with _ -> true) -> x | y -> y", - "function x when (let x = 3 in match x with _ -> true) -> x | y -> y" + module Lambda = + /// fun pat -> … + let expectFix pat expected = + let code = $"fun %s{pat} -> ()" + let expected = $"fun %s{expected} -> ()" + + expectFix code expected + + let bareAtomics = fmtAllAsMemberData bareAtomics + + let bareNonAtomics = + patterns + |> List.collect (function + | SynPat.Attrib _ + | SynPat.OptionalVal _ + | SynPat.IsInst _ + | SynPat.Atomic -> [] + | pat -> [ SynPat.Paren pat, SynPat.Paren pat ]) + |> fmtAllAsMemberData + + [] + let ``Bare atomic patterns`` original expected = expectFix original expected + + [] + let ``Bare non-atomic patterns`` original expected = expectFix original expected + + module HeadPat = + /// let pat = … + let expectFix pat expected = + let code = $"let %s{pat} = Unchecked.defaultof<_>" + let expected = $"let %s{expected} = Unchecked.defaultof<_>" + + expectFix code expected + + let bareAtomics = fmtAllAsMemberData bareAtomics + + let bareNonAtomics = + patterns + |> List.collect (function + | SynPat.Attrib _ + | SynPat.OptionalVal _ + | SynPat.IsInst _ + | SynPat.Atomic -> [] + | SynPat.Typed _ as pat -> [ SynPat.Paren pat, pat ] + | SynPat.Tuple pats as pat -> + [ + SynPat.Paren pat, pat + SynPat.Paren(SynPat.Tuple(SynPat.Typed(Wild, "obj") :: pats)), + SynPat.Paren(SynPat.Tuple(SynPat.Typed(Wild, "obj") :: pats)) + SynPat.Tuple(SynPat.Paren(SynPat.Typed(Wild, "obj")) :: pats), + SynPat.Tuple(SynPat.Paren(SynPat.Typed(Wild, "obj")) :: pats) + ] + | SynPat.LongIdentWithArgs _ + | SynPat.DanglingTyped as pat -> [ SynPat.Paren pat, SynPat.Paren pat ] + | pat -> [ SynPat.Paren pat, pat ]) + |> fmtAllAsMemberData + + [] + let ``Bare atomic patterns`` original expected = expectFix original expected + + [] + let ``Bare non-atomic patterns`` original expected = expectFix original expected + + module ArgPat = + module Let = + /// let f pat = … + let expectFix pat expected = + let code = $"let f %s{pat} = Unchecked.defaultof<_>" + let expected = $"let f %s{expected} = Unchecked.defaultof<_>" + + expectFix code expected + + let bareAtomics = fmtAllAsMemberData bareAtomics + + let bareNonAtomics = + patterns + |> List.choose (function + | SynPat.OptionalVal _ + | SynPat.Atomic -> None + | SynPat.NonAtomic as pat -> Some(SynPat.Paren pat, SynPat.Paren pat)) + |> fmtAllAsMemberData + + [] + let ``Bare atomic patterns`` original expected = expectFix original expected + + [] + let ``Bare non-atomic patterns`` original expected = expectFix original expected + + module Member = + /// member _.M pat = … + let expectFix pat expected = + let code = $"type T () = member _.M %s{pat} = Unchecked.defaultof<_>" + let expected = $"type T () = member _.M %s{expected} = Unchecked.defaultof<_>" + expectFix code expected + + let bareAtomics = fmtAllAsMemberData bareAtomics + + let bareNonAtomics = + patterns + |> List.choose (function + | SynPat.Atomic -> None + | SynPat.OptionalVal _ as pat -> Some(SynPat.Paren pat, pat) + | SynPat.NonAtomic as pat -> Some(SynPat.Paren pat, SynPat.Paren pat)) + |> fmtAllAsMemberData + + [] + let ``Bare atomic patterns`` original expected = expectFix original expected + + [] + let ``Bare non-atomic patterns`` original expected = expectFix original expected + + module LetBang = + /// let! pat = … + let expectFix pat expected = + let code = + $" + async {{ + let! %s{pat} = Unchecked.defaultof<_> + return () + }} + " - // Match - "match x with () -> ()", "match x with () -> ()" - "match x with (_) -> ()", "match x with _ -> ()" - "match x with (x) -> x", "match x with x -> x" - "match x with (x: int) -> x", "match x with (x: int) -> x" - "match x, y with (x: int, y) -> x", "match x, y with x: int, y -> x" - "match x, y with (x, y: int) -> x", "match x, y with (x, y: int) -> x" - "match x with (Lazy x) -> x", "match x with Lazy x -> x" - "match x with (x, y) -> x, y", "match x with x, y -> x, y" - "match x with (struct (x, y)) -> x, y", "match x with struct (x, y) -> x, y" - "match x with x when (true) -> x | y -> y", "match x with x when true -> x | y -> y" - "match x with x when (match x with _ -> true) -> x | y -> y", "match x with x when (match x with _ -> true) -> x | y -> y" - - "match x with x when (let x = 3 in match x with _ -> true) -> x | y -> y", - "match x with x when (let x = 3 in match x with _ -> true) -> x | y -> y" + let expected = + $" + async {{ + let! %s{expected} = Unchecked.defaultof<_> + return () + }} + " - // LetOrUse - "let () = () in ()", "let () = () in ()" - "let (()) = () in ()", "let () = () in ()" - "let (_) = y in ()", "let _ = y in ()" - "let (x) = y in ()", "let x = y in ()" - "let (x: int) = y in ()", "let x: int = y in ()" - "let (x, y) = x, y in ()", "let x, y = x, y in ()" - "let (x: int, y) = x, y in ()", "let (x: int, y) = x, y in ()" - "let (x: int -> int), (y: int) = x, y in ()", "let (x: int -> int), (y: int) = x, y in ()" - "let (struct (x, y)) = x, y in ()", "let struct (x, y) = x, y in ()" - "let (x & y) = z in ()", "let x & y = z in ()" - "let (x as y) = z in ()", "let x as y = z in ()" - "let (Lazy x) = y in ()", "let (Lazy x) = y in ()" - "let (Lazy _ | _) = z in ()", "let Lazy _ | _ = z in ()" - - "let f () = ()", "let f () = ()" - "let f (_) = ()", "let f _ = ()" - "let f (x) = x", "let f x = x" - "let f (x: int) = x", "let f (x: int) = x" - "let inline f ([] g) = g ()", "let inline f ([] g) = g ()" - "let f x (y) = x", "let f x y = x" - "let f (Lazy x) = x", "let f (Lazy x) = x" - "let f (Failure _ | _) = ()", "let f (Failure _ | _) = ()" - "let f (x & y) = ()", "let f (x & y) = ()" - "let f (x as y) = ()", "let f (x as y) = ()" - "let f (x :: xs) = ()", "let f (x :: xs) = ()" - "let f (x, y) = x, y", "let f (x, y) = x, y" - "let f (struct (x, y)) = x, y", "let f struct (x, y) = x, y" - - "let f _ () = ()", "let f _ () = ()" - "let f _ (_) = ()", "let f _ _ = ()" - "let f _ (x) = x", "let f _ x = x" - "let f _ (x: int) = x", "let f _ (x: int) = x" - "let inline f _ ([] g) = g ()", "let inline f _ ([] g) = g ()" - "let f _ x (y) = x", "let f _ x y = x" - "let f _ (Lazy x) = x", "let f _ (Lazy x) = x" - "let f _ (Failure _ | _) = ()", "let f _ (Failure _ | _) = ()" - "let f _ (x & y) = ()", "let f _ (x & y) = ()" - "let f _ (x as y) = ()", "let f _ (x as y) = ()" - "let f _ (x :: xs) = ()", "let f _ (x :: xs) = ()" - "let f _ (x, y) = x, y", "let f _ (x, y) = x, y" - "let f _ (struct (x, y)) = x, y", "let f _ struct (x, y) = x, y" + expectFix code expected + + let bareAtomics = fmtAllAsMemberData bareAtomics + + let bareNonAtomics = + patterns + |> List.choose (function + | SynPat.Attrib _ + | SynPat.OptionalVal _ + | SynPat.Atomic -> None + | SynPat.DanglingTyped as pat -> Some(SynPat.Paren pat, SynPat.Paren pat) + | pat -> Some(SynPat.Paren pat, pat)) + |> fmtAllAsMemberData + + [] + let ``Bare atomic patterns`` original expected = expectFix original expected + + [] + let ``Bare non-atomic patterns`` original expected = expectFix original expected + + module MatchClause = + /// match … with pat -> … + let expectFix pat expected = + let code = + $" + match Unchecked.defaultof<_> with + | %s{pat} -> + () + | _ -> () + " - // TryWith - "try raise null with () -> ()", "try raise null with () -> ()" - "try raise null with (_) -> ()", "try raise null with _ -> ()" - "try raise null with (x) -> x", "try raise null with x -> x" - "try raise null with (:? exn) -> ()", "try raise null with :? exn -> ()" - "try raise null with (Failure x) -> x", "try raise null with Failure x -> x" - "try raise null with x when (true) -> x | y -> y", "try raise null with x when true -> x | y -> y" + let expected = + $" + match Unchecked.defaultof<_> with + | %s{expected} -> + () + | _ -> () + " - "try raise null with x when (match x with _ -> true) -> x | y -> y", - "try raise null with x when (match x with _ -> true) -> x | y -> y" + expectFix code expected + + let bareAtomics = fmtAllAsMemberData bareAtomics + + let bareNonAtomics = + patterns + |> List.collect (function + | SynPat.Attrib _ + | SynPat.OptionalVal _ + | SynPat.Atomic -> [] + | SynPat.Tuple pats as pat -> + [ + SynPat.Paren pat, pat + SynPat.Paren(SynPat.Tuple(SynPat.Typed(Wild, "obj") :: pats)), SynPat.Tuple(SynPat.Typed(Wild, "obj") :: pats) + SynPat.Tuple(SynPat.Paren(SynPat.Typed(Wild, "obj")) :: pats), SynPat.Tuple(SynPat.Typed(Wild, "obj") :: pats) + SynPat.Paren(SynPat.Tuple(List.rev (SynPat.Typed(Wild, "obj") :: pats))), + SynPat.Paren(SynPat.Tuple(List.rev (SynPat.Typed(Wild, "obj") :: pats))) + SynPat.Tuple(List.rev (SynPat.Paren(SynPat.Typed(Wild, "obj")) :: pats)), + SynPat.Tuple(List.rev (SynPat.Paren(SynPat.Typed(Wild, "obj")) :: pats)) + ] + | SynPat.DanglingTyped as pat -> [ SynPat.Paren pat, SynPat.Paren pat ] + | pat -> [ SynPat.Paren pat, pat ]) + |> fmtAllAsMemberData + + [] + let ``Bare atomic patterns`` original expected = expectFix original expected + + [] + let ``Bare non-atomic patterns`` original expected = expectFix original expected + + // There is no one context in which all patterns are allowed + // and treated identically, so we arbitrarily use a match clause here. + let expectFix original expected = + let code = + $" + match Unchecked.defaultof<_> with + | + %s{original} + | _ -> () + " - "try raise null with x when (let x = 3 in match x with _ -> true) -> x | y -> y", - "try raise null with x when (let x = 3 in match x with _ -> true) -> x | y -> y" + let expected = + $" + match Unchecked.defaultof<_> with + | + %s{expected} + | _ -> () + " - // Sequential - "let (x) = y; z in x", "let x = y; z in x" + TopLevel.expectFix code expected - // LetOrUseBang - "let! () = ()", "let! () = ()" - "let! (()) = ()", "let! () = ()" - "let! (_) = y", "let! _ = y" - "let! (x) = y", "let! x = y" - "let! (x: int) = y", "let! (x: int) = y" - "let! (x, y) = x, y", "let! x, y = x, y" - "let! (struct (x, y)) = x, y", "let! struct (x, y) = x, y" - "let! (x & y) = z", "let! x & y = z" - "let! (x as y) = z", "let! x as y = z" - "let! (Lazy x) = y", "let! Lazy x = y" - "let! (Lazy _ | _) = z", "let! Lazy _ | _ = z" + let singlyNestedAtomicPatterns = fmtAllAsMemberData nestedAtomics + let deeplyNestedPatterns = fmtAllAsMemberData nestedOuters - // MatchBang - "async { match! x with () -> return () }", "async { match! x with () -> return () }" - "async { match! x with (_) -> return () }", "async { match! x with _ -> return () }" - "async { match! x with (x) -> return x }", "async { match! x with x -> return x }" - "async { match! x with (x: int) -> return x }", "async { match! x with (x: int) -> return x }" - "async { match! x with (Lazy x) -> return x }", "async { match! x with Lazy x -> return x }" - "async { match! x with (x, y) -> return x, y }", "async { match! x with x, y -> return x, y }" - "async { match! x with (struct (x, y)) -> return x, y }", "async { match! x with struct (x, y) -> return x, y }" - - "async { match! x with x when (true) -> return x | y -> return y }", - "async { match! x with x when true -> return x | y -> return y }" - - "async { match! x with x when (match x with _ -> true) -> return x | y -> return y }", - "async { match! x with x when (match x with _ -> true) -> return x | y -> return y }" - - "async { match! x with x when (let x = 3 in match x with _ -> true) -> return x | y -> return y }", - "async { match! x with x when (let x = 3 in match x with _ -> true) -> return x | y -> return y }" - } + [] + let ``Singly nested patterns`` original expected = expectFix original expected - [] - let ``Patterns in expressions`` original expected = Expressions.expectFix original expected + [] + let ``Deeply nested patterns`` original expected = expectFix original expected - let args = + let miscellaneous = memberData { - "type T = static member M() = ()", "type T = static member M() = ()" - "type T = static member M(_) = ()", "type T = static member M _ = ()" - "type T = static member M(x) = x", "type T = static member M x = x" - "type T = static member M(x: int) = x", "type T = static member M(x: int) = x" - "type T = static member inline M([] f) = f ()", "type T = static member inline M([] f) = f ()" - "type T = static member M x (y) = x", "type T = static member M x y = x" - "type T = static member M(Lazy x) = x", "type T = static member M(Lazy x) = x" - "type T = static member M(Failure _ | _) = ()", "type T = static member M(Failure _ | _) = ()" - "type T = static member M(x & y) = ()", "type T = static member M(x & y) = ()" - "type T = static member M(x as y) = ()", "type T = static member M(x as y) = ()" - "type T = static member M(x :: xs) = ()", "type T = static member M(x :: xs) = ()" - "type T = static member M(x, y) = x, y", "type T = static member M(x, y) = x, y" - "type T = static member M(struct (x, y)) = x, y", "type T = static member M struct (x, y) = x, y" - "type T = static member M(?x) = ()", "type T = static member M ?x = ()" - "type T = static member M(?x: int) = ()", "type T = static member M(?x: int) = ()" - "type T = static member M([x]) = x", "type T = static member M [x] = x" - "type T = static member M([|x|]) = x", "type T = static member M [|x|] = x" - "type T = static member M({ contents = x }) = x", "type T = static member M { contents = x } = x" - - "type T = member _.M() = ()", "type T = member _.M() = ()" - "type T = member _.M(_) = ()", "type T = member _.M _ = ()" - "type T = member _.M(x) = x", "type T = member _.M x = x" - "type T = member _.M(x: int) = x", "type T = member _.M(x: int) = x" - "type T = member inline _.M([] f) = f ()", "type T = member inline _.M([] f) = f ()" - "type T = member _.M x (y) = x", "type T = member _.M x y = x" - "type T = member _.M(Lazy x) = x", "type T = member _.M(Lazy x) = x" - "type T = member _.M(Failure _ | _) = ()", "type T = member _.M(Failure _ | _) = ()" - "type T = member _.M(x & y) = ()", "type T = member _.M(x & y) = ()" - "type T = member _.M(x as y) = ()", "type T = member _.M(x as y) = ()" - "type T = member _.M(x :: xs) = ()", "type T = member _.M(x :: xs) = ()" - "type T = member _.M(x, y) = x, y", "type T = member _.M(x, y) = x, y" - "type T = member _.M(struct (x, y)) = x, y", "type T = member _.M struct (x, y) = x, y" - "type T = member _.M(?x) = ()", "type T = member _.M ?x = ()" - "type T = member _.M(?x: int) = ()", "type T = member _.M(?x: int) = ()" - "type T = member _.M([x]) = x", "type T = member _.M [x] = x" - "type T = member _.M([|x|]) = x", "type T = member _.M [|x|] = x" - "type T = member _.M({ contents = x }) = x", "type T = member _.M { contents = x } = x" - // See https://github.com/dotnet/fsharp/issues/16254. " type C = abstract M : unit -> unit @@ -1787,7 +1995,7 @@ match Unchecked.defaultof<_> with ", " type C = abstract M : unit -> unit - let _ = { new C with override _.M (()) = () } + let _ = { new C with override _.M () = () } " // See https://github.com/dotnet/fsharp/issues/16254. @@ -1800,6 +2008,26 @@ match Unchecked.defaultof<_> with let _ = { new C with override _.M (()) = () } " + // See https://github.com/dotnet/fsharp/issues/16254. + " + type C<'T> = abstract M : 'T -> unit + type T () = interface C () with override _.M (()) = () + ", + " + type C<'T> = abstract M : 'T -> unit + type T () = interface C () with override _.M (()) = () + " + + // See https://github.com/dotnet/fsharp/issues/16254. + " + type [] C<'T> () = abstract M : 'T -> unit + type T () = inherit C () with override _.M (()) = () + ", + " + type [] C<'T> () = abstract M : 'T -> unit + type T () = inherit C () with override _.M (()) = () + " + // See https://github.com/dotnet/fsharp/issues/16257. " type T (x, y) = @@ -1809,7 +2037,21 @@ match Unchecked.defaultof<_> with " type T (x, y) = new (x, y, z) = T (x, y) - new (x) = T (x, 3) + new x = T (x, 3) + " + + // See https://github.com/dotnet/fsharp/issues/16257. + " + let f (x: string) = int x + type T (x, y) = + new (x) = T (f x, y) + new x = T (id x, 3) + ", + " + let f (x: string) = int x + type T (x, y) = + new (x) = T (f x, y) + new x = T (id x, 3) " // See https://github.com/dotnet/fsharp/issues/16257. @@ -1825,53 +2067,5 @@ match Unchecked.defaultof<_> with " } - [] - let ``Argument patterns`` original expected = TopLevel.expectFix original expected - - module InfixPatterns = - let infixPatterns = - memberData { - "A | (B | C)", "A | B | C" - "A & (B | C)", "A & (B | C)" - "A :: (B | C)", "A :: (B | C)" - "A as (B | C)", "A as (B | C)" - "A as (B | C) & D", "A as (B | C) & D" - "A as (_, _) & D", "A as (_, _) & D" - "A | (B & C)", "A | B & C" - "A & (B & C)", "A & B & C" - "A :: (B & C)", "A :: (B & C)" - "A as (B & C)", "A as (B & C)" - "A | (B :: C)", "A | B :: C" - "A & (B :: C)", "A & B :: C" - "A :: (B :: C)", "A :: B :: C" - "A as (B :: C)", "A as (B :: C)" - "A | (B as C)", "A | (B as C)" - "_ as x | (_ as x)", "_ as x | (_ as x)" - "A & (B as C)", "A & (B as C)" - "A :: (B as C)", "A :: (B as C)" - "A as (B as C)", "A as B as C" - - "(A | B) | C", "A | B | C" - "(A | B) & C", "(A | B) & C" - "(A | B) :: C", "(A | B) :: C" - "(A | B) as C", "A | B as C" - "(A & B) | C", "A & B | C" - "(A & B) & C", "A & B & C" - "(A & B) :: C", "(A & B) :: C" - "(A & B) as C", "A & B as C" - "A | (B & C) as _", "A | B & C as _" - "(A :: B) | C", "A :: B | C" - "(A :: B) & C", "A :: B & C" - "(x :: y) :: xs", "(x :: y) :: xs" - "(A :: B) as C", "A :: B as C" - "(A as B) | C", "(A as B) | C" - "(A as B) & C", "(A as B) & C" - "(A as B) :: C", "(A as B) :: C" - "(A as B) as C", "A as B as C" - "(A as B), C", "(A as B), C" - "(A, B) :: C", "(A, B) :: C" - "(struct (A, B)) :: C", "struct (A, B) :: C" - } - - [] - let ``Infix patterns`` pat expected = expectFix pat expected + [] + let ``Miscellaneous patterns`` original expected = TopLevel.expectFix original expected From 34929f91a1e7eb10fd342386f54a3b69ba456178 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Mon, 20 Nov 2023 15:00:55 -0500 Subject: [PATCH 03/11] Spacing tweaks --- .../FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs index 6a5e34823e3..e608d5ffb02 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs @@ -159,6 +159,7 @@ type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [ None + | '[', '|', (Punctuation | LetterOrDigit) -> None | _, ('(' | '[' | '{'), _ -> None | _, '>', _ -> Some ShouldPutSpaceBefore | ' ', '=', _ -> Some ShouldPutSpaceBefore @@ -173,7 +174,8 @@ type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [ None + | '>', '|' -> Some ShouldPutSpaceAfter + | _, (')' | ']' | '[' | '}' | '.' | ';' | ',' | '|') -> None | (Punctuation | Symbol), (Punctuation | Symbol | LetterOrDigit) -> Some ShouldPutSpaceAfter | LetterOrDigit, LetterOrDigit -> Some ShouldPutSpaceAfter | _ -> None From caea6f68ad1cbc87c0a9b2ec82cc4fadeb91ebcd Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Mon, 20 Nov 2023 15:54:47 -0500 Subject: [PATCH 04/11] Space --- src/Compiler/Service/ServiceAnalysis.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs index f19c29fc1de..83eaa009524 100644 --- a/src/Compiler/Service/ServiceAnalysis.fs +++ b/src/Compiler/Service/ServiceAnalysis.fs @@ -1479,7 +1479,7 @@ module UnnecessaryParentheses = | SynPat.Const _ | SynPat.LongIdent(argPats = SynArgPats.Pats []) | SynPat.Null _ - | SynPat.QuoteExpr _-> ValueSome Atomic + | SynPat.QuoteExpr _ -> ValueSome Atomic | _ -> ValueNone /// If the given pattern is a parenthesized pattern and the parentheses From 092d10eabd033aad25fa59b8eda8a8955683e2c8 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Mon, 20 Nov 2023 18:27:31 -0500 Subject: [PATCH 05/11] Handle quotations consistently --- .../CodeFixes/RemoveUnnecessaryParentheses.fs | 5 +++-- .../CodeFixes/RemoveUnnecessaryParenthesesTests.fs | 13 +++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs index e608d5ffb02..44d39a09407 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs @@ -161,7 +161,8 @@ type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [ None | '[', '|', (Punctuation | LetterOrDigit) -> None | _, ('(' | '[' | '{'), _ -> None - | _, '>', _ -> Some ShouldPutSpaceBefore + | _, '>', _ + | _, '[', '<' | ' ', '=', _ -> Some ShouldPutSpaceBefore | _, '=', ('(' | '[' | '{') -> None | _, '=', (Punctuation | Symbol) -> Some ShouldPutSpaceBefore @@ -174,7 +175,7 @@ type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [', '|' -> Some ShouldPutSpaceAfter + | '>', ('|' | ']') -> Some ShouldPutSpaceAfter | _, (')' | ']' | '[' | '}' | '.' | ';' | ',' | '|') -> None | (Punctuation | Symbol), (Punctuation | Symbol | LetterOrDigit) -> Some ShouldPutSpaceAfter | LetterOrDigit, LetterOrDigit -> Some ShouldPutSpaceAfter diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index 79667d6aac1..b91095f8842 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -1536,6 +1536,9 @@ module Patterns = | IsInst _ -> NonAtomic let fmt (sb: StringBuilder) pat = + let spaceIfGt (sb: StringBuilder) = + if sb.Chars(sb.Length - 1) = '>' then sb.Append ' ' else sb + let rec loop (sb: StringBuilder) (cont: StringBuilder -> StringBuilder) = function | Const c -> cont (sb.Append c) @@ -1554,14 +1557,8 @@ module Patterns = | Tuple pats -> separateBy ", " sb cont pats | StructTuple pats -> separateBy ", " (sb.Append "struct (") (cont << fun sb -> sb.Append ')') pats | Paren pat -> loop (sb.Append '(') (cont << fun sb -> sb.Append ')') pat - | List pats -> separateBy "; " (sb.Append '[') (cont << fun sb -> sb.Append ']') pats - | Array pats -> - separateBy - "; " - (sb.Append "[|") - (cont - << fun sb -> (if sb.Chars(sb.Length - 1) = '>' then sb.Append ' ' else sb).Append "|]") - pats + | List pats -> separateBy "; " (sb.Append '[') (cont << fun sb -> (spaceIfGt sb).Append ']') pats + | Array pats -> separateBy "; " (sb.Append "[|") (cont << fun sb -> (spaceIfGt sb).Append "|]") pats | Record fields -> fmtFields (sb.Append "{ ") (cont << fun (sb: StringBuilder) -> sb.Append " }") fields | Null -> cont (sb.Append "null") | OptionalVal pat -> loop (sb.Append '?') cont pat From 81606d728a744d9e4f4bfa192617f91b4b897fbd Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Mon, 20 Nov 2023 18:44:05 -0500 Subject: [PATCH 06/11] Meh --- .../FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs index 44d39a09407..6a60a139179 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs @@ -160,9 +160,9 @@ type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [ None | '[', '|', (Punctuation | LetterOrDigit) -> None + | _, '[', '<' -> Some ShouldPutSpaceBefore | _, ('(' | '[' | '{'), _ -> None - | _, '>', _ - | _, '[', '<' + | _, '>', _ -> Some ShouldPutSpaceBefore | ' ', '=', _ -> Some ShouldPutSpaceBefore | _, '=', ('(' | '[' | '{') -> None | _, '=', (Punctuation | Symbol) -> Some ShouldPutSpaceBefore From e02f1a45f51bb080a2211d6718d67e1178f70f98 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Tue, 21 Nov 2023 09:30:57 -0500 Subject: [PATCH 07/11] Handle following non-new members --- src/Compiler/Service/ServiceAnalysis.fs | 16 ++++++++++++---- .../RemoveUnnecessaryParenthesesTests.fs | 10 ++++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs index 83eaa009524..f509dfd78d0 100644 --- a/src/Compiler/Service/ServiceAnalysis.fs +++ b/src/Compiler/Service/ServiceAnalysis.fs @@ -1542,11 +1542,19 @@ module UnnecessaryParentheses = // new (x) = … // new (x, y) = … | SynPat.Paren (Atomic, range), - SyntaxNode.SynPat (SynPat.LongIdent(longDotId = SynLongIdent(id = [ Ident "new" ]))) :: SyntaxNode.SynBinding _ :: SyntaxNode.SynMemberDefn _ :: SyntaxNode.SynTypeDefn (SynTypeDefn(typeRepr = SynTypeDefnRepr.ObjectModel(members = Last (SynMemberDefn.Member(memberDefn = SynBinding(headPat = SynPat.LongIdent(argPats = SynArgPats.Pats [ arg ]))))))) :: _ -> - if obj.ReferenceEquals(pat, arg) then + SyntaxNode.SynPat (SynPat.LongIdent(longDotId = SynLongIdent(id = [ Ident "new" ]))) :: SyntaxNode.SynBinding _ :: SyntaxNode.SynMemberDefn _ :: SyntaxNode.SynTypeDefn (SynTypeDefn(typeRepr = SynTypeDefnRepr.ObjectModel (members = members))) :: _ -> + let lastNew = + (ValueNone, members) + ||> List.fold (fun lastNew ``member`` -> + match ``member`` with + | SynMemberDefn.Member(memberDefn = SynBinding(headPat = SynPat.LongIdent(longDotId = SynLongIdent(id = [ Ident "new" ])))) -> + ValueSome ``member`` + | _ -> lastNew) + + match lastNew with + | ValueSome (SynMemberDefn.Member(memberDefn = SynBinding(headPat = SynPat.LongIdent(argPats = SynArgPats.Pats [ Is pat ])))) -> ValueSome range - else - ValueNone + | _ -> ValueNone // Parens are otherwise never needed in these cases: // diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index b91095f8842..806a1e4d18b 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -2030,25 +2030,29 @@ module Patterns = type T (x, y) = new (x, y, z) = T (x, y) new (x) = T (x, 3) + member _.Z = x + y ", " type T (x, y) = new (x, y, z) = T (x, y) new x = T (x, 3) + member _.Z = x + y " // See https://github.com/dotnet/fsharp/issues/16257. " let f (x: string) = int x type T (x, y) = - new (x) = T (f x, y) + new (x) = T (f x, 3) new x = T (id x, 3) + member _.Z = x + y ", " let f (x: string) = int x type T (x, y) = - new (x) = T (f x, y) + new (x) = T (f x, 3) new x = T (id x, 3) + member _.Z = x + y " // See https://github.com/dotnet/fsharp/issues/16257. @@ -2056,11 +2060,13 @@ module Patterns = type T (x, y) = new (x) = T (x, 3) new (x, y, z) = T (x, y) + member _.Z = x + y ", " type T (x, y) = new (x) = T (x, 3) new (x, y, z) = T (x, y) + member _.Z = x + y " } From d1773e7f68d23d5705530d31bc443f1a0ef01fed Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Wed, 22 Nov 2023 11:13:01 -0500 Subject: [PATCH 08/11] =?UTF-8?q?Make=20paren=20tests=20run=202=E2=80=933?= =?UTF-8?q?=C3=97=20as=20fast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * This optimization could in theory be used for all tests in this project if we set up some kind of solution cache keyed by project & editor settings, but it looks like it would require some refactoring in RoslynTestHelpers. --- .../CodeFixes/CodeFixTestFramework.fs | 4 +- .../RemoveUnnecessaryParenthesesTests.fs | 46 +++++++++++++++---- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs index 0a0ca440236..3818f89a0d9 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs @@ -220,7 +220,7 @@ module Xunit = /// /// Thrown if a code fix is applied. /// - let expectNoFix (tryFix: string -> Task) code = + let expectNoFix (tryFix: string -> CancellableTask) code = cancellableTask { match! tryFix code with | None -> () @@ -246,7 +246,7 @@ module Xunit = /// /// Thrown if the generated fix does not match the expected fixed code. /// - let expectFix tryFix code fixedCode = + let expectFix (tryFix: string -> CancellableTask) code fixedCode = if code = fixedCode then expectNoFix tryFix code else diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index 806a1e4d18b..94da8b34540 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -3,7 +3,8 @@ module FSharp.Editor.Tests.CodeFixes.RemoveUnnecessaryParenthesesTests open System.Text -open FSharp.Compiler.Text +open FSharp.Editor.Tests.Helpers +open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text open Microsoft.VisualStudio.FSharp.Editor open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks @@ -13,17 +14,45 @@ open CodeFixTestFramework [] module private TopLevel = let private fixer = FSharpRemoveUnnecessaryParenthesesCodeFixProvider() - let private fixAllProvider = fixer.RegisterFsharpFixAll() + + // It is much (2–3×) faster to reuse the same solution + // rather than creating a new one for each test. + // Unfortunately, it is not safe to reuse the same solution across + // tests that may set different project or editor options, + // because they may run concurrently on other threads, + // and the in-memory settings store used for tests is global, + // so we restrict this optimization to this file. + let private sln, projId = + let projInfo = + RoslynTestHelpers.CreateProjectInfo (ProjectId.CreateNewId()) "C:\\test.fsproj" [] + + let sln = RoslynTestHelpers.CreateSolution [ projInfo ] + + let projectOptions = + { RoslynTestHelpers.DefaultProjectOptions with + OtherOptions = + [| + "--targetprofile:netcore" // without this lib some symbols are not loaded + "--nowarn:3384" // The .NET SDK for this script could not be determined + |] + } + + RoslynTestHelpers.SetProjectOptions projInfo.Id sln projectOptions + + RoslynTestHelpers.SetEditorOptions + sln + { CodeFixesOptions.Default with + RemoveParens = true + } + + sln, projInfo.Id let private tryFix (code: string) = cancellableTask { - let mode = - WithSettings - { CodeFixesOptions.Default with - RemoveParens = true - } + let document = + let docInfo = RoslynTestHelpers.CreateDocumentInfo projId "C:\\test.fs" code + (sln.AddDocument docInfo).GetDocument docInfo.Id - let document = Document.create mode code let sourceText = SourceText.From code let! diagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) @@ -34,7 +63,6 @@ module private TopLevel = |> ValueOption.either (fixer :> IFSharpCodeFixProvider).GetCodeFixIfAppliesAsync (CancellableTask.singleton ValueNone) |> CancellableTask.map (ValueOption.map (TestCodeFix.ofFSharpCodeFix sourceText) >> ValueOption.toOption) } - |> CancellableTask.startWithoutCancellation let expectFix = expectFix tryFix From d8876c5182e8790724eef6bbc2094b6f787128f7 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Wed, 22 Nov 2023 12:24:22 -0500 Subject: [PATCH 09/11] Clarify test organization & add comments --- .../RemoveUnnecessaryParenthesesTests.fs | 206 ++++++++++-------- 1 file changed, 116 insertions(+), 90 deletions(-) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index d9aa8973e88..680e105d6b3 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -1522,6 +1522,12 @@ module Patterns = | IsInst of string | QuoteExpr of string + /// The original pattern. + type OriginalPat = SynPat + + /// The pattern expected after the analyzer and code fix have been applied. + type ExpectedPat = SynPat + module SynPat = let (|DanglingTyped|_|) pat = let (|Last|) = List.last @@ -1563,6 +1569,7 @@ module Patterns = | OptionalVal _ | IsInst _ -> NonAtomic + /// Formats the given pattern to the given string builder. let fmt (sb: StringBuilder) pat = let spaceIfGt (sb: StringBuilder) = if sb.Chars(sb.Length - 1) = '>' then sb.Append ' ' else sb @@ -1607,12 +1614,17 @@ module Patterns = loop sb id pat - let parenthesize inner outer = + /// Returns pairings of the inner expression and nests it inside of the outer + /// paired with the expected outcome of running the analyzer and code fix + /// on the resulting code, i.e., either removal of the parentheses or not, + /// depending on the pattern and its context. + let parenthesize inner outer : (OriginalPat * ExpectedPat) list = let impl paren inner outer = let Paren = paren [ match outer, inner with + // Attributed patterns can never be nested. | _, Attrib _ -> () | LongIdentWithArgs (name, _), Atomic -> LongIdentWithArgs(name, Paren inner), LongIdentWithArgs(name, inner) @@ -1623,6 +1635,7 @@ module Patterns = LongIdentWithNamedArgs(name, args |> List.updateAt i (field, Paren inner)), LongIdentWithNamedArgs(name, args |> List.updateAt i (field, inner)) + // Quotations can never be nested in anything other than a LongIdent. | _, QuoteExpr _ -> () | Record fields, Atomic -> @@ -1742,7 +1755,7 @@ module Patterns = let patterns = atomicOrNullaryPatterns @ outerPatterns |> List.distinct - let bareAtomics = + let bareAtomics: (OriginalPat * ExpectedPat) list = [ for pat in patterns @@ -1757,18 +1770,21 @@ module Patterns = | _ -> Paren pat, pat ] - let nestedAtomics = + let nestedAtomics: (OriginalPat * ExpectedPat) list = [ for outer, inner in (outerPatterns, atomicOrNullaryPatterns) ||> Seq.allPairs do yield! SynPat.parenthesize inner outer ] - let nestedOuters = + let nestedOuters: (OriginalPat * ExpectedPat) list = [ for outer, inner in (outerPatterns, nestedAtomics |> Seq.map snd) ||> Seq.allPairs |> Seq.distinct do yield! SynPat.parenthesize inner outer ] + /// Formats the given sequence of original and + /// expected pattern pairs and returns them as + /// an obj array seq suitable for use as xUnit member data. let fmtAllAsMemberData pairs = memberData { let sb = StringBuilder() @@ -1782,70 +1798,89 @@ module Patterns = expected) } - module Lambda = - /// fun pat -> … - let expectFix pat expected = - let code = $"fun %s{pat} -> ()" - let expected = $"fun %s{expected} -> ()" + /// Tests patterns in head-pattern position in (let|and|use)(!) bindings. + module HeadPat = + /// Tests patterns in head-pattern position in let-bindings. + module Let = + /// let pat = … + let expectFix pat expected = + let code = $"let %s{pat} = Unchecked.defaultof<_>" + let expected = $"let %s{expected} = Unchecked.defaultof<_>" - expectFix code expected + expectFix code expected - let bareAtomics = fmtAllAsMemberData bareAtomics + let bareAtomics = fmtAllAsMemberData bareAtomics - let bareNonAtomics = - patterns - |> List.collect (function - | SynPat.Attrib _ - | SynPat.OptionalVal _ - | SynPat.IsInst _ - | SynPat.Atomic -> [] - | pat -> [ SynPat.Paren pat, SynPat.Paren pat ]) - |> fmtAllAsMemberData + let bareNonAtomics = + patterns + |> List.collect (function + | SynPat.Attrib _ + | SynPat.OptionalVal _ + | SynPat.IsInst _ + | SynPat.Atomic -> [] + | SynPat.Typed _ as pat -> [ SynPat.Paren pat, pat ] + | SynPat.Tuple pats as pat -> + [ + SynPat.Paren pat, pat + SynPat.Paren(SynPat.Tuple(SynPat.Typed(Wild, "obj") :: pats)), + SynPat.Paren(SynPat.Tuple(SynPat.Typed(Wild, "obj") :: pats)) + SynPat.Tuple(SynPat.Paren(SynPat.Typed(Wild, "obj")) :: pats), + SynPat.Tuple(SynPat.Paren(SynPat.Typed(Wild, "obj")) :: pats) + ] + | SynPat.LongIdentWithArgs _ + | SynPat.DanglingTyped as pat -> [ SynPat.Paren pat, SynPat.Paren pat ] + | pat -> [ SynPat.Paren pat, pat ]) + |> fmtAllAsMemberData - [] - let ``Bare atomic patterns`` original expected = expectFix original expected + [] + let ``Bare atomic patterns`` original expected = expectFix original expected - [] - let ``Bare non-atomic patterns`` original expected = expectFix original expected + [] + let ``Bare non-atomic patterns`` original expected = expectFix original expected - module HeadPat = - /// let pat = … - let expectFix pat expected = - let code = $"let %s{pat} = Unchecked.defaultof<_>" - let expected = $"let %s{expected} = Unchecked.defaultof<_>" + /// Tests patterns in head-pattern position in let!-bindings. + module LetBang = + /// let! pat = … + let expectFix pat expected = + let code = + $" + async {{ + let! %s{pat} = Unchecked.defaultof<_> + return () + }} + " + + let expected = + $" + async {{ + let! %s{expected} = Unchecked.defaultof<_> + return () + }} + " - expectFix code expected + expectFix code expected - let bareAtomics = fmtAllAsMemberData bareAtomics + let bareAtomics = fmtAllAsMemberData bareAtomics - let bareNonAtomics = - patterns - |> List.collect (function - | SynPat.Attrib _ - | SynPat.OptionalVal _ - | SynPat.IsInst _ - | SynPat.Atomic -> [] - | SynPat.Typed _ as pat -> [ SynPat.Paren pat, pat ] - | SynPat.Tuple pats as pat -> - [ - SynPat.Paren pat, pat - SynPat.Paren(SynPat.Tuple(SynPat.Typed(Wild, "obj") :: pats)), - SynPat.Paren(SynPat.Tuple(SynPat.Typed(Wild, "obj") :: pats)) - SynPat.Tuple(SynPat.Paren(SynPat.Typed(Wild, "obj")) :: pats), - SynPat.Tuple(SynPat.Paren(SynPat.Typed(Wild, "obj")) :: pats) - ] - | SynPat.LongIdentWithArgs _ - | SynPat.DanglingTyped as pat -> [ SynPat.Paren pat, SynPat.Paren pat ] - | pat -> [ SynPat.Paren pat, pat ]) - |> fmtAllAsMemberData + let bareNonAtomics = + patterns + |> List.choose (function + | SynPat.Attrib _ + | SynPat.OptionalVal _ + | SynPat.Atomic -> None + | SynPat.DanglingTyped as pat -> Some(SynPat.Paren pat, SynPat.Paren pat) + | pat -> Some(SynPat.Paren pat, pat)) + |> fmtAllAsMemberData - [] - let ``Bare atomic patterns`` original expected = expectFix original expected + [] + let ``Bare atomic patterns`` original expected = expectFix original expected - [] - let ``Bare non-atomic patterns`` original expected = expectFix original expected + [] + let ``Bare non-atomic patterns`` original expected = expectFix original expected + /// Tests patterns in argument position. module ArgPat = + /// Tests patterns in argument position in let-bound functions. module Let = /// let f pat = … let expectFix pat expected = @@ -1870,6 +1905,7 @@ module Patterns = [] let ``Bare non-atomic patterns`` original expected = expectFix original expected + /// Tests patterns in argument position in object members. module Member = /// member _.M pat = … let expectFix pat expected = @@ -1893,45 +1929,34 @@ module Patterns = [] let ``Bare non-atomic patterns`` original expected = expectFix original expected - module LetBang = - /// let! pat = … - let expectFix pat expected = - let code = - $" - async {{ - let! %s{pat} = Unchecked.defaultof<_> - return () - }} - " - - let expected = - $" - async {{ - let! %s{expected} = Unchecked.defaultof<_> - return () - }} - " + /// Tests patterns in argument position in lambda expressions. + module Lambda = + /// fun pat -> … + let expectFix pat expected = + let code = $"fun %s{pat} -> ()" + let expected = $"fun %s{expected} -> ()" - expectFix code expected + expectFix code expected - let bareAtomics = fmtAllAsMemberData bareAtomics + let bareAtomics = fmtAllAsMemberData bareAtomics - let bareNonAtomics = - patterns - |> List.choose (function - | SynPat.Attrib _ - | SynPat.OptionalVal _ - | SynPat.Atomic -> None - | SynPat.DanglingTyped as pat -> Some(SynPat.Paren pat, SynPat.Paren pat) - | pat -> Some(SynPat.Paren pat, pat)) - |> fmtAllAsMemberData + let bareNonAtomics = + patterns + |> List.collect (function + | SynPat.Attrib _ + | SynPat.OptionalVal _ + | SynPat.IsInst _ + | SynPat.Atomic -> [] + | pat -> [ SynPat.Paren pat, SynPat.Paren pat ]) + |> fmtAllAsMemberData - [] - let ``Bare atomic patterns`` original expected = expectFix original expected + [] + let ``Bare atomic patterns`` original expected = expectFix original expected - [] - let ``Bare non-atomic patterns`` original expected = expectFix original expected + [] + let ``Bare non-atomic patterns`` original expected = expectFix original expected + /// Tests patterns in match clauses. module MatchClause = /// match … with pat -> … let expectFix pat expected = @@ -1981,8 +2006,6 @@ module Patterns = [] let ``Bare non-atomic patterns`` original expected = expectFix original expected - // There is no one context in which all patterns are allowed - // and treated identically, so we arbitrarily use a match clause here. let expectFix original expected = let code = $" @@ -2005,9 +2028,12 @@ module Patterns = let singlyNestedAtomicPatterns = fmtAllAsMemberData nestedAtomics let deeplyNestedPatterns = fmtAllAsMemberData nestedOuters + /// Tests atomic patterns nested one level deep inside of all non-nullary patterns + /// (patterns inside of which other patterns can be nested). [] let ``Singly nested patterns`` original expected = expectFix original expected + /// Tests non-nullary patterns (atomic or not) nested inside of other non-nullary patterns. [] let ``Deeply nested patterns`` original expected = expectFix original expected From c2543c892bf7c179100c368fa0af071d918a7a25 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Wed, 22 Nov 2023 12:25:03 -0500 Subject: [PATCH 10/11] Apply new Fantomas --- .../RemoveUnnecessaryParenthesesTests.fs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index 680e105d6b3..ed5b2593195 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -1535,11 +1535,11 @@ module Patterns = let rec loop pat = match pat with | Typed _ -> Some DanglingTyped - | Or (_, pat) - | ListCons (_, pat) - | As (_, pat) - | Ands (Last pat) - | Tuple (Last pat) -> loop pat + | Or(_, pat) + | ListCons(_, pat) + | As(_, pat) + | Ands(Last pat) + | Tuple(Last pat) -> loop pat | _ -> None loop pat @@ -1579,15 +1579,15 @@ module Patterns = | Const c -> cont (sb.Append c) | Wild -> cont (sb.Append "_") | Named name -> cont (sb.Append name) - | Typed (pat, ty) -> loop sb (cont << fun sb -> sb.Append(" : ").Append ty) pat - | Attrib (attrib, pat) -> loop (sb.Append("[<").Append(attrib).Append(">] ")) cont pat - | Or (l, r) -> loop sb (fun sb -> loop (sb.Append " | ") cont r) l - | ListCons (h, t) -> loop sb (fun sb -> loop (sb.Append " :: ") cont t) h + | Typed(pat, ty) -> loop sb (cont << fun sb -> sb.Append(" : ").Append ty) pat + | Attrib(attrib, pat) -> loop (sb.Append("[<").Append(attrib).Append(">] ")) cont pat + | Or(l, r) -> loop sb (fun sb -> loop (sb.Append " | ") cont r) l + | ListCons(h, t) -> loop sb (fun sb -> loop (sb.Append " :: ") cont t) h | Ands pats -> separateBy " & " sb cont pats - | As (l, r) -> loop sb (fun sb -> loop (sb.Append " as ") cont r) l + | As(l, r) -> loop sb (fun sb -> loop (sb.Append " as ") cont r) l | LongIdent id -> cont (sb.Append id) - | LongIdentWithArgs (id, arg) -> loop (sb.Append(id).Append ' ') cont arg - | LongIdentWithNamedArgs (id, args) -> + | LongIdentWithArgs(id, arg) -> loop (sb.Append(id).Append ' ') cont arg + | LongIdentWithNamedArgs(id, args) -> fmtFields (sb.Append(id).Append " (") (cont << fun (sb: StringBuilder) -> sb.Append ')') args | Tuple pats -> separateBy ", " sb cont pats | StructTuple pats -> separateBy ", " (sb.Append "struct (") (cont << fun sb -> sb.Append ')') pats @@ -1627,10 +1627,10 @@ module Patterns = // Attributed patterns can never be nested. | _, Attrib _ -> () - | LongIdentWithArgs (name, _), Atomic -> LongIdentWithArgs(name, Paren inner), LongIdentWithArgs(name, inner) - | LongIdentWithArgs (name, _), NonAtomic -> LongIdentWithArgs(name, Paren inner), LongIdentWithArgs(name, Paren inner) + | LongIdentWithArgs(name, _), Atomic -> LongIdentWithArgs(name, Paren inner), LongIdentWithArgs(name, inner) + | LongIdentWithArgs(name, _), NonAtomic -> LongIdentWithArgs(name, Paren inner), LongIdentWithArgs(name, Paren inner) - | LongIdentWithNamedArgs (name, args), _ -> + | LongIdentWithNamedArgs(name, args), _ -> for i, (field, _) in Seq.indexed args do LongIdentWithNamedArgs(name, args |> List.updateAt i (field, Paren inner)), LongIdentWithNamedArgs(name, args |> List.updateAt i (field, inner)) @@ -1642,9 +1642,9 @@ module Patterns = for i, (field, _) in Seq.indexed fields do Record(fields |> List.updateAt i (field, Paren inner)), Record(fields |> List.updateAt i (field, inner)) - | Typed (_, ty), (Atomic | Typed _ | IsInst _) -> Typed(Paren inner, ty), Typed(inner, ty) - | Typed (_, ty), NonAtomic -> Typed(Paren inner, ty), Typed(Paren inner, ty) - | Attrib (attrib, _), Atomic -> Attrib(attrib, Paren inner), Attrib(attrib, inner) + | Typed(_, ty), (Atomic | Typed _ | IsInst _) -> Typed(Paren inner, ty), Typed(inner, ty) + | Typed(_, ty), NonAtomic -> Typed(Paren inner, ty), Typed(Paren inner, ty) + | Attrib(attrib, _), Atomic -> Attrib(attrib, Paren inner), Attrib(attrib, inner) | OptionalVal _, _ -> () | ListCons _, Tuple _ -> ListCons(Paren inner, Wild), ListCons(Paren inner, Wild) From 897a828ed7ef02549679fecf30a6957a99b085c1 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Wed, 22 Nov 2023 12:52:46 -0500 Subject: [PATCH 11/11] Remove redundant tests --- .../RemoveUnnecessaryParenthesesTests.fs | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index ed5b2593195..27bce12bb2d 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -1734,7 +1734,7 @@ module Patterns = IsInst(nameof obj) ] - let outerPatterns = + let nonNullaryPatterns = [ Typed(Wild, nameof obj) Attrib("OptionalArgument", Wild) @@ -1753,7 +1753,7 @@ module Patterns = OptionalVal(Named "y") ] - let patterns = atomicOrNullaryPatterns @ outerPatterns |> List.distinct + let patterns = atomicOrNullaryPatterns @ nonNullaryPatterns |> List.distinct let bareAtomics: (OriginalPat * ExpectedPat) list = [ @@ -1770,15 +1770,22 @@ module Patterns = | _ -> Paren pat, pat ] - let nestedAtomics: (OriginalPat * ExpectedPat) list = + let nestedAtomicsOrNullaries: (OriginalPat * ExpectedPat) list = [ - for outer, inner in (outerPatterns, atomicOrNullaryPatterns) ||> Seq.allPairs do + for outer, inner in (nonNullaryPatterns, atomicOrNullaryPatterns) ||> Seq.allPairs do yield! SynPat.parenthesize inner outer ] let nestedOuters: (OriginalPat * ExpectedPat) list = [ - for outer, inner in (outerPatterns, nestedAtomics |> Seq.map snd) ||> Seq.allPairs |> Seq.distinct do + let nonAtomics = + nestedAtomicsOrNullaries + |> Seq.map snd + |> Seq.filter (function + | SynPat.NonAtomic -> true + | _ -> false) + + for outer, inner in (nonNullaryPatterns, nonAtomics) ||> Seq.allPairs |> Seq.distinct do yield! SynPat.parenthesize inner outer ] @@ -2025,15 +2032,16 @@ module Patterns = TopLevel.expectFix code expected - let singlyNestedAtomicPatterns = fmtAllAsMemberData nestedAtomics + let singlyNestedAtomicOrNullaryPatterns = + fmtAllAsMemberData nestedAtomicsOrNullaries + let deeplyNestedPatterns = fmtAllAsMemberData nestedOuters - /// Tests atomic patterns nested one level deep inside of all non-nullary patterns - /// (patterns inside of which other patterns can be nested). - [] + /// Tests atomic or nullary patterns nested one level deep inside of all non-nullary patterns. + [] let ``Singly nested patterns`` original expected = expectFix original expected - /// Tests non-nullary patterns (atomic or not) nested inside of other non-nullary patterns. + /// Tests non-nullary, non-atomic patterns nested inside of other non-nullary patterns. [] let ``Deeply nested patterns`` original expected = expectFix original expected