Skip to content

Initial C# code generator#4314

Merged
pepone merged 19 commits intoicerpc:mainfrom
pepone:zeroc-slice-generator
Mar 20, 2026
Merged

Initial C# code generator#4314
pepone merged 19 commits intoicerpc:mainfrom
pepone:zeroc-slice-generator

Conversation

@pepone
Copy link
Copy Markdown
Member

@pepone pepone commented Mar 17, 2026

  • ZeroC.Slice.Generator generates C# code for Slice types
  • IceRpc.Slice.Generator generates C# code for proxy/dispatch
  • ZeroC.Slice.Symbols C# model build from compiler definitions to use in C# code genertors.

The generator can be run with:

 ../slicec/target/debug/slicec tests/IceRpc.Slice.CodeGen.Tests/*.slice -R slice | dotnet run --project src/ZeroC.Slice.Generator/ZeroC.Slice.Generator.csproj

or

 ../slicec/target/debug/slicec tests/IceRpc.Slice.CodeGen.Tests/*.slice -R slice | dotnet run --project src/IceRpc.Slice.Generator/IceRpc.Slice.Generator.csproj
  • DocComment generation is not yet done, in part because slicec is not yet encoding comments.
  • ZeroC.Slice.Generator/GeneratorDriver.cs contains the boilerplate code to decode and encode the generator request and convert from the compiler symbols to the symbols used by the generators.

There are separate generators for enums, enum struct, struct. Removed the base Generator class that did too many things from previous PR, and split this into extension for IType, Field, ...

- ZeroC.Slice.Generator generates C# code from slicec encoded definitions
- ZeroC.Slice.Symbols C# model build from compiler definitions to use in
  C# code genertors.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an initial Slice-to-C# code generation pipeline by adding a decoded-compiler model (ZeroC.Slice.Symbols) and a generator executable (ZeroC.Slice.Generator) that reads Slice2-encoded requests, validates C#-specific attributes, and emits generated C# code.

Changes:

  • Added ZeroC.Slice.Symbols to model compiler-decoded Slice definitions with resolved type references.
  • Added ZeroC.Slice.Generator executable to validate cs::* attributes and generate C# code for structs and enums.
  • Wired new projects into the solution and made small CodeBuilder pragma formatting adjustments.

Reviewed changes

Copilot reviewed 44 out of 46 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/ZeroC.Slice.Symbols/ZeroC.Slice.Symbols.csproj New symbols/model project for decoded Slice compiler output.
src/ZeroC.Slice.Symbols/TypeRef.cs Adds TypeRef model (type + attributes).
src/ZeroC.Slice.Symbols/TypeAlias.cs Adds Slice type-alias symbol model.
src/ZeroC.Slice.Symbols/SymbolConverter.cs Converts compiler DTOs into rich symbol graph with resolution/caching.
src/ZeroC.Slice.Symbols/Struct.cs Adds Slice struct symbol model.
src/ZeroC.Slice.Symbols/SliceFile.cs Adds Slice file container model.
src/ZeroC.Slice.Symbols/SequenceType.cs Adds sequence type symbol model.
src/ZeroC.Slice.Symbols/ResultType.cs Adds built-in result type model.
src/ZeroC.Slice.Symbols/Operation.cs Adds interface operation model.
src/ZeroC.Slice.Symbols/Module.cs Adds module model (identifier + attributes).
src/ZeroC.Slice.Symbols/Interface.cs Adds interface symbol model.
src/ZeroC.Slice.Symbols/IType.cs Adds marker interface for referencable types.
src/ZeroC.Slice.Symbols/ISymbol.cs Adds marker interface for file-level symbols.
src/ZeroC.Slice.Symbols/Field.cs Adds shared field model (struct fields/op params/enum fields).
src/ZeroC.Slice.Symbols/EnumWithUnderlying.cs Adds enum-with-underlying models (typed enumerator values).
src/ZeroC.Slice.Symbols/EnumWithFields.cs Adds enum-with-fields model for discriminated unions.
src/ZeroC.Slice.Symbols/Entity.cs Adds shared base for named Slice entities (identifier/attributes/module).
src/ZeroC.Slice.Symbols/DictionaryType.cs Adds dictionary type symbol model.
src/ZeroC.Slice.Symbols/CustomType.cs Adds custom type symbol model.
src/ZeroC.Slice.Symbols/Compiler/SyntaxElements.cs Adds auto-generated compiler DTOs for syntax elements.
src/ZeroC.Slice.Symbols/Compiler/DocComment.cs Adds auto-generated compiler DTOs for doc comments.
src/ZeroC.Slice.Symbols/Compiler/CodeGenerator.cs Adds auto-generated compiler DTOs for generator I/O messages.
src/ZeroC.Slice.Symbols/Builtin.cs Adds builtin type kind enum and builtin type model.
src/ZeroC.Slice.Symbols/AttributeExtensions.cs Adds attribute list query helpers on symbol attributes.
src/ZeroC.Slice.Symbols/Attribute.cs Adds attribute value type (directive + args).
src/ZeroC.Slice.Generator/ZeroC.Slice.Generator.csproj New generator executable project and dependencies.
src/ZeroC.Slice.Generator/TypeRefExtensions.cs Adds C#-generation helpers for TypeRef (sizes, value-type checks, lambdas).
src/ZeroC.Slice.Generator/StructGenerator.cs Generates C# record structs + encode/decode plumbing for Slice structs.
src/ZeroC.Slice.Generator/StringExtensions.cs Adds identifier casing helpers (Pascal/camel).
src/ZeroC.Slice.Generator/Program.cs Implements generator executable request decode + response encode.
src/ZeroC.Slice.Generator/ModuleExtensions.cs Maps Slice modules to C# namespaces (supports cs::namespace).
src/ZeroC.Slice.Generator/ITypeExtensions.cs Central C# mapping/encode/decode expressions for all IType shapes.
src/ZeroC.Slice.Generator/FieldExtensions.cs Adds field ordering + encode/decode expression helpers.
src/ZeroC.Slice.Generator/EnumWithUnderlyingGenerator.cs Generates C# enums + encoder/decoder extension classes.
src/ZeroC.Slice.Generator/EnumWithFieldsGenerator.cs Generates Dunet unions for enums with fields + codec extensions.
src/ZeroC.Slice.Generator/EntityExtensions.cs Adds naming/access modifier helpers based on cs::* attributes.
src/ZeroC.Slice.Generator/CodeBlockExtensions.cs Writes cs::attribute as real C# attributes.
src/ZeroC.Slice.Generator/CSAttributeValidator.cs Validates cs::* attribute usage and produces diagnostics.
src/ZeroC.Slice.Generator/CSAttribute.cs Defines cs::* directive constants.
src/ZeroC.Slice.Generator/BuiltinExtensions.cs Maps Slice builtins to C# types and codec suffixes.
src/ZeroC.Slice.Generator/BuilderExtensions.cs Applies cs::attribute to CodeBuilder builders.
src/ZeroC.Slice.Generator/AttributeExtensions.cs Generator-specific attribute filtering (cs::attribute).
src/ZeroC.CodeBuilder/CommentTag.cs Adjusts pragma formatting in code generation comments.
src/ZeroC.CodeBuilder/CodeBlock.cs Adjusts pragma formatting in indentation helper.
IceRpc.slnx Adds the new generator + symbols projects to the solution.
.vscode/cspell.json Adds a small spelling dictionary entry used by new code.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +52 to +58
string moduleScope = file.ModuleDeclaration.Identifier;
foreach (Compiler.Symbol symbol in file.Contents)
{
if (GetNamedIdentifier(symbol) is string id)
{
_named.TryAdd($"{moduleScope}::{id}", (file, symbol));
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module is always not empty, we should remove the other check.

Comment on lines +360 to +366
Enumerators = raw.Enumerators.Select(e => new EnumWithFields.Enumerator
{
Identifier = e.EntityInfo.Identifier,
Attributes = ConvertAttributes(e.EntityInfo.Attributes),
Module = module,
Discriminant = (int)e.Value.AbsoluteValue,
Fields = e.Fields.Select(f => ConvertField(f, file, module)).ToImmutableList(),
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cast to int is fine here

The range of these discriminants is 0 to 2,147,483,647 (int32 max).

See https://docs.icerpc.dev/slice2/language-guide/enum-types

Comment on lines +160 to +166
body.WriteLine($$"""
bitSequenceWriter.Write({param} != null);
if ({{param}} != null)
{
{{encodeExpr}};
}
""");
Comment on lines +65 to +66
string decodeExpr = field.DecodeField(currentNamespace);
return $"bitSequenceReader.Read() ? {decodeExpr} : null";
e.Value.ToString(null, CultureInfo.InvariantCulture);

private static string GetArticle(string word) =>
word.Length > 0 && "aeiouAEIOU".Contains(word[0], StringComparison.Ordinal) ? "an" : "a";
Comment on lines +122 to +125
string minValue = enumDef.Enumerators.Min(e => Convert.ToInt64(e.Value, CultureInfo.InvariantCulture))
.ToString(CultureInfo.InvariantCulture);
string maxValue = enumDef.Enumerators.Max(e => Convert.ToInt64(e.Value, CultureInfo.InvariantCulture))
.ToString(CultureInfo.InvariantCulture);
Comment on lines +238 to +242
var values = enumDef.Enumerators.Select(e => Convert.ToInt64(e.Value, CultureInfo.InvariantCulture)).ToList();
long min = values.Min();
long max = values.Max();
return enumDef.Enumerators.Count < (max - min + 1);
}
Comment thread src/ZeroC.Slice.Generator/Program.cs Outdated
Comment on lines +33 to +37
string op = decoder.DecodeString();

// Decode source files and reference files.
Compiler.SliceFile[] sourceFiles = decoder.DecodeSequence((ref decoder) => new Compiler.SliceFile(ref decoder));
Compiler.SliceFile[] referenceFiles = decoder.DecodeSequence((ref decoder) => new Compiler.SliceFile(ref decoder));
Copy link
Copy Markdown
Member

@bernardnormier bernardnormier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Will re-review later.

Comment thread .vscode/cspell.json Outdated
"language": "en",
"words": [
"aarch",
"aeiou",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some words like aeiou, it's better to disable cspell with

// cspell:disable-line

public required ImmutableList<Attribute> Attributes { get; init; }

/// <summary>Gets the list of symbols defined in the Slice file.</summary>
public required ImmutableList<ISymbol> Contents { get; init; }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it, an ISymbol is either a type or an interface. So if we split this Contents in Types and Interfaces, we can eliminate ISymbol?


/// <summary>Base for all symbols that can appear in a <see cref="SliceFile"/>.</summary>
#pragma warning disable CA1040 // Avoid empty interfaces
public interface ISymbol;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to avoid ISymbol as it's also Microsoft.CodeAnalysis.ISymbol.

Comment thread src/ZeroC.Slice.Symbols/TypeRef.cs Outdated
pepone added 7 commits March 18, 2026 15:27
…ze optimization

- Constrain EnumWithUnderlying<T> to INumber<T> instead of just struct, enabling
  generic math (Min/Max, CreateChecked, arithmetic) without Convert.ToInt64
- Add full-range early return in NeedsHashSetValidation to prevent overflow for
  small types like sbyte
- Add tagged collection size optimization for sequences with fixed-size elements
  (> 1 byte) and dictionaries with all-fixed-size key/value types
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an initial Slice-to-C# code generation pipeline by introducing a rich Slice symbol model (ZeroC.Slice.Symbols) plus two generator executables: a general Slice codec generator (ZeroC.Slice.Generator) and an IceRPC-specific generator for Slice interfaces (IceRpc.Slice.Generator).

Changes:

  • Introduce ZeroC.Slice.Symbols with a SymbolConverter that resolves compiler-decoded Slice definitions into a generator-friendly symbol graph.
  • Add ZeroC.Slice.Generator codegen entrypoint + generator pipeline (stdin/stdout protocol), and generators for structs and enums (with underlying / with fields).
  • Add IceRpc.Slice.Generator to generate proxy/dispatch code for Slice interfaces and include the new projects in the solution.

Reviewed changes

Copilot reviewed 51 out of 53 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/ZeroC.Slice.Symbols/ZeroC.Slice.Symbols.csproj New symbols project for generator-facing model types.
src/ZeroC.Slice.Symbols/TypeRef.cs Adds TypeRef model used throughout symbol graph.
src/ZeroC.Slice.Symbols/TypeAlias.cs Adds symbol model for Slice type aliases.
src/ZeroC.Slice.Symbols/SymbolConverter.cs Converts compiler-decoded symbols into rich symbols and resolves references.
src/ZeroC.Slice.Symbols/Struct.cs Adds symbol model for Slice structs.
src/ZeroC.Slice.Symbols/SliceFile.cs Adds model for a Slice file (path/module/contents).
src/ZeroC.Slice.Symbols/SequenceType.cs Adds symbol model for Slice sequence types.
src/ZeroC.Slice.Symbols/ResultType.cs Adds symbol model for Slice result type.
src/ZeroC.Slice.Symbols/Operation.cs Adds symbol model for Slice operations.
src/ZeroC.Slice.Symbols/Module.cs Adds symbol model for Slice modules.
src/ZeroC.Slice.Symbols/Interface.cs Adds symbol model for Slice interfaces.
src/ZeroC.Slice.Symbols/IType.cs Adds marker interface for referencable types.
src/ZeroC.Slice.Symbols/ISymbol.cs Adds marker interface for file-level symbols.
src/ZeroC.Slice.Symbols/Field.cs Adds symbol model for fields (struct/enum/op contexts).
src/ZeroC.Slice.Symbols/EnumWithUnderlying.cs Adds enum model for enums with underlying integral type.
src/ZeroC.Slice.Symbols/EnumWithFields.cs Adds enum model for enums with fields (union-like).
src/ZeroC.Slice.Symbols/Entity.cs Adds base type for named Slice entities.
src/ZeroC.Slice.Symbols/DictionaryType.cs Adds symbol model for Slice dictionary types.
src/ZeroC.Slice.Symbols/CustomType.cs Adds model for user-mapped custom types.
src/ZeroC.Slice.Symbols/Compiler/SyntaxElements.cs Adds generated compiler AST model types (auto-generated).
src/ZeroC.Slice.Symbols/Compiler/DocComment.cs Adds generated doc-comment model types (auto-generated).
src/ZeroC.Slice.Symbols/Compiler/CodeGenerator.cs Adds generated codegen protocol model types (auto-generated).
src/ZeroC.Slice.Symbols/Builtin.cs Adds builtin kind enumeration + builtin type symbol.
src/ZeroC.Slice.Symbols/AttributeExtensions.cs Adds helpers for querying Slice attributes.
src/ZeroC.Slice.Symbols/Attribute.cs Adds attribute model (directive + args).
src/ZeroC.Slice.Generator/ZeroC.Slice.Generator.csproj New executable project for Slice codec code generation.
src/ZeroC.Slice.Generator/TypeRefExtensions.cs Adds C# mapping helpers for TypeRef in generated code.
src/ZeroC.Slice.Generator/StructGenerator.cs Generates C# record structs + encode/decode members for Slice structs.
src/ZeroC.Slice.Generator/StringExtensions.cs Adds identifier casing helpers for generated C# names.
src/ZeroC.Slice.Generator/Program.cs Generator entrypoint wiring symbol-to-code generation.
src/ZeroC.Slice.Generator/ModuleExtensions.cs Adds module-to-namespace mapping logic.
src/ZeroC.Slice.Generator/ITypeExtensions.cs Core encode/decode/type-string generation logic per Slice type.
src/ZeroC.Slice.Generator/GeneratorDriver.cs Implements stdin/stdout protocol + orchestration for generation.
src/ZeroC.Slice.Generator/FieldExtensions.cs Shared field encode/decode helpers used by multiple generators.
src/ZeroC.Slice.Generator/EnumWithUnderlyingGenerator.cs Generates C# enums + encoder/decoder extensions.
src/ZeroC.Slice.Generator/EnumWithFieldsGenerator.cs Generates Dunet unions + encoder/decoder extensions for enums-with-fields.
src/ZeroC.Slice.Generator/EntityExtensions.cs Adds naming/access/extension-class helpers for symbol entities.
src/ZeroC.Slice.Generator/CodeBlockExtensions.cs Adds helpers to emit attributes into generated code blocks.
src/ZeroC.Slice.Generator/CSAttributeValidator.cs Validates cs::* attribute usage and reports diagnostics.
src/ZeroC.Slice.Generator/CSAttribute.cs Declares constants for cs::* attribute directives.
src/ZeroC.Slice.Generator/BuiltinExtensions.cs Adds C# type and codec suffix mapping for builtins.
src/ZeroC.Slice.Generator/BuilderExtensions.cs Adds helper to attach cs::attribute attributes to builders.
src/ZeroC.Slice.Generator/AttributeExtensions.cs Adds generator-specific attribute filtering helpers.
src/ZeroC.CodeBuilder/ContainerBuilder.cs Minor typing adjustments in loops for comments/attributes.
src/ZeroC.CodeBuilder/CommentTag.cs Moves pragmas; affects formatting/warning scope.
src/ZeroC.CodeBuilder/CodeBlock.cs Moves pragmas; affects formatting/warning scope.
src/IceRpc.Slice.Generator/ProxyGenerator.cs Generates client interface + proxy struct + request/response helpers.
src/IceRpc.Slice.Generator/Program.cs Generator entrypoint for IceRPC Slice interface/operations output.
src/IceRpc.Slice.Generator/OperationHelpers.cs Shared helpers for operation signatures and payload encode/decode.
src/IceRpc.Slice.Generator/OperationExtensions.cs Adds operation/interface helpers for streamed args/returns and bases.
src/IceRpc.Slice.Generator/IceRpc.Slice.Generator.csproj New executable project for IceRPC-specific Slice generation.
src/IceRpc.Slice.Generator/DispatchGenerator.cs Generates server-side service interface + request/response helpers.
IceRpc.slnx Adds new generator and symbols projects to the solution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

var defaultCtor = new FunctionBuilder("public", "", proxyName, FunctionType.BlockBody);
defaultCtor.AddComment(
"summary",
@"Constructs a proxy with an icerpc service address with path <see cref=""DefaultServicePath"" />.");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty body is correct, this is as per slicec-cs impl:

    /// <summary>Constructs a proxy with an icerpc service address with path <see cref="DefaultServicePath" />.</summary>
    public MoreDerivedProxy()
    {
    }


if (includeTagEndMarker)
{
body.WriteLine($"{encoderName}.EncodeVarInt32(Slice2Definitions.TagEndMarker);");
Comment thread src/ZeroC.Slice.Generator/GeneratorDriver.cs Outdated
Comment thread src/ZeroC.Slice.Generator/GeneratorDriver.cs Outdated
parts.Count switch
{
0 => taskType,
1 => $"{taskType}<{parts[0].Split(' ')[0]}>",
<PropertyGroup>
<OutputType>Exe</OutputType>
<!--Because an application's API isn't typically referenced from outside the assembly, types can be made internal -->
<NoWarn>CA1515</NoWarn>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1515

Meaning we have public types that can/should be internal. Why disable this warning?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got this when including the code builder

eroC.Slice.Generator net10.0 failed with 11 error(s) (0,2s)
    CSC : error CA1515: Because an application's API isn't typically referenced from outside the assembly, types can be made internal (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515)
    /Users/jose/Documents/IceRPC/icerpc-csharp/src/ZeroC.CodeBuilder/CodeBlock.cs(8,21): error CA1515: Because an application's API isn't typically referenced from outside the assembly, types can be made internal (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515)
    /Users/jose/Documents/IceRPC/icerpc-csharp/src/ZeroC.CodeBuilder/CommentTag.cs(8,21): error CA1515: Because an application's API isn't typically referenced from outside the assembly, types can be made internal (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515)


namespace ZeroC.Slice.Generator;

/// <summary>Helper methods for generating operation encode/decode code blocks.</summary>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more typical C#-way to write helpers is to add extension method, such as OperationExtensions.

}

/// <summary>Builds the EncodeStreamOf{Op} method for a streamed field (parameter or return value).</summary>
internal static CodeBlock BuildEncodeStreamMethod(Operation op, Field streamParam, string currentNamespace)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am surprised you don't get a build warning for defining an internal method after a private one.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to op extensions

string opName = op.Name;
string streamType = OperationExtensions.GetStreamTypeString(streamParam, currentNamespace);

var fn = new FunctionBuilder(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer "builder" or "functionBuilder" over "fn"

Comment thread src/IceRpc.Slice.Generator/ProxyGenerator.cs Outdated
@pepone pepone merged commit 7555ccc into icerpc:main Mar 20, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants