diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index abac577c5..3888e1c05 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -5,6 +5,7 @@
### Fixed
* Fix tooltip not being interactive: moving the mouse from a code token into its tooltip now keeps the tooltip open, allowing users to select and copy the tooltip text. [#949](https://github.com/fsprojects/FSharp.Formatting/issues/949)
* `fsdocs watch` rebuilds a page when a file its script depends on changes, following `#load` transitively and `#r` to local files, wherever those files are (a dot folder, outside the input folder). The directives are read from the syntax tree, so a `#load` in a comment does not count. [#1309](https://github.com/fsprojects/FSharp.Formatting/issues/1309)
+* API docs keep the comment of a type abbreviation whose target is a tuple, a list or a BCL type such as `string`, instead of dropping it with "The entity ... was not registered before". An abbreviation page no longer lists the members of the target type (`Item1`, `Item2`, ...) as its own. [#1314](https://github.com/fsprojects/FSharp.Formatting/issues/1314)
## [23.0.0-alpha.1] - 2026-09-08
diff --git a/src/FSharp.Formatting.ApiDocs/CrossReferenceResolver.fs b/src/FSharp.Formatting.ApiDocs/CrossReferenceResolver.fs
index d259e3e84..85fe2f3f4 100644
--- a/src/FSharp.Formatting.ApiDocs/CrossReferenceResolver.fs
+++ b/src/FSharp.Formatting.ApiDocs/CrossReferenceResolver.fs
@@ -209,8 +209,12 @@ type internal CrossReferenceResolver(root, collectionName, qualify, extensions)
for nested in entity.NestedEntities do
registerEntity nested
- for memb in entity.TryGetMembersFunctionsAndValues() do
- registerMember memb
+ // A type abbreviation has no members of its own: the compiler reports the members of the
+ // abbreviated type (e.g. System.Tuple.Item1), whose declaring entity is not part of this
+ // documentation set. Registering them would make them look local.
+ if not entity.IsFSharpAbbreviation then
+ for memb in entity.TryGetMembersFunctionsAndValues() do
+ registerMember memb
/// Returns the previously-assigned URL base name for a registered entity,
/// raising an exception if the entity has not been registered.
@@ -473,13 +477,19 @@ type internal CrossReferenceResolver(root, collectionName, qualify, extensions)
match mfv.DeclaringEntity with
| None -> failwith $"%s{mfv.DisplayName} does not have a DeclaringEntity"
| Some declaringEntity ->
- let entityUrlBaseName = getUrlBaseNameForRegisteredEntity declaringEntity
-
- {
- IsInternal = true
- ReferenceLink = internalCrossReferenceForMember entityUrlBaseName mfv
- NiceName = declaringEntity.DisplayName + "." + mfv.DisplayName
- }
+ match registeredSymbolsToUrlBaseName.TryGetValue(declaringEntity) with
+ | true, entityUrlBaseName ->
+ {
+ IsInternal = true
+ ReferenceLink = internalCrossReferenceForMember entityUrlBaseName mfv
+ NiceName = declaringEntity.DisplayName + "." + mfv.DisplayName
+ }
+ | _ ->
+ // The declaring entity is not part of this documentation set (e.g. a member of
+ // System.Tuple reached through a type abbreviation), so link externally instead.
+ let typeName = defaultArg declaringEntity.TryFullName declaringEntity.DisplayName
+ let memberName = typeName + "." + mfv.DisplayName
+ externalDocsLink true (declaringEntity.DisplayName + "." + mfv.DisplayName) typeName memberName
/// Tries to resolve a cross-reference for a member given its XML doc signature
/// (must start with "M:", "P:", "F:", or "E:").
diff --git a/src/FSharp.Formatting.ApiDocs/SymbolReader.fs b/src/FSharp.Formatting.ApiDocs/SymbolReader.fs
index 019226f8e..655d9e7ae 100644
--- a/src/FSharp.Formatting.ApiDocs/SymbolReader.fs
+++ b/src/FSharp.Formatting.ApiDocs/SymbolReader.fs
@@ -1014,9 +1014,14 @@ module internal SymbolReader =
readCommentsInto typ ctx xmlDocSig (fun cat catidx exclude _cmds comment ->
let entityUrl = ctx.UrlMap.ResolveUrlBaseNameForEntity typ
+ // A type abbreviation declares no members of its own. The compiler reports the
+ // members of the abbreviated type (e.g. Item1/Item2 for a tuple), which belong to
+ // and are documented on that target type, so they must not be listed here.
let rec getMembers (typ: FSharpEntity) =
[
- yield! typ.MembersFunctionsAndValues
+ if not typ.IsFSharpAbbreviation then
+ yield! typ.MembersFunctionsAndValues
+
match typ.BaseType with
| Some baseType ->
let loc = typ.DeclarationLocation
diff --git a/tests/FSharp.ApiDocs.Tests/ApiDocsTests.fs b/tests/FSharp.ApiDocs.Tests/ApiDocsTests.fs
index 05ba18177..f7ac24bbc 100644
--- a/tests/FSharp.ApiDocs.Tests/ApiDocsTests.fs
+++ b/tests/FSharp.ApiDocs.Tests/ApiDocsTests.fs
@@ -519,6 +519,37 @@ let ``ApiDocs InheritedMembers is populated for derived types (issue 590)`` () =
memberNames |> shouldContain "BaseMethod"
memberNames |> shouldContain "BaseStaticMethod"
+[]
+let ``ApiDocs reads comments of type abbreviations to tuples, lists and BCL types (issue 1314)`` () =
+ let libraries = [ testBin > "FsLib2.dll" ]
+
+ // warn=true is what triggers the cross-reference lookup that used to throw
+ let inputs = [ for lib in libraries -> ApiDocInput.FromFile(lib, mdcomments = false, warn = true) ]
+
+ let model =
+ ApiDocs.GenerateModel(inputs, collectionName = "FsLib", substitutions = substitutions, libDirs = [ testBin ])
+
+ let abbreviations =
+ model.Collection.Namespaces.[0].Entities
+ |> List.find (fun e -> e.Name = "Abbreviations")
+
+ let find name =
+ abbreviations.NestedEntities |> List.find (fun e -> e.Name = name)
+
+ let expectations =
+ [
+ "Position01", "A position as a zero-based line and column"
+ "LongIdent", "A long identifier"
+ "FileName", "A file name"
+ ]
+
+ for name, summary in expectations do
+ let entity = find name
+ entity.Comment.Summary.HtmlText |> shouldContainText summary
+ entity.AbbreviatedType.IsSome |> shouldEqual true
+ // An abbreviation declares no members; the target type's members must not leak in
+ entity.AllMembers |> shouldEqual []
+
[]
[]
let ``ApiDocs renders inherited members section in output (issue 590)`` (format: OutputFormat) =
diff --git a/tests/FSharp.ApiDocs.Tests/files/FsLib2/Library2.fs b/tests/FSharp.ApiDocs.Tests/files/FsLib2/Library2.fs
index 5e2cc884b..d1980a48e 100644
--- a/tests/FSharp.ApiDocs.Tests/files/FsLib2/Library2.fs
+++ b/tests/FSharp.ApiDocs.Tests/files/FsLib2/Library2.fs
@@ -218,3 +218,14 @@ module TypeConstraintTests =
member _.Value = value
/// Compares with another wrapper
member x.CompareTo(other: ComparisonWrapper<'T>) = compare x.Value other.Value
+
+/// Type abbreviations whose target is a tuple, a list or a BCL type (issue 1314)
+module Abbreviations =
+ /// A position as a zero-based line and column
+ type Position01 = int * int
+
+ /// A long identifier
+ type LongIdent = string list
+
+ /// A file name
+ type FileName = string