Skip to content

Commit fe42ffe

Browse files
live1206Copilot
andcommitted
Fix MRW JSON write core override for custom bases
Detect overridable JsonModelWriteCore methods on custom and framework base types so derived model serialization emits override instead of hiding the base member. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ae88584 commit fe42ffe

3 files changed

Lines changed: 57 additions & 22 deletions

File tree

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ private MethodProvider BuildXmlModelWriteCoreMethod()
6767
MethodSignatureModifiers modifiers = _isStruct
6868
? MethodSignatureModifiers.Private
6969
: MethodSignatureModifiers.Internal | MethodSignatureModifiers.Virtual;
70-
if (_shouldOverrideMethods)
70+
if (_shouldOverrideXmlMethods)
7171
{
7272
modifiers = MethodSignatureModifiers.Internal | MethodSignatureModifiers.Override;
7373
}
@@ -81,7 +81,7 @@ private MethodProvider BuildXmlModelWriteCoreMethod()
8181

8282
private MethodBodyStatement[] BuildXmlModelWriteCoreMethodBody()
8383
{
84-
var categorizedProperties = _shouldOverrideMethods
84+
var categorizedProperties = _shouldOverrideXmlMethods
8585
? CategorizedXmlProperties
8686
: AllCategorizedXmlProperties;
8787
var statements = new List<MethodBodyStatement>
@@ -90,7 +90,7 @@ private MethodBodyStatement[] BuildXmlModelWriteCoreMethodBody()
9090
MethodBodyStatement.EmptyLine
9191
};
9292

93-
if (_shouldOverrideMethods)
93+
if (_shouldOverrideXmlMethods)
9494
{
9595
statements.Add(Base.Invoke(XmlModelWriteCoreMethodName, _xmlWriterParameter, _serializationOptionsParameter).Terminate());
9696
}

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,12 @@ public partial class MrwSerializationTypeDefinition : TypeProvider
5353
private readonly ScopedApi<ModelReaderWriterOptions> _mrwOptionsParameterSnippet;
5454
private readonly ScopedApi<JsonElement> _jsonElementParameterSnippet;
5555
private readonly ScopedApi<bool> _isNotEqualToWireConditionSnippet;
56-
private readonly CSharpType _jsonModelTInterface;
57-
private readonly CSharpType? _jsonModelObjectInterface;
58-
private readonly CSharpType _persistableModelTInterface;
59-
private readonly CSharpType? _persistableModelObjectInterface;
56+
private CSharpType? _jsonModelTInterfaceValue;
57+
private CSharpType _jsonModelTInterface => _jsonModelTInterfaceValue ??= new CSharpType(typeof(IJsonModel<>), SerializationInterfaceType.Type);
58+
private CSharpType? _jsonModelObjectInterface => _isStruct ? (CSharpType)typeof(IJsonModel<object>) : null;
59+
private CSharpType? _persistableModelTInterfaceValue;
60+
private CSharpType _persistableModelTInterface => _persistableModelTInterfaceValue ??= new CSharpType(typeof(IPersistableModel<>), SerializationInterfaceType.Type);
61+
private CSharpType? _persistableModelObjectInterface => _isStruct ? (CSharpType)typeof(IPersistableModel<object>) : null;
6062
private readonly ModelProvider _model;
6163
private readonly InputModelType _inputModel;
6264
private readonly FieldProvider? _rawDataField;
@@ -67,10 +69,16 @@ public partial class MrwSerializationTypeDefinition : TypeProvider
6769
private readonly bool _supportsXml;
6870
private ConstructorProvider? _serializationConstructor;
6971
// Flag to determine if the model should override the serialization methods
70-
private readonly bool _shouldOverrideMethods;
71-
private readonly bool _shouldSkipDerivedSerializationMethodOverrides;
72+
private bool ShouldOverrideMethods => _model.BaseModelProvider != null && !_isStruct;
73+
private bool ShouldSkipSerializationMethodOverrides => ShouldSkipDerivedSerializationMethodOverrides(_model.BaseModelProvider);
74+
private readonly bool _shouldOverrideXmlMethods;
7275
private readonly Lazy<PropertyProvider[]> _additionalProperties;
7376

