From 4b6b4969c5ee4387321b77dfc62c90536a33bc73 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Sun, 12 Nov 2023 15:33:06 -0500 Subject: [PATCH 1/3] Miscellaneous parenthesization fixes --- src/Compiler/Service/ServiceAnalysis.fs | 159 +++++++++++++----- .../RemoveUnnecessaryParenthesesTests.fs | 102 +++++++++++ 2 files changed, 223 insertions(+), 38 deletions(-) diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs index 3bc2eeb973e..d8719640614 100644 --- a/src/Compiler/Service/ServiceAnalysis.fs +++ b/src/Compiler/Service/ServiceAnalysis.fs @@ -456,6 +456,8 @@ module UnusedDeclarations = module UnnecessaryParentheses = open System + let (|Ident|) (ident: Ident) = ident.idText + /// Represents an expression's precedence, or, /// for a few few types of expression whose exact /// kind can be significant, the expression's exact kind. @@ -873,11 +875,27 @@ module UnnecessaryParentheses = | SynExpr.DotIndexedGet(objectExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Dot, Left) | _ -> ValueNone + /// Matches a SynExpr.App nested in a sequence of dot-gets. + /// + /// x.M.N().O + [] + let (|NestedApp|_|) expr = + let rec loop = + function + | SynExpr.DotGet (expr = expr) + | SynExpr.DotIndexedGet (objectExpr = expr) -> loop expr + | SynExpr.App _ -> ValueSome NestedApp + | _ -> ValueNone + + loop expr + /// Returns the given expression's precedence, if applicable. [] let (|InnerBinaryExpr|_|) expr : Precedence voption = match expr with | SynExpr.Tuple(isStruct = false) -> ValueSome Comma + | SynExpr.DotGet(expr = NestedApp) + | SynExpr.DotIndexedGet(objectExpr = NestedApp) -> ValueSome Apply | SynExpr.DotGet _ | SynExpr.DotIndexedGet _ -> ValueSome Dot | PrefixApp prec -> ValueSome prec @@ -936,6 +954,13 @@ module UnnecessaryParentheses = | SynExpr.IfThenElse _ as expr -> Some expr | _ -> None) + /// Matches a dangling sequential expression. + [] + let (|Sequential|_|) = + dangling (function + | SynExpr.Sequential _ as expr -> Some expr + | _ -> None) + /// Matches a dangling try-with or try-finally construct. [] let (|Try|_|) = @@ -1192,6 +1217,19 @@ module UnnecessaryParentheses = SyntaxNode.SynExpr (SynExpr.App _) :: SyntaxNode.SynExpr (SynExpr.App(argExpr = SynExpr.ArrayOrListComputed(isArray = false))) :: _ -> ValueNone + // Parens must stay around binary equals expressions in argument + // position lest they be interpreted as named argument assignments: + // + // o.M((x = y)) + // o.N((x = y), z) + | SynExpr.Paren(expr = SynExpr.Paren(expr = InfixApp (Eq, _))), + SyntaxNode.SynExpr (SynExpr.App(funcExpr = SynExpr.LongIdent _)) :: _ + | SynExpr.Paren(expr = InfixApp (Eq, _)), + SyntaxNode.SynExpr (SynExpr.Paren _) :: SyntaxNode.SynExpr (SynExpr.App(funcExpr = SynExpr.LongIdent _)) :: _ + | SynExpr.Paren(expr = InfixApp (Eq, _)), + SyntaxNode.SynExpr (SynExpr.Tuple(isStruct = false)) :: SyntaxNode.SynExpr (SynExpr.Paren _) :: SyntaxNode.SynExpr (SynExpr.App(funcExpr = SynExpr.LongIdent _)) :: _ -> + ValueNone + // The :: operator is parsed differently from other symbolic infix operators, // so we need to give it special treatment. @@ -1249,6 +1287,8 @@ module UnnecessaryParentheses = match outer, inner with | ConfusableWithTypeApp, _ -> ValueNone + | SynExpr.IfThenElse _, Dangling.Sequential _ -> ValueNone + | SynExpr.IfThenElse (trivia = trivia), Dangling.IfThen ifThenElse when problematic ifThenElse.Range trivia.ThenKeyword || trivia.ElseKeyword |> Option.exists (problematic ifThenElse.Range) @@ -1258,15 +1298,42 @@ module UnnecessaryParentheses = | SynExpr.TryFinally (trivia = trivia), Dangling.Try tryExpr when problematic tryExpr.Range trivia.FinallyKeyword -> ValueNone - | (SynExpr.Match (clauses = clauses) | SynExpr.MatchLambda (matchClauses = clauses) | SynExpr.MatchBang (clauses = clauses)), - Dangling.Match matchOrTry when anyProblematic matchOrTry.Range clauses -> ValueNone + | SynExpr.Match (clauses = clauses; trivia = { WithKeyword = withKeyword }), Dangling.Match matchOrTry when + problematic matchOrTry.Range withKeyword + || anyProblematic matchOrTry.Range clauses + -> + ValueNone + + | SynExpr.MatchBang (clauses = clauses; trivia = { WithKeyword = withKeyword }), Dangling.Match matchOrTry when + problematic matchOrTry.Range withKeyword + || anyProblematic matchOrTry.Range clauses + -> + ValueNone + + | SynExpr.MatchLambda (matchClauses = clauses), Dangling.Match matchOrTry when anyProblematic matchOrTry.Range clauses -> + ValueNone | SynExpr.TryWith (withCases = clauses; trivia = trivia), Dangling.Match matchOrTry when - anyProblematic matchOrTry.Range clauses - || problematic matchOrTry.Range trivia.WithKeyword + problematic matchOrTry.Range trivia.WithKeyword + || anyProblematic matchOrTry.Range clauses -> ValueNone + | SynExpr.Paren _, SynExpr.Typed _ + | SynExpr.Quote _, SynExpr.Typed _ + | SynExpr.AnonRecd _, SynExpr.Typed _ + | SynExpr.Record _, SynExpr.Typed _ + | SynExpr.While(doExpr = SynExpr.Paren(expr = Is inner)), SynExpr.Typed _ + | SynExpr.WhileBang(doExpr = SynExpr.Paren(expr = Is inner)), SynExpr.Typed _ + | SynExpr.For(doBody = Is inner), SynExpr.Typed _ + | SynExpr.ForEach(bodyExpr = Is inner), SynExpr.Typed _ + | SynExpr.Match _, SynExpr.Typed _ + | SynExpr.Do _, SynExpr.Typed _ + | SynExpr.LetOrUse(body = Is inner), SynExpr.Typed _ + | SynExpr.TryWith _, SynExpr.Typed _ + | SynExpr.TryFinally _, SynExpr.Typed _ -> ValueSome range + | _, SynExpr.Typed _ -> ValueNone + | OuterBinaryExpr inner (outerPrecedence, side), InnerBinaryExpr innerPrecedence -> let ambiguous = match Precedence.compare outerPrecedence innerPrecedence with @@ -1295,31 +1362,6 @@ module UnnecessaryParentheses = | OuterBinaryExpr inner (_, Right), (SynExpr.Sequential _ | SynExpr.LetOrUse(trivia = { InKeyword = None })) -> ValueNone | OuterBinaryExpr inner (_, Right), inner -> if dangling inner then ValueNone else ValueSome range - | SynExpr.Typed _, SynExpr.Typed _ - | SynExpr.WhileBang(whileExpr = SynExpr.Paren(expr = Is inner)), SynExpr.Typed _ - | SynExpr.While(whileExpr = SynExpr.Paren(expr = Is inner)), SynExpr.Typed _ - | SynExpr.For(identBody = Is inner), SynExpr.Typed _ - | SynExpr.For(toBody = Is inner), SynExpr.Typed _ - | SynExpr.ForEach(enumExpr = Is inner), SynExpr.Typed _ - | SynExpr.ArrayOrList _, SynExpr.Typed _ - | SynExpr.ArrayOrListComputed _, SynExpr.Typed _ - | SynExpr.IndexRange _, SynExpr.Typed _ - | SynExpr.IndexFromEnd _, SynExpr.Typed _ - | SynExpr.ComputationExpr _, SynExpr.Typed _ - | SynExpr.Lambda _, SynExpr.Typed _ - | SynExpr.Assert _, SynExpr.Typed _ - | SynExpr.App _, SynExpr.Typed _ - | SynExpr.Lazy _, SynExpr.Typed _ - | SynExpr.LongIdentSet _, SynExpr.Typed _ - | SynExpr.DotSet _, SynExpr.Typed _ - | SynExpr.Set _, SynExpr.Typed _ - | SynExpr.DotIndexedSet _, SynExpr.Typed _ - | SynExpr.NamedIndexedPropertySet _, SynExpr.Typed _ - | SynExpr.Upcast _, SynExpr.Typed _ - | SynExpr.Downcast _, SynExpr.Typed _ - | SynExpr.AddressOf _, SynExpr.Typed _ - | SynExpr.JoinIn _, SynExpr.Typed _ -> ValueNone - // new T(expr) | SynExpr.New _, AtomicExprAfterType -> ValueSome range | SynExpr.New _, _ -> ValueNone @@ -1331,7 +1373,6 @@ module UnnecessaryParentheses = | _, SynExpr.Paren _ | _, SynExpr.Quote _ | _, SynExpr.Const _ - | _, SynExpr.Typed _ | _, SynExpr.Tuple(isStruct = true) | _, SynExpr.AnonRecd _ | _, SynExpr.ArrayOrList _ @@ -1378,29 +1419,51 @@ module UnnecessaryParentheses = | _ -> ValueNone module SynPat = + [] + let (|AnyTyped|_|) pats = + if + pats + |> List.exists (function + | SynPat.Typed _ -> true + | _ -> false) + then + ValueSome AnyTyped + else + 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: // // let (Pattern …) = … + // let (x: …, y…) = … + // let (x: …), (y: …) = … // let! (x: …) = … // and! (x: …) = … // use! (x: …) = … // _.member M(x: …) = … // match … with (x: …) -> … + // match … with (x, y: …) -> … // function (x: …) -> … // fun (x, y, …) -> … // fun (x: …) -> … // fun (Pattern …) -> … - | SynPat.Paren _, SyntaxNode.SynExpr (SynExpr.LetOrUseBang(pat = SynPat.Paren(pat = SynPat.Typed _))) :: _ - | SynPat.Paren _, SyntaxNode.SynMatchClause (SynMatchClause(pat = SynPat.Paren(pat = SynPat.Typed _))) :: _ - | SynPat.Paren(pat = SynPat.LongIdent _), SyntaxNode.SynBinding _ :: _ - | SynPat.Paren(pat = SynPat.LongIdent _), SyntaxNode.SynExpr (SynExpr.Lambda _) :: _ - | SynPat.Paren _, SyntaxNode.SynExpr (SynExpr.Lambda(args = SynSimplePats.SimplePats(pats = _ :: _ :: _))) :: _ - | SynPat.Paren _, SyntaxNode.SynExpr (SynExpr.Lambda(args = SynSimplePats.SimplePats(pats = [ SynSimplePat.Typed _ ]))) :: _ -> - ValueNone + | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ + | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynPat (SynPat.Tuple _) :: 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.Tuple (isStruct = false; elementPats = AnyTyped), _), SyntaxNode.SynBinding _ :: _ + | SynPat.Paren (SynPat.LongIdent _, _), SyntaxNode.SynBinding _ :: _ + | SynPat.Paren (SynPat.LongIdent _, _), 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… // @@ -1415,6 +1478,24 @@ module UnnecessaryParentheses = | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynMatchClause _ :: _ -> ValueNone + // (()) is required when overriding a generic member + // where unit is the generic type argument: + // + // 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 + + // Parens are required for the first of multiple additional constructors. + // We simply require them always. + // + // type T … = + // new (x) = … + // new (x, y) = … + | SynPat.Paren _, SyntaxNode.SynPat (SynPat.LongIdent(longDotId = SynLongIdent(id = [ Ident "new" ]))) :: _ -> ValueNone + // Parens are otherwise never needed in these cases: // // let (x: …) = … @@ -1437,7 +1518,9 @@ module UnnecessaryParentheses = | SynPat.Paren (inner, range), SyntaxNode.SynPat outer :: _ -> match outer, inner with // (x :: xs) :: ys - | SynPat.ListCons(lhsPat = SynPat.Paren(pat = Is inner)), SynPat.ListCons _ -> ValueNone + // (x, xs) :: ys + | SynPat.ListCons(lhsPat = SynPat.Paren(pat = Is inner)), SynPat.ListCons _ + | SynPat.ListCons(lhsPat = SynPat.Paren(pat = Is inner)), SynPat.Tuple _ -> ValueNone // A as (B | C) // A as (B & C) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index a5bd9d67d45..093c67ec6ea 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -103,6 +103,7 @@ let _ = "struct ((1), 1)", "struct (1, 1)" "struct (1, (1))", "struct (1, 1)" "(fun x -> x), y", "(fun x -> x), y" + "3, (null: string)", "3, (null: string)" // AnonymousRecord "{| A = (1) |}", "{| A = 1 |}" @@ -222,6 +223,17 @@ let _ = "(match x with _ -> 3) > 3", "(match x with _ -> 3) > 3" "match x with 1 -> (fun x -> x) | _ -> id", "match x with 1 -> (fun x -> x) | _ -> id" + "match (try () with _ -> ()) with () -> ()", "match (try () with _ -> ()) with () -> ()" + + " + match (try () with _ -> ()) with + | () -> () + ", + " + match (try () with _ -> ()) with + | () -> () + " + " 3 > (match x with | 1 @@ -469,6 +481,21 @@ let _ = else 3 " + """ + if + (printfn "1" + true) + then + () + """, + """ + if + (printfn "1" + true) + then + () + """ + // LongIdent "(|Failure|_|) null", "(|Failure|_|) null" @@ -477,6 +504,7 @@ let _ = // DotGet "([]).Length", "[].Length" + "([] : int list).Length", "([] : int list).Length" // DotLambda "[{| A = x |}] |> List.map (_.A)", "[{| A = x |}] |> List.map _.A" @@ -757,6 +785,7 @@ let _ = // Typed "id (x : int)", "id (x : int)" + """ "abc".Contains (x : char, StringComparison.Ordinal) """, """ "abc".Contains (x : char, StringComparison.Ordinal) """ // Tuple "id (x, y)", "id (x, y)" @@ -968,9 +997,14 @@ let _ = """id ("x").[0]""", """id "x".[0]""" """(id("x")).[0]""", """(id "x").[0]""" """(id "x").[0]""", """(id "x").[0]""" + """id ("".ToCharArray().[0])""", """id ("".ToCharArray().[0])""" + """id ("".ToCharArray()[0])""", """id ("".ToCharArray()[0])""" + """id ("".ToCharArray()[0])""", """id ("".ToCharArray()[0])""" // DotIndexedSet "id ([|x|].[y] <- z)", "id ([|x|].[y] <- z)" + """id ("".ToCharArray().[0] <- '0')""", """id ("".ToCharArray().[0] <- '0')""" + """id ("".ToCharArray()[0] <- '0')""", """id ("".ToCharArray()[0] <- '0')""" // NamedIndexedPropertySet "let xs = [|0|] in id (xs.Item 0 <- 0)", "let xs = [|0|] in id (xs.Item 0 <- 0)" @@ -1036,6 +1070,10 @@ let _ = // InterpolatedString """ id ($"{x}") """, """ id $"{x}" """ + + // Miscellaneous + "System.Threading.Tasks.Task.CompletedTask.ConfigureAwait((x = x))", + "System.Threading.Tasks.Task.CompletedTask.ConfigureAwait((x = x))" } [] @@ -1144,6 +1182,17 @@ let _ = (2 + 2) { return 5 } val Message2 : string new (str1, str2) = { inherit exn (str1); Message2 = str2 } " + + " + type T = static member M (x : bool, y) = () + let x = 3 + T.M ((x = 4), 5) + ", + " + type T = static member M (x : bool, y) = () + let x = 3 + T.M ((x = 4), 5) + " } [] @@ -1436,6 +1485,8 @@ 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" @@ -1447,6 +1498,8 @@ match Unchecked.defaultof<_> with "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" @@ -1465,6 +1518,8 @@ match Unchecked.defaultof<_> 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" @@ -1481,6 +1536,8 @@ match Unchecked.defaultof<_> with "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 ()" @@ -1580,6 +1637,50 @@ 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) = ()" + + // See https://github.com/dotnet/fsharp/issues/16254. + " + type C = abstract M : unit -> unit + let _ = { new C with override _.M (()) = () } + ", + " + type C = abstract M : unit -> unit + let _ = { new C with override _.M (()) = () } + " + + // See https://github.com/dotnet/fsharp/issues/16254. + " + type C<'T> = abstract M : 'T -> unit + let _ = { new C with override _.M (()) = () } + ", + " + type C<'T> = abstract M : 'T -> unit + let _ = { new C with override _.M (()) = () } + " + + // See https://github.com/dotnet/fsharp/issues/16257. + " + type T (x, y) = + new (x, y, z) = T (x, y) + new (x) = T (x, 3) + ", + " + type T (x, y) = + new (x, y, z) = T (x, y) + new (x) = T (x, 3) + " + + // See https://github.com/dotnet/fsharp/issues/16257. + " + type T (x, y) = + new (x) = T (x, 3) + new (x, y, z) = T (x, y) + ", + " + type T (x, y) = + new (x) = T (x, 3) + new (x, y, z) = T (x, y) + " } [] @@ -1626,6 +1727,7 @@ match Unchecked.defaultof<_> with "(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" } [] From f96e9bd6d0e93f1d508687b46361eeb50bc70436 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Mon, 13 Nov 2023 08:37:16 -0500 Subject: [PATCH 2/3] Only non-struct --- src/Compiler/Service/ServiceAnalysis.fs | 2 +- .../CodeFixes/RemoveUnnecessaryParenthesesTests.fs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs index d8719640614..260a20765de 100644 --- a/src/Compiler/Service/ServiceAnalysis.fs +++ b/src/Compiler/Service/ServiceAnalysis.fs @@ -1520,7 +1520,7 @@ module UnnecessaryParentheses = // (x :: xs) :: ys // (x, xs) :: ys | SynPat.ListCons(lhsPat = SynPat.Paren(pat = Is inner)), SynPat.ListCons _ - | SynPat.ListCons(lhsPat = SynPat.Paren(pat = Is inner)), SynPat.Tuple _ -> ValueNone + | SynPat.ListCons(lhsPat = SynPat.Paren(pat = Is inner)), SynPat.Tuple(isStruct = false) -> ValueNone // A as (B | C) // A as (B & C) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index 093c67ec6ea..92dcde18386 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -1728,6 +1728,7 @@ match Unchecked.defaultof<_> with "(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" } [] From 4c36a98cbfc8a7198ff7f754703a9b111fa64549 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Mon, 13 Nov 2023 11:30:36 -0500 Subject: [PATCH 3/3] Handle dangling exprs in sequentials --- src/Compiler/Service/ServiceAnalysis.fs | 2 ++ .../CodeFixes/RemoveUnnecessaryParenthesesTests.fs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs index 260a20765de..bf856cfd95a 100644 --- a/src/Compiler/Service/ServiceAnalysis.fs +++ b/src/Compiler/Service/ServiceAnalysis.fs @@ -1319,6 +1319,8 @@ module UnnecessaryParentheses = -> ValueNone + | SynExpr.Sequential(expr1 = SynExpr.Paren(expr = Is inner)), Dangling.Problematic _ -> ValueNone + | SynExpr.Paren _, SynExpr.Typed _ | SynExpr.Quote _, SynExpr.Typed _ | SynExpr.AnonRecd _, SynExpr.Typed _ diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index 92dcde18386..36a39c1b92f 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -156,6 +156,9 @@ let _ = "for x in [] do (ignore 3)", "for x in [] do ignore 3" // ArrayOrListComputed + "[1; 2; (if x then 3 else 4); 5]", "[1; 2; (if x then 3 else 4); 5]" + "[|1; 2; (if x then 3 else 4); 5|]", "[|1; 2; (if x then 3 else 4); 5|]" + // IndexRange "[(1)..10]", "[1..10]" "[1..(10)]", "[1..10]"