diff --git a/docs/fcs/untypedtree-apis.fsx b/docs/fcs/untypedtree-apis.fsx
index cded5567df..c713ad8acf 100644
--- a/docs/fcs/untypedtree-apis.fsx
+++ b/docs/fcs/untypedtree-apis.fsx
@@ -128,13 +128,6 @@ let isPosInTypeDefn' = // Still true.
*)
(*** hide ***)
-
-module SynExpr =
- let shouldBeParenthesizedInContext (getLineStr: int -> string) (path: SyntaxVisitorPath) (expr: SynExpr) : bool = failwith "Nope."
-
-module SynPat =
- let shouldBeParenthesizedInContext (path: SyntaxVisitorPath) (pat: SynPat) : bool = failwith "Nope."
-
let getLineStr (line: int) : string = failwith "Nope."
(**
diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md
index 1cd2b82d88..dce836b32b 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md
@@ -14,4 +14,5 @@
* `implicitCtorSynPats` in `SynTypeDefnSimpleRepr.General` is now `SynPat option` instead of `SynSimplePats option`. ([PR #16425](https://github.com/dotnet/fsharp/pull/16425))
* `SyntaxVisitorBase<'T>.VisitSimplePats` now takes `SynPat` instead of `SynSimplePat list`. ([PR #16425](https://github.com/dotnet/fsharp/pull/16425))
* Reduce allocations in compiler checking via `ValueOption` usage ([PR #16323](https://github.com/dotnet/fsharp/pull/16323))
-* Reverted [#16348](https://github.com/dotnet/fsharp/pull/16348) `ThreadStatic` `CancellationToken` changes to improve test stability and prevent potential unwanted cancellations. ([PR #16536](https://github.com/dotnet/fsharp/pull/16536))
\ No newline at end of file
+* Reverted [#16348](https://github.com/dotnet/fsharp/pull/16348) `ThreadStatic` `CancellationToken` changes to improve test stability and prevent potential unwanted cancellations. ([PR #16536](https://github.com/dotnet/fsharp/pull/16536))
+* Refactored parenthesization API. ([PR #16461])(https://github.com/dotnet/fsharp/pull/16461))
diff --git a/docs/release-notes/.VisualStudio/17.10.md b/docs/release-notes/.VisualStudio/17.10.md
index a1bf148428..0045b1bd64 100644
--- a/docs/release-notes/.VisualStudio/17.10.md
+++ b/docs/release-notes/.VisualStudio/17.10.md
@@ -1,3 +1,7 @@
### Fixed
* Show signature help mid-pipeline in more scenarios. ([PR #16462](https://github.com/dotnet/fsharp/pull/16462))
+
+### Changed
+
+* Use refactored parenthesization API in unnecessary parentheses code fix. ([PR #16461])(https://github.com/dotnet/fsharp/pull/16461))
diff --git a/src/Compiler/FSharp.Compiler.Service.fsproj b/src/Compiler/FSharp.Compiler.Service.fsproj
index 86389d684b..dd7b6e25c1 100644
--- a/src/Compiler/FSharp.Compiler.Service.fsproj
+++ b/src/Compiler/FSharp.Compiler.Service.fsproj
@@ -484,6 +484,10 @@
+
+
+
+
diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs
index 89569a802c..29df63b119 100644
--- a/src/Compiler/Service/ServiceAnalysis.fs
+++ b/src/Compiler/Service/ServiceAnalysis.fs
@@ -8,7 +8,6 @@ open System.Runtime.CompilerServices
open Internal.Utilities.Library
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Symbols
-open FSharp.Compiler.Syntax
open FSharp.Compiler.Syntax.PrettyNaming
open FSharp.Compiler.Text
open FSharp.Compiler.Text.Range
@@ -463,1210 +462,3 @@ module UnusedDeclarations =
let unusedRanges = getUnusedDeclarationRanges allSymbolUsesInFile isScriptFile
return unusedRanges
}
-
-module UnnecessaryParentheses =
- open System
-
- let (|Ident|) (ident: Ident) = ident.idText
-
- /// Represents a symbolic infix operator with the precedence of *, /, or %.
- /// All instances of this type are considered equal.
- []
- type MulDivMod =
- | Mul
- | Div
- | Mod
-
- member _.CompareTo(_other: MulDivMod) = 0
- override this.Equals obj = this.CompareTo(unbox obj) = 0
- override _.GetHashCode() = 0
-
- interface IComparable with
- member this.CompareTo obj = this.CompareTo(unbox obj)
-
- /// Represents a symbolic infix operator with the precedence of + or -.
- /// All instances of this type are considered equal.
- []
- type AddSub =
- | Add
- | Sub
-
- member _.CompareTo(_other: AddSub) = 0
- override this.Equals obj = this.CompareTo(unbox obj) = 0
- override _.GetHashCode() = 0
-
- interface IComparable with
- member this.CompareTo obj = this.CompareTo(unbox obj)
-
- /// Holds a symbolic operator's original notation.
- /// Equality is based on the contents of the string.
- /// Comparison always returns 0.
- []
- type OriginalNotation =
- | OriginalNotation of string
-
- member _.CompareTo(_other: OriginalNotation) = 0
-
- override this.Equals obj =
- match this, obj with
- | OriginalNotation this, (:? OriginalNotation as OriginalNotation other) -> String.Equals(this, other, StringComparison.Ordinal)
- | _ -> false
-
- override this.GetHashCode() =
- match this with
- | OriginalNotation notation -> notation.GetHashCode()
-
- interface IComparable with
- member this.CompareTo obj = this.CompareTo(unbox obj)
-
- /// Represents an expression's precedence.
- /// Comparison is based only on the precedence case.
- /// Equality considers the embedded original notation, if any.
- ///
- /// For example:
- ///
- /// compare (AddSub (Add, OriginalNotation "+")) (AddSub (Add, OriginalNotation "++")) = 0
- ///
- /// but
- ///
- /// AddSub (Add, OriginalNotation "+") <> AddSub (Add, OriginalNotation "++")
- type Precedence =
- /// yield, yield!, return, return!
- | Low
-
- /// <-
- | Set
-
- /// :=
- | ColonEquals
-
- /// ,
- | Comma
-
- /// or, ||
- ///
- /// Refers to the exact operators or and ||.
- /// Instances with leading dots or question marks or trailing characters are parsed as Bar instead.
- | BarBar of OriginalNotation
-
- /// &, &&
- ///
- /// Refers to the exact operators & and &&.
- /// Instances with leading dots or question marks or trailing characters are parsed as Amp instead.
- | AmpAmp of OriginalNotation
-
- /// :>, :?>
- | UpcastDowncast
-
- /// =…, |…, &…, $…, >…, <…, !=…
- | Relational of OriginalNotation
-
- /// ^…, @…
- | HatAt
-
- /// ::
- | Cons
-
- /// :?
- | TypeTest
-
- /// +…, -…
- | AddSub of AddSub * OriginalNotation
-
- /// *…, /…, %…
- | MulDivMod of MulDivMod * OriginalNotation
-
- /// **…
- | Exp
-
- /// - x
- | UnaryPrefix
-
- /// f x
- | Apply
-
- /// -x, !… x, ~~… x
- | High
-
- // x.y
- | Dot
-
- /// Associativity/association.
- type Assoc =
- /// Non-associative or no association.
- | Non
-
- /// Left-associative or left-hand association.
- | Left
-
- /// Right-associative or right-hand association.
- | Right
-
- module Assoc =
- let ofPrecedence precedence =
- match precedence with
- | Low -> Non
- | Set -> Non
- | ColonEquals -> Right
- | Comma -> Non
- | BarBar _ -> Left
- | AmpAmp _ -> Left
- | UpcastDowncast -> Right
- | Relational _ -> Left
- | HatAt -> Right
- | Cons -> Right
- | TypeTest -> Non
- | AddSub _ -> Left
- | MulDivMod _ -> Left
- | Exp -> Right
- | UnaryPrefix -> Left
- | Apply -> Left
- | High -> Left
- | Dot -> Left
-
- /// Matches if the two expressions or patterns refer to the same object.
- []
- let inline (|Is|_|) (inner1: 'a) (inner2: 'a) =
- if obj.ReferenceEquals(inner1, inner2) then
- ValueSome Is
- else
- ValueNone
-
- module SynExpr =
- open FSharp.Compiler.SyntaxTrivia
-
- /// See atomicExprAfterType in pars.fsy.
- []
- let (|AtomicExprAfterType|_|) expr =
- match expr with
- | SynExpr.Paren _
- | SynExpr.Quote _
- | SynExpr.Const _
- | SynExpr.Tuple(isStruct = true)
- | SynExpr.Record _
- | SynExpr.AnonRecd _
- | SynExpr.InterpolatedString _
- | SynExpr.Null _
- | SynExpr.ArrayOrList(isArray = true)
- | SynExpr.ArrayOrListComputed(isArray = true) -> ValueSome AtomicExprAfterType
- | _ -> ValueNone
-
- /// Matches if the given expression represents a high-precedence
- /// function application, e.g.,
- ///
- /// f x
- ///
- /// (+) x y
- []
- let (|HighPrecedenceApp|_|) expr =
- match expr with
- | SynExpr.App(isInfix = false; funcExpr = SynExpr.Ident _)
- | SynExpr.App(isInfix = false; funcExpr = SynExpr.LongIdent _)
- | SynExpr.App(isInfix = false; funcExpr = SynExpr.App(isInfix = false)) -> ValueSome HighPrecedenceApp
- | _ -> ValueNone
-
- module FuncExpr =
- /// Matches when the given funcExpr is a direct application
- /// of a symbolic operator, e.g., -, _not_ (~-).
- []
- let (|SymbolicOperator|_|) funcExpr =
- match funcExpr with
- | SynExpr.LongIdent(longDotId = SynLongIdent(trivia = trivia)) ->
- let rec tryPick =
- function
- | [] -> ValueNone
- | Some(IdentTrivia.OriginalNotation op) :: _ -> ValueSome op
- | _ :: rest -> tryPick rest
-
- tryPick trivia
- | _ -> ValueNone
-
- /// Matches when the given expression is a prefix operator application, e.g.,
- ///
- /// -x
- ///
- /// ~~~x
- []
- let (|PrefixApp|_|) expr : Precedence voption =
- match expr with
- | SynExpr.App(isInfix = false; funcExpr = funcExpr & FuncExpr.SymbolicOperator op; argExpr = argExpr) ->
- if funcExpr.Range.IsAdjacentTo argExpr.Range then
- ValueSome High
- else
- assert (op.Length > 0)
-
- match op[0] with
- | '!'
- | '~' -> ValueSome High
- | _ -> ValueSome UnaryPrefix
-
- | SynExpr.AddressOf(expr = expr; opRange = opRange) ->
- if opRange.IsAdjacentTo expr.Range then
- ValueSome High
- else
- ValueSome UnaryPrefix
-
- | _ -> ValueNone
-
- /// Tries to parse the given original notation as a symbolic infix operator.
- []
- let (|SymbolPrec|_|) (originalNotation: string) =
- // Trim any leading dots or question marks from the given symbolic operator.
- // Leading dots or question marks have no effect on operator precedence or associativity
- // with the exception of &, &&, and ||.
- let ignoredLeadingChars = ".?".AsSpan()
- let trimmed = originalNotation.AsSpan().TrimStart ignoredLeadingChars
- assert (trimmed.Length > 0)
-
- match trimmed[0], originalNotation with
- | _, ":=" -> ValueSome ColonEquals
- | _, ("||" | "or") -> ValueSome(BarBar(OriginalNotation originalNotation))
- | _, ("&" | "&&") -> ValueSome(AmpAmp(OriginalNotation originalNotation))
- | '|', _
- | '&', _
- | '<', _
- | '>', _
- | '=', _
- | '$', _ -> ValueSome(Relational(OriginalNotation originalNotation))
- | '!', _ when trimmed.Length > 1 && trimmed[1] = '=' -> ValueSome(Relational(OriginalNotation originalNotation))
- | '^', _
- | '@', _ -> ValueSome HatAt
- | _, "::" -> ValueSome Cons
- | '+', _ -> ValueSome(AddSub(Add, OriginalNotation originalNotation))
- | '-', _ -> ValueSome(AddSub(Sub, OriginalNotation originalNotation))
- | '/', _ -> ValueSome(MulDivMod(Div, OriginalNotation originalNotation))
- | '%', _ -> ValueSome(MulDivMod(Mod, OriginalNotation originalNotation))
- | '*', _ when trimmed.Length > 1 && trimmed[1] = '*' -> ValueSome Exp
- | '*', _ -> ValueSome(MulDivMod(Mul, OriginalNotation originalNotation))
- | _ -> ValueNone
-
- []
- let (|Contains|_|) (c: char) (s: string) =
- if s.IndexOf c >= 0 then ValueSome Contains else ValueNone
-
- /// Any expressions in which the removal of parens would
- /// lead to something like the following that would be
- /// confused by the parser with a type parameter application:
- ///
- /// xz
- ///
- /// xz
- []
- let rec (|ConfusableWithTypeApp|_|) synExpr =
- match synExpr with
- | SynExpr.Paren(expr = ConfusableWithTypeApp)
- | SynExpr.App(funcExpr = ConfusableWithTypeApp)
- | SynExpr.App(isInfix = true; funcExpr = FuncExpr.SymbolicOperator(Contains '>'); argExpr = ConfusableWithTypeApp) ->
- ValueSome ConfusableWithTypeApp
- | SynExpr.App(isInfix = true; funcExpr = funcExpr & FuncExpr.SymbolicOperator(Contains '<'); argExpr = argExpr) when
- argExpr.Range.IsAdjacentTo funcExpr.Range
- ->
- ValueSome ConfusableWithTypeApp
- | SynExpr.Tuple(exprs = exprs) ->
- let rec anyButLast =
- function
- | _ :: []
- | [] -> ValueNone
- | ConfusableWithTypeApp :: _ -> ValueSome ConfusableWithTypeApp
- | _ :: tail -> anyButLast tail
-
- anyButLast exprs
- | _ -> ValueNone
-
- /// Matches when the expression represents the infix application of a symbolic operator.
- ///
- /// (x λ y) ρ z
- ///
- /// x λ (y ρ z)
- []
- let (|InfixApp|_|) synExpr : struct (Precedence * Assoc) voption =
- match synExpr with
- | SynExpr.App(funcExpr = SynExpr.App(isInfix = true; funcExpr = FuncExpr.SymbolicOperator(SymbolPrec prec))) ->
- ValueSome(prec, Right)
- | SynExpr.App(isInfix = true; funcExpr = FuncExpr.SymbolicOperator(SymbolPrec prec)) -> ValueSome(prec, Left)
- | SynExpr.Upcast _
- | SynExpr.Downcast _ -> ValueSome(UpcastDowncast, Left)
- | SynExpr.TypeTest _ -> ValueSome(TypeTest, Left)
- | _ -> ValueNone
-
- /// Returns the given expression's precedence and the side of the inner expression,
- /// if applicable.
- []
- let (|OuterBinaryExpr|_|) inner outer : struct (Precedence * Assoc) voption =
- match outer with
- | SynExpr.YieldOrReturn _
- | SynExpr.YieldOrReturnFrom _ -> ValueSome(Low, Right)
- | SynExpr.Tuple(exprs = SynExpr.Paren(expr = Is inner) :: _) -> ValueSome(Comma, Left)
- | SynExpr.Tuple _ -> ValueSome(Comma, Right)
- | InfixApp(Cons, side) -> ValueSome(Cons, side)
- | SynExpr.Assert _
- | SynExpr.Lazy _
- | SynExpr.InferredUpcast _
- | SynExpr.InferredDowncast _ -> ValueSome(Apply, Non)
- | PrefixApp prec -> ValueSome(prec, Non)
- | InfixApp(prec, side) -> ValueSome(prec, side)
- | SynExpr.App(argExpr = SynExpr.ComputationExpr _) -> ValueSome(UnaryPrefix, Left)
- | SynExpr.App(funcExpr = SynExpr.Paren(expr = SynExpr.App _)) -> ValueSome(Apply, Left)
- | SynExpr.App _ -> ValueSome(Apply, Non)
- | SynExpr.DotSet(targetExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Dot, Left)
- | SynExpr.DotSet(rhsExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Set, Right)
- | SynExpr.DotIndexedSet(objectExpr = SynExpr.Paren(expr = Is inner))
- | SynExpr.DotNamedIndexedPropertySet(targetExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Dot, Left)
- | SynExpr.DotIndexedSet(valueExpr = SynExpr.Paren(expr = Is inner))
- | SynExpr.DotNamedIndexedPropertySet(rhsExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Set, Right)
- | SynExpr.LongIdentSet(expr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Set, Right)
- | SynExpr.Set _ -> ValueSome(Set, Non)
- | SynExpr.DotGet _ -> ValueSome(Dot, Left)
- | 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
- | InfixApp(prec, _) -> ValueSome prec
- | SynExpr.App _
- | SynExpr.Assert _
- | SynExpr.Lazy _
- | SynExpr.For _
- | SynExpr.ForEach _
- | SynExpr.While _
- | SynExpr.Do _
- | SynExpr.New _
- | SynExpr.InferredUpcast _
- | SynExpr.InferredDowncast _ -> ValueSome Apply
- | SynExpr.DotIndexedSet _
- | SynExpr.DotNamedIndexedPropertySet _
- | SynExpr.DotSet _ -> ValueSome Set
- | _ -> ValueNone
-
- module Dangling =
- /// Returns the first matching nested right-hand target expression, if any.
- let private dangling (target: SynExpr -> SynExpr option) =
- let (|Target|_|) = target
- let (|Last|) = List.last
-
- let rec loop expr =
- match expr with
- | Target expr -> ValueSome expr
- | SynExpr.Tuple(isStruct = false; exprs = Last expr)
- | SynExpr.App(argExpr = expr)
- | SynExpr.IfThenElse(elseExpr = Some expr)
- | SynExpr.IfThenElse(ifExpr = expr)
- | SynExpr.Sequential(expr2 = expr)
- | SynExpr.YieldOrReturn(expr = expr)
- | SynExpr.YieldOrReturnFrom(expr = expr)
- | SynExpr.Set(rhsExpr = expr)
- | SynExpr.DotSet(rhsExpr = expr)
- | SynExpr.DotNamedIndexedPropertySet(rhsExpr = expr)
- | SynExpr.DotIndexedSet(valueExpr = expr)
- | SynExpr.LongIdentSet(expr = expr)
- | SynExpr.LetOrUse(body = expr)
- | SynExpr.Lambda(body = expr)
- | SynExpr.Match(clauses = Last(SynMatchClause(resultExpr = expr)))
- | SynExpr.MatchLambda(matchClauses = Last(SynMatchClause(resultExpr = expr)))
- | SynExpr.MatchBang(clauses = Last(SynMatchClause(resultExpr = expr)))
- | SynExpr.TryWith(withCases = Last(SynMatchClause(resultExpr = expr)))
- | SynExpr.TryFinally(finallyExpr = expr) -> loop expr
- | _ -> ValueNone
-
- loop
-
- /// Matches a dangling if-then construct.
- []
- let (|IfThen|_|) =
- dangling (function
- | 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|_|) =
- dangling (function
- | SynExpr.TryWith _
- | SynExpr.TryFinally _ as expr -> Some expr
- | _ -> None)
-
- /// Matches a dangling match-like construct.
- []
- let (|Match|_|) =
- dangling (function
- | SynExpr.Match _
- | SynExpr.MatchBang _
- | SynExpr.MatchLambda _
- | SynExpr.TryWith _
- | SynExpr.Lambda _ as expr -> Some expr
- | _ -> None)
-
- /// Matches a nested dangling construct that could become problematic
- /// if the surrounding parens were removed.
- []
- let (|Problematic|_|) =
- dangling (function
- | SynExpr.Lambda _
- | SynExpr.MatchLambda _
- | SynExpr.Match _
- | SynExpr.MatchBang _
- | SynExpr.TryWith _
- | SynExpr.TryFinally _
- | SynExpr.IfThenElse _
- | SynExpr.Sequential _
- | SynExpr.LetOrUse _
- | SynExpr.Set _
- | SynExpr.LongIdentSet _
- | SynExpr.DotIndexedSet _
- | SynExpr.DotNamedIndexedPropertySet _
- | SynExpr.DotSet _
- | SynExpr.NamedIndexedPropertySet _ as expr -> Some expr
- | _ -> None)
-
- /// If the given expression is a parenthesized expression and the parentheses
- /// are unnecessary in the given context, returns the unnecessary parentheses' range.
- let rec unnecessaryParentheses (getSourceLineStr: int -> string) expr path =
- let unnecessaryParentheses = unnecessaryParentheses getSourceLineStr
-
- // Indicates whether the parentheses with the given range
- // enclose an expression whose indentation would be invalid
- // in context if it were not surrounded by parentheses.
- let containsSensitiveIndentation outerOffsides (parenRange: range) =
- let startLine = parenRange.StartLine
- let endLine = parenRange.EndLine
-
- if startLine = endLine then
- false
- else
- let rec loop offsides lineNo startCol =
- if lineNo <= endLine then
- let line = getSourceLineStr lineNo
-
- match offsides with
- | ValueNone ->
- let i = line.AsSpan(startCol).IndexOfAnyExcept(' ', ')')
-
- if i >= 0 then
- let newOffsides = i + startCol
- newOffsides <= outerOffsides || loop (ValueSome newOffsides) (lineNo + 1) 0
- else
- loop offsides (lineNo + 1) 0
-
- | ValueSome offsidesCol ->
- let i = line.AsSpan(0, min offsidesCol line.Length).IndexOfAnyExcept(' ', ')')
-
- if i >= 0 && i < offsidesCol then
- let slice = line.AsSpan(i, min (offsidesCol - i) (line.Length - i))
- let j = slice.IndexOfAnyExcept("*/%-+:^@><=!|0$.?".AsSpan())
-
- let lo = i + (if j >= 0 && slice[j] = ' ' then j else 0)
- lo < offsidesCol - 1 || lo <= outerOffsides || loop offsides (lineNo + 1) 0
- else
- loop offsides (lineNo + 1) 0
- else
- false
-
- loop ValueNone startLine (parenRange.StartColumn + 1)
-
- // Matches if the given expression starts with a symbol, e.g., <@ … @>, $"…", @"…", +1, -1…
- let (|StartsWithSymbol|_|) =
- let (|TextStartsWith|) (m: range) =
- let line = getSourceLineStr m.StartLine
- line[m.StartColumn]
-
- let (|StartsWith|) (s: string) = s[0]
-
- function
- | SynExpr.Quote _
- | SynExpr.InterpolatedString _
- | SynExpr.Const(SynConst.String(synStringKind = SynStringKind.Verbatim), _)
- | SynExpr.Const(SynConst.Byte _, TextStartsWith '+')
- | SynExpr.Const(SynConst.UInt16 _, TextStartsWith '+')
- | SynExpr.Const(SynConst.UInt32 _, TextStartsWith '+')
- | SynExpr.Const(SynConst.UInt64 _, TextStartsWith '+')
- | SynExpr.Const(SynConst.UIntPtr _, TextStartsWith '+')
- | SynExpr.Const(SynConst.SByte _, TextStartsWith('-' | '+'))
- | SynExpr.Const(SynConst.Int16 _, TextStartsWith('-' | '+'))
- | SynExpr.Const(SynConst.Int32 _, TextStartsWith('-' | '+'))
- | SynExpr.Const(SynConst.Int64 _, TextStartsWith('-' | '+'))
- | SynExpr.Const(SynConst.IntPtr _, TextStartsWith('-' | '+'))
- | SynExpr.Const(SynConst.Decimal _, TextStartsWith('-' | '+'))
- | SynExpr.Const(SynConst.Double _, TextStartsWith('-' | '+'))
- | SynExpr.Const(SynConst.Single _, TextStartsWith('-' | '+'))
- | SynExpr.Const(SynConst.Measure(_, TextStartsWith('-' | '+'), _, _), _)
- | SynExpr.Const(SynConst.UserNum(StartsWith('-' | '+'), _), _) -> Some StartsWithSymbol
- | _ -> None
-
- // Matches if the given expression is a numeric literal
- // that it is safe to "dot into," e.g., 1l, 0b1, 1e10, 1d, 1.0…
- let (|DotSafeNumericLiteral|_|) =
- /// 1l, 1d, 0b1, 0x1, 0o1, 1e10…
- let (|TextContainsLetter|_|) (m: range) =
- let line = getSourceLineStr m.StartLine
- let span = line.AsSpan(m.StartColumn, m.EndColumn - m.StartColumn)
-
- if span.LastIndexOfAnyInRange('A', 'z') >= 0 then
- Some TextContainsLetter
- else
- None
-
- // 1.0…
- let (|TextEndsWithNumber|_|) (m: range) =
- let line = getSourceLineStr m.StartLine
- let span = line.AsSpan(m.StartColumn, m.EndColumn - m.StartColumn)
-
- if Char.IsDigit span[span.Length - 1] then
- Some TextEndsWithNumber
- else
- None
-
- function
- | SynExpr.Const(SynConst.Byte _, _)
- | SynExpr.Const(SynConst.UInt16 _, _)
- | SynExpr.Const(SynConst.UInt32 _, _)
- | SynExpr.Const(SynConst.UInt64 _, _)
- | SynExpr.Const(SynConst.UIntPtr _, _)
- | SynExpr.Const(SynConst.SByte _, _)
- | SynExpr.Const(SynConst.Int16 _, _)
- | SynExpr.Const(SynConst.Int32 _, TextContainsLetter)
- | SynExpr.Const(SynConst.Int64 _, _)
- | SynExpr.Const(SynConst.IntPtr _, _)
- | SynExpr.Const(SynConst.Decimal _, _)
- | SynExpr.Const(SynConst.Double _, (TextEndsWithNumber | TextContainsLetter))
- | SynExpr.Const(SynConst.Single _, _)
- | SynExpr.Const(SynConst.Measure _, _)
- | SynExpr.Const(SynConst.UserNum _, _) -> Some DotSafeNumericLiteral
- | _ -> None
-
- match expr, path with
- // Check for nested matches, e.g.,
- //
- // match … with … -> (…, match … with … -> … | … -> …) | … -> …
- | SynExpr.Paren _, SyntaxNode.SynMatchClause _ :: path -> unnecessaryParentheses expr path
-
- // We always need parens for trait calls, e.g.,
- //
- // let inline f x = (^a : (static member Parse : string -> ^a) x)
- | SynExpr.Paren(expr = SynExpr.TraitCall _), _ -> ValueNone
-
- // Don't touch library-only stuff:
- //
- // (# "ldlen.multi 2 0" array : int #)
- | SynExpr.Paren(expr = SynExpr.LibraryOnlyILAssembly _), _
- | SynExpr.Paren(expr = SynExpr.LibraryOnlyStaticOptimization _), _
- | SynExpr.Paren(expr = SynExpr.LibraryOnlyUnionCaseFieldGet _), _
- | SynExpr.Paren(expr = SynExpr.LibraryOnlyUnionCaseFieldSet _), _ -> ValueNone
-
- // Parens are required around the body expresion of a binding
- // if the parenthesized expression would be invalid without its parentheses, e.g.,
- //
- // let x = (x
- // + y)
- | SynExpr.Paren(rightParenRange = Some _; range = parenRange),
- SyntaxNode.SynBinding(SynBinding(trivia = { LeadingKeyword = leadingKeyword })) :: _ when
- containsSensitiveIndentation leadingKeyword.Range.StartColumn parenRange
- ->
- ValueNone
-
- // Parens are otherwise never required for binding bodies or for top-level expressions, e.g.,
- //
- // let x = (…)
- // _.member X = (…)
- // (printfn "Hello, world.")
- | SynExpr.Paren(rightParenRange = Some _; range = range), SyntaxNode.SynBinding _ :: _
- | SynExpr.Paren(rightParenRange = Some _; range = range), SyntaxNode.SynModule _ :: _ -> ValueSome range
-
- // Parens must be kept when there is a high-precedence function application
- // before a prefix operator application before another expression that starts with a symbol, e.g.,
- //
- // id -(-x)
- // id -(-1y)
- // id -($"")
- // id -(@"")
- // id -(<@ ValueNone @>)
- // let (~+) _ = true in assert +($"{true}")
- | SynExpr.Paren(expr = PrefixApp _ | StartsWithSymbol),
- SyntaxNode.SynExpr(SynExpr.App _) :: SyntaxNode.SynExpr(HighPrecedenceApp | SynExpr.Assert _ | SynExpr.InferredUpcast _ | SynExpr.InferredDowncast _) :: _ ->
- ValueNone
-
- // Parens are never required around suffixed or infixed numeric literals, e.g.,
- //
- // (1l).ToString()
- // (1uy).ToString()
- // (0b1).ToString()
- // (1e10).ToString()
- // (1.0).ToString()
- | SynExpr.Paren(expr = DotSafeNumericLiteral; rightParenRange = Some _; range = range), _ -> ValueSome range
-
- // Parens are required around bare decimal ints or doubles ending
- // in dots when being dotted into, e.g.,
- //
- // (1).ToString()
- // (1.).ToString()
- | SynExpr.Paren(expr = SynExpr.Const(constant = SynConst.Int32 _ | SynConst.Double _)),
- SyntaxNode.SynExpr(SynExpr.DotGet _) :: _ -> ValueNone
-
- // Parens are required around join conditions:
- //
- // join … on (… = …)
- | SynExpr.Paren(expr = SynExpr.App _), SyntaxNode.SynExpr(SynExpr.App _) :: SyntaxNode.SynExpr(SynExpr.JoinIn _) :: _ ->
- ValueNone
-
- // Parens are not required around a few anointed expressions after inherit:
- //
- // inherit T(3)
- // inherit T(null)
- // inherit T("")
- // …
- | SynExpr.Paren(expr = AtomicExprAfterType; range = range), SyntaxNode.SynMemberDefn(SynMemberDefn.ImplicitInherit _) :: _ ->
- ValueSome range
-
- // Parens are otherwise required in inherit T(x), etc.
- | SynExpr.Paren _, SyntaxNode.SynMemberDefn(SynMemberDefn.ImplicitInherit _) :: _ -> ValueNone
-
- // We can't remove parens when they're required for fluent calls:
- //
- // x.M(y).N z
- // x.M(y).[z]
- // _.M(x)
- // (f x)[z]
- // (f(x))[z]
- // x.M(y)[z]
- | SynExpr.Paren _,
- SyntaxNode.SynExpr(SynExpr.App _) :: SyntaxNode.SynExpr(SynExpr.DotGet _ | SynExpr.DotIndexedGet _ | SynExpr.DotLambda _) :: _
- | SynExpr.Paren(expr = SynExpr.App _),
- SyntaxNode.SynExpr(SynExpr.App(argExpr = SynExpr.ArrayOrListComputed(isArray = false))) :: _
- | SynExpr.Paren _,
- 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(Relational(OriginalNotation "="), _))),
- SyntaxNode.SynExpr(SynExpr.App(funcExpr = SynExpr.LongIdent _)) :: _
- | SynExpr.Paren(expr = InfixApp(Relational(OriginalNotation "="), _)),
- SyntaxNode.SynExpr(SynExpr.Paren _) :: SyntaxNode.SynExpr(SynExpr.App(funcExpr = SynExpr.LongIdent _)) :: _
- | SynExpr.Paren(expr = InfixApp(Relational(OriginalNotation "="), _)),
- 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.
-
- // Outer right:
- //
- // (x) :: xs
- // (x * y) :: zs
- // …
- | SynExpr.Paren(rightParenRange = Some _),
- SyntaxNode.SynExpr(SynExpr.Tuple(isStruct = false; exprs = [ SynExpr.Paren _; _ ])) :: (SyntaxNode.SynExpr(SynExpr.App(
- isInfix = true)) :: _ as path) -> unnecessaryParentheses expr path
-
- // Outer left:
- //
- // x :: (xs)
- // x :: (ys @ zs)
- // …
- | SynExpr.Paren(rightParenRange = Some _) as argExpr,
- SyntaxNode.SynExpr(SynExpr.Tuple(isStruct = false; exprs = [ _; SynExpr.Paren _ ])) :: SyntaxNode.SynExpr(SynExpr.App(
- isInfix = true) as outer) :: path ->
- unnecessaryParentheses
- expr
- (SyntaxNode.SynExpr(SynExpr.App(ExprAtomicFlag.NonAtomic, false, outer, argExpr, outer.Range))
- :: path)
-
- // Ordinary nested expressions.
- | SynExpr.Paren(expr = inner; leftParenRange = leftParenRange; rightParenRange = Some _ as rightParenRange; range = range),
- SyntaxNode.SynExpr outer :: outerPath when not (containsSensitiveIndentation outer.Range.StartColumn range) ->
- let dangling expr =
- match expr with
- | Dangling.Problematic subExpr ->
- let parenzedSubExpr = SynExpr.Paren(subExpr, leftParenRange, rightParenRange, range)
-
- match outer with
- | SynExpr.Tuple(exprs = exprs) -> not (obj.ReferenceEquals(subExpr, List.last exprs))
- | InfixApp(_, Left) -> true
- | _ -> unnecessaryParentheses parenzedSubExpr outerPath |> ValueOption.isNone
-
- | _ -> false
-
- let problematic (exprRange: range) (delimiterRange: range) =
- exprRange.EndLine = delimiterRange.EndLine
- && exprRange.EndColumn < delimiterRange.StartColumn
-
- let anyProblematic matchOrTryRange clauses =
- let rec loop =
- function
- | [] -> false
- | SynMatchClause(trivia = trivia) :: clauses ->
- trivia.BarRange |> Option.exists (problematic matchOrTryRange)
- || trivia.ArrowRange |> Option.exists (problematic matchOrTryRange)
- || loop clauses
-
- loop clauses
-
- 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)
- ->
- ValueNone
-
- | SynExpr.TryFinally(trivia = trivia), Dangling.Try tryExpr when problematic tryExpr.Range trivia.FinallyKeyword ->
- 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
- problematic matchOrTry.Range trivia.WithKeyword
- || anyProblematic matchOrTry.Range clauses
- ->
- ValueNone
-
- | SynExpr.Sequential(expr1 = SynExpr.Paren(expr = Is inner); expr2 = expr2), Dangling.Problematic _ when
- problematic inner.Range expr2.Range
- ->
- ValueNone
-
- | SynExpr.Record(copyInfo = Some(SynExpr.Paren(expr = Is inner), _)), Dangling.Problematic _
- | SynExpr.AnonRecd(copyInfo = Some(SynExpr.Paren(expr = Is inner), _)), Dangling.Problematic _ -> ValueNone
-
- | SynExpr.Record(recordFields = recordFields), Dangling.Problematic _ ->
- let rec loop recordFields =
- match recordFields with
- | [] -> ValueSome range
- | SynExprRecordField(expr = Some(SynExpr.Paren(expr = Is inner)); blockSeparator = Some _) :: SynExprRecordField(
- fieldName = SynLongIdent(id = id :: _), _) :: _ ->
- if problematic inner.Range id.idRange then
- ValueNone
- else
- ValueSome range
- | _ :: recordFields -> loop recordFields
-
- loop recordFields
-
- | SynExpr.AnonRecd(recordFields = recordFields), Dangling.Problematic _ ->
- let rec loop recordFields =
- match recordFields with
- | [] -> ValueSome range
- | (_, Some _blockSeparator, SynExpr.Paren(expr = Is inner)) :: (SynLongIdent(id = id :: _), _, _) :: _ ->
- if problematic inner.Range id.idRange then
- ValueNone
- else
- ValueSome range
- | _ :: recordFields -> loop recordFields
-
- loop recordFields
-
- | 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 compare outerPrecedence innerPrecedence with
- | 0 ->
- match side, Assoc.ofPrecedence innerPrecedence with
- | Non, _
- | _, Non
- | Left, Right -> true
- | Right, Right
- | Left, Left -> false
- | Right, Left ->
- outerPrecedence <> innerPrecedence
- || match outerPrecedence, innerPrecedence with
- | _, MulDivMod(Div, _)
- | _, MulDivMod(Mod, _)
- | _, AddSub(Sub, _) -> true
- | Relational _, Relational _ -> true
- | _ -> false
-
- | c -> c > 0
-
- if ambiguous || dangling inner then
- ValueNone
- else
- ValueSome range
-
- | OuterBinaryExpr inner (_, Right), (SynExpr.Sequential _ | SynExpr.LetOrUse(trivia = { InKeyword = None })) -> ValueNone
- | OuterBinaryExpr inner (_, Right), inner -> if dangling inner then ValueNone else ValueSome range
-
- // new T(expr)
- | SynExpr.New _, AtomicExprAfterType -> ValueSome range
- | SynExpr.New _, _ -> ValueNone
-
- // { inherit T(expr); … }
- | SynExpr.Record(baseInfo = Some(_, SynExpr.Paren(expr = Is inner), _, _, _)), AtomicExprAfterType -> ValueSome range
- | SynExpr.Record(baseInfo = Some(_, SynExpr.Paren(expr = Is inner), _, _, _)), _ -> ValueNone
-
- | _, SynExpr.Paren _
- | _, SynExpr.Quote _
- | _, SynExpr.Const _
- | _, SynExpr.Tuple(isStruct = true)
- | _, SynExpr.AnonRecd _
- | _, SynExpr.ArrayOrList _
- | _, SynExpr.Record _
- | _, SynExpr.ObjExpr _
- | _, SynExpr.ArrayOrListComputed _
- | _, SynExpr.ComputationExpr _
- | _, SynExpr.TypeApp _
- | _, SynExpr.Ident _
- | _, SynExpr.LongIdent _
- | _, SynExpr.DotGet _
- | _, SynExpr.DotLambda _
- | _, SynExpr.DotIndexedGet _
- | _, SynExpr.Null _
- | _, SynExpr.InterpolatedString _
-
- | SynExpr.Paren _, _
- | SynExpr.Quote _, _
- | SynExpr.Typed _, _
- | SynExpr.AnonRecd _, _
- | SynExpr.Record _, _
- | SynExpr.ObjExpr _, _
- | SynExpr.While _, _
- | SynExpr.WhileBang _, _
- | SynExpr.For _, _
- | SynExpr.ForEach _, _
- | SynExpr.Lambda _, _
- | SynExpr.MatchLambda _, _
- | SynExpr.Match _, _
- | SynExpr.MatchBang _, _
- | SynExpr.LetOrUse _, _
- | SynExpr.LetOrUseBang _, _
- | SynExpr.Sequential _, _
- | SynExpr.Do _, _
- | SynExpr.DoBang _, _
- | SynExpr.IfThenElse _, _
- | SynExpr.TryWith _, _
- | SynExpr.TryFinally _, _
- | SynExpr.ComputationExpr _, _
- | SynExpr.InterpolatedString _, _ -> ValueSome range
-
- | _ -> ValueNone
-
- | _ -> ValueNone
-
- module SynPat =
- let (|Last|) = List.last
-
- /// Matches if any pattern in the given list is a SynPat.Typed.
- []
- let (|AnyTyped|_|) pats =
- if
- pats
- |> List.exists (function
- | SynPat.Typed _ -> true
- | _ -> false)
- then
- ValueSome AnyTyped
- 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 =
- 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 _
- | 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 =
- 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(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(isStruct = false)) :: SyntaxNode.SynExpr(SynExpr.LetOrUseBang _) :: _
- | SynPat.Paren(SynPat.Tuple(isStruct = false; elementPats = AnyTyped), _), SyntaxNode.SynExpr(SynExpr.LetOrUseBang _) :: _
- | 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(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.
- | SynPat.Paren(SynPat.Const(SynConst.Unit, _), _), _ -> 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 _ :: 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 around the atomic argument of
- // any additional `new` constructor that is not the last.
- //
- // type T … =
- // 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 = 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
- | _ -> ValueNone
-
- // Parens are otherwise never needed in these cases:
- //
- // let (x: …) = …
- // for (…) in (…) do …
- // let! (…) = …
- // and! (…) = …
- // use! (…) = …
- // match … with (…) -> …
- // function (…) -> …
- // function (Pattern …) -> …
- // fun (x) -> …
- | SynPat.Paren(_, range), SyntaxNode.SynBinding _ :: _
- | SynPat.Paren(_, range), SyntaxNode.SynExpr(SynExpr.ForEach _) :: _
- | SynPat.Paren(_, range), SyntaxNode.SynExpr(SynExpr.LetOrUseBang _) :: _
- | SynPat.Paren(_, range), SyntaxNode.SynMatchClause _ :: _
- | SynPat.Paren(Atomic, range), SyntaxNode.SynExpr(SynExpr.Lambda(parsedData = Some _)) :: _ -> ValueSome range
-
- // Nested patterns.
- | SynPat.Paren(inner, range), SyntaxNode.SynPat outer :: _ ->
- match outer, inner with
- // (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(isStruct = false) -> ValueNone
-
- // A as (B | C)
- // A as (B & C)
- // x as (y, z)
- // xs as (y :: zs)
- | SynPat.As(rhsPat = SynPat.Paren(pat = Is inner)),
- (SynPat.Or _ | SynPat.Ands _ | SynPat.Tuple(isStruct = false) | SynPat.ListCons _) -> ValueNone
-
- // (A | B) :: xs
- // (A & B) :: xs
- // (x as y) :: xs
- | SynPat.ListCons _, SynPat.Or _
- | SynPat.ListCons _, SynPat.Ands _
- | SynPat.ListCons _, SynPat.As _ -> ValueNone
-
- // Pattern (x = (…))
- | SynPat.LongIdent(argPats = SynArgPats.NamePatPairs _), _ -> ValueSome range
-
- // Pattern (x : int)
- // Pattern ([] x)
- // Pattern (:? int)
- // Pattern (A :: _)
- // Pattern (A | B)
- // Pattern (A & B)
- // Pattern (A as B)
- // Pattern (A, B)
- // Pattern1 (Pattern2 (x = A))
- // Pattern1 (Pattern2 x y)
- | SynPat.LongIdent _, SynPat.Typed _
- | SynPat.LongIdent _, SynPat.Attrib _
- | SynPat.LongIdent _, SynPat.IsInst _
- | SynPat.LongIdent _, SynPat.ListCons _
- | SynPat.LongIdent _, SynPat.Or _
- | SynPat.LongIdent _, SynPat.Ands _
- | SynPat.LongIdent _, SynPat.As _
- | SynPat.LongIdent _, SynPat.Tuple(isStruct = false)
- | SynPat.LongIdent _, SynPat.LongIdent(argPats = SynArgPats.NamePatPairs _)
- | SynPat.LongIdent _, SynPat.LongIdent(argPats = SynArgPats.Pats(_ :: _))
-
- // A | (B as C)
- // A & (B as C)
- // A, (B as C)
- | SynPat.Or _, SynPat.As _
- | SynPat.Ands _, SynPat.As _
- | SynPat.Tuple _, SynPat.As _
-
- // x, (y, z)
- // x & (y, z)
- // (x, y) & z
- | SynPat.Tuple _, SynPat.Tuple(isStruct = false)
- | SynPat.Ands _, SynPat.Tuple(isStruct = false)
-
- // A, (B | C)
- // A & (B | C)
- | SynPat.Tuple _, SynPat.Or _
- | SynPat.Ands _, SynPat.Or _ -> ValueNone
-
- // (x : int) & y
- // 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 _
- | _, SynPat.Named _
- | _, SynPat.Typed _
- | _, SynPat.LongIdent(argPats = SynArgPats.Pats [])
- | _, SynPat.Tuple(isStruct = true)
- | _, SynPat.Paren _
- | _, SynPat.ArrayOrList _
- | _, SynPat.Record _
- | _, SynPat.Null _
- | _, SynPat.OptionalVal _
- | _, SynPat.IsInst _
- | _, SynPat.QuoteExpr _
-
- | SynPat.Or _, _
- | SynPat.ListCons _, _
- | SynPat.Ands _, _
- | SynPat.As _, _
- | SynPat.LongIdent _, _
- | SynPat.Tuple _, _
- | SynPat.Paren _, _
- | SynPat.ArrayOrList _, _
- | SynPat.Record _, _ -> ValueSome range
-
- | _ -> ValueNone
-
- | _ -> ValueNone
-
- let getUnnecessaryParentheses (getSourceLineStr: int -> string) (parsedInput: ParsedInput) : Async =
- async {
- let ranges = HashSet Range.comparer
-
- let visitor =
- { new SyntaxVisitorBase() with
- member _.VisitExpr(path, _, defaultTraverse, expr) =
- SynExpr.unnecessaryParentheses getSourceLineStr expr path
- |> ValueOption.iter (ranges.Add >> ignore)
-
- defaultTraverse expr
-
- member _.VisitPat(path, defaultTraverse, pat) =
- SynPat.unnecessaryParentheses pat path
- |> ValueOption.iter (ranges.Add >> ignore)
-
- defaultTraverse pat
- }
-
- SyntaxTraversal.traverseAll visitor parsedInput
- return ranges
- }
diff --git a/src/Compiler/Service/ServiceAnalysis.fsi b/src/Compiler/Service/ServiceAnalysis.fsi
index 836bfce0c5..672cf08875 100644
--- a/src/Compiler/Service/ServiceAnalysis.fsi
+++ b/src/Compiler/Service/ServiceAnalysis.fsi
@@ -3,7 +3,6 @@
namespace FSharp.Compiler.EditorServices
open FSharp.Compiler.CodeAnalysis
-open FSharp.Compiler.Syntax
open FSharp.Compiler.Text
module public UnusedOpens =
@@ -32,14 +31,3 @@ module public UnusedDeclarations =
/// Get all unused declarations in a file
val getUnusedDeclarations: checkFileResults: FSharpCheckFileResults * isScriptFile: bool -> Async>
-
-module public UnnecessaryParentheses =
-
- /// Gets the ranges of all unnecessary pairs of parentheses in a file.
- ///
- /// Note that this may include pairs of nested ranges each of whose
- /// lack of necessity depends on the other's presence, such
- /// that it is valid to remove either set of parentheses but not both, e.g.:
- ///
- /// (x.M(y)).N → (x.M y).N ↮ x.M(y).N
- val getUnnecessaryParentheses: getSourceLineStr: (int -> string) -> parsedInput: ParsedInput -> Async
diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fs b/src/Compiler/Service/ServiceParseTreeWalk.fs
index 2e3b10126e..ec5b623d0b 100644
--- a/src/Compiler/Service/ServiceParseTreeWalk.fs
+++ b/src/Compiler/Service/ServiceParseTreeWalk.fs
@@ -1111,28 +1111,10 @@ module SyntaxTraversal =
| SyntaxNode.SynMemberSig memberSig -> dive memberSig memberSig.Range (traverseSynMemberSig []))
|> pick fileRange ast
- let traverseAll (visitor: SyntaxVisitorBase<'T>) (parseTree: ParsedInput) : unit =
- let pick _ _ _ diveResults =
- let rec loop diveResults =
- match diveResults with
- | [] -> None
- | (_, project) :: rest ->
- ignore (project ())
- loop rest
-
- loop diveResults
-
- ignore<'T option> (traverseUntil pick parseTree.Range.End visitor parseTree.Contents)
-
/// traverse an implementation file walking all the way down to SynExpr or TypeAbbrev at a particular location
///
- let Traverse (pos: pos, parseTree, visitor: SyntaxVisitorBase<'T>) =
- let contents =
- match parseTree with
- | ParsedInput.ImplFile implFile -> implFile.Contents |> List.map SyntaxNode.SynModuleOrNamespace
- | ParsedInput.SigFile sigFile -> sigFile.Contents |> List.map SyntaxNode.SynModuleOrNamespaceSig
-
- traverseUntil pick pos visitor contents
+ let Traverse (pos: pos, parseTree: ParsedInput, visitor: SyntaxVisitorBase<'T>) =
+ traverseUntil pick pos visitor parseTree.Contents
[]
[]
diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fsi b/src/Compiler/Service/ServiceParseTreeWalk.fsi
index d0df7f5a1e..86ca17380e 100644
--- a/src/Compiler/Service/ServiceParseTreeWalk.fsi
+++ b/src/Compiler/Service/ServiceParseTreeWalk.fsi
@@ -204,8 +204,6 @@ module public SyntaxTraversal =
val internal pick:
pos: pos -> outerRange: range -> debugObj: obj -> diveResults: (range * (unit -> 'a option)) list -> 'a option
- val internal traverseAll: visitor: SyntaxVisitorBase<'T> -> parseTree: ParsedInput -> unit
-
val Traverse: pos: pos * parseTree: ParsedInput * visitor: SyntaxVisitorBase<'T> -> 'T option
///
diff --git a/src/Compiler/Service/SynExpr.fs b/src/Compiler/Service/SynExpr.fs
new file mode 100644
index 0000000000..0df9a1a1c1
--- /dev/null
+++ b/src/Compiler/Service/SynExpr.fs
@@ -0,0 +1,931 @@
+namespace FSharp.Compiler.Syntax
+
+open System
+open FSharp.Compiler.SyntaxTrivia
+open FSharp.Compiler.Text
+
+[]
+[]
+module SynExpr =
+ let (|Last|) = List.last
+
+ /// Matches if the two values refer to the same object.
+ []
+ let inline (|Is|_|) (inner1: 'a) (inner2: 'a) =
+ if obj.ReferenceEquals(inner1, inner2) then
+ ValueSome Is
+ else
+ ValueNone
+
+ /// Represents a symbolic infix operator with the precedence of *, /, or %.
+ /// All instances of this type are considered equal.
+ []
+ type MulDivMod =
+ | Mul
+ | Div
+ | Mod
+
+ member _.CompareTo(_other: MulDivMod) = 0
+ override this.Equals obj = this.CompareTo(unbox obj) = 0
+ override _.GetHashCode() = 0
+
+ interface IComparable with
+ member this.CompareTo obj = this.CompareTo(unbox obj)
+
+ /// Represents a symbolic infix operator with the precedence of + or -.
+ /// All instances of this type are considered equal.
+ []
+ type AddSub =
+ | Add
+ | Sub
+
+ member _.CompareTo(_other: AddSub) = 0
+ override this.Equals obj = this.CompareTo(unbox obj) = 0
+ override _.GetHashCode() = 0
+
+ interface IComparable with
+ member this.CompareTo obj = this.CompareTo(unbox obj)
+
+ /// Holds a symbolic operator's original notation.
+ /// Equality is based on the contents of the string.
+ /// Comparison always returns 0.
+ []
+ type OriginalNotation =
+ | OriginalNotation of string
+
+ member _.CompareTo(_other: OriginalNotation) = 0
+
+ override this.Equals obj =
+ match this, obj with
+ | OriginalNotation this, (:? OriginalNotation as OriginalNotation other) -> String.Equals(this, other, StringComparison.Ordinal)
+ | _ -> false
+
+ override this.GetHashCode() =
+ match this with
+ | OriginalNotation notation -> notation.GetHashCode()
+
+ interface IComparable with
+ member this.CompareTo obj = this.CompareTo(unbox obj)
+
+ /// Represents an expression's precedence.
+ /// Comparison is based only on the precedence case.
+ /// Equality considers the embedded original notation, if any.
+ ///
+ /// For example:
+ ///
+ /// compare (AddSub (Add, OriginalNotation "+")) (AddSub (Add, OriginalNotation "++")) = 0
+ ///
+ /// but
+ ///
+ /// AddSub (Add, OriginalNotation "+") <> AddSub (Add, OriginalNotation "++")
+ type Precedence =
+ /// yield, yield!, return, return!
+ | Low
+
+ /// <-
+ | Set
+
+ /// :=
+ | ColonEquals
+
+ /// ,
+ | Comma
+
+ /// or, ||
+ ///
+ /// Refers to the exact operators or and ||.
+ /// Instances with leading dots or question marks or trailing characters are parsed as Bar instead.
+ | BarBar of OriginalNotation
+
+ /// &, &&
+ ///
+ /// Refers to the exact operators & and &&.
+ /// Instances with leading dots or question marks or trailing characters are parsed as Amp instead.
+ | AmpAmp of OriginalNotation
+
+ /// :>, :?>
+ | UpcastDowncast
+
+ /// =…, |…, &…, $…, >…, <…, !=…
+ | Relational of OriginalNotation
+
+ /// ^…, @…
+ | HatAt
+
+ /// ::
+ | Cons
+
+ /// :?
+ | TypeTest
+
+ /// +…, -…
+ | AddSub of AddSub * OriginalNotation
+
+ /// *…, /…, %…
+ | MulDivMod of MulDivMod * OriginalNotation
+
+ /// **…
+ | Exp
+
+ /// - x
+ | UnaryPrefix
+
+ /// f x
+ | Apply
+
+ /// -x, !… x, ~~… x
+ | High
+
+ // x.y
+ | Dot
+
+ /// Associativity/association.
+ type Assoc =
+ /// Non-associative or no association.
+ | Non
+
+ /// Left-associative or left-hand association.
+ | Left
+
+ /// Right-associative or right-hand association.
+ | Right
+
+ module Assoc =
+ let ofPrecedence precedence =
+ match precedence with
+ | Low -> Non
+ | Set -> Non
+ | ColonEquals -> Right
+ | Comma -> Non
+ | BarBar _ -> Left
+ | AmpAmp _ -> Left
+ | UpcastDowncast -> Right
+ | Relational _ -> Left
+ | HatAt -> Right
+ | Cons -> Right
+ | TypeTest -> Non
+ | AddSub _ -> Left
+ | MulDivMod _ -> Left
+ | Exp -> Right
+ | UnaryPrefix -> Left
+ | Apply -> Left
+ | High -> Left
+ | Dot -> Left
+
+ /// See atomicExprAfterType in pars.fsy.
+ []
+ let (|AtomicExprAfterType|_|) expr =
+ match expr with
+ | SynExpr.Paren _
+ | SynExpr.Quote _
+ | SynExpr.Const _
+ | SynExpr.Tuple(isStruct = true)
+ | SynExpr.Record _
+ | SynExpr.AnonRecd _
+ | SynExpr.InterpolatedString _
+ | SynExpr.Null _
+ | SynExpr.ArrayOrList(isArray = true)
+ | SynExpr.ArrayOrListComputed(isArray = true) -> ValueSome AtomicExprAfterType
+ | _ -> ValueNone
+
+ /// Matches if the given expression represents a high-precedence
+ /// function application, e.g.,
+ ///
+ /// f x
+ ///
+ /// (+) x y
+ []
+ let (|HighPrecedenceApp|_|) expr =
+ match expr with
+ | SynExpr.App(isInfix = false; funcExpr = SynExpr.Ident _)
+ | SynExpr.App(isInfix = false; funcExpr = SynExpr.LongIdent _)
+ | SynExpr.App(isInfix = false; funcExpr = SynExpr.App(isInfix = false)) -> ValueSome HighPrecedenceApp
+ | _ -> ValueNone
+
+ module FuncExpr =
+ /// Matches when the given funcExpr is a direct application
+ /// of a symbolic operator, e.g., -, _not_ (~-).
+ []
+ let (|SymbolicOperator|_|) funcExpr =
+ match funcExpr with
+ | SynExpr.LongIdent(longDotId = SynLongIdent(trivia = trivia)) ->
+ let rec tryPick =
+ function
+ | [] -> ValueNone
+ | Some(IdentTrivia.OriginalNotation op) :: _ -> ValueSome op
+ | _ :: rest -> tryPick rest
+
+ tryPick trivia
+ | _ -> ValueNone
+
+ /// Matches when the given expression is a prefix operator application, e.g.,
+ ///
+ /// -x
+ ///
+ /// ~~~x
+ []
+ let (|PrefixApp|_|) expr : Precedence voption =
+ match expr with
+ | SynExpr.App(isInfix = false; funcExpr = funcExpr & FuncExpr.SymbolicOperator op; argExpr = argExpr) ->
+ if funcExpr.Range.IsAdjacentTo argExpr.Range then
+ ValueSome High
+ else
+ assert (op.Length > 0)
+
+ match op[0] with
+ | '!'
+ | '~' -> ValueSome High
+ | _ -> ValueSome UnaryPrefix
+
+ | SynExpr.AddressOf(expr = expr; opRange = opRange) ->
+ if opRange.IsAdjacentTo expr.Range then
+ ValueSome High
+ else
+ ValueSome UnaryPrefix
+
+ | _ -> ValueNone
+
+ /// Tries to parse the given original notation as a symbolic infix operator.
+ []
+ let (|SymbolPrec|_|) (originalNotation: string) =
+ // Trim any leading dots or question marks from the given symbolic operator.
+ // Leading dots or question marks have no effect on operator precedence or associativity
+ // with the exception of &, &&, and ||.
+ let ignoredLeadingChars = ".?".AsSpan()
+ let trimmed = originalNotation.AsSpan().TrimStart ignoredLeadingChars
+ assert (trimmed.Length > 0)
+
+ match trimmed[0], originalNotation with
+ | _, ":=" -> ValueSome ColonEquals
+ | _, ("||" | "or") -> ValueSome(BarBar(OriginalNotation originalNotation))
+ | _, ("&" | "&&") -> ValueSome(AmpAmp(OriginalNotation originalNotation))
+ | '|', _
+ | '&', _
+ | '<', _
+ | '>', _
+ | '=', _
+ | '$', _ -> ValueSome(Relational(OriginalNotation originalNotation))
+ | '!', _ when trimmed.Length > 1 && trimmed[1] = '=' -> ValueSome(Relational(OriginalNotation originalNotation))
+ | '^', _
+ | '@', _ -> ValueSome HatAt
+ | _, "::" -> ValueSome Cons
+ | '+', _ -> ValueSome(AddSub(Add, OriginalNotation originalNotation))
+ | '-', _ -> ValueSome(AddSub(Sub, OriginalNotation originalNotation))
+ | '/', _ -> ValueSome(MulDivMod(Div, OriginalNotation originalNotation))
+ | '%', _ -> ValueSome(MulDivMod(Mod, OriginalNotation originalNotation))
+ | '*', _ when trimmed.Length > 1 && trimmed[1] = '*' -> ValueSome Exp
+ | '*', _ -> ValueSome(MulDivMod(Mul, OriginalNotation originalNotation))
+ | _ -> ValueNone
+
+ []
+ let (|Contains|_|) (c: char) (s: string) =
+ if s.IndexOf c >= 0 then ValueSome Contains else ValueNone
+
+ /// Any expressions in which the removal of parens would
+ /// lead to something like the following that would be
+ /// confused by the parser with a type parameter application:
+ ///
+ /// xz
+ ///
+ /// xz
+ []
+ let rec (|ConfusableWithTypeApp|_|) synExpr =
+ match synExpr with
+ | SynExpr.Paren(expr = ConfusableWithTypeApp)
+ | SynExpr.App(funcExpr = ConfusableWithTypeApp)
+ | SynExpr.App(isInfix = true; funcExpr = FuncExpr.SymbolicOperator(Contains '>'); argExpr = ConfusableWithTypeApp) ->
+ ValueSome ConfusableWithTypeApp
+ | SynExpr.App(isInfix = true; funcExpr = funcExpr & FuncExpr.SymbolicOperator(Contains '<'); argExpr = argExpr) when
+ argExpr.Range.IsAdjacentTo funcExpr.Range
+ ->
+ ValueSome ConfusableWithTypeApp
+ | SynExpr.Tuple(exprs = exprs) ->
+ let rec anyButLast =
+ function
+ | _ :: []
+ | [] -> ValueNone
+ | ConfusableWithTypeApp :: _ -> ValueSome ConfusableWithTypeApp
+ | _ :: tail -> anyButLast tail
+
+ anyButLast exprs
+ | _ -> ValueNone
+
+ /// Matches when the expression represents the infix application of a symbolic operator.
+ ///
+ /// (x λ y) ρ z
+ ///
+ /// x λ (y ρ z)
+ []
+ let (|InfixApp|_|) synExpr : struct (Precedence * Assoc) voption =
+ match synExpr with
+ | SynExpr.App(funcExpr = SynExpr.App(isInfix = true; funcExpr = FuncExpr.SymbolicOperator(SymbolPrec prec))) ->
+ ValueSome(prec, Right)
+ | SynExpr.App(isInfix = true; funcExpr = FuncExpr.SymbolicOperator(SymbolPrec prec)) -> ValueSome(prec, Left)
+ | SynExpr.Upcast _
+ | SynExpr.Downcast _ -> ValueSome(UpcastDowncast, Left)
+ | SynExpr.TypeTest _ -> ValueSome(TypeTest, Left)
+ | _ -> ValueNone
+
+ /// Returns the given expression's precedence and the side of the inner expression,
+ /// if applicable.
+ []
+ let (|OuterBinaryExpr|_|) inner outer : struct (Precedence * Assoc) voption =
+ match outer with
+ | SynExpr.YieldOrReturn _
+ | SynExpr.YieldOrReturnFrom _ -> ValueSome(Low, Right)
+ | SynExpr.Tuple(exprs = SynExpr.Paren(expr = Is inner) :: _) -> ValueSome(Comma, Left)
+ | SynExpr.Tuple _ -> ValueSome(Comma, Right)
+ | InfixApp(Cons, side) -> ValueSome(Cons, side)
+ | SynExpr.Assert _
+ | SynExpr.Lazy _
+ | SynExpr.InferredUpcast _
+ | SynExpr.InferredDowncast _ -> ValueSome(Apply, Non)
+ | PrefixApp prec -> ValueSome(prec, Non)
+ | InfixApp(prec, side) -> ValueSome(prec, side)
+ | SynExpr.App(argExpr = SynExpr.ComputationExpr _) -> ValueSome(UnaryPrefix, Left)
+ | SynExpr.App(funcExpr = SynExpr.Paren(expr = SynExpr.App _)) -> ValueSome(Apply, Left)
+ | SynExpr.App _ -> ValueSome(Apply, Non)
+ | SynExpr.DotSet(targetExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Dot, Left)
+ | SynExpr.DotSet(rhsExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Set, Right)
+ | SynExpr.DotIndexedSet(objectExpr = SynExpr.Paren(expr = Is inner))
+ | SynExpr.DotNamedIndexedPropertySet(targetExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Dot, Left)
+ | SynExpr.DotIndexedSet(valueExpr = SynExpr.Paren(expr = Is inner))
+ | SynExpr.DotNamedIndexedPropertySet(rhsExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Set, Right)
+ | SynExpr.LongIdentSet(expr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Set, Right)
+ | SynExpr.Set _ -> ValueSome(Set, Non)
+ | SynExpr.DotGet _ -> ValueSome(Dot, Left)
+ | 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
+ | InfixApp(prec, _) -> ValueSome prec
+ | SynExpr.App _
+ | SynExpr.Assert _
+ | SynExpr.Lazy _
+ | SynExpr.For _
+ | SynExpr.ForEach _
+ | SynExpr.While _
+ | SynExpr.Do _
+ | SynExpr.New _
+ | SynExpr.InferredUpcast _
+ | SynExpr.InferredDowncast _ -> ValueSome Apply
+ | SynExpr.DotIndexedSet _
+ | SynExpr.DotNamedIndexedPropertySet _
+ | SynExpr.DotSet _ -> ValueSome Set
+ | _ -> ValueNone
+
+ module Dangling =
+ /// Returns the first matching nested right-hand target expression, if any.
+ let private dangling (target: SynExpr -> SynExpr option) =
+ let (|Target|_|) = target
+
+ let rec loop expr =
+ match expr with
+ | Target expr -> ValueSome expr
+ | SynExpr.Tuple(isStruct = false; exprs = Last expr)
+ | SynExpr.App(argExpr = expr)
+ | SynExpr.IfThenElse(elseExpr = Some expr)
+ | SynExpr.IfThenElse(ifExpr = expr)
+ | SynExpr.Sequential(expr2 = expr)
+ | SynExpr.YieldOrReturn(expr = expr)
+ | SynExpr.YieldOrReturnFrom(expr = expr)
+ | SynExpr.Set(rhsExpr = expr)
+ | SynExpr.DotSet(rhsExpr = expr)
+ | SynExpr.DotNamedIndexedPropertySet(rhsExpr = expr)
+ | SynExpr.DotIndexedSet(valueExpr = expr)
+ | SynExpr.LongIdentSet(expr = expr)
+ | SynExpr.LetOrUse(body = expr)
+ | SynExpr.Lambda(body = expr)
+ | SynExpr.Match(clauses = Last(SynMatchClause(resultExpr = expr)))
+ | SynExpr.MatchLambda(matchClauses = Last(SynMatchClause(resultExpr = expr)))
+ | SynExpr.MatchBang(clauses = Last(SynMatchClause(resultExpr = expr)))
+ | SynExpr.TryWith(withCases = Last(SynMatchClause(resultExpr = expr)))
+ | SynExpr.TryFinally(finallyExpr = expr) -> loop expr
+ | _ -> ValueNone
+
+ loop
+
+ /// Matches a dangling if-then construct.
+ []
+ let (|IfThen|_|) =
+ dangling (function
+ | 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|_|) =
+ dangling (function
+ | SynExpr.TryWith _
+ | SynExpr.TryFinally _ as expr -> Some expr
+ | _ -> None)
+
+ /// Matches a dangling match-like construct.
+ []
+ let (|Match|_|) =
+ dangling (function
+ | SynExpr.Match _
+ | SynExpr.MatchBang _
+ | SynExpr.MatchLambda _
+ | SynExpr.TryWith _
+ | SynExpr.Lambda _ as expr -> Some expr
+ | _ -> None)
+
+ /// Matches a nested dangling construct that could become problematic
+ /// if the surrounding parens were removed.
+ []
+ let (|Problematic|_|) =
+ dangling (function
+ | SynExpr.Lambda _
+ | SynExpr.MatchLambda _
+ | SynExpr.Match _
+ | SynExpr.MatchBang _
+ | SynExpr.TryWith _
+ | SynExpr.TryFinally _
+ | SynExpr.IfThenElse _
+ | SynExpr.Sequential _
+ | SynExpr.LetOrUse _
+ | SynExpr.Set _
+ | SynExpr.LongIdentSet _
+ | SynExpr.DotIndexedSet _
+ | SynExpr.DotNamedIndexedPropertySet _
+ | SynExpr.DotSet _
+ | SynExpr.NamedIndexedPropertySet _ as expr -> Some expr
+ | _ -> None)
+
+ /// Indicates whether the expression with the given range
+ /// includes indentation that would be invalid
+ /// in context if it were not wrapped in parentheses.
+ let containsSensitiveIndentation (getSourceLineStr: int -> string) outerOffsidesColumn (range: range) =
+ let startLine = range.StartLine
+ let endLine = range.EndLine
+
+ if startLine = endLine then
+ range.StartColumn <= outerOffsidesColumn
+ else
+ let rec loop offsides lineNo startCol =
+ if lineNo <= endLine then
+ let line = getSourceLineStr lineNo
+
+ match offsides with
+ | ValueNone ->
+ let i = line.AsSpan(startCol).IndexOfAnyExcept(' ', ')')
+
+ if i >= 0 then
+ let newOffsides = i + startCol
+
+ newOffsides <= outerOffsidesColumn
+ || loop (ValueSome newOffsides) (lineNo + 1) 0
+ else
+ loop offsides (lineNo + 1) 0
+
+ | ValueSome offsidesCol ->
+ let i = line.AsSpan(0, min offsidesCol line.Length).IndexOfAnyExcept(' ', ')')
+
+ if i >= 0 && i < offsidesCol then
+ let slice = line.AsSpan(i, min (offsidesCol - i) (line.Length - i))
+ let j = slice.IndexOfAnyExcept("*/%-+:^@><=!|0$.?".AsSpan())
+
+ let lo = i + (if j >= 0 && slice[j] = ' ' then j else 0)
+
+ lo < offsidesCol - 1
+ || lo <= outerOffsidesColumn
+ || loop offsides (lineNo + 1) 0
+ else
+ loop offsides (lineNo + 1) 0
+ else
+ false
+
+ loop ValueNone startLine range.StartColumn
+
+ let rec shouldBeParenthesizedInContext (getSourceLineStr: int -> string) path expr : bool =
+ let shouldBeParenthesizedInContext = shouldBeParenthesizedInContext getSourceLineStr
+ let containsSensitiveIndentation = containsSensitiveIndentation getSourceLineStr
+
+ // Matches if the given expression starts with a symbol, e.g., <@ … @>, $"…", @"…", +1, -1…
+ let (|StartsWithSymbol|_|) =
+ let (|TextStartsWith|) (m: range) =
+ let line = getSourceLineStr m.StartLine
+ line[m.StartColumn]
+
+ let (|StartsWith|) (s: string) = s[0]
+
+ function
+ | SynExpr.Quote _
+ | SynExpr.InterpolatedString _
+ | SynExpr.Const(SynConst.String(synStringKind = SynStringKind.Verbatim), _)
+ | SynExpr.Const(SynConst.Byte _, TextStartsWith '+')
+ | SynExpr.Const(SynConst.UInt16 _, TextStartsWith '+')
+ | SynExpr.Const(SynConst.UInt32 _, TextStartsWith '+')
+ | SynExpr.Const(SynConst.UInt64 _, TextStartsWith '+')
+ | SynExpr.Const(SynConst.UIntPtr _, TextStartsWith '+')
+ | SynExpr.Const(SynConst.SByte _, TextStartsWith('-' | '+'))
+ | SynExpr.Const(SynConst.Int16 _, TextStartsWith('-' | '+'))
+ | SynExpr.Const(SynConst.Int32 _, TextStartsWith('-' | '+'))
+ | SynExpr.Const(SynConst.Int64 _, TextStartsWith('-' | '+'))
+ | SynExpr.Const(SynConst.IntPtr _, TextStartsWith('-' | '+'))
+ | SynExpr.Const(SynConst.Decimal _, TextStartsWith('-' | '+'))
+ | SynExpr.Const(SynConst.Double _, TextStartsWith('-' | '+'))
+ | SynExpr.Const(SynConst.Single _, TextStartsWith('-' | '+'))
+ | SynExpr.Const(SynConst.Measure(_, TextStartsWith('-' | '+'), _, _), _)
+ | SynExpr.Const(SynConst.UserNum(StartsWith('-' | '+'), _), _) -> Some StartsWithSymbol
+ | _ -> None
+
+ // Matches if the given expression is a numeric literal
+ // that it is safe to "dot into," e.g., 1l, 0b1, 1e10, 1d, 1.0…
+ let (|DotSafeNumericLiteral|_|) =
+ /// 1l, 1d, 0b1, 0x1, 0o1, 1e10…
+ let (|TextContainsLetter|_|) (m: range) =
+ let line = getSourceLineStr m.StartLine
+ let span = line.AsSpan(m.StartColumn, m.EndColumn - m.StartColumn)
+
+ if span.LastIndexOfAnyInRange('A', 'z') >= 0 then
+ Some TextContainsLetter
+ else
+ None
+
+ // 1.0…
+ let (|TextEndsWithNumber|_|) (m: range) =
+ let line = getSourceLineStr m.StartLine
+ let span = line.AsSpan(m.StartColumn, m.EndColumn - m.StartColumn)
+
+ if Char.IsDigit span[span.Length - 1] then
+ Some TextEndsWithNumber
+ else
+ None
+
+ function
+ | SynExpr.Const(SynConst.Byte _, _)
+ | SynExpr.Const(SynConst.UInt16 _, _)
+ | SynExpr.Const(SynConst.UInt32 _, _)
+ | SynExpr.Const(SynConst.UInt64 _, _)
+ | SynExpr.Const(SynConst.UIntPtr _, _)
+ | SynExpr.Const(SynConst.SByte _, _)
+ | SynExpr.Const(SynConst.Int16 _, _)
+ | SynExpr.Const(SynConst.Int32 _, TextContainsLetter)
+ | SynExpr.Const(SynConst.Int64 _, _)
+ | SynExpr.Const(SynConst.IntPtr _, _)
+ | SynExpr.Const(SynConst.Decimal _, _)
+ | SynExpr.Const(SynConst.Double _, (TextEndsWithNumber | TextContainsLetter))
+ | SynExpr.Const(SynConst.Single _, _)
+ | SynExpr.Const(SynConst.Measure _, _)
+ | SynExpr.Const(SynConst.UserNum _, _) -> Some DotSafeNumericLiteral
+ | _ -> None
+
+ match expr, path with
+ // 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 = InfixApp(Relational(OriginalNotation "="), _)),
+ SyntaxNode.SynExpr(SynExpr.App(funcExpr = SynExpr.LongIdent _)) :: _
+ | InfixApp(Relational(OriginalNotation "="), _),
+ SyntaxNode.SynExpr(SynExpr.Paren _) :: SyntaxNode.SynExpr(SynExpr.App(funcExpr = SynExpr.LongIdent _)) :: _
+ | InfixApp(Relational(OriginalNotation "="), _),
+ SyntaxNode.SynExpr(SynExpr.Tuple(isStruct = false)) :: SyntaxNode.SynExpr(SynExpr.Paren _) :: SyntaxNode.SynExpr(SynExpr.App(
+ funcExpr = SynExpr.LongIdent _)) :: _ -> true
+
+ // Already parenthesized.
+ | _, SyntaxNode.SynExpr(SynExpr.Paren _) :: _ -> false
+
+ // Parens must stay around indentation that would otherwise be invalid:
+ //
+ // let _ = (x
+ // +y)
+ | _, SyntaxNode.SynBinding(SynBinding(trivia = trivia)) :: _ when
+ containsSensitiveIndentation trivia.LeadingKeyword.Range.StartColumn expr.Range
+ ->
+ true
+
+ // Parens must stay around indentation that would otherwise be invalid:
+ //
+ // return (
+ // x
+ // )
+ | _, SyntaxNode.SynExpr outer :: _ when containsSensitiveIndentation outer.Range.StartColumn expr.Range -> true
+
+ // Check for nested matches, e.g.,
+ //
+ // match … with … -> (…, match … with … -> … | … -> …) | … -> …
+ | _, SyntaxNode.SynMatchClause _ :: path -> shouldBeParenthesizedInContext path expr
+
+ // We always need parens for trait calls, e.g.,
+ //
+ // let inline f x = (^a : (static member Parse : string -> ^a) x)
+ | SynExpr.TraitCall _, _ -> true
+
+ // Don't touch library-only stuff:
+ //
+ // (# "ldlen.multi 2 0" array : int #)
+ | SynExpr.LibraryOnlyILAssembly _, _
+ | SynExpr.LibraryOnlyStaticOptimization _, _
+ | SynExpr.LibraryOnlyUnionCaseFieldGet _, _
+ | SynExpr.LibraryOnlyUnionCaseFieldSet _, _ -> true
+
+ // Parens are otherwise never required for binding bodies or for top-level expressions, e.g.,
+ //
+ // let x = (…)
+ // _.member X = (…)
+ // (printfn "Hello, world.")
+ | _, SyntaxNode.SynBinding _ :: _
+ | _, SyntaxNode.SynModule _ :: _ -> false
+
+ // Parens must be kept when there is a high-precedence function application
+ // before a prefix operator application before another expression that starts with a symbol, e.g.,
+ //
+ // id -(-x)
+ // id -(-1y)
+ // id -($"")
+ // id -(@"")
+ // id -(<@ ValueNone @>)
+ // let (~+) _ = true in assert +($"{true}")
+ | (PrefixApp _ | StartsWithSymbol),
+ SyntaxNode.SynExpr(SynExpr.App _) :: SyntaxNode.SynExpr(HighPrecedenceApp | SynExpr.Assert _ | SynExpr.InferredUpcast _ | SynExpr.InferredDowncast _) :: _ ->
+ true
+
+ // Parens are never required around suffixed or infixed numeric literals, e.g.,
+ //
+ // (1l).ToString()
+ // (1uy).ToString()
+ // (0b1).ToString()
+ // (1e10).ToString()
+ // (1.0).ToString()
+ | DotSafeNumericLiteral, _ -> false
+
+ // Parens are required around bare decimal ints or doubles ending
+ // in dots when being dotted into, e.g.,
+ //
+ // (1).ToString()
+ // (1.).ToString()
+ | SynExpr.Const(constant = SynConst.Int32 _ | SynConst.Double _), SyntaxNode.SynExpr(SynExpr.DotGet _) :: _ -> true
+
+ // Parens are required around join conditions:
+ //
+ // join … on (… = …)
+ | SynExpr.App _, SyntaxNode.SynExpr(SynExpr.App _) :: SyntaxNode.SynExpr(SynExpr.JoinIn _) :: _ -> true
+
+ // Parens are not required around a few anointed expressions after inherit:
+ //
+ // inherit T(3)
+ // inherit T(null)
+ // inherit T("")
+ // …
+ | AtomicExprAfterType, SyntaxNode.SynMemberDefn(SynMemberDefn.ImplicitInherit _) :: _ -> false
+
+ // Parens are otherwise required in inherit T(x), etc.
+ | _, SyntaxNode.SynMemberDefn(SynMemberDefn.ImplicitInherit _) :: _ -> true
+
+ // We can't remove parens when they're required for fluent calls:
+ //
+ // x.M(y).N z
+ // x.M(y).[z]
+ // _.M(x)
+ // (f x)[z]
+ // (f(x))[z]
+ // x.M(y)[z]
+ | _, SyntaxNode.SynExpr(SynExpr.App _) :: SyntaxNode.SynExpr(SynExpr.DotGet _ | SynExpr.DotIndexedGet _ | SynExpr.DotLambda _) :: _
+ | SynExpr.App _, SyntaxNode.SynExpr(SynExpr.App(argExpr = SynExpr.ArrayOrListComputed(isArray = false))) :: _
+ | _,
+ SyntaxNode.SynExpr(SynExpr.App _) :: SyntaxNode.SynExpr(SynExpr.App(argExpr = SynExpr.ArrayOrListComputed(isArray = false))) :: _ ->
+ true
+
+ // The :: operator is parsed differently from other symbolic infix operators,
+ // so we need to give it special treatment.
+
+ // Outer right:
+ //
+ // (x) :: xs
+ // (x * y) :: zs
+ // …
+ | _,
+ SyntaxNode.SynExpr(SynExpr.Tuple(isStruct = false; exprs = [ SynExpr.Paren _; _ ])) :: (SyntaxNode.SynExpr(SynExpr.App(
+ isInfix = true)) :: _ as path) -> shouldBeParenthesizedInContext path expr
+
+ // Outer left:
+ //
+ // x :: (xs)
+ // x :: (ys @ zs)
+ // …
+ | argExpr,
+ SyntaxNode.SynExpr(SynExpr.Tuple(isStruct = false; exprs = [ _; SynExpr.Paren _ ])) :: SyntaxNode.SynExpr(SynExpr.App(
+ isInfix = true) as outer) :: path ->
+ shouldBeParenthesizedInContext
+ (SyntaxNode.SynExpr(SynExpr.App(ExprAtomicFlag.NonAtomic, false, outer, argExpr, outer.Range))
+ :: path)
+ expr
+
+ // Ordinary nested expressions.
+ | inner, SyntaxNode.SynExpr outer :: outerPath ->
+ let dangling expr =
+ match expr with
+ | Dangling.Problematic subExpr ->
+ match outer with
+ | SynExpr.Tuple(exprs = exprs) -> not (obj.ReferenceEquals(subExpr, List.last exprs))
+ | InfixApp(_, Left) -> true
+ | _ -> shouldBeParenthesizedInContext outerPath subExpr
+
+ | _ -> false
+
+ let problematic (exprRange: range) (delimiterRange: range) =
+ exprRange.EndLine = delimiterRange.EndLine
+ && exprRange.EndColumn < delimiterRange.StartColumn
+
+ let anyProblematic matchOrTryRange clauses =
+ let rec loop =
+ function
+ | [] -> false
+ | SynMatchClause(trivia = trivia) :: clauses ->
+ trivia.BarRange |> Option.exists (problematic matchOrTryRange)
+ || trivia.ArrowRange |> Option.exists (problematic matchOrTryRange)
+ || loop clauses
+
+ loop clauses
+
+ match outer, inner with
+ | ConfusableWithTypeApp, _ -> true
+
+ | SynExpr.IfThenElse _, Dangling.Sequential _ -> true
+
+ | SynExpr.IfThenElse(trivia = trivia), Dangling.IfThen ifThenElse when
+ problematic ifThenElse.Range trivia.ThenKeyword
+ || trivia.ElseKeyword |> Option.exists (problematic ifThenElse.Range)
+ ->
+ true
+
+ | SynExpr.TryFinally(trivia = trivia), Dangling.Try tryExpr when problematic tryExpr.Range trivia.FinallyKeyword -> true
+
+ | SynExpr.Match(clauses = clauses; trivia = { WithKeyword = withKeyword }), Dangling.Match matchOrTry when
+ problematic matchOrTry.Range withKeyword
+ || anyProblematic matchOrTry.Range clauses
+ ->
+ true
+
+ | SynExpr.MatchBang(clauses = clauses; trivia = { WithKeyword = withKeyword }), Dangling.Match matchOrTry when
+ problematic matchOrTry.Range withKeyword
+ || anyProblematic matchOrTry.Range clauses
+ ->
+ true
+
+ | SynExpr.MatchLambda(matchClauses = clauses), Dangling.Match matchOrTry when anyProblematic matchOrTry.Range clauses -> true
+
+ | SynExpr.TryWith(withCases = clauses; trivia = trivia), Dangling.Match matchOrTry when
+ problematic matchOrTry.Range trivia.WithKeyword
+ || anyProblematic matchOrTry.Range clauses
+ ->
+ true
+
+ | SynExpr.Sequential(expr1 = SynExpr.Paren(expr = Is inner); expr2 = expr2), Dangling.Problematic _ when
+ problematic inner.Range expr2.Range
+ ->
+ true
+
+ | SynExpr.Record(copyInfo = Some(SynExpr.Paren(expr = Is inner), _)), Dangling.Problematic _
+ | SynExpr.AnonRecd(copyInfo = Some(SynExpr.Paren(expr = Is inner), _)), Dangling.Problematic _ -> true
+
+ | SynExpr.Record(recordFields = recordFields), Dangling.Problematic _ ->
+ let rec loop recordFields =
+ match recordFields with
+ | [] -> false
+ | SynExprRecordField(expr = Some(SynExpr.Paren(expr = Is inner)); blockSeparator = Some _) :: SynExprRecordField(
+ fieldName = SynLongIdent(id = id :: _), _) :: _ -> problematic inner.Range id.idRange
+ | _ :: recordFields -> loop recordFields
+
+ loop recordFields
+
+ | SynExpr.AnonRecd(recordFields = recordFields), Dangling.Problematic _ ->
+ let rec loop recordFields =
+ match recordFields with
+ | [] -> false
+ | (_, Some _blockSeparator, SynExpr.Paren(expr = Is inner)) :: (SynLongIdent(id = id :: _), _, _) :: _ ->
+ problematic inner.Range id.idRange
+ | _ :: recordFields -> loop recordFields
+
+ loop recordFields
+
+ | 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 _ -> false
+ | _, SynExpr.Typed _ -> true
+
+ | OuterBinaryExpr inner (outerPrecedence, side), InnerBinaryExpr innerPrecedence ->
+ let ambiguous =
+ match compare outerPrecedence innerPrecedence with
+ | 0 ->
+ match side, Assoc.ofPrecedence innerPrecedence with
+ | Non, _
+ | _, Non
+ | Left, Right -> true
+ | Right, Right
+ | Left, Left -> false
+ | Right, Left ->
+ outerPrecedence <> innerPrecedence
+ || match outerPrecedence, innerPrecedence with
+ | _, MulDivMod(Div, _)
+ | _, MulDivMod(Mod, _)
+ | _, AddSub(Sub, _) -> true
+ | Relational _, Relational _ -> true
+ | _ -> false
+
+ | c -> c > 0
+
+ ambiguous || dangling inner
+
+ | OuterBinaryExpr inner (_, Right), (SynExpr.Sequential _ | SynExpr.LetOrUse(trivia = { InKeyword = None })) -> true
+ | OuterBinaryExpr inner (_, Right), inner -> dangling inner
+
+ // new T(expr)
+ | SynExpr.New _, AtomicExprAfterType -> false
+ | SynExpr.New _, _ -> true
+
+ // { inherit T(expr); … }
+ | SynExpr.Record(baseInfo = Some(_, SynExpr.Paren(expr = Is inner), _, _, _)), AtomicExprAfterType -> false
+ | SynExpr.Record(baseInfo = Some(_, SynExpr.Paren(expr = Is inner), _, _, _)), _ -> true
+
+ | _, SynExpr.Paren _
+ | _, SynExpr.Quote _
+ | _, SynExpr.Const _
+ | _, SynExpr.Tuple(isStruct = true)
+ | _, SynExpr.AnonRecd _
+ | _, SynExpr.ArrayOrList _
+ | _, SynExpr.Record _
+ | _, SynExpr.ObjExpr _
+ | _, SynExpr.ArrayOrListComputed _
+ | _, SynExpr.ComputationExpr _
+ | _, SynExpr.TypeApp _
+ | _, SynExpr.Ident _
+ | _, SynExpr.LongIdent _
+ | _, SynExpr.DotGet _
+ | _, SynExpr.DotLambda _
+ | _, SynExpr.DotIndexedGet _
+ | _, SynExpr.Null _
+ | _, SynExpr.InterpolatedString _
+
+ | SynExpr.Paren _, _
+ | SynExpr.Quote _, _
+ | SynExpr.Typed _, _
+ | SynExpr.AnonRecd _, _
+ | SynExpr.Record _, _
+ | SynExpr.ObjExpr _, _
+ | SynExpr.While _, _
+ | SynExpr.WhileBang _, _
+ | SynExpr.For _, _
+ | SynExpr.ForEach _, _
+ | SynExpr.Lambda _, _
+ | SynExpr.MatchLambda _, _
+ | SynExpr.Match _, _
+ | SynExpr.MatchBang _, _
+ | SynExpr.LetOrUse _, _
+ | SynExpr.LetOrUseBang _, _
+ | SynExpr.Sequential _, _
+ | SynExpr.Do _, _
+ | SynExpr.DoBang _, _
+ | SynExpr.IfThenElse _, _
+ | SynExpr.TryWith _, _
+ | SynExpr.TryFinally _, _
+ | SynExpr.ComputationExpr _, _
+ | SynExpr.InterpolatedString _, _ -> false
+
+ | _ -> true
+
+ | _ -> true
diff --git a/src/Compiler/Service/SynExpr.fsi b/src/Compiler/Service/SynExpr.fsi
new file mode 100644
index 0000000000..9e74ce6c1e
--- /dev/null
+++ b/src/Compiler/Service/SynExpr.fsi
@@ -0,0 +1,15 @@
+namespace FSharp.Compiler.Syntax
+
+[]
+[]
+module public SynExpr =
+
+ ///
+ /// Returns true if the given expression should be parenthesized in the given context, otherwise false.
+ ///
+ /// A function for getting the text of a given source line.
+ /// The expression's ancestor nodes.
+ /// The expression to check.
+ /// True if the given expression should be parenthesized in the given context, otherwise false.
+ val shouldBeParenthesizedInContext:
+ getSourceLineStr: (int -> string) -> path: SyntaxVisitorPath -> expr: SynExpr -> bool
diff --git a/src/Compiler/Service/SynPat.fs b/src/Compiler/Service/SynPat.fs
new file mode 100644
index 0000000000..53212cf17d
--- /dev/null
+++ b/src/Compiler/Service/SynPat.fs
@@ -0,0 +1,253 @@
+namespace FSharp.Compiler.Syntax
+
+[]
+[]
+module SynPat =
+ let (|Last|) = List.last
+
+ /// Matches if the two values refer to the same object.
+ []
+ let inline (|Is|_|) (inner1: 'a) (inner2: 'a) =
+ if obj.ReferenceEquals(inner1, inner2) then
+ ValueSome Is
+ else
+ ValueNone
+
+ let (|Ident|) (ident: Ident) = ident.idText
+
+ /// Matches if any pattern in the given list is a SynPat.Typed.
+ []
+ let (|AnyTyped|_|) pats =
+ if
+ pats
+ |> List.exists (function
+ | SynPat.Typed _ -> true
+ | _ -> false)
+ then
+ ValueSome AnyTyped
+ 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 =
+ 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 _
+ | SynPat.QuoteExpr _ -> ValueSome Atomic
+ | _ -> ValueNone
+
+ let shouldBeParenthesizedInContext path pat : bool =
+ 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.Typed _, SyntaxNode.SynPat(Rightmost(SynPat.Paren(Is pat, _))) :: SyntaxNode.SynMatchClause _ :: _
+ | Rightmost(SynPat.Typed _), SyntaxNode.SynMatchClause _ :: _
+ | SynPat.Typed _, SyntaxNode.SynExpr(SynExpr.LetOrUseBang _) :: _
+ | SynPat.Typed _, SyntaxNode.SynPat(SynPat.Tuple(isStruct = false)) :: SyntaxNode.SynExpr(SynExpr.LetOrUseBang _) :: _
+ | SynPat.Tuple(isStruct = false; elementPats = AnyTyped), SyntaxNode.SynExpr(SynExpr.LetOrUseBang _) :: _
+ | SynPat.Typed _, SyntaxNode.SynPat(SynPat.Tuple(isStruct = false)) :: SyntaxNode.SynBinding _ :: _
+ | SynPat.Tuple(isStruct = false; elementPats = AnyTyped), SyntaxNode.SynBinding _ :: _
+ | SynPat.LongIdent(argPats = SynArgPats.Pats(_ :: _)), SyntaxNode.SynBinding _ :: _
+ | SynPat.LongIdent(argPats = SynArgPats.Pats(_ :: _)), SyntaxNode.SynExpr(SynExpr.Lambda _) :: _
+ | SynPat.Tuple(isStruct = false), SyntaxNode.SynExpr(SynExpr.Lambda(parsedData = Some _)) :: _
+ | SynPat.Typed _, SyntaxNode.SynExpr(SynExpr.Lambda(parsedData = Some _)) :: _ -> true
+
+ // () is parsed as this.
+ | SynPat.Const(SynConst.Unit, _), _ -> true
+
+ // (()) 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.Const(SynConst.Unit, _), _),
+ SyntaxNode.SynPat(SynPat.LongIdent _) :: SyntaxNode.SynBinding _ :: SyntaxNode.SynExpr(SynExpr.ObjExpr(
+ objType = SynType.App(typeArgs = _ :: _) | SynType.LongIdentApp(typeArgs = _ :: _))) :: _
+ | SynPat.Paren(SynPat.Const(SynConst.Unit, _), _),
+ SyntaxNode.SynPat(SynPat.LongIdent _) :: SyntaxNode.SynBinding _ :: SyntaxNode.SynMemberDefn _ :: SyntaxNode.SynTypeDefn(SynTypeDefn(
+ typeRepr = SynTypeDefnRepr.ObjectModel(members = AnyGenericInheritOrInterfaceImpl))) :: _ -> true
+
+ // Parens are required around the atomic argument of
+ // any additional `new` constructor that is not the last.
+ //
+ // type T … =
+ // new (x) = …
+ // new (x, y) = …
+ | Atomic,
+ 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 [ SynPat.Paren(Is pat, _) ])))) -> false
+ | _ -> true
+
+ // Parens are otherwise never needed in these cases:
+ //
+ // let (x: …) = …
+ // for (…) in (…) do …
+ // let! (…) = …
+ // and! (…) = …
+ // use! (…) = …
+ // match … with (…) -> …
+ // function (…) -> …
+ // function (Pattern …) -> …
+ // fun (x) -> …
+ | _, SyntaxNode.SynBinding _ :: _
+ | _, SyntaxNode.SynExpr(SynExpr.ForEach _) :: _
+ | _, SyntaxNode.SynExpr(SynExpr.LetOrUseBang _) :: _
+ | _, SyntaxNode.SynMatchClause _ :: _
+ | Atomic, SyntaxNode.SynExpr(SynExpr.Lambda(parsedData = Some _)) :: _ -> false
+
+ // Nested patterns.
+ | inner, SyntaxNode.SynPat outer :: _ ->
+ match outer, inner with
+ // (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(isStruct = false) -> true
+
+ // A as (B | C)
+ // A as (B & C)
+ // x as (y, z)
+ // xs as (y :: zs)
+ | SynPat.As(rhsPat = SynPat.Paren(pat = Is inner)),
+ (SynPat.Or _ | SynPat.Ands _ | SynPat.Tuple(isStruct = false) | SynPat.ListCons _) -> true
+
+ // (A | B) :: xs
+ // (A & B) :: xs
+ // (x as y) :: xs
+ | SynPat.ListCons _, SynPat.Or _
+ | SynPat.ListCons _, SynPat.Ands _
+ | SynPat.ListCons _, SynPat.As _ -> true
+
+ // Pattern (x = (…))
+ | SynPat.LongIdent(argPats = SynArgPats.NamePatPairs _), _ -> false
+
+ // Pattern (x : int)
+ // Pattern ([] x)
+ // Pattern (:? int)
+ // Pattern (A :: _)
+ // Pattern (A | B)
+ // Pattern (A & B)
+ // Pattern (A as B)
+ // Pattern (A, B)
+ // Pattern1 (Pattern2 (x = A))
+ // Pattern1 (Pattern2 x y)
+ | SynPat.LongIdent _, SynPat.Typed _
+ | SynPat.LongIdent _, SynPat.Attrib _
+ | SynPat.LongIdent _, SynPat.IsInst _
+ | SynPat.LongIdent _, SynPat.ListCons _
+ | SynPat.LongIdent _, SynPat.Or _
+ | SynPat.LongIdent _, SynPat.Ands _
+ | SynPat.LongIdent _, SynPat.As _
+ | SynPat.LongIdent _, SynPat.Tuple(isStruct = false)
+ | SynPat.LongIdent _, SynPat.LongIdent(argPats = SynArgPats.NamePatPairs _)
+ | SynPat.LongIdent _, SynPat.LongIdent(argPats = SynArgPats.Pats(_ :: _))
+
+ // A | (B as C)
+ // A & (B as C)
+ // A, (B as C)
+ | SynPat.Or _, SynPat.As _
+ | SynPat.Ands _, SynPat.As _
+ | SynPat.Tuple _, SynPat.As _
+
+ // x, (y, z)
+ // x & (y, z)
+ // (x, y) & z
+ | SynPat.Tuple _, SynPat.Tuple(isStruct = false)
+ | SynPat.Ands _, SynPat.Tuple(isStruct = false)
+
+ // A, (B | C)
+ // A & (B | C)
+ | SynPat.Tuple _, SynPat.Or _
+ | SynPat.Ands _, SynPat.Or _ -> true
+
+ // (x : int) & y
+ // x & (y : int) & z
+ | SynPat.Ands(Last(SynPat.Paren(pat = Is inner)), _), SynPat.Typed _ -> false
+ | SynPat.Ands _, SynPat.Typed _ -> true
+
+ | _, SynPat.Const _
+ | _, SynPat.Wild _
+ | _, SynPat.Named _
+ | _, SynPat.Typed _
+ | _, SynPat.LongIdent(argPats = SynArgPats.Pats [])
+ | _, SynPat.Tuple(isStruct = true)
+ | _, SynPat.Paren _
+ | _, SynPat.ArrayOrList _
+ | _, SynPat.Record _
+ | _, SynPat.Null _
+ | _, SynPat.OptionalVal _
+ | _, SynPat.IsInst _
+ | _, SynPat.QuoteExpr _
+
+ | SynPat.Or _, _
+ | SynPat.ListCons _, _
+ | SynPat.Ands _, _
+ | SynPat.As _, _
+ | SynPat.LongIdent _, _
+ | SynPat.Tuple _, _
+ | SynPat.Paren _, _
+ | SynPat.ArrayOrList _, _
+ | SynPat.Record _, _ -> false
+
+ | _ -> true
+
+ | _ -> true
diff --git a/src/Compiler/Service/SynPat.fsi b/src/Compiler/Service/SynPat.fsi
new file mode 100644
index 0000000000..77bcd9c600
--- /dev/null
+++ b/src/Compiler/Service/SynPat.fsi
@@ -0,0 +1,13 @@
+namespace FSharp.Compiler.Syntax
+
+[]
+[]
+module public SynPat =
+
+ ///
+ /// Returns true if the given pattern should be parenthesized in the given context, otherwise false.
+ ///
+ /// The pattern's ancestor nodes.
+ /// The pattern to check.
+ /// True if the given pattern should be parenthesized in the given context, otherwise false.
+ val shouldBeParenthesizedInContext: path: SyntaxVisitorPath -> pat: SynPat -> bool
diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl
index eeb6051162..b7484492a4 100644
--- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl
+++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl
@@ -4326,7 +4326,6 @@ FSharp.Compiler.EditorServices.TupledArgumentLocation: Int32 GetHashCode()
FSharp.Compiler.EditorServices.TupledArgumentLocation: Int32 GetHashCode(System.Collections.IEqualityComparer)
FSharp.Compiler.EditorServices.TupledArgumentLocation: System.String ToString()
FSharp.Compiler.EditorServices.TupledArgumentLocation: Void .ctor(Boolean, FSharp.Compiler.Text.Range)
-FSharp.Compiler.EditorServices.UnnecessaryParentheses: Microsoft.FSharp.Control.FSharpAsync`1[System.Collections.Generic.IEnumerable`1[FSharp.Compiler.Text.Range]] getUnnecessaryParentheses(Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,System.String], FSharp.Compiler.Syntax.ParsedInput)
FSharp.Compiler.EditorServices.UnresolvedSymbol: Boolean Equals(FSharp.Compiler.EditorServices.UnresolvedSymbol)
FSharp.Compiler.EditorServices.UnresolvedSymbol: Boolean Equals(System.Object)
FSharp.Compiler.EditorServices.UnresolvedSymbol: Boolean Equals(System.Object, System.Collections.IEqualityComparer)
@@ -7433,6 +7432,7 @@ FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Text.Range range
FSharp.Compiler.Syntax.SynExprAndBang: Int32 Tag
FSharp.Compiler.Syntax.SynExprAndBang: Int32 get_Tag()
FSharp.Compiler.Syntax.SynExprAndBang: System.String ToString()
+FSharp.Compiler.Syntax.SynExprModule: Boolean shouldBeParenthesizedInContext(Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,System.String], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SyntaxNode], FSharp.Compiler.Syntax.SynExpr)
FSharp.Compiler.Syntax.SynExprRecordField: FSharp.Compiler.Syntax.SynExprRecordField NewSynExprRecordField(System.Tuple`2[FSharp.Compiler.Syntax.SynLongIdent,System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Syntax.SynExpr], Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`2[FSharp.Compiler.Text.Range,Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Position]]])
FSharp.Compiler.Syntax.SynExprRecordField: Int32 Tag
FSharp.Compiler.Syntax.SynExprRecordField: Int32 get_Tag()
@@ -8446,6 +8446,7 @@ FSharp.Compiler.Syntax.SynPat: FSharp.Compiler.Text.Range get_Range()
FSharp.Compiler.Syntax.SynPat: Int32 Tag
FSharp.Compiler.Syntax.SynPat: Int32 get_Tag()
FSharp.Compiler.Syntax.SynPat: System.String ToString()
+FSharp.Compiler.Syntax.SynPatModule: Boolean shouldBeParenthesizedInContext(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SyntaxNode], FSharp.Compiler.Syntax.SynPat)
FSharp.Compiler.Syntax.SynRationalConst+Integer: FSharp.Compiler.Text.Range get_range()
FSharp.Compiler.Syntax.SynRationalConst+Integer: FSharp.Compiler.Text.Range range
FSharp.Compiler.Syntax.SynRationalConst+Integer: Int32 get_value()
diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
index 909402a2a6..780ba10f3f 100644
--- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
+++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
@@ -92,7 +92,8 @@
-
+
+
Program.fs
diff --git a/tests/FSharp.Compiler.Service.Tests/SynExprTests.fs b/tests/FSharp.Compiler.Service.Tests/SynExprTests.fs
new file mode 100644
index 0000000000..1e402a1d25
--- /dev/null
+++ b/tests/FSharp.Compiler.Service.Tests/SynExprTests.fs
@@ -0,0 +1,88 @@
+module FSharp.Compiler.Syntax.Tests.SynExpr
+
+open FSharp.Compiler.Service.Tests.Common
+open FSharp.Compiler.Syntax
+open FSharp.Compiler.Text
+open NUnit.Framework
+
+type Parenthesization = Needed | Unneeded
+
+module Parenthesization =
+ let ofBool shouldParenthesize =
+ if shouldParenthesize then Needed
+ else Unneeded
+
+let exprs: obj array list =
+ [
+ [|([] : Parenthesization list); "()"|]
+ [|[Needed]; "(1 + 2) * 3"|]
+ [|[Unneeded]; "1 + (2 * 3)"|]
+ [|[Unneeded]; "1 * (2 * 3)"|]
+ [|[Unneeded]; "(1 * 2) * 3"|]
+ [|[Needed]; "1 / (2 / 3)"|]
+ [|[Unneeded]; "(1 / 2) / 3"|]
+ [|[Unneeded]; "(printfn \"Hello, world.\")"|]
+ [|[Needed]; "let (~-) x = x in id -(<@ 3 @>)"|]
+ [|[Unneeded; Unneeded]; "let (~-) x = x in id (-(<@ 3 @>))"|]
+ [|[Unneeded]; "(())"|]
+ [|[Unneeded]; "(3)"|]
+ [|[Needed];
+ "
+ let x = (x
+ + y)
+ in x
+ "
+ |]
+ [|[Unneeded];
+ "
+ let x = (x
+ + y)
+ in x
+ "
+ |]
+ [|[Needed];
+ "
+ async {
+ return (
+ 1
+ )
+ }
+ "
+ |]
+ [|[Unneeded];
+ "
+ async {
+ return (
+ 1
+ )
+ }
+ "
+ |]
+ ]
+
+#if !NET6_0_OR_GREATER
+open System
+
+type String with
+ // This is not a true polyfill, but it suffices for the .NET Framework target.
+ member this.ReplaceLineEndings() = this.Replace("\r", "")
+#endif
+
+// `expected` represents whether each parenthesized expression, from the inside outward, requires its parentheses.
+[]
+let shouldBeParenthesizedInContext (expected: Parenthesization list) src =
+ let ast = getParseResults src
+
+ let getSourceLineStr =
+ let lines = src.ReplaceLineEndings().Split '\n'
+ Line.toZ >> Array.get lines
+
+ let actual =
+ ([], ast)
+ ||> ParsedInput.fold (fun actual path node ->
+ match node, path with
+ | SyntaxNode.SynExpr expr, SyntaxNode.SynExpr(SynExpr.Paren _) :: path ->
+ Parenthesization.ofBool (SynExpr.shouldBeParenthesizedInContext getSourceLineStr path expr) :: actual
+ | _ -> actual)
+
+ CollectionAssert.AreEqual(expected, actual)
diff --git a/tests/FSharp.Compiler.Service.Tests/SynPatTests.fs b/tests/FSharp.Compiler.Service.Tests/SynPatTests.fs
new file mode 100644
index 0000000000..42a5e8711e
--- /dev/null
+++ b/tests/FSharp.Compiler.Service.Tests/SynPatTests.fs
@@ -0,0 +1,36 @@
+module FSharp.Compiler.Syntax.Tests.SynPat
+
+open FSharp.Compiler.Service.Tests.Common
+open FSharp.Compiler.Syntax
+open NUnit.Framework
+
+type Parenthesization = Needed | Unneeded
+
+module Parenthesization =
+ let ofBool shouldParenthesize =
+ if shouldParenthesize then Needed
+ else Unneeded
+
+let pats: obj array list =
+ [
+ [|[Needed]; "match () with () -> ()"|]
+ [|[Needed]; "let (Lazy x) = lazy 1"|]
+ [|[Unneeded; Unneeded]; "let ((Lazy x)) = lazy 1"|]
+ [|[Needed; Unneeded]; "let (()) = ()"|]
+ [|[Needed; Unneeded; Unneeded]; "let ((())) = ()"|]
+ ]
+
+// `expected` represents whether each parenthesized pattern, from the inside outward, requires its parentheses.
+[]
+let shouldBeParenthesizedInContext (expected: Parenthesization list) src =
+ let ast = getParseResults src
+
+ let actual =
+ ([], ast)
+ ||> ParsedInput.fold (fun actual path node ->
+ match node, path with
+ | SyntaxNode.SynPat pat, SyntaxNode.SynPat(SynPat.Paren _) :: path ->
+ Parenthesization.ofBool (SynPat.shouldBeParenthesizedInContext path pat) :: actual
+ | _ -> actual)
+
+ CollectionAssert.AreEqual(expected, actual)
\ No newline at end of file
diff --git a/tests/FSharp.Compiler.Service.Tests/UnnecessaryParenthesesTests.fs b/tests/FSharp.Compiler.Service.Tests/UnnecessaryParenthesesTests.fs
deleted file mode 100644
index 4ccbe93e47..0000000000
--- a/tests/FSharp.Compiler.Service.Tests/UnnecessaryParenthesesTests.fs
+++ /dev/null
@@ -1,52 +0,0 @@
-module FSharp.Compiler.EditorServices.Tests.UnnecessaryParenthesesTests
-
-open FSharp.Compiler.EditorServices
-open FSharp.Compiler.Service.Tests.Common
-open NUnit.Framework
-
-let noUnneededParens =
- [
- "printfn \"Hello, world.\""
- "()"
- "(1 + 2) * 3"
- "let (~-) x = x in id -(<@ 3 @>)"
- ]
-
-[]
-let ``No results returned when there are no unnecessary parentheses`` src =
- task {
- let ast = getParseResults src
- let! unnecessaryParentheses = UnnecessaryParentheses.getUnnecessaryParentheses (fun _ -> src) ast
- Assert.IsEmpty unnecessaryParentheses
- }
-
-let unneededParens =
- [
- "(printfn \"Hello, world.\")"
- "(())"
- "(1 * 2) * 3"
- "let (~-) x = x in -(<@ 3 @>)"
- ]
-
-[]
-let ``Results returned when there are unnecessary parentheses`` src =
- task {
- let ast = getParseResults src
- let! unnecessaryParentheses = UnnecessaryParentheses.getUnnecessaryParentheses (fun _ -> src) ast
- Assert.AreEqual(1, Seq.length unnecessaryParentheses, $"Expected one range but got: %A{unnecessaryParentheses}.")
- }
-
-let nestedUnneededParens =
- [
- "((printfn \"Hello, world.\"))"
- "((3))"
- "let (~-) x = x in id (-(<@ 3 @>))"
- ]
-
-[]
-let ``Results returned for nested, potentially mutually-exclusive, unnecessary parentheses`` src =
- task {
- let ast = getParseResults src
- let! unnecessaryParentheses = UnnecessaryParentheses.getUnnecessaryParentheses (fun _ -> src) ast
- Assert.AreEqual(2, Seq.length unnecessaryParentheses, $"Expected two ranges but got: %A{unnecessaryParentheses}.")
- }
diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/UnnecessaryParenthesesDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/UnnecessaryParenthesesDiagnosticAnalyzer.fs
index bc433015ec..07201f8a0a 100644
--- a/vsintegration/src/FSharp.Editor/Diagnostics/UnnecessaryParenthesesDiagnosticAnalyzer.fs
+++ b/vsintegration/src/FSharp.Editor/Diagnostics/UnnecessaryParenthesesDiagnosticAnalyzer.fs
@@ -3,11 +3,12 @@
namespace Microsoft.VisualStudio.FSharp.Editor
open System.Composition
+open System.Collections.Generic
open System.Collections.Immutable
open System.Runtime.Caching
open System.Threading
open System.Threading.Tasks
-open FSharp.Compiler.EditorServices
+open FSharp.Compiler.Syntax
open FSharp.Compiler.Text
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics
@@ -70,13 +71,31 @@ type internal UnnecessaryParenthesesDiagnosticAnalyzer []
let getLineString line =
sourceText.Lines[Line.toZ line].ToString()
- let! unnecessaryParentheses = UnnecessaryParentheses.getUnnecessaryParentheses getLineString parseResults.ParseTree
+ let unnecessaryParentheses =
+ (HashSet Range.comparer, parseResults.ParseTree)
+ ||> ParsedInput.fold (fun ranges path node ->
+ match node with
+ | SyntaxNode.SynExpr(SynExpr.Paren(expr = inner; rightParenRange = Some _; range = range)) when
+ not (SynExpr.shouldBeParenthesizedInContext getLineString path inner)
+ ->
+ ignore (ranges.Add range)
+ ranges
+
+ | SyntaxNode.SynPat(SynPat.Paren(inner, range)) when not (SynPat.shouldBeParenthesizedInContext path inner) ->
+ ignore (ranges.Add range)
+ ranges
+
+ | _ -> ranges)
let diagnostics =
- unnecessaryParentheses
- |> Seq.map (fun range ->
- Diagnostic.Create(descriptor, RoslynHelpers.RangeToLocation(range, sourceText, document.FilePath)))
- |> Seq.toImmutableArray
+ let builder = ImmutableArray.CreateBuilder unnecessaryParentheses.Count
+
+ for range in unnecessaryParentheses do
+ builder.Add(
+ Diagnostic.Create(descriptor, RoslynHelpers.RangeToLocation(range, sourceText, document.FilePath))
+ )
+
+ builder.MoveToImmutable()
ignore (cache.Remove key)