77+
private TypeProvider SerializationInterfaceType => _serializationInterfaceType ??= _inputModel.IsUnknownDiscriminatorModel
78+
? ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(_inputModel.BaseModel!)!
79+
: _model;
80+
private TypeProvider? _serializationInterfaceType;
81+
7482
private CSharpType RootType => _rootType ??= GetRootModelType();
7583
private CSharpType? _rootType;
7684

@@ -84,17 +92,10 @@ public MrwSerializationTypeDefinition(InputModelType inputModel, ModelProvider m
8492
_isStruct = _model.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Struct);
8593
_supportsXml = inputModel.Usage.HasFlag(InputModelTypeUsage.Xml);
8694
_supportsJson = inputModel.Usage.HasFlag(InputModelTypeUsage.Json) || !_supportsXml;
87-
// Initialize the serialization interfaces
88-
var interfaceType = inputModel.IsUnknownDiscriminatorModel ? ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel.BaseModel!)! : _model;
89-
_jsonModelTInterface = new CSharpType(typeof(IJsonModel<>), interfaceType.Type);
90-
_jsonModelObjectInterface = _isStruct ? (CSharpType)typeof(IJsonModel<object>) : null;
91-
_persistableModelTInterface = new CSharpType(typeof(IPersistableModel<>), interfaceType.Type);
92-
_persistableModelObjectInterface = _isStruct ? (CSharpType)typeof(IPersistableModel<object>) : null;
95+
_shouldOverrideXmlMethods = _model.BaseModelProvider != null && !_isStruct;
9396
_rawDataField = _model.Fields.FirstOrDefault(f => f.Name == AdditionalPropertiesHelper.AdditionalBinaryDataPropsFieldName);
9497
_additionalBinaryDataProperty = new(GetAdditionalBinaryDataPropertiesProp);
9598
_additionalProperties = new(() => [.. _model.Properties.Where(p => p.IsAdditionalProperties)]);
96-
_shouldOverrideMethods = _model.BaseModelProvider != null && !_isStruct;
97-
_shouldSkipDerivedSerializationMethodOverrides = ShouldSkipDerivedSerializationMethodOverrides(_model.BaseModelProvider);
9899
_utf8JsonWriterSnippet = _utf8JsonWriterParameter.As<Utf8JsonWriter>();
99100
_mrwOptionsParameterSnippet = _serializationOptionsParameter.As<ModelReaderWriterOptions>();
100101
_jsonElementParameterSnippet = _jsonElementDeserializationParam.As<JsonElement>();
@@ -530,7 +531,7 @@ internal MethodProvider BuildJsonModelWriteCoreMethod()
530531
MethodSignatureModifiers modifiers = _isStruct
531532
? MethodSignatureModifiers.Private
532533
: MethodSignatureModifiers.Protected | MethodSignatureModifiers.Virtual;
533-
if (_shouldOverrideMethods)
534+
if (ShouldOverrideMethods)
534535
{
535536
modifiers = MethodSignatureModifiers.Protected | MethodSignatureModifiers.Override;
536537
}
@@ -552,7 +553,7 @@ internal MethodProvider BuildPersistableModelWriteCoreMethod()
552553
? MethodSignatureModifiers.Private
553554
: MethodSignatureModifiers.Protected | MethodSignatureModifiers.Virtual;
554555

555-
if (_shouldOverrideMethods && !_shouldSkipDerivedSerializationMethodOverrides)
556+
if (ShouldOverrideMethods && !ShouldSkipSerializationMethodOverrides)
556557
{
557558
modifiers = MethodSignatureModifiers.Protected | MethodSignatureModifiers.Override;
558559
}
@@ -576,7 +577,7 @@ internal MethodProvider BuildPersistableModelCreateCoreMethod()
576577
? MethodSignatureModifiers.Private
577578
: MethodSignatureModifiers.Protected | MethodSignatureModifiers.Virtual;
578579

