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.