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 1db6639b99b..6a4d44d2aec 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md @@ -4,6 +4,7 @@ * Code generated files with > 64K methods and generated symbols crash when loaded. Use infered sequence points for debugging. ([Issue #16399](https://github.com/dotnet/fsharp/issues/16399), [#PR 16514](https://github.com/dotnet/fsharp/pull/16514)) * `nameof Module` expressions and patterns are processed to link files in `--test:GraphBasedChecking`. ([PR #16550](https://github.com/dotnet/fsharp/pull/16550)) * Graph Based Checking doesn't throw on invalid parsed input so it can be used for IDE scenarios ([PR #16575](https://github.com/dotnet/fsharp/pull/16575), [PR #16588](https://github.com/dotnet/fsharp/pull/16588), [PR #16643](https://github.com/dotnet/fsharp/pull/16643)) +* Various parenthesization API fixes. ([PR #16578](https://github.com/dotnet/fsharp/pull/16578), [PR #16666](https://github.com/dotnet/fsharp/pull/16666)) * Keep parens for problematic exprs (`if`, `match`, etc.) in `$"{(…):N0}"`, `$"{(…),-3}"`, etc. ([PR #16578](https://github.com/dotnet/fsharp/pull/16578)) * Fix crash in DOTNET_SYSTEM_GLOBALIZATION_INVARIANT mode ([PR #16471](https://github.com/dotnet/fsharp/pull/16471)) * `[]` member should not produce property symbol. ([Issue #16640](https://github.com/dotnet/fsharp/issues/16640), [PR #16658](https://github.com/dotnet/fsharp/pull/16658)) diff --git a/docs/release-notes/.VisualStudio/17.10.md b/docs/release-notes/.VisualStudio/17.10.md index 0045b1bd64b..a4b0a8a9f0d 100644 --- a/docs/release-notes/.VisualStudio/17.10.md +++ b/docs/release-notes/.VisualStudio/17.10.md @@ -1,6 +1,7 @@ ### Fixed * Show signature help mid-pipeline in more scenarios. ([PR #16462](https://github.com/dotnet/fsharp/pull/16462)) +* Various unneeded parentheses code fix improvements. ([PR #16578](https://github.com/dotnet/fsharp/pull/16578), [PR #16666](https://github.com/dotnet/fsharp/pull/16666)) ### Changed diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fs b/src/Compiler/Service/ServiceParseTreeWalk.fs index ec5b623d0b8..d0c0132dc6b 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fs +++ b/src/Compiler/Service/ServiceParseTreeWalk.fs @@ -1163,8 +1163,8 @@ module SyntaxNode = [] [] -module ParsedInput = - let fold folder state (parsedInput: ParsedInput) = +module internal SyntaxNodes = + let fold folder state (ast: SyntaxNode list) = let mutable state = state let visitor = @@ -1270,7 +1270,6 @@ module ParsedInput = loop diveResults - let ast = parsedInput.Contents let m = (range0, ast) ||> List.fold (fun acc node -> unionRanges acc node.Range) ignore (SyntaxTraversal.traverseUntil pickAll m.End visitor ast) state @@ -1454,7 +1453,7 @@ module ParsedInput = ignore (SyntaxTraversal.traverseUntil pick pos visitor ast) state - let foldWhile folder state (parsedInput: ParsedInput) = + let foldWhile folder state (ast: SyntaxNode list) = let pickAll _ _ _ diveResults = let rec loop diveResults = match diveResults with @@ -1465,11 +1464,10 @@ module ParsedInput = loop diveResults - let ast = parsedInput.Contents let m = (range0, ast) ||> List.fold (fun acc node -> unionRanges acc node.Range) foldWhileImpl pickAll m.End folder state ast - let tryPick chooser position (parsedInput: ParsedInput) = + let tryPick chooser position (ast: SyntaxNode list) = let visitor = { new SyntaxVisitorBase<'T>() with member _.VisitExpr(path, _, defaultTraverse, expr) = @@ -1545,25 +1543,46 @@ module ParsedInput = | _ -> None } - SyntaxTraversal.traverseUntil SyntaxTraversal.pick position visitor parsedInput.Contents + SyntaxTraversal.traverseUntil SyntaxTraversal.pick position visitor ast - let tryPickLast chooser position (parsedInput: ParsedInput) = - (None, parsedInput.Contents) + let tryPickLast chooser position (ast: SyntaxNode list) = + (None, ast) ||> foldWhileImpl SyntaxTraversal.pick position (fun prev path node -> match chooser path node with | Some _ as next -> Some next | None -> Some prev) - let tryNode position (parsedInput: ParsedInput) = + let tryNode position (ast: SyntaxNode list) = let Matching = Some - (None, parsedInput.Contents) + (None, ast) ||> foldWhileImpl SyntaxTraversal.pick position (fun _prev path node -> if rangeContainsPos node.Range position then Some(Matching(node, path)) else None) - let exists predicate position parsedInput = - tryPick (fun path node -> if predicate path node then Some() else None) position parsedInput + let exists predicate position ast = + tryPick (fun path node -> if predicate path node then Some() else None) position ast |> Option.isSome + +[] +[] +module ParsedInput = + let fold folder state (parsedInput: ParsedInput) = + SyntaxNodes.fold folder state parsedInput.Contents + + let foldWhile folder state (parsedInput: ParsedInput) = + SyntaxNodes.foldWhile folder state parsedInput.Contents + + let tryPick chooser position (parsedInput: ParsedInput) = + SyntaxNodes.tryPick chooser position parsedInput.Contents + + let tryPickLast chooser position (parsedInput: ParsedInput) = + SyntaxNodes.tryPickLast chooser position parsedInput.Contents + + let tryNode position (parsedInput: ParsedInput) = + SyntaxNodes.tryNode position parsedInput.Contents + + let exists predicate position (parsedInput: ParsedInput) = + SyntaxNodes.exists predicate position parsedInput.Contents diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fsi b/src/Compiler/Service/ServiceParseTreeWalk.fsi index 86ca17380ee..ab9e98f6e81 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fsi +++ b/src/Compiler/Service/ServiceParseTreeWalk.fsi @@ -219,6 +219,135 @@ module SyntaxNode = /// val (|Attributes|): node: SyntaxNode -> SynAttributes +/// +/// Holds operations for working with the untyped abstract syntax tree. +/// +[] +[] +module internal SyntaxNodes = + /// + /// Applies the given predicate to each node of the AST and its context (path) + /// down to a given position, returning true if a matching node is found, otherwise false. + /// Traversal is short-circuited if no matching node is found through the given position. + /// + /// The predicate to match each node against. + /// The position in the input file down to which to apply the function. + /// The AST to search. + /// True if a matching node is found, or false if no matching node is found. + /// + /// + /// let isInTypeDefn = + /// (pos, ast) + /// ||> SyntaxNodes.exists (fun _path node -> + /// match node with + /// | SyntaxNode.SynTypeDefn _ -> true + /// | _ -> false) + /// + /// + val exists: predicate: (SyntaxVisitorPath -> SyntaxNode -> bool) -> position: pos -> ast: SyntaxNode list -> bool + + /// + /// Applies a function to each node of the AST and its context (path), + /// threading an accumulator through the computation. + /// + /// The function to use to update the state given each node and its context. + /// The initial state. + /// The AST to fold over. + /// The final state. + /// + /// + /// let unnecessaryParentheses = + /// (HashSet Range.comparer, ast) ||> SyntaxNodes.fold (fun acc path node -> + /// match node with + /// | SyntaxNode.SynExpr (SynExpr.Paren (expr = inner; rightParenRange = Some _; range = range)) when + /// not (SynExpr.shouldBeParenthesizedInContext getLineString path inner) + /// -> + /// ignore (acc.Add range) + /// acc + /// + /// | SyntaxNode.SynPat (SynPat.Paren (inner, range)) when + /// not (SynPat.shouldBeParenthesizedInContext path inner) + /// -> + /// ignore (acc.Add range) + /// acc + /// + /// | _ -> acc) + /// + /// + val fold: + folder: ('State -> SyntaxVisitorPath -> SyntaxNode -> 'State) -> state: 'State -> ast: SyntaxNode list -> 'State + + /// + /// Applies a function to each node of the AST and its context (path) + /// until the folder returns None, threading an accumulator through the computation. + /// + /// The function to use to update the state given each node and its context, or to stop traversal by returning None. + /// The initial state. + /// The AST to fold over. + /// The final state. + val foldWhile: + folder: ('State -> SyntaxVisitorPath -> SyntaxNode -> 'State option) -> + state: 'State -> + ast: SyntaxNode list -> + 'State + + /// + /// Dives to the deepest node that contains the given position, + /// returning the node and its path if found, or None if no + /// node contains the position. + /// + /// The position in the input file down to which to dive. + /// The AST to search. + /// The deepest node containing the given position, along with the path taken through the node's ancestors to find it. + val tryNode: position: pos -> ast: SyntaxNode list -> (SyntaxNode * SyntaxVisitorPath) option + + /// + /// Applies the given function to each node of the AST and its context (path) + /// down to a given position, returning Some x for the first node + /// for which the function returns Some x for some value x, otherwise None. + /// Traversal is short-circuited if no matching node is found through the given position. + /// + /// The function to apply to each node and its context to derive an optional value. + /// The position in the input file down to which to apply the function. + /// The AST to search. + /// The first value for which the function returns Some, or None if no matching node is found. + /// + /// + /// let range = + /// (pos, ast) ||> SyntaxNodes.tryPick (fun _path node -> + /// match node with + /// | SyntaxNode.SynExpr (SynExpr.InterpolatedString (range = range)) when + /// rangeContainsPos range pos + /// -> Some range + /// | _ -> None) + /// + /// + val tryPick: + chooser: (SyntaxVisitorPath -> SyntaxNode -> 'T option) -> position: pos -> ast: SyntaxNode list -> 'T option + + /// + /// Applies the given function to each node of the AST and its context (path) + /// down to a given position, returning Some x for the last (deepest) node + /// for which the function returns Some x for some value x, otherwise None. + /// Traversal is short-circuited if no matching node is found through the given position. + /// + /// The function to apply to each node and its context to derive an optional value. + /// The position in the input file down to which to apply the function. + /// The AST to search. + /// The last (deepest) value for which the function returns Some, or None if no matching node is found. + /// + /// + /// let range = + /// (pos, ast) + /// ||> SyntaxNodes.tryPickLast (fun path node -> + /// match node, path with + /// | FuncIdent range -> Some range + /// | _ -> None) + /// + /// + val tryPickLast: + chooser: (SyntaxVisitorPath -> SyntaxNode -> 'T option) -> position: pos -> ast: SyntaxNode list -> 'T option + /// /// Holds operations for working with the /// untyped abstract syntax tree (). diff --git a/src/Compiler/Service/SynExpr.fs b/src/Compiler/Service/SynExpr.fs index 41dbd4d4d18..07321ce1646 100644 --- a/src/Compiler/Service/SynExpr.fs +++ b/src/Compiler/Service/SynExpr.fs @@ -774,6 +774,23 @@ module SynExpr = loop clauses + let innerBindingsWouldShadowOuter expr1 (expr2: SynExpr) = + let identsBoundInInner = + (Set.empty, [ SyntaxNode.SynExpr expr1 ]) + ||> SyntaxNodes.fold (fun idents _path node -> + match node with + | SyntaxNode.SynPat(SynPat.Named(ident = SynIdent(ident = ident))) -> idents.Add ident.idText + | _ -> idents) + + if identsBoundInInner.IsEmpty then + false + else + (expr2.Range.End, [ SyntaxNode.SynExpr expr2 ]) + ||> SyntaxNodes.exists (fun _path node -> + match node with + | SyntaxNode.SynExpr(SynExpr.Ident ident) -> identsBoundInInner.Contains ident.idText + | _ -> false) + match outer, inner with | ConfusableWithTypeApp, _ -> true @@ -812,6 +829,21 @@ module SynExpr = -> true + // Keep parens if a name in the outer scope is rebound + // in the inner scope and would shadow the outer binding + // if the parens were removed, e.g.: + // + // let x = 3 + // ( + // let x = 4 + // printfn $"{x}" + // ) + // x + | SynExpr.Sequential(expr1 = SynExpr.Paren(expr = Is inner); expr2 = expr2), _ when innerBindingsWouldShadowOuter inner expr2 -> + true + + | SynExpr.InterpolatedString _, SynExpr.Sequential _ -> true + | SynExpr.InterpolatedString(contents = contents), (SynExpr.Tuple(isStruct = false) | Dangling.Problematic _) -> contents |> List.exists (function diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs index 5b4e0e994f1..60cbf8ccd76 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs @@ -28,7 +28,38 @@ module private Patterns = [] module SourceText = - /// Returns true if the given span contains an expression + /// E.g., something like: + /// + /// let … = (␤ + /// … + /// ) + [] + let (|TrailingOpen|_|) (span: TextSpan) (sourceText: SourceText) = + let linePosition = sourceText.Lines.GetLinePosition span.Start + let line = (sourceText.Lines.GetLineFromPosition span.Start).ToString() + + if + line.AsSpan(0, linePosition.Character).LastIndexOfAnyExcept(' ', '(') >= 0 + && line.AsSpan(linePosition.Character).IndexOfAnyExcept('(', ' ') < 0 + then + ValueSome TrailingOpen + else + ValueNone + + /// Trim only spaces from the start if there is something else + /// before the open paren on the same line (or else we could move + /// the whole inner expression up a line); otherwise trim all whitespace + // from start and end. + let (|Trim|) (span: TextSpan) (sourceText: SourceText) = + let linePosition = sourceText.Lines.GetLinePosition span.Start + let line = (sourceText.Lines.GetLineFromPosition span.Start).ToString() + + if line.AsSpan(0, linePosition.Character).LastIndexOfAnyExcept(' ', '(') >= 0 then + fun (s: string) -> s.TrimEnd().TrimStart ' ' + else + fun (s: string) -> s.Trim() + + /// Returns the offsides diff if the given span contains an expression /// whose indentation would be made invalid if the open paren /// were removed (because the offside line would be shifted), e.g., /// @@ -43,70 +74,32 @@ module private Patterns = /// // Valid. /// ◌let x = 2 /// x◌ - let containsSensitiveIndentation (span: TextSpan) (sourceText: SourceText) = + [] + let (|OffsidesDiff|_|) (span: TextSpan) (sourceText: SourceText) = let startLinePosition = sourceText.Lines.GetLinePosition span.Start let endLinePosition = sourceText.Lines.GetLinePosition span.End - let startLine = startLinePosition.Line - let startCol = startLinePosition.Character - let endLine = endLinePosition.Line + let startLineNo = startLinePosition.Line + let endLineNo = endLinePosition.Line - if startLine = endLine then - false + if startLineNo = endLineNo then + ValueNone else - let rec loop offsides lineNo startCol = - if lineNo <= endLine then + let rec loop innerOffsides lineNo startCol = + if lineNo <= endLineNo then let line = sourceText.Lines[lineNo].ToString() - match offsides with - | ValueNone -> - let i = line.AsSpan(startCol).IndexOfAnyExcept(' ', ')') - - if i >= 0 then - loop (ValueSome(i + startCol)) (lineNo + 1) 0 - else - loop offsides (lineNo + 1) 0 - - | ValueSome offsidesCol -> - let i = line.AsSpan(0, min offsidesCol line.Length).IndexOfAnyExcept(' ', ')') - i <= offsidesCol || loop offsides (lineNo + 1) 0 + match line.AsSpan(startCol).IndexOfAnyExcept(' ', ')') with + | -1 -> loop innerOffsides (lineNo + 1) 0 + | i -> loop (i + startCol) (lineNo + 1) 0 else - false + ValueSome(startLinePosition.Character - innerOffsides) - loop ValueNone startLine startCol + loop startLinePosition.Character startLineNo (startLinePosition.Character + 1) - let hasPrecedingConstructOnSameLine (span: TextSpan) (sourceText: SourceText) = - let linePosition = sourceText.Lines.GetLinePosition span.Start - let line = (sourceText.Lines.GetLineFromPosition span.Start).ToString() - line.AsSpan(0, linePosition.Character).LastIndexOfAnyExcept(' ', '(') >= 0 - - let followingLineMovesOffsidesRightward (span: TextSpan) (sourceText: SourceText) = - let startLinePosition = sourceText.Lines.GetLinePosition span.Start - let startLine = startLinePosition.Line - let endLinePosition = sourceText.Lines.GetLinePosition span.End - let endLine = endLinePosition.Line - let offsides = startLinePosition.Character - - let rec loop lineNo = - if lineNo <= endLine then - let line = sourceText.Lines[lineNo].ToString().AsSpan() - let i = line.IndexOfAnyExcept("*/%-+:^@><=!|0$.?) ".AsSpan()) - i > offsides || loop (lineNo + 1) - else - false - - loop (startLine + 1) - - [] - let (|ContainsSensitiveIndentation|_|) span sourceText = - toPat (containsSensitiveIndentation span) sourceText - - [] - let (|HasPrecedingConstructOnSameLine|_|) span sourceText = - toPat (hasPrecedingConstructOnSameLine span) sourceText - - [] - let (|FollowingLineMovesOffsidesRightward|_|) span sourceText = - toPat (followingLineMovesOffsidesRightward span) sourceText + let (|ShiftLeft|NoShift|ShiftRight|) n = + if n < 0 then ShiftLeft -n + elif n = 0 then NoShift + else ShiftRight n [] type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [] () = @@ -154,52 +147,51 @@ type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [ - let (|ShouldPutSpaceBefore|_|) (s: string) = - // "……(……)" - // ↑↑ ↑ - match sourceText[max (context.Span.Start - 2) 0], sourceText[max (context.Span.Start - 1) 0], s[1] with - | _, _, ('\n' | '\r') -> None - | '[', '|', (Punctuation | LetterOrDigit) -> None - | _, '[', '<' -> Some ShouldPutSpaceBefore - | _, ('(' | '[' | '{'), _ -> None - | _, '>', _ -> Some ShouldPutSpaceBefore - | ' ', '=', _ -> Some ShouldPutSpaceBefore - | _, '=', ('(' | '[' | '{') -> None - | _, '=', (Punctuation | Symbol) -> Some ShouldPutSpaceBefore - | _, LetterOrDigit, '(' -> None - | _, (LetterOrDigit | '`'), _ -> Some ShouldPutSpaceBefore - | _, (Punctuation | Symbol), (Punctuation | Symbol) -> Some ShouldPutSpaceBefore - | _ -> None - - let (|ShouldPutSpaceAfter|_|) (s: string) = - // "(……)…" - // ↑ ↑ - match s[s.Length - 2], sourceText[min context.Span.End (sourceText.Length - 1)] with - | '>', ('|' | ']') -> Some ShouldPutSpaceAfter - | _, (')' | ']' | '[' | '}' | '.' | ';' | ',' | '|') -> None - | (Punctuation | Symbol), (Punctuation | Symbol | LetterOrDigit) -> Some ShouldPutSpaceAfter - | LetterOrDigit, LetterOrDigit -> Some ShouldPutSpaceAfter - | _ -> None - - let (|NewOffsidesOnFirstLine|_|) (s: string) = - let s = s.AsSpan 1 // (… - let newline = s.IndexOfAny('\n', '\r') - - if newline < 0 || s.Slice(0, newline).IndexOfAnyExcept(@"\r\n ".AsSpan()) >= 0 then - Some NewOffsidesOnFirstLine - else - None + let adjusted = + match sourceText with + | TrailingOpen context.Span -> txt[1 .. txt.Length - 2].TrimEnd() + + | Trim context.Span trim & OffsidesDiff context.Span spaces -> + match spaces with + | NoShift -> trim txt[1 .. txt.Length - 2] + | ShiftLeft spaces -> trim (txt[1 .. txt.Length - 2].Replace("\n" + String(' ', spaces), "\n")) + | ShiftRight spaces -> trim (txt[1 .. txt.Length - 2].Replace("\n", "\n" + String(' ', spaces))) + + | _ -> txt[1 .. txt.Length - 2].Trim() let newText = - match txt, sourceText with - | ShouldPutSpaceBefore & ShouldPutSpaceAfter, _ -> " " + txt[1 .. txt.Length - 2] + " " - | ShouldPutSpaceBefore, _ -> " " + txt[1 .. txt.Length - 2] - | ShouldPutSpaceAfter, _ -> txt[1 .. txt.Length - 2] + " " - | NewOffsidesOnFirstLine, - ContainsSensitiveIndentation context.Span & (HasPrecedingConstructOnSameLine context.Span | FollowingLineMovesOffsidesRightward context.Span) -> - txt[1 .. txt.Length - 2].Replace("\n ", "\n") - | NewOffsidesOnFirstLine, ContainsSensitiveIndentation context.Span -> " " + txt[1 .. txt.Length - 2] - | _ -> txt[1 .. txt.Length - 2] + let (|ShouldPutSpaceBefore|_|) (s: string) = + // "……(……)" + // ↑↑ ↑ + match sourceText[max (context.Span.Start - 2) 0], sourceText[max (context.Span.Start - 1) 0], s[0] with + | _, _, ('\n' | '\r') -> None + | '[', '|', (Punctuation | LetterOrDigit) -> None + | _, '[', '<' -> Some ShouldPutSpaceBefore + | _, ('(' | '[' | '{'), _ -> None + | _, '>', _ -> Some ShouldPutSpaceBefore + | ' ', '=', _ -> Some ShouldPutSpaceBefore + | _, '=', ('(' | '[' | '{') -> None + | _, '=', (Punctuation | Symbol) -> Some ShouldPutSpaceBefore + | _, LetterOrDigit, '(' -> None + | _, (LetterOrDigit | '`'), _ -> Some ShouldPutSpaceBefore + | _, (Punctuation | Symbol), (Punctuation | Symbol) -> Some ShouldPutSpaceBefore + | _ -> None + + let (|ShouldPutSpaceAfter|_|) (s: string) = + // "(……)…" + // ↑ ↑ + match s[s.Length - 1], sourceText[min context.Span.End (sourceText.Length - 1)] with + | '>', ('|' | ']') -> Some ShouldPutSpaceAfter + | _, (')' | ']' | '[' | '}' | '.' | ';' | ',' | '|') -> None + | (Punctuation | Symbol), (Punctuation | Symbol | LetterOrDigit) -> Some ShouldPutSpaceAfter + | LetterOrDigit, LetterOrDigit -> Some ShouldPutSpaceAfter + | _ -> None + + match adjusted with + | ShouldPutSpaceBefore & ShouldPutSpaceAfter -> " " + adjusted + " " + | ShouldPutSpaceBefore -> " " + adjusted + | ShouldPutSpaceAfter -> adjusted + " " + | adjusted -> adjusted return ValueSome diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index cd3bbdd2bfc..ff57ffac6d8 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -244,7 +244,6 @@ let _ = async { return 1 - } " @@ -343,7 +342,7 @@ let _ = " let x = 2 - + 2 + + 2 in x " @@ -362,9 +361,9 @@ let _ = 2 - - - + 2 + + + + 2 in x " @@ -399,7 +398,7 @@ let _ = ", " let x = 2 - + 2 + + 2 in x " @@ -410,7 +409,7 @@ let _ = ", " let x = 2 - + 2 + + 2 in x " @@ -421,7 +420,7 @@ let _ = ", " let x = x - +y + +y in x " @@ -443,7 +442,7 @@ let _ = ", " let x = 2 - <<< 2 + <<< 2 in x " @@ -456,7 +455,7 @@ in x " let (<<<<<<<<) = (<<<) let x = 2 -<<<<<<<< 2 + <<<<<<<< 2 in x " @@ -506,7 +505,7 @@ in x let y = 2 - + 2 + + 2 in x + y " @@ -533,8 +532,7 @@ in x " x < 2 - + 3 - + + 3 " // LetOrUse @@ -543,6 +541,83 @@ in x "let x = 3 in let y =(4) in x + y", "let x = 3 in let y = 4 in x + y" "let x = 3 in let y=(4) in x + y", "let x = 3 in let y=4 in x + y" + " + let _ = + let _ = 3 + ( + 44 + ) + ", + " + let _ = + let _ = 3 + 44 + " + + " + let (++++++) = (+) + let x = + () + ( + 2 + ++++++ 2 + ) + in x + ", + " + let (++++++) = (+) + let x = + () + 2 + ++++++ 2 + in x + " + + " + let (!+++++) = (+) + let x = + () + ( + !+++++2 + ) + in x + ", + " + let (!+++++) = (+) + let x = + () + !+++++2 + in x + " + + " + let _ = + ( + printfn \"\" + +2 + ) + ", + " + let _ = + printfn \"\" + +2 + " + + " + let _ = + let x = 3 + ( + let y = 99 + y - x + ) + ", + " + let _ = + let x = 3 + let y = 99 + y - x + " + // TryWith "try (raise null) with _ -> reraise ()", "try raise null with _ -> reraise ()" "try raise null with (_) -> reraise ()", "try raise null with _ -> reraise ()" @@ -569,6 +644,82 @@ in x """ printfn "1"; (printfn "2") """, """ printfn "1"; printfn "2" """ "let x = 3; (5) in x", "let x = 3; 5 in x" + " + let _ = + let y = 100 + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + y + ", + " + let _ = + let y = 100 + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + y + " + + " + let _ = + let y = 100 + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + ", + " + let _ = + let y = 100 + let x = 3 + let y = 99 + ignore (y - x) + x + " + + " + let f y = + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + y + ", + " + let f y = + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + y + " + + " + let f y = + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + ", + " + let f y = + let x = 3 + let y = 99 + ignore (y - x) + x + " + // IfThenElse "if (3 = 3) then 3 else 3", "if 3 = 3 then 3 else 3" "if 3 = 3 then (3) else 3", "if 3 = 3 then 3 else 3" @@ -655,7 +806,7 @@ in x """ let mutable x = 3 x <- 3 - <<< 3 + <<< 3 """ // DotIndexedGet @@ -818,6 +969,10 @@ in x "$\"{(1, 2):N0}\"", "$\"{(1, 2):N0}\"" "$\"{(1, 2),-3}\"", "$\"{(1, 2),-3}\"" + "$\"{((); ())}\"", "$\"{((); ())}\"" + "$\"{(do (); ())}\"", "$\"{do (); ()}\"" + "$\"{(let x = 3 in ignore x; 99)}\"", "$\"{let x = 3 in ignore x; 99}\"" + """ $"{(3 + LanguagePrimitives.GenericZero):N0}" """,