579-
if (_shouldOverrideMethods && !_shouldSkipDerivedSerializationMethodOverrides)
580+
if (ShouldOverrideMethods && !ShouldSkipSerializationMethodOverrides)
580581
{
581582
modifiers = MethodSignatureModifiers.Protected | MethodSignatureModifiers.Override;
582583
}
@@ -624,7 +625,7 @@ internal MethodProvider BuildJsonModelCreateCoreMethod()
624625
? MethodSignatureModifiers.Private
625626
: MethodSignatureModifiers.Protected | MethodSignatureModifiers.Virtual;
626627

627-
if (_shouldOverrideMethods && !_shouldSkipDerivedSerializationMethodOverrides)
628+
if (ShouldOverrideMethods && !ShouldSkipSerializationMethodOverrides)
628629
{
629630
modifiers = MethodSignatureModifiers.Protected | MethodSignatureModifiers.Override;
630631
}
@@ -1055,7 +1056,7 @@ private MethodBodyStatement[] BuildPersistableModelCreateCoreMethodBody()
10551056
private MethodBodyStatement CallBaseJsonModelWriteCore(bool isDynamicModelWithNonDynamicBase)
10561057
{
10571058
// base.<JsonModelWriteCore>()
1058-
bool callBaseWriteMethod = _shouldOverrideMethods
1059+
bool callBaseWriteMethod = ShouldOverrideMethods
10591060
&& (_jsonPatchProperty is null || !isDynamicModelWithNonDynamicBase);
10601061
return callBaseWriteMethod ?
10611062
Base.Invoke(JsonModelWriteCoreMethodName, [_utf8JsonWriterParameter, _serializationOptionsParameter]).Terminate()

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SystemObjectModelSerializationTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,31 @@ public void JsonModelWriteCore_IsOverride_WhenBaseIsRegularModel()
121121
"JsonModelWriteCore should be 'override' with regular base too");
122122
}
123123

124+
[Test]
125+
public void JsonModelWriteCore_IsOverride_WhenBaseProviderIsResolvedAfterSerialization()
126+
{
127+
var baseInputModel = InputFactory.Model("Resource");
128+
var derivedInputModel = InputFactory.Model("TrackedResource", properties: [InputFactory.Property("Location", InputPrimitiveType.String)]);
129+
MockHelpers.LoadMockGenerator(inputModels: () => [baseInputModel, derivedInputModel]);
130+
131+
var derived = new DelayedBaseModelProvider(derivedInputModel);
132+
var serialization = new MrwSerializationTypeDefinition(derivedInputModel, derived);
133+
134+
// The serialization provider can be constructed before later visitors/customization
135+
// resolution make the base model provider available.
136+
derived.BaseModel = new SystemObjectModelProvider(new CSharpType(typeof(object)), baseInputModel);
137+
138+
var method = serialization.BuildJsonModelWriteCoreMethod();
139+
140+
Assert.AreEqual(derived.BaseModel.Type, derived.Type.BaseType,
141+
"The generated model type should inherit the base resolved after serialization construction.");
142+
Assert.AreEqual(derived.BaseModel.Type, serialization.Type.BaseType,
143+
"The serialization type should inherit the same resolved base.");
144+
Assert.IsTrue(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Override),
145+
"JsonModelWriteCore should evaluate BaseModelProvider when the method is built, not when serialization is constructed");
146+
Assert.IsFalse(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Virtual));
147+
}
148+
124149
// -------------------------------------------------------------------
125150
// PersistableModelWriteCore: 'virtual' with system base, 'override' with regular
126151
// (the framework base already implements this; derived model re-introduces it)
@@ -328,5 +353,14 @@ FakeMrwBase IPersistableModel<FakeMrwBase>.Create(BinaryData data, ModelReaderWr
328353
string IPersistableModel<FakeMrwBase>.GetFormatFromOptions(ModelReaderWriterOptions options)
329354
=> "J";
330355
}
356+
357+
private class DelayedBaseModelProvider(InputModelType inputModel) : ModelProvider(inputModel)
358+
{
359+
public ModelProvider? BaseModel { get; set; }
360+
361+
protected override ModelProvider? BuildBaseModelProvider() => BaseModel;
362+
363+
protected override CSharpType? BuildBaseType() => BaseModel?.Type;
364+
}
331365
}
332366
}

0 commit comments

Comments
 (0)