From 20bd881987e5f39febc1d0dfd46d0c81a7ce1642 Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 29 Apr 2026 10:55:00 +0200 Subject: [PATCH 1/3] Escape cref value in generated doc comments cs::type can contain XML-special characters (e.g. generics like List), which would produce malformed XML doc comments when spliced raw into a cref attribute. --- src/ZeroC.Slice.Generator/DocCommentFormatter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ZeroC.Slice.Generator/DocCommentFormatter.cs b/src/ZeroC.Slice.Generator/DocCommentFormatter.cs index 9a19bf2949..8f57adf9c7 100644 --- a/src/ZeroC.Slice.Generator/DocCommentFormatter.cs +++ b/src/ZeroC.Slice.Generator/DocCommentFormatter.cs @@ -47,7 +47,8 @@ internal static IEnumerable FormatSeeAlsoTags(Comment? comment, stri { // Type aliases don't generate C# types, so output the identifier as plain text. ResolvedCommentLink { Entity: TypeAlias } r => $"{CommentTag.XmlEscape(r.Entity.Identifier)}", - ResolvedCommentLink r => $"""""", + ResolvedCommentLink r => + $"""""", UnresolvedCommentLink u => $"{CommentTag.XmlEscape(u.Identifier)}", _ => "" }; From d80accd3b0888f0f1f7a798bbdf6f38153f261cd Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 30 Apr 2026 12:46:34 +0200 Subject: [PATCH 2/3] Map TypeAlias doc links to the underlying C# type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For inline @link to a TypeAlias, emit when the mapped type is non-generic, or MappedType (XML-escaped) when it is generic — closed generics like IList have no valid C# cref form, so a clickable link isn't possible. For @see, emit only for non-generic mapped types and skip generic ones. Add typealias coverage to DocumentationTests.slice for non-generic, sequence, dictionary, and result mappings. --- .../DocCommentFormatter.cs | 34 ++++++++++++++++--- .../DocumentationTests.slice | 28 +++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/ZeroC.Slice.Generator/DocCommentFormatter.cs b/src/ZeroC.Slice.Generator/DocCommentFormatter.cs index 8f57adf9c7..68c9faadb6 100644 --- a/src/ZeroC.Slice.Generator/DocCommentFormatter.cs +++ b/src/ZeroC.Slice.Generator/DocCommentFormatter.cs @@ -36,7 +36,22 @@ internal static IEnumerable FormatSeeAlsoTags(Comment? comment, stri foreach (CommentLink link in seeTags) { - if (link is ResolvedCommentLink r) + if (link is not ResolvedCommentLink r) + { + continue; + } + + if (r.Entity is TypeAlias alias) + { + // Type aliases don't generate a C# type; map to the underlying C# type. We can't reliably + // produce a seealso for a generic mapped type without C# parsing, so skip those. + string mapped = alias.UnderlyingType.Type.ToTypeString(currentNamespace); + if (!mapped.Contains('<', StringComparison.Ordinal)) + { + yield return new CommentTag("seealso", "cref", mapped, ""); + } + } + else { yield return new CommentTag("seealso", "cref", FormatEntityCref(r.Entity, currentNamespace), ""); } @@ -45,14 +60,23 @@ internal static IEnumerable FormatSeeAlsoTags(Comment? comment, stri private static string FormatInlineLink(CommentLink link, string currentNamespace) => link switch { - // Type aliases don't generate C# types, so output the identifier as plain text. - ResolvedCommentLink { Entity: TypeAlias } r => $"{CommentTag.XmlEscape(r.Entity.Identifier)}", - ResolvedCommentLink r => - $"""""", + ResolvedCommentLink { Entity: TypeAlias alias } => FormatTypeAliasInline(alias, currentNamespace), + ResolvedCommentLink r => $"""""", UnresolvedCommentLink u => $"{CommentTag.XmlEscape(u.Identifier)}", _ => "" }; + private static string FormatTypeAliasInline(TypeAlias alias, string currentNamespace) + { + // Type aliases don't generate a C# type; link to the underlying C# type instead. For generic mapped + // types we emit the type name as inline code (XML-escaped) to avoid constructing cref-friendly forms + // like IList{T} or IDictionary{T, U}. + string mapped = alias.UnderlyingType.Type.ToTypeString(currentNamespace); + return mapped.Contains('<', StringComparison.Ordinal) + ? $"{CommentTag.XmlEscape(mapped)}" + : $""""""; + } + private static string FormatEntityCref(Entity entity, string currentNamespace) { string name = entity switch diff --git a/tests/IceRpc.Slice.Generator.Tests/DocumentationTests.slice b/tests/IceRpc.Slice.Generator.Tests/DocumentationTests.slice index 721b120a1a..8b51ebc562 100644 --- a/tests/IceRpc.Slice.Generator.Tests/DocumentationTests.slice +++ b/tests/IceRpc.Slice.Generator.Tests/DocumentationTests.slice @@ -4,7 +4,35 @@ module IceRpc::Slice::Generator::Tests // This file contains Slice definitions for testing the mapping of doc comments +// Typealiases used to test how doc comment links to a typealias are mapped to its underlying C# type: +// - UserName maps to a non-generic C# type (string), so links resolve to . +// - UserList, UserMap, UserResult map to generic C# types, so inline @link is emitted as ... +// (XML-escaped) and @see is skipped. +typealias UserName = string typealias UserList = Sequence +typealias UserMap = Dictionary +typealias UserResult = Result + +/// Tests how doc comment links to typealiases are mapped to their underlying C# types. +/// The {@link UserName} alias maps to a non-generic type while {@link UserList}, +/// {@link UserMap}, and {@link UserResult} map to generic types. +/// @see UserName +/// @see UserList +/// @see UserMap +/// @see UserResult +interface MyTypealiasDocs { + /// Returns the {@link UserName} for the active user. + getName() -> UserName + + /// Returns a {@link UserList} of active user names. + getNames() -> UserList + + /// Returns a {@link UserMap} keyed by user name. + getSessions() -> UserMap + + /// Returns a {@link UserResult} containing the session or an error message. + getResult() -> UserResult +} /// This is the session manager interface summary. The caller must use the {@link MyAuthorizationManager::createAuthToken} /// operation to create an authentication token before creating a session. From d020a82d8562173d05173208dbfdbe68fde987c7 Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 30 Apr 2026 12:53:32 +0200 Subject: [PATCH 3/3] Fix spell-check warnings in DocumentationTests.slice --- tests/IceRpc.Slice.Generator.Tests/DocumentationTests.slice | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/IceRpc.Slice.Generator.Tests/DocumentationTests.slice b/tests/IceRpc.Slice.Generator.Tests/DocumentationTests.slice index 8b51ebc562..a3a5ee0c7c 100644 --- a/tests/IceRpc.Slice.Generator.Tests/DocumentationTests.slice +++ b/tests/IceRpc.Slice.Generator.Tests/DocumentationTests.slice @@ -4,7 +4,7 @@ module IceRpc::Slice::Generator::Tests // This file contains Slice definitions for testing the mapping of doc comments -// Typealiases used to test how doc comment links to a typealias are mapped to its underlying C# type: +// Type aliases used to test how doc comment links to a typealias are mapped to its underlying C# type: // - UserName maps to a non-generic C# type (string), so links resolve to . // - UserList, UserMap, UserResult map to generic C# types, so inline @link is emitted as ... // (XML-escaped) and @see is skipped. @@ -13,7 +13,7 @@ typealias UserList = Sequence typealias UserMap = Dictionary typealias UserResult = Result -/// Tests how doc comment links to typealiases are mapped to their underlying C# types. +/// Tests how doc comment links to type aliases are mapped to their underlying C# types. /// The {@link UserName} alias maps to a non-generic type while {@link UserList}, /// {@link UserMap}, and {@link UserResult} map to generic types. /// @see UserName