From 29670dce3505482d9c42c156536cbf829b3ca0f3 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 07:30:29 +0000 Subject: [PATCH] Update terminology from nested property to struct-typed property --- README.ja.md | 16 ++-- README.md | 4 +- benchmark/Benchmark.cs | 44 +++++------ src/PropertyGenerationModel.cs | 6 +- src/PropertySerializationKind.cs | 2 +- src/ZeroSerializerGenerator.cs | 108 +++++++++++++-------------- tests-unity/UnityCompatibility.cs | 50 ++++++------- tests/Diagnostics/DiagnosticTests.cs | 16 ++-- tests/SerializationModels.cs | 14 ++-- tests/SerializationTests.cs | 40 +++++----- tests/ShapeTagEmissionTests.cs | 8 +- 11 files changed, 154 insertions(+), 154 deletions(-) diff --git a/README.ja.md b/README.ja.md index c6a5865..19a4d4d 100644 --- a/README.ja.md +++ b/README.ja.md @@ -63,19 +63,19 @@ property offset は型の先頭からの相対位置で、`0` は null を表し - `[ZeroSerializer]` と `[StructLayout(LayoutKind.Sequential, Pack = 1)]` を再帰的に満たす Blittable Struct。 - `string`。 - 一次元配列。要素は Blittable primitive、enum、または Blittable Struct に限定されます。 -- `[ZeroSerializer]` を付けたネスト型。非 Blittable Struct はネストした View として公開されます。 +- `[ZeroSerializer]` を付けた構造体型プロパティー。非 Blittable Struct は View として公開されます。 Blittable Struct は全ケースで offset table を持たない raw payload として扱います。 -- 単体とネストプロパティーは `MemoryMarshal.Write/Read`。 +- 単体と構造体型プロパティーは `MemoryMarshal.Write/Read`。 - 配列は `MemoryMarshal.AsBytes/Cast`。 - nullable は property offset `0` で null を表し、非 null の場合だけ raw payload。 -`[ZeroSerializer]` が付いていても Blittable Struct はネスト View 化しません。親が非 Blittable 型なら親の property offset table は存在しますが、Blittable Struct payload 内部には table を生成しません。 +`[ZeroSerializer]` が付いていても Blittable Struct は View 化しません。親が非 Blittable 型なら親の property offset table は存在しますが、Blittable Struct payload 内部には table を生成しません。 全プロパティー型が Blittable 対応済みで、自身の `StructLayout(LayoutKind.Sequential, Pack = 1)` だけが不足する `[ZeroSerializer]` struct には、型名 identifier へ `ZEROS006` warning を出します。 -ネストした class/struct にも `[ZeroSerializer]` が必要です。未修飾の型は `ZEROS003` error になります。 +構造体型プロパティーの class/struct にも `[ZeroSerializer]` が必要です。未修飾の型は `ZEROS003` error になります。 ## null と可変長データ @@ -108,7 +108,7 @@ null は property offset `0` で表し、length header と payload を格納し ## View と長さ -生成 View は `ReadOnlyMemory` を保持する通常の `readonly struct` です。コンストラクターはデータ先頭が揃った `ReadOnlyMemory` だけを受け取り、offset は受け取りません。呼び出し側が必要な位置で `Slice` し、ネストした非 Blittable 型も property offset で `Slice` した Memory から View を生成します。固定長・可変長ともにコンストラクターでは長さを検証せず、プロパティーアクセス時の Memory・Span・index・`BinaryPrimitives`・`MemoryMarshal` の標準例外に範囲検証を任せます。 +生成 View は `ReadOnlyMemory` を保持する通常の `readonly struct` です。コンストラクターはデータ先頭が揃った `ReadOnlyMemory` だけを受け取り、offset は受け取りません。呼び出し側が必要な位置で `Slice` し、構造体型プロパティーの非 Blittable 型も property offset で `Slice` した Memory から View を生成します。固定長・可変長ともにコンストラクターでは長さを検証せず、プロパティーアクセス時の Memory・Span・index・`BinaryPrimitives`・`MemoryMarshal` の標準例外に範囲検証を任せます。 View は `ReadOnlySpan` と `ReadOnlyMemory` へ暗黙変換でき、どちらもコピーや再シリアライズを行いません。`RequiredByteLength >= 0` の固定長 View は先頭から `RequiredByteLength` までを返します。負値になる可変長 View は総バイト長を wire format から復元できないため、コンストラクターへ渡した借用領域全体を返します。可変長データのハッシュや検証では、未使用のバッファ末尾を含めないよう `writtenBytes` で Memory を切り詰めてから View を作成します。 @@ -146,8 +146,8 @@ ReadOnlyMemory directMemory = view.AsMemory(); 2. **可変長データ型のプロパティー** - `string` - 一次元配列(例: `int[]` など) -3. **サイズ予測不可能なネスト型のプロパティー** - - `[ZeroSerializer]` が付与されたネスト型であって、その内部に上記の Nullable 型、参照型、可変長データ型、またはその他のサイズ予測不可能なプロパティーを含んでいるもの +3. **サイズ予測不可能な構造体型プロパティー** + - `[ZeroSerializer]` が付与された構造体型プロパティーであって、その内部に上記の Nullable 型、参照型、可変長データ型、またはその他のサイズ予測不可能なプロパティーを含んでいるもの 4. **再帰・循環参照構造** - 自分自身や他の型を相互に参照し合うなどして、依存関係が再帰・循環している場合 5. **サイズ計算時の算術オーバーフロー** @@ -157,6 +157,6 @@ Blittable Struct は property offset table を持たないため、`RequiredByte `RequiredByteLength >= 0` は固定長であることだけを表し、Blittable 判定とは独立しています。class と非 Blittable struct は固定長でも property offset table から各プロパティーを読みます。struct 全体を `MemoryMarshal.Read` するのは Blittable Struct だけです。 -`Serialize` は呼び出し側が渡した `Span` に直接書き込み、書き込んだ総バイト数(offset table を含む)を `int` で返します。ネスト型の書き込みは生成時にルート `Serialize` の本体へ展開され、実行時に別の `Serialize` を再帰呼び出ししません。`MemoryMarshal.Write` が読み取り専用の `in T` を受け取る .NET 8 以降では、参照型と `RequiredByteLength` の絶対値が `16` 以下の構造体は通常の `this`、`16` を超える構造体は `this in T source` で宣言します。書き込み可能な `ref T` を受け取る .NET 7 以前では、構造体を通常の `this` で受け取り、Blittable Struct のルート値を `ref source` で直接書き込みます。`in` は宣言側だけに付き、呼び出しは常に `source.Serialize(buffer)` です。固定長・可変長ともに独自の長さ検証を生成せず、範囲外を index・Span・`BinaryPrimitives`・`MemoryMarshal` の標準例外に任せます。 +`Serialize` は呼び出し側が渡した `Span` に直接書き込み、書き込んだ総バイト数(offset table を含む)を `int` で返します。構造体型プロパティーの書き込みは生成時にルート `Serialize` の本体へ展開され、実行時に別の `Serialize` を再帰呼び出ししません。`MemoryMarshal.Write` が読み取り専用の `in T` を受け取る .NET 8 以降では、参照型と `RequiredByteLength` の絶対値が `16` 以下の構造体は通常の `this`、`16` を超える構造体は `this in T source` で宣言します。書き込み可能な `ref T` を受け取る .NET 7 以前では、構造体を通常の `this` で受け取り、Blittable Struct のルート値を `ref source` で直接書き込みます。`in` は宣言側だけに付き、呼び出しは常に `source.Serialize(buffer)` です。固定長・可変長ともに独自の長さ検証を生成せず、範囲外を index・Span・`BinaryPrimitives`・`MemoryMarshal` の標準例外に任せます。 string と Blittable payload は native memory image を利用するため、Serializer と View はリトルエンディアン環境だけを受け付けます。 diff --git a/README.md b/README.md index a2e0b59..fbdca1f 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Transform original = view.Materialize(); - Primitives and enums - Nullable values - `string` stored as UTF-16 (UTF-8 can be stored as byte[] by hand) -- Nested `[ZeroSerializer]` types +- Struct-typed `[ZeroSerializer]` properties - Blittable structs with `[StructLayout(LayoutKind.Sequential, Pack = 1)]` - One-dimensional arrays of blittable values or structs @@ -130,4 +130,4 @@ Blittable structs are stored directly as raw struct bytes without an offset tabl - The wire format requires a little-endian runtime. - View structs expose a compile-time constant `IsBlittable`, indicating whether the underlying serialized type is a blittable struct. - You can use `.AsMemory()` extension method (returns `ReadOnlyMemory`) or `.Materialize()` extension method (for views of blittable structs to convert them back to the original struct). -- Nested classes and structs must be marked with `[ZeroSerializer]`; otherwise the generator reports an unsupported property diagnostic. +- Struct-typed properties must be marked with `[ZeroSerializer]`; otherwise the generator reports an unsupported property diagnostic. diff --git a/benchmark/Benchmark.cs b/benchmark/Benchmark.cs index 3a8eead..18b6f5d 100644 --- a/benchmark/Benchmark.cs +++ b/benchmark/Benchmark.cs @@ -83,13 +83,13 @@ public void Setup() Integers = integers, Longs = longs, PackedValues = packedValues, - Nested = new NestedPayload + StructTyped = new StructTypedPayload { Version = random.Next(), Label = CreateRandomString(random, 128), Summary = packedValues[0], }, - NestedStruct = new NestedStructPayload( + StructTypedStruct = new StructTypedStructPayload( random.Next(), CreateRandomInt64(random), CreateRandomString(random, 128)), @@ -126,14 +126,14 @@ public int DeserializeAllProperties() ReadOnlySpan integers = view.Integers; ReadOnlySpan longs = view.Longs; ReadOnlySpan packedValues = view.PackedValues; - NestedPayloadView? nested = view.Nested; - int nestedVersion = nested!.Value.Version; - ReadOnlySpan nestedLabel = nested!.Value.Label; - PackedBenchmarkValueView nestedSummary = nested!.Value.Summary; - NestedStructPayloadView nestedStruct = view.NestedStruct; - int nestedStructCode = nestedStruct.Code; - long nestedStructAmount = nestedStruct.Amount; - ReadOnlySpan nestedStructLabel = nestedStruct.Label; + StructTypedPayloadView? structTyped = view.StructTyped; + int structTypedVersion = structTyped!.Value.Version; + ReadOnlySpan structTypedLabel = structTyped!.Value.Label; + PackedBenchmarkValueView structTypedSummary = structTyped!.Value.Summary; + StructTypedStructPayloadView structTypedStruct = view.StructTypedStruct; + int structTypedStructCode = structTypedStruct.Code; + long structTypedStructAmount = structTypedStruct.Amount; + ReadOnlySpan structTypedStructLabel = structTypedStruct.Label; // Consume getter results without traversing or validating collection contents. unchecked @@ -149,13 +149,13 @@ public int DeserializeAllProperties() consumedValue = (consumedValue * 31) + integers.Length; consumedValue = (consumedValue * 31) + longs.Length; consumedValue = (consumedValue * 31) + packedValues.Length; - consumedValue = (consumedValue * 31) + nestedVersion; - consumedValue = (consumedValue * 31) + nestedLabel.Length; - consumedValue = (consumedValue * 31) + nestedSummary.Number; - consumedValue = (consumedValue * 31) + nestedSummary.Amount.GetHashCode(); - consumedValue = (consumedValue * 31) + nestedStructCode; - consumedValue = (consumedValue * 31) + nestedStructAmount.GetHashCode(); - consumedValue = (consumedValue * 31) + nestedStructLabel.Length; + consumedValue = (consumedValue * 31) + structTypedVersion; + consumedValue = (consumedValue * 31) + structTypedLabel.Length; + consumedValue = (consumedValue * 31) + structTypedSummary.Number; + consumedValue = (consumedValue * 31) + structTypedSummary.Amount.GetHashCode(); + consumedValue = (consumedValue * 31) + structTypedStructCode; + consumedValue = (consumedValue * 31) + structTypedStructAmount.GetHashCode(); + consumedValue = (consumedValue * 31) + structTypedStructLabel.Length; return consumedValue; } } @@ -288,13 +288,13 @@ public sealed class BenchmarkPayload public PackedBenchmarkValue[] PackedValues { get; init; } = Array.Empty(); - public NestedPayload Nested { get; init; } = new(); + public StructTypedPayload StructTyped { get; init; } = new(); - public NestedStructPayload NestedStruct { get; init; } + public StructTypedStructPayload StructTypedStruct { get; init; } } [ZeroSerializerAttribute] - public sealed class NestedPayload + public sealed class StructTypedPayload { public int Version { get; init; } @@ -304,9 +304,9 @@ public sealed class NestedPayload } [ZeroSerializerAttribute] - public readonly struct NestedStructPayload + public readonly struct StructTypedStructPayload { - public NestedStructPayload(int code, long amount, string label) + public StructTypedStructPayload(int code, long amount, string label) { Code = code; Amount = amount; diff --git a/src/PropertyGenerationModel.cs b/src/PropertyGenerationModel.cs index d7e0f37..ae0d7e3 100644 --- a/src/PropertyGenerationModel.cs +++ b/src/PropertyGenerationModel.cs @@ -14,7 +14,7 @@ internal PropertyGenerationModel( PropertySerializationKind kind, int elementByteCount, ITypeSymbol? arrayElementType, - INamedTypeSymbol? nestedSerializableType, + INamedTypeSymbol? structTypedSerializableType, ITypeSymbol? nullableUnderlyingType = null, bool isNullableType = false) { @@ -22,7 +22,7 @@ internal PropertyGenerationModel( Kind = kind; ElementByteCount = elementByteCount; ArrayElementType = arrayElementType; - NestedSerializableType = nestedSerializableType; + StructTypedSerializableType = structTypedSerializableType; NullableUnderlyingType = nullableUnderlyingType; IsNullableType = isNullableType; } @@ -37,7 +37,7 @@ internal PropertyGenerationModel( internal ITypeSymbol? ArrayElementType { get; } - internal INamedTypeSymbol? NestedSerializableType { get; } + internal INamedTypeSymbol? StructTypedSerializableType { get; } internal ITypeSymbol? NullableUnderlyingType { get; } diff --git a/src/PropertySerializationKind.cs b/src/PropertySerializationKind.cs index 79d0c4e..9b41862 100644 --- a/src/PropertySerializationKind.cs +++ b/src/PropertySerializationKind.cs @@ -11,6 +11,6 @@ internal enum PropertySerializationKind String, BlittableStruct, Array, - Nested, + StructTyped, InvalidArray, } diff --git a/src/ZeroSerializerGenerator.cs b/src/ZeroSerializerGenerator.cs index bbf1538..bd68530 100644 --- a/src/ZeroSerializerGenerator.cs +++ b/src/ZeroSerializerGenerator.cs @@ -62,7 +62,7 @@ public sealed class ZeroSerializerGenerator : ISourceGenerator private static readonly DiagnosticDescriptor InvalidSerializableDependency = new( "ZEROS004", - "Invalid nested serializable type", + "Invalid struct-typed serializable type", "Property '{0}' refers to serializable type '{1}', but that type contains errors", SerializerName, DiagnosticSeverity.Error, @@ -213,20 +213,20 @@ private static void ExecuteCore( continue; } - PropertyGenerationModel? invalidNestedProperty = null; - foreach (PropertyGenerationModel nestedPropertyCandidate in generationModel.Properties) + PropertyGenerationModel? invalidStructTypedProperty = null; + foreach (PropertyGenerationModel structTypedPropertyCandidate in generationModel.Properties) { - if (nestedPropertyCandidate.NestedSerializableType is not null + if (structTypedPropertyCandidate.StructTypedSerializableType is not null && generationModels.TryGetValue( - nestedPropertyCandidate.NestedSerializableType, - out TypeGenerationModel? nestedModel) - && !nestedModel.IsValid) + structTypedPropertyCandidate.StructTypedSerializableType, + out TypeGenerationModel? structTypedModel) + && !structTypedModel.IsValid) { - invalidNestedProperty = nestedPropertyCandidate; + invalidStructTypedProperty = structTypedPropertyCandidate; break; } } - if (invalidNestedProperty is null) + if (invalidStructTypedProperty is null) { continue; } @@ -235,11 +235,11 @@ private static void ExecuteCore( invalidDependencyFound = true; executionContext.ReportDiagnostic(Diagnostic.Create( InvalidSerializableDependency, - invalidNestedProperty.Symbol.Locations.IsDefaultOrEmpty + invalidStructTypedProperty.Symbol.Locations.IsDefaultOrEmpty ? null - : invalidNestedProperty.Symbol.Locations[0], - invalidNestedProperty.Symbol.Name, - invalidNestedProperty.NestedSerializableType!.ToDisplayString())); + : invalidStructTypedProperty.Symbol.Locations[0], + invalidStructTypedProperty.Symbol.Name, + invalidStructTypedProperty.StructTypedSerializableType!.ToDisplayString())); } } while (invalidDependencyFound); @@ -502,7 +502,7 @@ private static bool ShouldEmitShapeTag(INamedTypeSymbol serializableType) { return new PropertyGenerationModel( serializableProperty, - PropertySerializationKind.Nested, + PropertySerializationKind.StructTyped, 0, null, namedNullableUnderlyingType, @@ -572,7 +572,7 @@ private static bool ShouldEmitShapeTag(INamedTypeSymbol serializableType) { return new PropertyGenerationModel( serializableProperty, - PropertySerializationKind.Nested, + PropertySerializationKind.StructTyped, 0, null, namedPropertyType, @@ -615,12 +615,12 @@ private static bool TryGetFixedTypeByteCount( int accumulatedByteCount = 0; foreach (ISymbol declaredMember in structType.GetMembers()) { - if (declaredMember is not IFieldSymbol nestedField || nestedField.IsStatic) + if (declaredMember is not IFieldSymbol field || field.IsStatic) { continue; } - if (!TryGetFixedTypeByteCount(nestedField.Type, typesBeingInspected, out int nestedFieldByteCount)) + if (!TryGetFixedTypeByteCount(field.Type, typesBeingInspected, out int fieldByteCount)) { typesBeingInspected.Remove(candidateType); byteCount = 0; @@ -629,7 +629,7 @@ private static bool TryGetFixedTypeByteCount( try { - accumulatedByteCount = checked(accumulatedByteCount + nestedFieldByteCount); + accumulatedByteCount = checked(accumulatedByteCount + fieldByteCount); } // Ignore exception: an overflowing property total means the struct cannot have a supported fixed byte count. catch (OverflowException) @@ -772,7 +772,7 @@ private static bool HasBlittableCompatiblePropertyShape(INamedTypeSymbol candida return false; } - // Ignore only the candidate's own layout; every nested struct must already be a valid Blittable Struct. + // Ignore only the candidate's own layout; every struct-typed property must already be a valid Blittable Struct. var typesBeingInspected = new HashSet(SymbolEqualityComparer.Default) { candidateStruct, @@ -1004,24 +1004,24 @@ private static void EmitWriteProperty( sourceBuilder.AppendLine($"{CreateLocalName(localNamePrefix, "Payload")}.CopyTo(destination.Slice(writtenBytes));"); sourceBuilder.AppendLine($"writtenBytes += {CreateLocalName(localNamePrefix, "Payload")}.Length;"); break; - case PropertySerializationKind.Nested: - string nestedSourceExpression; - if (property.NestedSerializableType!.TypeKind == TypeKind.Struct + case PropertySerializationKind.StructTyped: + string structTypedSourceExpression; + if (property.StructTypedSerializableType!.TypeKind == TypeKind.Struct && property.NullableUnderlyingType is not null) { string nullableStructValueName = CreateLocalName(localNamePrefix, "NullableStructValue"); sourceBuilder.AppendLine($"var {nullableStructValueName} = {serializationValueExpression};"); - nestedSourceExpression = nullableStructValueName; + structTypedSourceExpression = nullableStructValueName; } else { - nestedSourceExpression = serializationValueExpression; + structTypedSourceExpression = serializationValueExpression; } EmitWriteBody( sourceBuilder, - modelLookup[property.NestedSerializableType], + modelLookup[property.StructTypedSerializableType], modelLookup, - nestedSourceExpression, + structTypedSourceExpression, localNamePrefix); break; } @@ -1053,12 +1053,12 @@ private static void EmitView( } string shapeTagPrefix = generationModel.EmitShapeTag ? string.Empty : "//"; sourceBuilder.AppendLine($"{shapeTagPrefix}/// "); - sourceBuilder.AppendLine($"{shapeTagPrefix}/// A structural signature that describes the layout of the serialized type and any nested structures."); + sourceBuilder.AppendLine($"{shapeTagPrefix}/// A structural signature that describes the layout of the serialized type and any struct-typed properties."); sourceBuilder.AppendLine($"{shapeTagPrefix}/// "); sourceBuilder.AppendLine($"{shapeTagPrefix}public const string ShapeTag = \"{shapeTag}\";"); sourceBuilder.AppendLine(); sourceBuilder.AppendLine("/// "); - sourceBuilder.AppendLine("/// A hash of the structural signature that describes the layout of the serialized type and any nested structures."); + sourceBuilder.AppendLine("/// A hash of the structural signature that describes the layout of the serialized type and any struct-typed properties."); sourceBuilder.AppendLine("/// "); sourceBuilder.AppendLine($"public const uint ShapeHash = {shapeHash}U;"); sourceBuilder.AppendLine(); @@ -1069,7 +1069,7 @@ private static void EmitView( sourceBuilder.AppendLine($"public const int RequiredByteLength = {requiredByteLength};"); sourceBuilder.AppendLine($"public const bool IsBlittable = {generationModel.IsBlittableStruct.ToString().ToLowerInvariant()};"); sourceBuilder.AppendLine(); - // ReadOnlyMemory keeps the borrowed byte array reusable by ordinary and nested View structs without allocation. + // ReadOnlyMemory keeps the borrowed byte array reusable by ordinary and struct-typed View structs without allocation. sourceBuilder.AppendLine("private readonly ReadOnlyMemory serializedMemory;"); sourceBuilder.AppendLine(); sourceBuilder.AppendLine($"public {generationModel.ViewTypeName}(ReadOnlyMemory containingSerializedMemory)"); @@ -1191,18 +1191,18 @@ private static bool TryCalculatePredictableRequiredByteLength( { propertyBytesLength = propertyModel.ElementByteCount; } - else if (propertyModel.Kind == PropertySerializationKind.Nested - && propertyModel.NestedSerializableType is not null - && modelLookup.TryGetValue(propertyModel.NestedSerializableType, out TypeGenerationModel? nestedGenerationModel)) + else if (propertyModel.Kind == PropertySerializationKind.StructTyped + && propertyModel.StructTypedSerializableType is not null + && modelLookup.TryGetValue(propertyModel.StructTypedSerializableType, out TypeGenerationModel? structTypedGenerationModel)) { - bool isNestedSizePredictable = TryCalculatePredictableRequiredByteLength( - nestedGenerationModel, + bool isStructTypedSizePredictable = TryCalculatePredictableRequiredByteLength( + structTypedGenerationModel, modelLookup, typesBeingInspected, - out int nestedBytesLength); - if (isNestedSizePredictable) + out int structTypedBytesLength); + if (isStructTypedSizePredictable) { - propertyBytesLength = nestedBytesLength; + propertyBytesLength = structTypedBytesLength; } else { @@ -1251,15 +1251,15 @@ private static void EmitViewProperty( { propertyType = $"ReadOnlySpan<{property.ArrayElementType!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>"; } - else if (property.Kind == PropertySerializationKind.Nested) + else if (property.Kind == PropertySerializationKind.StructTyped) { - propertyType = GetQualifiedViewName(property.NestedSerializableType!); + propertyType = GetQualifiedViewName(property.StructTypedSerializableType!); } else if (property.Kind == PropertySerializationKind.BlittableStruct && !containingModel.IsBlittableStruct - && property.NestedSerializableType is not null) + && property.StructTypedSerializableType is not null) { - string viewName = GetQualifiedViewName(property.NestedSerializableType); + string viewName = GetQualifiedViewName(property.StructTypedSerializableType); propertyType = property.IsNullableType ? viewName + "?" : viewName; } else @@ -1268,10 +1268,10 @@ private static void EmitViewProperty( } var propertyReturnType - = property.Kind is PropertySerializationKind.BlittableStruct or PropertySerializationKind.Nested + = property.Kind is PropertySerializationKind.BlittableStruct or PropertySerializationKind.StructTyped ? (property.NullableUnderlyingType is not null || property.Symbol.Type.TypeKind is TypeKind.Class) - ? GetQualifiedViewName(property.NestedSerializableType!) + "?" - : GetQualifiedViewName(property.NestedSerializableType!) + ? GetQualifiedViewName(property.StructTypedSerializableType!) + "?" + : GetQualifiedViewName(property.StructTypedSerializableType!) : propertyType; sourceBuilder.AppendLine($"{propertyAccessibility} {propertyReturnType} {EscapeIdentifier(property.Symbol.Name)}"); sourceBuilder.OpenBlock(); @@ -1280,9 +1280,9 @@ var propertyReturnType if (containingModel.IsBlittableStruct) { if (property.Kind == PropertySerializationKind.BlittableStruct - && property.NestedSerializableType is not null) + && property.StructTypedSerializableType is not null) { - sourceBuilder.AppendLine($"return new {GetQualifiedViewName(property.NestedSerializableType)}(serializedMemory.Slice({property.BlittableByteOffset}, {property.ElementByteCount}));"); + sourceBuilder.AppendLine($"return new {GetQualifiedViewName(property.StructTypedSerializableType)}(serializedMemory.Slice({property.BlittableByteOffset}, {property.ElementByteCount}));"); } else { @@ -1316,9 +1316,9 @@ var propertyReturnType "propertyDataOffset"); break; case PropertySerializationKind.BlittableStruct: - if (property.NestedSerializableType is not null) + if (property.StructTypedSerializableType is not null) { - sourceBuilder.AppendLine($"return new {GetQualifiedViewName(property.NestedSerializableType)}(serializedMemory.Slice(propertyDataOffset, {property.ElementByteCount}));"); + sourceBuilder.AppendLine($"return new {GetQualifiedViewName(property.StructTypedSerializableType)}(serializedMemory.Slice(propertyDataOffset, {property.ElementByteCount}));"); } else { @@ -1342,9 +1342,9 @@ var propertyReturnType sourceBuilder.AppendLine($"return MemoryMarshal.Cast(serializedData.Slice(propertyDataOffset + 4, propertyPayloadByteCount));"); } break; - case PropertySerializationKind.Nested: - // Nested views receive an already-positioned memory region; their constructor has no offset semantics. - sourceBuilder.AppendLine($"return new {GetQualifiedViewName(property.NestedSerializableType!)}(serializedMemory.Slice(propertyDataOffset));"); + case PropertySerializationKind.StructTyped: + // Struct-typed views receive an already-positioned memory region; their constructor has no offset semantics. + sourceBuilder.AppendLine($"return new {GetQualifiedViewName(property.StructTypedSerializableType!)}(serializedMemory.Slice(propertyDataOffset));"); break; } @@ -1463,9 +1463,9 @@ private static string GetPropertyLengthExpression( case PropertySerializationKind.String: case PropertySerializationKind.Array: return $"4 + BinaryPrimitives.ReadInt32LittleEndian({spanVarName}.Slice({offsetVarName}, 4))"; - case PropertySerializationKind.Nested: - string nestedViewTypeName = GetQualifiedViewName(property.NestedSerializableType!); - return $"new {nestedViewTypeName}({memoryVarName}.Slice({offsetVarName})).GetByteLength()"; + case PropertySerializationKind.StructTyped: + string structTypedViewTypeName = GetQualifiedViewName(property.StructTypedSerializableType!); + return $"new {structTypedViewTypeName}({memoryVarName}.Slice({offsetVarName})).GetByteLength()"; default: throw new InvalidOperationException("Unknown property kind"); } diff --git a/tests-unity/UnityCompatibility.cs b/tests-unity/UnityCompatibility.cs index 8913194..1124974 100644 --- a/tests-unity/UnityCompatibility.cs +++ b/tests-unity/UnityCompatibility.cs @@ -50,7 +50,7 @@ && fixedView.State == fixedPacket.State && fixedView.Position.X == fixedPacket.Position.X && fixedView.Position.Y == fixedPacket.Position.Y, - "Fixed-size primitive, enum, or nested Blittable value did not match its source."); + "Fixed-size primitive, enum, or struct-typed Blittable value did not match its source."); ReadOnlySpan fixedSerializedSpan = fixedView; ReadOnlyMemory fixedSerializedMemory = fixedView; @@ -131,7 +131,7 @@ && variableView.FloatValues[1] == 2.5f && variableView.DoubleValues.Length == 3 && variableView.DoubleValues[2] == 3.25, - "Runtime-sized, nullable, array, string, or nested View value did not match its source."); + "Runtime-sized, nullable, array, string, or struct-typed View value did not match its source."); ReadOnlySpan variableSerializedSpan = variableView; ReadOnlyMemory variableSerializedMemory = variableView; @@ -213,45 +213,45 @@ && blittableRecordStructWritten == 12, "UnitySimpleBlittableRecordStruct did not match its source or IsBlittable was incorrect."); -// Nested blittable record struct container verification +// Struct-typed blittable record struct container verification var firstRecord = new UnitySimpleBlittableRecordStruct { IntValue = 88, DoubleValue = 88.88 }; var secondRecord = new UnitySimpleBlittableRecordStruct { IntValue = 99, DoubleValue = 99.99 }; -var nestedBlittableRecordContainer = new UnityBlittableRecordStructContainer +var structTypedBlittableRecordContainer = new UnityBlittableRecordStructContainer { Value = firstRecord, OptionalValue = secondRecord, Values = [firstRecord, secondRecord], }; -var nestedBlittableRecordBuffer = new byte[256]; -int nestedBlittableRecordWritten = nestedBlittableRecordContainer.Serialize(nestedBlittableRecordBuffer); -var nestedBlittableRecordView = new UnityBlittableRecordStructContainerView(nestedBlittableRecordBuffer.AsMemory(0, nestedBlittableRecordWritten)); +var structTypedBlittableRecordBuffer = new byte[256]; +int structTypedBlittableRecordWritten = structTypedBlittableRecordContainer.Serialize(structTypedBlittableRecordBuffer); +var structTypedBlittableRecordView = new UnityBlittableRecordStructContainerView(structTypedBlittableRecordBuffer.AsMemory(0, structTypedBlittableRecordWritten)); RequireCondition( - nestedBlittableRecordView.Value.IntValue == 88 - && nestedBlittableRecordView.Value.DoubleValue == 88.88 - && nestedBlittableRecordView.OptionalValue!.Value.IntValue == 99 - && nestedBlittableRecordView.OptionalValue!.Value.DoubleValue == 99.99 - && nestedBlittableRecordView.Values.Length == 2 - && nestedBlittableRecordView.Values[0].IntValue == 88 - && nestedBlittableRecordView.Values[0].DoubleValue == 88.88 - && nestedBlittableRecordView.Values[1].IntValue == 99 - && nestedBlittableRecordView.Values[1].DoubleValue == 99.99 - && nestedBlittableRecordView.GetByteLength() == nestedBlittableRecordWritten, + structTypedBlittableRecordView.Value.IntValue == 88 + && structTypedBlittableRecordView.Value.DoubleValue == 88.88 + && structTypedBlittableRecordView.OptionalValue!.Value.IntValue == 99 + && structTypedBlittableRecordView.OptionalValue!.Value.DoubleValue == 99.99 + && structTypedBlittableRecordView.Values.Length == 2 + && structTypedBlittableRecordView.Values[0].IntValue == 88 + && structTypedBlittableRecordView.Values[0].DoubleValue == 88.88 + && structTypedBlittableRecordView.Values[1].IntValue == 99 + && structTypedBlittableRecordView.Values[1].DoubleValue == 99.99 + && structTypedBlittableRecordView.GetByteLength() == structTypedBlittableRecordWritten, "UnityBlittableRecordStructContainer non-null roundtrip failed."); -var nestedBlittableRecordNullsContainer = new UnityBlittableRecordStructContainer +var structTypedBlittableRecordNullsContainer = new UnityBlittableRecordStructContainer { Value = firstRecord, OptionalValue = null, Values = null, }; -int nestedBlittableRecordNullsWritten = nestedBlittableRecordNullsContainer.Serialize(nestedBlittableRecordBuffer); -var nestedBlittableRecordNullsView = new UnityBlittableRecordStructContainerView(nestedBlittableRecordBuffer.AsMemory(0, nestedBlittableRecordNullsWritten)); +int structTypedBlittableRecordNullsWritten = structTypedBlittableRecordNullsContainer.Serialize(structTypedBlittableRecordBuffer); +var structTypedBlittableRecordNullsView = new UnityBlittableRecordStructContainerView(structTypedBlittableRecordBuffer.AsMemory(0, structTypedBlittableRecordNullsWritten)); RequireCondition( - nestedBlittableRecordNullsView.Value.IntValue == 88 - && nestedBlittableRecordNullsView.Value.DoubleValue == 88.88 - && nestedBlittableRecordNullsView.OptionalValue is null - && nestedBlittableRecordNullsView.Values.IsEmpty - && nestedBlittableRecordNullsView.GetByteLength() == nestedBlittableRecordNullsWritten, + structTypedBlittableRecordNullsView.Value.IntValue == 88 + && structTypedBlittableRecordNullsView.Value.DoubleValue == 88.88 + && structTypedBlittableRecordNullsView.OptionalValue is null + && structTypedBlittableRecordNullsView.Values.IsEmpty + && structTypedBlittableRecordNullsView.GetByteLength() == structTypedBlittableRecordNullsWritten, "UnityBlittableRecordStructContainer nulls roundtrip failed."); Console.WriteLine("ZeroSerializer Unity compatibility tests passed."); diff --git a/tests/Diagnostics/DiagnosticTests.cs b/tests/Diagnostics/DiagnosticTests.cs index f3efd7f..17fa361 100644 --- a/tests/Diagnostics/DiagnosticTests.cs +++ b/tests/Diagnostics/DiagnosticTests.cs @@ -319,7 +319,7 @@ await CSharpSourceGeneratorVerifier.VerifySourceGenerat } [Fact] - public async Task ZEROS002_Violation_UnmarkedBlittableNestedStruct() + public async Task ZEROS002_Violation_UnmarkedBlittableStructProperty() { string source = @" using System.Runtime.InteropServices; @@ -351,7 +351,7 @@ await CSharpSourceGeneratorVerifier.VerifySourceGenerat } [Fact] - public async Task ZEROS002_Violation_UnmarkedNestedClass() + public async Task ZEROS002_Violation_UnmarkedStructTypedClass() { string source = @" using ZeroSerializer; @@ -377,7 +377,7 @@ await CSharpSourceGeneratorVerifier.VerifySourceGenerat } [Fact] - public async Task ZEROS002_Compliant_MarkedBlittableNestedStruct() + public async Task ZEROS002_Compliant_MarkedBlittableStructProperty() { string source = @" using System.Runtime.InteropServices; @@ -458,7 +458,7 @@ await CSharpSourceGeneratorVerifier.VerifySourceGenerat } [Fact] - public async Task ZEROS004_Violation_InvalidNestedSerializableType() + public async Task ZEROS004_Violation_InvalidStructTypedSerializableType() { string source = @" using System.Runtime.InteropServices; @@ -479,7 +479,7 @@ public class InvalidType [ZeroSerializer] public class Container { - public InvalidType {|#1:NestedValue|} { get; set; } + public InvalidType {|#1:StructTypedValue|} { get; set; } } "; @@ -490,12 +490,12 @@ await CSharpSourceGeneratorVerifier.VerifySourceGenerat .WithArguments("Value", "PackedValue"), new DiagnosticResult("ZEROS004", DiagnosticSeverity.Error) .WithLocation(1) - .WithArguments("NestedValue", "InvalidType") + .WithArguments("StructTypedValue", "InvalidType") ); } [Fact] - public async Task ZEROS004_Compliant_ValidNestedSerializableType() + public async Task ZEROS004_Compliant_ValidStructTypedSerializableType() { string source = @" using System.Runtime.InteropServices; @@ -517,7 +517,7 @@ public class ValidType [ZeroSerializer] public class Container { - public ValidType NestedValue { get; set; } + public ValidType StructTypedValue { get; set; } } "; diff --git a/tests/SerializationModels.cs b/tests/SerializationModels.cs index efb6443..cbf9148 100644 --- a/tests/SerializationModels.cs +++ b/tests/SerializationModels.cs @@ -204,7 +204,7 @@ public struct EmptyStruct } [ZeroSerializer] -public sealed class ZeroLengthNestedStructContainer +public sealed class ZeroLengthStructTypedContainer { public int Before { get; init; } @@ -347,13 +347,13 @@ public class DuplicateInstanceContainer public class SharedClassItem { public int Value { get; set; } - public SharedClassNested? Nested { get; set; } + public SharedClassStructTyped? StructTyped { get; set; } } [ZeroSerializer] -public class SharedClassNested +public class SharedClassStructTyped { - public int NestedValue { get; set; } + public int StructTypedValue { get; set; } } [ZeroSerializer] @@ -439,9 +439,9 @@ public sealed class SequentialPackOneClass [ZeroSerializer(EmitShapeTag = true)] public sealed class SchemaSignatureTestsModel { - // 1. blittable and non-blittable nested type combo - public PackedRecord BlittableNested { get; init; } - public FixedClass NonBlittableNested { get; init; } + // 1. blittable and non-blittable struct-typed property combo + public PackedRecord BlittableStructTyped { get; init; } + public FixedClass NonBlittableStructTyped { get; init; } // 2. blittable struct array public PackedRecord[] BlittableStructArray { get; init; } diff --git a/tests/SerializationTests.cs b/tests/SerializationTests.cs index 09d0357..7ab5de9 100644 --- a/tests/SerializationTests.cs +++ b/tests/SerializationTests.cs @@ -117,7 +117,7 @@ public void BlittableStructSerializesAndDeserializesCorrectly() } [Fact] - public void NestedNullableAndArrayBlittableStructsSerializeAndDeserializeCorrectly() + public void StructTypedNullableAndArrayBlittableStructsSerializeAndDeserializeCorrectly() { var first = new PackedRecord { Number = 10, State = SignedState.Negative }; var second = new PackedRecord { Number = 20, State = SignedState.Positive }; @@ -355,22 +355,22 @@ public void EmptyClassAndStructSerializeZeroBytesWithoutErrors() } [Fact] - public void ZeroLengthNestedStructSharesOffsetWithNextProperty() + public void ZeroLengthStructTypedSharesOffsetWithNextProperty() { - var source = new ZeroLengthNestedStructContainer + var source = new ZeroLengthStructTypedContainer { Before = 123, Empty = new EmptyStruct(), After = 456, }; - var buffer = new byte[ZeroLengthNestedStructContainerView.RequiredByteLength]; + var buffer = new byte[ZeroLengthStructTypedContainerView.RequiredByteLength]; int writtenBytes = source.Serialize(buffer); - var view = new ZeroLengthNestedStructContainerView(buffer); + var view = new ZeroLengthStructTypedContainerView(buffer); EmptyStructView emptyView = view.Empty; - // A zero-byte nested payload and its following property intentionally share one offset. - TestAssert.Equal(20, ZeroLengthNestedStructContainerView.RequiredByteLength, nameof(ZeroLengthNestedStructContainerView.RequiredByteLength)); + // A zero-byte struct-typed payload and its following property intentionally share one offset. + TestAssert.Equal(20, ZeroLengthStructTypedContainerView.RequiredByteLength, nameof(ZeroLengthStructTypedContainerView.RequiredByteLength)); TestAssert.Equal(20, writtenBytes, nameof(writtenBytes)); TestAssert.Equal(12, BinaryPrimitives.ReadInt32LittleEndian(buffer.AsSpan(0, 4)), "Before offset"); TestAssert.Equal(16, BinaryPrimitives.ReadInt32LittleEndian(buffer.AsSpan(4, 4)), "Empty offset"); @@ -765,9 +765,9 @@ public void PrimitiveAndEnumArraysSerializeAndDeserializeCorrectly() } [Fact] - public void NestedSerializableTypePropertiesReturnViewStructInstances() + public void StructTypedSerializablePropertiesReturnViewStructInstances() { - // 1. Assert that nested blittable type returns view + // 1. Assert that struct-typed blittable property returns view PropertyInfo? valueProperty = typeof(PackedContainerView).GetProperty(nameof(PackedContainerView.Value)); Assert.NotNull(valueProperty); Assert.Equal(typeof(PackedRecordView), valueProperty!.PropertyType); @@ -776,7 +776,7 @@ public void NestedSerializableTypePropertiesReturnViewStructInstances() Assert.NotNull(optionalValueProperty); Assert.Equal(typeof(Nullable), optionalValueProperty!.PropertyType); - // 2. Assert that nested non-blittable type returns view + // 2. Assert that struct-typed non-blittable property returns view PropertyInfo? childProperty = typeof(VariableRecordView).GetProperty(nameof(VariableRecordView.Child)); Assert.NotNull(childProperty); Assert.Equal(typeof(FixedClassView?), childProperty!.PropertyType); @@ -1047,7 +1047,7 @@ public void GetByteLengthCalculatesTotalSizeWhenStringIsLastField() } [Fact] - public void GetByteLengthCalculatesTotalSizeWhenNestedBlittableStructIsLastField() + public void GetByteLengthCalculatesTotalSizeWhenStructTypedBlittableStructIsLastProperty() { var source = new VariableStructWithBlittableStructAtEnd { Text = "hello", Blittable = new PackedRecord { Number = 5, State = SignedState.Positive } }; var buffer = new byte[64]; @@ -1177,7 +1177,7 @@ public void BlittableRecordStructSerializesAsRawPayload() } [Fact] - public void NestedBlittableRecordStructsReturnViewStructs() + public void StructTypedBlittableRecordStructsReturnViewStructs() { // 1. Verify view property types via reflection PropertyInfo? valueProperty = typeof(BlittableRecordStructContainerView).GetProperty(nameof(BlittableRecordStructContainerView.Value)); @@ -1288,16 +1288,16 @@ public void UnalignedPackOneStructSerializesWithoutAlignmentPadding() [Fact] public void SharedReferenceTypeInstancesAreSerializedSequentially() { - var sharedNested = new SharedClassNested { NestedValue = 42 }; - var sharedItem = new SharedClassItem { Value = 100, Nested = sharedNested }; - var distinctItemWithSharedNested = new SharedClassItem { Value = 200, Nested = sharedNested }; + var sharedStructTyped = new SharedClassStructTyped { StructTypedValue = 42 }; + var sharedItem = new SharedClassItem { Value = 100, StructTyped = sharedStructTyped }; + var distinctItemWithSharedStructTyped = new SharedClassItem { Value = 200, StructTyped = sharedStructTyped }; - // Foo and Bar share SharedClassItem instance; Baz has a distinct SharedClassItem instance but shares the same SharedClassNested instance + // Foo and Bar share SharedClassItem instance; Baz has a distinct SharedClassItem instance but shares the same SharedClassStructTyped instance var container = new DuplicateInstanceContainer { Foo = sharedItem, Bar = sharedItem, - Baz = distinctItemWithSharedNested, + Baz = distinctItemWithSharedStructTyped, }; var buffer = new byte[256]; @@ -1308,9 +1308,9 @@ public void SharedReferenceTypeInstancesAreSerializedSequentially() TestAssert.Equal(100, view.Bar?.Value, nameof(container.Bar.Value)); TestAssert.Equal(200, view.Baz?.Value, nameof(container.Baz.Value)); - TestAssert.Equal(42, view.Foo?.Nested?.NestedValue, nameof(container.Foo.Nested.NestedValue)); - TestAssert.Equal(42, view.Bar?.Nested?.NestedValue, nameof(container.Bar.Nested.NestedValue)); - TestAssert.Equal(42, view.Baz?.Nested?.NestedValue, nameof(container.Baz.Nested.NestedValue)); + TestAssert.Equal(42, view.Foo?.StructTyped?.StructTypedValue, nameof(container.Foo.StructTyped.StructTypedValue)); + TestAssert.Equal(42, view.Bar?.StructTyped?.StructTypedValue, nameof(container.Bar.StructTyped.StructTypedValue)); + TestAssert.Equal(42, view.Baz?.StructTyped?.StructTypedValue, nameof(container.Baz.StructTyped.StructTypedValue)); TestAssert.Equal(writtenBytes, view.GetByteLength(), "SharedReferenceInstances GetByteLength"); } diff --git a/tests/ShapeTagEmissionTests.cs b/tests/ShapeTagEmissionTests.cs index d74ab97..a2e876b 100644 --- a/tests/ShapeTagEmissionTests.cs +++ b/tests/ShapeTagEmissionTests.cs @@ -24,11 +24,11 @@ public void ShapeTagIsCommentedOutByDefault() Assert.DoesNotContain(" public const string ShapeTag", generatedView); Assert.Contains(" // Note: Emitting ShapeTag requires `EmitShapeTag = true` on ZeroSerializerAttribute.", generatedView); Assert.Contains(" ///// ", generatedView); - Assert.Contains(" ///// A structural signature that describes the layout of the serialized type and any nested structures.", generatedView); + Assert.Contains(" ///// A structural signature that describes the layout of the serialized type and any struct-typed properties.", generatedView); Assert.Contains(" ///// ", generatedView); Assert.Contains(" //public const string ShapeTag = \"v1/{int}\";", generatedView); Assert.Contains(" /// ", generatedView); - Assert.Contains(" /// A hash of the structural signature that describes the layout of the serialized type and any nested structures.", generatedView); + Assert.Contains(" /// A hash of the structural signature that describes the layout of the serialized type and any struct-typed properties.", generatedView); Assert.Contains(" /// ", generatedView); Assert.Contains(" public const uint ShapeHash = ", generatedView); } @@ -42,11 +42,11 @@ public void ShapeTagAndRemarksAreEmittedWhenRequested() Assert.Contains(" /// ", generatedView); Assert.Contains(" /// ShapeTag: `v1/{int}`", generatedView); Assert.Contains(" /// ", generatedView); - Assert.Contains(" /// A structural signature that describes the layout of the serialized type and any nested structures.", generatedView); + Assert.Contains(" /// A structural signature that describes the layout of the serialized type and any struct-typed properties.", generatedView); Assert.Contains(" /// ", generatedView); Assert.Contains(" public const string ShapeTag = \"v1/{int}\";", generatedView); Assert.Contains(" /// ", generatedView); - Assert.Contains(" /// A hash of the structural signature that describes the layout of the serialized type and any nested structures.", generatedView); + Assert.Contains(" /// A hash of the structural signature that describes the layout of the serialized type and any struct-typed properties.", generatedView); Assert.Contains(" /// ", generatedView); Assert.Contains(" public const uint ShapeHash = ", generatedView); }