From 8344bc8c49541ce83738d92e04e34e6f53d6a4b0 Mon Sep 17 00:00:00 2001 From: edgargonzalez Date: Fri, 26 Jun 2026 11:43:52 -0500 Subject: [PATCH 1/3] Allow closing '>' of multiline nested type arguments to align with the opener (#15171) A nested multiline type-argument list whose closing '>' was aligned with the column of the opening type name was rejected with FS0010, because the '>' at the block-start column was treated as a sequence-block separator (OBLOCKSEP). A closing '>' of a type-application is a closing bracket like ')' or ']', so add it to isSeqBlockElementContinuator (scoped to GREATER true, the type-app close, leaving the '>' comparison operator unaffected). --- .../.FSharp.Compiler.Service/11.0.100.md | 1 + src/Compiler/SyntaxTree/LexFilter.fs | 5 +++++ .../MultilineNestedTypeArguments.fs | 17 +++++++++++++++++ .../OffsideExceptions/OffsideExceptions.fs | 13 ++++++++++++- 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/MultilineNestedTypeArguments.fs diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index ba843ca702c..9b7ad651b60 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -3,6 +3,7 @@ * Semantic classification no longer marks recursive object self-references (`as this`, `let rec` self-refs) as mutable. ([Issue #5229](https://github.com/dotnet/fsharp/issues/5229)) * Fix `MethodAccessException` under `--realsig+` when a closure (inner `let rec`, `task`/`async` state machine, or quotation splice) inside a member defined in an intrinsic type augmentation (`type C with member ...`) accesses a `private` member of `C`. The synthesized closure is now nested inside the declaring type instead of beside it in the module class. ([Issue #19933](https://github.com/dotnet/fsharp/issues/19933), [PR #19955](https://github.com/dotnet/fsharp/pull/19955)) * Preserve source range for type errors on empty-bodied computation expressions (e.g. `foo {}`) in pipelines, function arguments, and type-annotated contexts, instead of reporting `unknown(1,1)`. ([Issue #19550](https://github.com/dotnet/fsharp/issues/19550), [PR #19849](https://github.com/dotnet/fsharp/pull/19849)) +* Fix multiline nested type arguments failing to parse when the closing `>` aligns with the opening type name's column. ([Issue #15171](https://github.com/dotnet/fsharp/issues/15171)) * Tooltip "Full name" now shows demangled companion module names (e.g. `MyType.func` instead of `MyTypeModule.func`). ([Issue #17335](https://github.com/dotnet/fsharp/issues/17335), [PR #19867](https://github.com/dotnet/fsharp/pull/19867)) * Fix internal error (FS0193) when calling an indexed property setter with a named argument that matches an indexer parameter. ([Issue #16034](https://github.com/dotnet/fsharp/issues/16034), [PR #19851](https://github.com/dotnet/fsharp/pull/19851)) * Fix missing FS1182 ("unused binding") warning for unused `let` function bindings inside class types. ([Issue #13849](https://github.com/dotnet/fsharp/issues/13849), [PR #19805](https://github.com/dotnet/fsharp/pull/19805)) diff --git a/src/Compiler/SyntaxTree/LexFilter.fs b/src/Compiler/SyntaxTree/LexFilter.fs index e0e450c398f..16376828996 100644 --- a/src/Compiler/SyntaxTree/LexFilter.fs +++ b/src/Compiler/SyntaxTree/LexFilter.fs @@ -383,6 +383,11 @@ let rec isSeqBlockElementContinuator token = // Shortcut.CtrlO) | END | AND | WITH | THEN | RPAREN | RBRACE _ | BAR_RBRACE | RBRACK | BAR_RBRACK | RQUOTE _ -> true + // A closing '>' of a (possibly multiline) type-argument list is a closing bracket, like ')' or ']' + // above: it may align with the first column of a sequence block without starting a new element. + // See dotnet/fsharp#15171. + | GREATER true -> true + // The following arise during reprocessing of the inserted tokens when we hit a DONE | ORIGHT_BLOCK_END _ | OBLOCKEND _ | ODECLEND (_, _) -> true | ODUMMY token -> isSeqBlockElementContinuator token diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/MultilineNestedTypeArguments.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/MultilineNestedTypeArguments.fs new file mode 100644 index 00000000000..06cf74ba3ac --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/MultilineNestedTypeArguments.fs @@ -0,0 +1,17 @@ +// #Regression #Conformance #LexFilter #Exceptions +// https://github.com/dotnet/fsharp/issues/15171 +// The closing '>' of a nested, multiline type-argument list may align with the column of the +// opening type name (here the inner 'Foo'); it must not be treated as a new sequence-block item. + +open System + +type Bar = class end +type Foo<'a> = class end + +type Terminal = + abstract onKey: + IEvent< + Foo< + Bar * int + > + > with get, set diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs index 9dc910ba249..effaefb51c8 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs @@ -9,12 +9,23 @@ open FSharp.Test.Compiler.Assertions.StructuredResultsAsserts module OffsideExceptions = + // https://github.com/dotnet/fsharp/issues/15171 + // The closing '>' of a nested multiline type-argument list may align with the opening type name. + [] + let MultilineNestedTypeArguments compilation = + compilation + |> getCompilation + |> asFsx + |> typecheck + |> shouldSucceed + |> ignore + // This test was automatically generated (moved from FSharpQA suite - Conformance/LexicalFiltering/Basic/OffsideExceptions) // [] let InfixTokenPlusOne compilation = compilation - |> getCompilation + |> getCompilation |> asFsx |> typecheck |> shouldSucceed From df6a03f140c78de9ef444c9da2352efc19c5643d Mon Sep 17 00:00:00 2001 From: edgargonzalez Date: Fri, 26 Jun 2026 12:21:19 -0500 Subject: [PATCH 2/3] Add SyntaxTree baseline for multiline nested type arguments (#15171) --- ...AppNestedMultilineClosingGreaterAligned.fs | 9 +++ ...estedMultilineClosingGreaterAligned.fs.bsl | 67 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs create mode 100644 tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs.bsl diff --git a/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs b/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs new file mode 100644 index 00000000000..8ad2005dc3a --- /dev/null +++ b/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs @@ -0,0 +1,9 @@ +type Foo<'T> = class end + +type Terminal = + abstract onKey: + Foo< + Foo< + int + > + > with get, set diff --git a/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs.bsl b/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs.bsl new file mode 100644 index 00000000000..bb10728f5c3 --- /dev/null +++ b/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs.bsl @@ -0,0 +1,67 @@ +ImplFile + (ParsedImplFileInput + ("/root/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs", false, + QualifiedNameOfFile SynTypeAppNestedMultilineClosingGreaterAligned, [], + [SynModuleOrNamespace + ([SynTypeAppNestedMultilineClosingGreaterAligned], false, AnonModule, + [Types + ([SynTypeDefn + (SynComponentInfo + ([], + Some + (PostfixList + ([SynTyparDecl + ([], SynTypar (T, None, false), [], + { AmpersandRanges = [] })], [], (1,8--1,12))), [], + [Foo], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), + true, None, (1,5--1,8)), + ObjectModel (Class, [], (1,15--1,24)), [], None, (1,5--1,24), + { LeadingKeyword = Type (1,0--1,4) + EqualsRange = Some (1,13--1,14) + WithKeyword = None })], (1,0--1,24)); + Types + ([SynTypeDefn + (SynComponentInfo + ([], None, [], [Terminal], + PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector), + false, None, (3,5--3,13)), + ObjectModel + (Unspecified, + [AbstractSlot + (SynValSig + ([], SynIdent (onKey, None), + SynValTyparDecls (None, true), + App + (LongIdent (SynLongIdent ([Foo], [], [None])), + Some (5,11--5,12), + [App + (LongIdent (SynLongIdent ([Foo], [], [None])), + Some (6,15--6,16), + [LongIdent (SynLongIdent ([int], [], [None]))], + [], Some (8,12--8,13), false, (6,12--8,13))], + [], Some (9,8--9,9), false, (5,8--9,9)), + SynValInfo ([], SynArgInfo ([], false, None)), false, + false, + PreXmlDoc ((4,4), FSharp.Compiler.Xml.XmlDocCollector), + Single None, None, (4,4--9,23), + { LeadingKeyword = Abstract (4,4--4,12) + InlineKeyword = None + WithKeyword = Some (9,10--9,14) + EqualsRange = None }), + { IsInstance = true + IsDispatchSlot = true + IsOverrideOrExplicitImpl = false + IsFinal = false + GetterOrSetterIsCompilerGenerated = false + MemberKind = PropertyGetSet }, (4,4--9,23), + { GetSetKeywords = + Some (GetSet ((9,15--9,18), (9,20--9,23))) })], + (4,4--9,23)), [], None, (3,5--9,23), + { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,14--3,15) + WithKeyword = None })], (3,0--9,23))], PreXmlDocEmpty, [], + None, (1,0--10,0), { LeadingKeyword = None })], (true, true), + { ConditionalDirectives = [] + WarnDirectives = [] + CodeComments = [] }, set [])) From b4652e4ee220c02df5321e9f86e5974512cd247c Mon Sep 17 00:00:00 2001 From: edgargonzalez Date: Sat, 27 Jun 2026 09:59:52 -0500 Subject: [PATCH 3/3] Minimize #15171 SyntaxTree test to a parse-only case --- ...AppNestedMultilineClosingGreaterAligned.fs | 12 ++-- ...estedMultilineClosingGreaterAligned.fs.bsl | 62 +++++++------------ 2 files changed, 27 insertions(+), 47 deletions(-) diff --git a/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs b/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs index 8ad2005dc3a..dddf7143e80 100644 --- a/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs +++ b/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs @@ -1,9 +1,7 @@ -type Foo<'T> = class end - -type Terminal = - abstract onKey: - Foo< - Foo< +type T = + abstract M: + A< + B< int > - > with get, set + > diff --git a/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs.bsl b/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs.bsl index bb10728f5c3..a33d80c9764 100644 --- a/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs.bsl +++ b/tests/service/data/SyntaxTree/SynType/SynTypeAppNestedMultilineClosingGreaterAligned.fs.bsl @@ -7,61 +7,43 @@ ImplFile [Types ([SynTypeDefn (SynComponentInfo - ([], - Some - (PostfixList - ([SynTyparDecl - ([], SynTypar (T, None, false), [], - { AmpersandRanges = [] })], [], (1,8--1,12))), [], - [Foo], + ([], None, [], [T], PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), - true, None, (1,5--1,8)), - ObjectModel (Class, [], (1,15--1,24)), [], None, (1,5--1,24), - { LeadingKeyword = Type (1,0--1,4) - EqualsRange = Some (1,13--1,14) - WithKeyword = None })], (1,0--1,24)); - Types - ([SynTypeDefn - (SynComponentInfo - ([], None, [], [Terminal], - PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector), - false, None, (3,5--3,13)), + false, None, (1,5--1,6)), ObjectModel (Unspecified, [AbstractSlot (SynValSig - ([], SynIdent (onKey, None), + ([], SynIdent (M, None), SynValTyparDecls (None, true), App - (LongIdent (SynLongIdent ([Foo], [], [None])), - Some (5,11--5,12), + (LongIdent (SynLongIdent ([A], [], [None])), + Some (3,9--3,10), [App - (LongIdent (SynLongIdent ([Foo], [], [None])), - Some (6,15--6,16), + (LongIdent (SynLongIdent ([B], [], [None])), + Some (4,13--4,14), [LongIdent (SynLongIdent ([int], [], [None]))], - [], Some (8,12--8,13), false, (6,12--8,13))], - [], Some (9,8--9,9), false, (5,8--9,9)), + [], Some (6,12--6,13), false, (4,12--6,13))], + [], Some (7,8--7,9), false, (3,8--7,9)), SynValInfo ([], SynArgInfo ([], false, None)), false, false, - PreXmlDoc ((4,4), FSharp.Compiler.Xml.XmlDocCollector), - Single None, None, (4,4--9,23), - { LeadingKeyword = Abstract (4,4--4,12) + PreXmlDoc ((2,4), FSharp.Compiler.Xml.XmlDocCollector), + Single None, None, (2,4--7,9), + { LeadingKeyword = Abstract (2,4--2,12) InlineKeyword = None - WithKeyword = Some (9,10--9,14) + WithKeyword = None EqualsRange = None }), { IsInstance = true IsDispatchSlot = true IsOverrideOrExplicitImpl = false IsFinal = false GetterOrSetterIsCompilerGenerated = false - MemberKind = PropertyGetSet }, (4,4--9,23), - { GetSetKeywords = - Some (GetSet ((9,15--9,18), (9,20--9,23))) })], - (4,4--9,23)), [], None, (3,5--9,23), - { LeadingKeyword = Type (3,0--3,4) - EqualsRange = Some (3,14--3,15) - WithKeyword = None })], (3,0--9,23))], PreXmlDocEmpty, [], - None, (1,0--10,0), { LeadingKeyword = None })], (true, true), - { ConditionalDirectives = [] - WarnDirectives = [] - CodeComments = [] }, set [])) + MemberKind = PropertyGet }, (2,4--7,9), + { GetSetKeywords = None })], (2,4--7,9)), [], None, + (1,5--7,9), { LeadingKeyword = Type (1,0--1,4) + EqualsRange = Some (1,7--1,8) + WithKeyword = None })], (1,0--7,9))], + PreXmlDocEmpty, [], None, (1,0--8,0), { LeadingKeyword = None })], + (true, true), { ConditionalDirectives = [] + WarnDirectives = [] + CodeComments = [] }, set []))