diff --git a/src/ZeroC.Slice.Symbols/BasicEnum.cs b/src/ZeroC.Slice.Symbols/BasicEnum.cs
index dd77a3034..f307e91a0 100644
--- a/src/ZeroC.Slice.Symbols/BasicEnum.cs
+++ b/src/ZeroC.Slice.Symbols/BasicEnum.cs
@@ -15,6 +15,9 @@ public abstract class BasicEnum : Entity, ISymbol, IType
/// Finds an enumerator by its Slice identifier.
public abstract Entity? FindEnumeratorByIdentifier(string identifier);
+
+ /// Gets the enumerators as a sequence of entities (without their typed values).
+ internal abstract IEnumerable EnumeratorEntities { get; }
}
/// Represents a Slice basic enumeration with a typed underlying value.
@@ -27,6 +30,8 @@ public sealed class BasicEnum : BasicEnum where T : struct, System.Numerics.I
public override Entity? FindEnumeratorByIdentifier(string identifier) =>
Enumerators.FirstOrDefault(e => e.Identifier == identifier);
+ internal override IEnumerable EnumeratorEntities => Enumerators;
+
/// Represents an enumerator in a Slice basic enumeration.
public sealed class Enumerator : Entity
{
diff --git a/src/ZeroC.Slice.Symbols/Entity.cs b/src/ZeroC.Slice.Symbols/Entity.cs
index aa725fa6d..f6c465091 100644
--- a/src/ZeroC.Slice.Symbols/Entity.cs
+++ b/src/ZeroC.Slice.Symbols/Entity.cs
@@ -20,7 +20,7 @@ public class Entity
public Entity? Parent { get; internal set; }
/// Gets the doc comment associated with this entity, if any.
- public Comment? Comment { get; init; }
+ public Comment? Comment { get; internal set; }
/// Gets the fully scoped Slice identifier (e.g. "MyModule::MyType" or
/// "MyModule::MyInterface::MyOperation").
diff --git a/src/ZeroC.Slice.Symbols/SymbolConverter.cs b/src/ZeroC.Slice.Symbols/SymbolConverter.cs
index 81e59a9e8..9916116ab 100644
--- a/src/ZeroC.Slice.Symbols/SymbolConverter.cs
+++ b/src/ZeroC.Slice.Symbols/SymbolConverter.cs
@@ -14,7 +14,7 @@ internal static ImmutableList ConvertFiles(
IEnumerable referenceFiles)
{
var converter = new SymbolConverter(sourceFiles.Concat(referenceFiles));
- return sourceFiles.Select(converter.ConvertFile).ToImmutableList();
+ return [.. sourceFiles.Select(converter.ConvertFile)];
}
private static readonly Dictionary _builtins = new(StringComparer.Ordinal)
@@ -60,13 +60,25 @@ private SymbolConverter(IEnumerable allFiles)
}
}
- // Convert all named symbols in dependency order to fully populate _cache.
+ // Phase 1: convert all named symbols in dependency order. Doc-comment links are recorded as
+ // UnresolvedCommentLink and resolved in phase 2 — eager resolution here would miss any reference
+ // that targets a symbol not yet in _cache (forward references, self-references, or cycles between
+ // entities that aren't related by type dependencies).
foreach (string typeId in TopologicalSort())
{
(Compiler.SliceFile file, Compiler.Symbol symbol) = _named[typeId];
Module module = ConvertModule(file.ModuleDeclaration);
_cache[typeId] = ConvertSymbol(symbol, file, module);
}
+
+ // Phase 2: resolve doc-comment links now that the symbol table is fully populated.
+ foreach (ISymbol symbol in _cache.Values)
+ {
+ if (symbol is Entity entity)
+ {
+ ResolveEntityComments(entity);
+ }
+ }
}
private SliceFile ConvertFile(Compiler.SliceFile file)
@@ -74,7 +86,7 @@ private SliceFile ConvertFile(Compiler.SliceFile file)
string moduleScope = file.ModuleDeclaration.Identifier;
Module module = ConvertModule(file.ModuleDeclaration);
- var contents = ImmutableList.CreateBuilder();
+ ImmutableList.Builder contents = ImmutableList.CreateBuilder();
for (int i = 0; i < file.Contents.Count; i++)
{
Compiler.Symbol raw = file.Contents[i];
@@ -144,7 +156,7 @@ private List TopologicalSort()
foreach ((string typeId, (Compiler.SliceFile file, Compiler.Symbol symbol)) in _named)
{
var deps = new HashSet(StringComparer.Ordinal);
- CollectNamedTypeIds(symbol, file, deps);
+ CollectDependencies(symbol, file, deps);
pending[typeId] = deps;
}
@@ -180,7 +192,7 @@ private List TopologicalSort()
return sorted;
- void CollectNamedTypeIds(Compiler.Symbol symbol, Compiler.SliceFile file, HashSet result)
+ void CollectDependencies(Compiler.Symbol symbol, Compiler.SliceFile file, HashSet result)
{
IEnumerable typeIds = symbol switch
{
@@ -204,7 +216,7 @@ void CollectNamedTypeIds(Compiler.Symbol symbol, Compiler.SliceFile file, HashSe
else if (int.TryParse(typeId, out int index))
{
// Anonymous type — recurse to collect any named types it references.
- CollectNamedTypeIds(file.Contents[index], file, result);
+ CollectDependencies(file.Contents[index], file, result);
}
// else: builtin, ignore.
}
@@ -310,7 +322,7 @@ private Struct ConvertStruct(Compiler.Struct raw, Compiler.SliceFile file, Modul
return result;
}
- private ISymbol ConvertBasicEnum(Compiler.BasicEnum raw, Module module)
+ private static ISymbol ConvertBasicEnum(Compiler.BasicEnum raw, Module module)
{
Builtin builtin = _builtins[raw.Underlying];
return builtin.Kind switch
@@ -360,8 +372,7 @@ private ISymbol ConvertBasicEnum(Compiler.BasicEnum raw, Module module)
module,
builtin,
(abs, _) => abs),
- _ => throw new InvalidOperationException(
- $"Unsupported enum underlying type: {builtin.Kind}"),
+ _ => throw new InvalidOperationException($"Unsupported enum underlying type: {builtin.Kind}"),
};
}
@@ -375,15 +386,17 @@ private ISymbol ConvertVariantEnum(Compiler.VariantEnum raw, Compiler.SliceFile
Module = module,
IsCompact = raw.IsCompact,
IsUnchecked = raw.IsUnchecked,
- Variants = raw.Variants.Select(v => new VariantEnum.Variant
- {
- Identifier = v.EntityInfo.Identifier,
- Attributes = ConvertAttributes(v.EntityInfo.Attributes),
- Comment = ConvertComment(v.EntityInfo.Comment),
- Module = module,
- Discriminant = v.Discriminant,
- Fields = v.Fields.Select(f => ConvertField(f, file, module)).ToImmutableList(),
- }).ToImmutableList(),
+ Variants = [
+ .. raw.Variants.Select(v => new VariantEnum.Variant
+ {
+ Identifier = v.EntityInfo.Identifier,
+ Attributes = ConvertAttributes(v.EntityInfo.Attributes),
+ Comment = ConvertComment(v.EntityInfo.Comment),
+ Module = module,
+ Discriminant = v.Discriminant,
+ Fields = v.Fields.Select(f => ConvertField(f, file, module)).ToImmutableList(),
+ })
+ ],
};
SetParent(result, result.Variants);
foreach (VariantEnum.Variant variant in result.Variants)
@@ -401,10 +414,7 @@ private Interface ConvertInterface(Compiler.Interface raw, Compiler.SliceFile fi
Attributes = ConvertAttributes(raw.EntityInfo.Attributes),
Comment = ConvertComment(raw.EntityInfo.Comment),
Module = module,
- Bases = raw.Bases
- .Select(baseId => ResolveNamedSymbol(baseId))
- .OfType()
- .ToImmutableList(),
+ Bases = [.. raw.Bases.Select(ResolveNamedSymbol).OfType()],
Operations = raw.Operations.Select(op => ConvertOperation(op, file, module)).ToImmutableList(),
};
SetParent(result, result.Operations);
@@ -433,7 +443,7 @@ private Operation ConvertOperation(
return result;
}
- private BasicEnum CreateBasicEnum(
+ private static BasicEnum CreateBasicEnum(
Compiler.BasicEnum raw,
Module module,
Builtin builtin,
@@ -447,14 +457,16 @@ private BasicEnum CreateBasicEnum(
Module = module,
IsUnchecked = raw.IsUnchecked,
Underlying = builtin,
- Enumerators = raw.Enumerators.Select(e => new BasicEnum.Enumerator
- {
- Identifier = e.EntityInfo.Identifier,
- Attributes = ConvertAttributes(e.EntityInfo.Attributes),
- Comment = ConvertComment(e.EntityInfo.Comment),
- Module = module,
- Value = toValue(e.AbsoluteValue, e.HasNegativeValue),
- }).ToImmutableList(),
+ Enumerators = [
+ .. raw.Enumerators.Select(e => new BasicEnum.Enumerator
+ {
+ Identifier = e.EntityInfo.Identifier,
+ Attributes = ConvertAttributes(e.EntityInfo.Attributes),
+ Comment = ConvertComment(e.EntityInfo.Comment),
+ Module = module,
+ Value = toValue(e.AbsoluteValue, e.HasNegativeValue),
+ })
+ ],
};
SetParent(result, result.Enumerators);
return result;
@@ -492,13 +504,15 @@ private static void SetParent(Entity parent, IEnumerable children)
}
private static ImmutableList ConvertAttributes(IList raw) =>
- raw.Select(a => new Attribute
- {
- Directive = a.Directive,
- Args = [.. a.Args],
- }).ToImmutableList();
+ [
+ .. raw.Select(a => new Attribute
+ {
+ Directive = a.Directive,
+ Args = [.. a.Args],
+ })
+ ];
- private Comment? ConvertComment(Compiler.DocComment? raw)
+ private static Comment? ConvertComment(Compiler.DocComment? raw)
{
if (raw is null)
{
@@ -508,18 +522,86 @@ private static ImmutableList ConvertAttributes(IList(c => c switch
{
Compiler.MessageComponent.Text t => new CommentText(t.V),
- Compiler.MessageComponent.Link l => new CommentInlineLink(ResolveLink(l.V)),
+ Compiler.MessageComponent.Link l => new CommentInlineLink(new UnresolvedCommentLink(l.V)),
_ => throw new InvalidOperationException($"Unknown MessageComponent kind: {c.GetType().FullName}")
}).ToImmutableList();
- var seeTags = raw.Value.SeeTags.Select(ResolveLink).ToImmutableList();
+ var seeTags = raw.Value.SeeTags
+ .Select(id => (CommentLink)new UnresolvedCommentLink(id))
+ .ToImmutableList();
+
+ return new Comment { Overview = overview, SeeTags = seeTags };
+ }
+
+ private void ResolveEntityComments(Entity entity)
+ {
+ if (entity.Comment is Comment comment)
+ {
+ entity.Comment = ResolveComment(comment);
+ }
+
+ switch (entity)
+ {
+ case Interface i:
+ foreach (Operation op in i.Operations)
+ {
+ ResolveEntityComments(op);
+ }
+ break;
+ case Operation op:
+ foreach (Field p in op.Parameters)
+ {
+ ResolveEntityComments(p);
+ }
+ foreach (Field r in op.ReturnType)
+ {
+ ResolveEntityComments(r);
+ }
+ break;
+ case Struct s:
+ foreach (Field f in s.Fields)
+ {
+ ResolveEntityComments(f);
+ }
+ break;
+ case VariantEnum v:
+ foreach (VariantEnum.Variant variant in v.Variants)
+ {
+ ResolveEntityComments(variant);
+ }
+ break;
+ case VariantEnum.Variant variant:
+ foreach (Field f in variant.Fields)
+ {
+ ResolveEntityComments(f);
+ }
+ break;
+ case BasicEnum basicEnum:
+ foreach (Entity enumerator in basicEnum.EnumeratorEntities)
+ {
+ ResolveEntityComments(enumerator);
+ }
+ break;
+ }
+ }
+
+ private Comment ResolveComment(Comment comment)
+ {
+ var overview = comment.Overview.Select(c => c switch
+ {
+ CommentInlineLink l => new CommentInlineLink(ResolveOrKeep(l.Target)),
+ _ => c,
+ })
+ .ToImmutableList();
+
+ var seeTags = comment.SeeTags.Select(ResolveOrKeep).ToImmutableList();
return new Comment { Overview = overview, SeeTags = seeTags };
- CommentLink ResolveLink(string entityId) =>
- ResolveEntityById(entityId) is Entity entity
+ CommentLink ResolveOrKeep(CommentLink link) =>
+ link is UnresolvedCommentLink unresolved && ResolveEntityById(unresolved.Identifier) is Entity entity
? new ResolvedCommentLink(entity)
- : new UnresolvedCommentLink(entityId);
+ : link;
}
private Entity? ResolveEntityById(string entityId)