diff --git a/src/IceRpc.Slice.Generator/OperationExtensions.cs b/src/IceRpc.Slice.Generator/OperationExtensions.cs
index f995e51a35..b643cd9bc2 100644
--- a/src/IceRpc.Slice.Generator/OperationExtensions.cs
+++ b/src/IceRpc.Slice.Generator/OperationExtensions.cs
@@ -125,7 +125,7 @@ internal CodeBlock BuildEncodeStreamMethod(
bool useSegments;
if (streamField.DataTypeIsOptional)
{
- encodeLambda = GetStreamOfOptionalEncodeLambda(streamField, currentNamespace);
+ encodeLambda = streamField.DataType.GetEncodeLambdaWithNullMarker(currentNamespace);
useSegments = true;
}
else
@@ -368,28 +368,6 @@ private static string BuildServiceReturnTypeCore(string taskType, Operation op,
return count == 1 ? $"{taskType}<{parts[0]}>" : $"{taskType}<({string.Join(", ", parts)})>";
}
- /// Returns an encode lambda for an optional stream element with a one bit bit-sequence.
- internal static string GetStreamOfOptionalEncodeLambda(Field streamField, string currentNamespace)
- {
- IType elemType = streamField.DataType.Type;
- string csType = streamField.DataType.FieldTypeString(true, currentNamespace);
- // CustomType → (value ?? default!), value types → value!.Value, reference types → value!
- string valueExpr = elemType is CustomType
- ? "(value ?? default!)"
- : streamField.DataType.IsValueType ? "value!.Value" : "value!";
- string encodeExpr = elemType.EncodeExpression(currentNamespace, valueExpr);
- return $$"""
- (ref SliceEncoder encoder, {{csType}} value) =>
- {
- encoder.EncodeBool(value is not null);
- if (value is not null)
- {
- {{encodeExpr}};
- }
- }
- """;
- }
-
/// Returns a decode lambda for an optional stream element with a one bit bit-sequence.
internal static string GetStreamOfOptionalDecodeLambda(Field streamField, string currentNamespace)
{
diff --git a/src/ZeroC.Slice.Generator/FieldExtensions.cs b/src/ZeroC.Slice.Generator/FieldExtensions.cs
index 55ccb137bf..ce4726fc9b 100644
--- a/src/ZeroC.Slice.Generator/FieldExtensions.cs
+++ b/src/ZeroC.Slice.Generator/FieldExtensions.cs
@@ -209,10 +209,10 @@ internal static CodeBlock GenerateEncodeBody(
}
else
{
- string valueParam = field.DataType.Type is CustomType
- ? $"({param} ?? default!)"
- : field.DataType.IsValueType ? $"{param}.Value" : param;
- CodeBlock encodeExpr = field.DataType.EncodeExpression(currentNamespace, valueParam, encoderName);
+ CodeBlock encodeExpr = field.DataType.EncodeExpression(
+ currentNamespace,
+ field.DataType.UnwrapNonNullOptional(param),
+ encoderName);
body.WriteLine($$"""
bitSequenceWriter.Write({{param}} != null);
if ({{param}} != null)
diff --git a/src/ZeroC.Slice.Generator/ITypeExtensions.cs b/src/ZeroC.Slice.Generator/ITypeExtensions.cs
index 5c5bf76d85..85c6b80cc0 100644
--- a/src/ZeroC.Slice.Generator/ITypeExtensions.cs
+++ b/src/ZeroC.Slice.Generator/ITypeExtensions.cs
@@ -191,70 +191,25 @@ static string EncodeSequence(
string param,
string encoderName)
{
- IType elemType = seq.ElementType.Type;
- if (seq.ElementTypeIsOptional)
- {
- string csOptType = seq.ElementType.FieldTypeString(true, currentNamespace);
- string lambda = EncodeOptionalValueLambda(elemType, csOptType, currentNamespace);
- return $$"""
- {{encoderName}}.EncodeSequenceOfOptionals(
- {{param}},
- {{lambda}})
- """;
- }
-
// Fixed-size primitives use the optimized EncodeSequence overload (no lambda).
- if (!seq.ElementTypeIsOptional && elemType is Builtin builtin && builtin.IsFixedSize)
+ if (!seq.ElementTypeIsOptional && seq.ElementType.Type is Builtin { IsFixedSize: true })
{
return $"{encoderName}.EncodeSequence({param})";
}
CodeBlock elementEncodeLambda = seq.ElementType.GetEncodeLambda(seq.ElementTypeIsOptional, currentNamespace);
+ string method = seq.ElementTypeIsOptional ? "EncodeSequenceOfOptionals" : "EncodeSequence";
return $$"""
- {{encoderName}}.EncodeSequence(
+ {{encoderName}}.{{method}}(
{{param}},
{{elementEncodeLambda.Indent()}})
""";
-
- static string EncodeOptionalValueLambda(IType elemType, string csOptType, string currentNamespace)
- {
- // CustomType → (value ?? default!), value types → value!.Value, reference types → value!
- string valueExpr = elemType is CustomType
- ? "(value ?? default!)"
- : elemType is Struct or BasicEnum or Builtin { IsValueType: true } ? "value!.Value" : "value!";
- string encodeExpr = elemType.EncodeExpression(currentNamespace, valueExpr);
- return $"(ref SliceEncoder encoder, {csOptType} value) => {encodeExpr}";
- }
}
- // Returns an encode lambda for a result success/failure type, handling optional inner types with a
- // one bit bit-sequence.
- static string ResultEncodeLambda(TypeRef typeRef, bool isOptional, string currentNamespace)
- {
- IType type = typeRef.Type;
-
- if (!isOptional)
- {
- return typeRef.GetEncodeLambda(isOptional: false, currentNamespace);
- }
- string csType = typeRef.FieldTypeString(true, currentNamespace);
-
- // CustomType → (value ?? default!), value types → value!.Value, reference types → value!
- string valueParam = type is CustomType
- ? "(value ?? default!)"
- : typeRef.IsValueType ? "value!.Value" : "value!";
- CodeBlock encodeBody = type.EncodeExpression(currentNamespace, valueParam);
- return $$"""
- (ref SliceEncoder encoder, {{csType}} value) =>
- {
- encoder.EncodeBool(value is not null);
- if (value is not null)
- {
- {{encodeBody.Indent().Indent()}};
- }
- }
- """;
- }
+ static string ResultEncodeLambda(TypeRef typeRef, bool isOptional, string currentNamespace) =>
+ isOptional
+ ? typeRef.GetEncodeLambdaWithNullMarker(currentNamespace)
+ : typeRef.GetEncodeLambda(isOptional: false, currentNamespace);
}
/// Returns a decode lambda for a type. When is true, a cast to the field
diff --git a/src/ZeroC.Slice.Generator/TypeRefExtensions.cs b/src/ZeroC.Slice.Generator/TypeRefExtensions.cs
index c16ddd0a3f..d6eeae8789 100644
--- a/src/ZeroC.Slice.Generator/TypeRefExtensions.cs
+++ b/src/ZeroC.Slice.Generator/TypeRefExtensions.cs
@@ -1,5 +1,6 @@
// Copyright (c) ZeroC, Inc.
+using ZeroC.CodeBuilder;
using ZeroC.Slice.Symbols;
namespace ZeroC.Slice.Generator;
@@ -7,7 +8,6 @@ namespace ZeroC.Slice.Generator;
/// C#-specific extension methods for .
internal static class TypeRefExtensions
{
-
/// Generates decode expression for a type reference. When the TypeRef has a cs::type attribute,
/// it is passed through as the concrete type for dictionary/sequence factory construction.
internal static string DecodeExpression(this TypeRef typeRef, string currentNamespace)
@@ -45,13 +45,28 @@ internal static string GetEncodeLambda(this TypeRef typeRef, bool isOptional, st
}
string csType = typeRef.Type.ToTypeString(currentNamespace) + "?";
- string param = typeRef.Type is CustomType
- ? "(value ?? default!)"
- : typeRef.IsValueType ? "value!.Value" : "value!";
- string encodeExpr = typeRef.Type.EncodeExpression(currentNamespace, param);
+ string encodeExpr = typeRef.Type.EncodeExpression(currentNamespace, typeRef.UnwrapNonNullOptional("value"));
return $"(ref SliceEncoder encoder, {csType} value) => {encodeExpr}";
}
+ /// Returns an encode lambda for an optional type reference that writes a bool null marker before the
+ /// value, for use where the caller has no bit sequence.
+ internal static string GetEncodeLambdaWithNullMarker(this TypeRef typeRef, string currentNamespace)
+ {
+ string csType = typeRef.FieldTypeString(true, currentNamespace);
+ CodeBlock encodeBody = typeRef.EncodeExpression(currentNamespace, typeRef.UnwrapNonNullOptional("value"));
+ return $$"""
+ (ref SliceEncoder encoder, {{csType}} value) =>
+ {
+ encoder.EncodeBool(value is not null);
+ if (value is not null)
+ {
+ {{encodeBody.Indent().Indent()}};
+ }
+ }
+ """;
+ }
+
/// Returns the C# type string for an incoming parameter (decode target). Sequences map to arrays,
/// dictionaries map to Dictionary<K,V>. Respects cs::type attribute.
internal static string IncomingParameterTypeString(this TypeRef typeRef, bool isOptional, string currentNamespace)
@@ -109,6 +124,11 @@ internal static string OutgoingParameterTypeString(this TypeRef typeRef, bool is
return (isOptional && !ignoreOptional) ? $"{baseType}?" : baseType;
}
+ /// Returns the expression that unwraps a non-null optional value for encoding. A custom type can map to
+ /// either a C# value type or a reference type, so it uses ?? instead of !.
+ internal static string UnwrapNonNullOptional(this TypeRef typeRef, string param) =>
+ typeRef.Type is CustomType ? $"({param} ?? default!)" : typeRef.IsValueType ? $"{param}!.Value" : $"{param}!";
+
extension(TypeRef value)
{
/// Returns the fixed wire size for a type reference, or null if variable-size.