From 5daeaebe8598d63f8e7f961a3c82e9f0440d9143 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Mon, 2 Mar 2026 16:56:35 +0800 Subject: [PATCH 1/9] Add SystemObjectModelProvider and fix BuildBaseModelProvider name-based fallback Add SystemObjectModelProvider extending ModelProvider for downstream generators that map input models to existing framework types (e.g., ARM Resource to ResourceData). Unlike SystemObjectTypeProvider which extends TypeProvider, this class can serve as BaseModelProvider for derived models. Fix BuildBaseModelProvider() to use name+namespace based CSharpTypeMap fallback when strict CSharpType equality fails. This resolves the case where custom code overrides a base type to an external type (e.g., TrackedResourceData) but the CSharpTypeMap lookup fails due to framework vs non-framework CSharpType mismatch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 17 ++++++- .../Providers/SystemObjectModelProvider.cs | 50 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs index 4682a736fb4..aab38e22765 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs @@ -315,10 +315,23 @@ private static bool IsDiscriminator(InputProperty property) return customBaseModel; } - // If the custom base type has a namespace (external type), we don't return it here - // as it's handled by BuildBaseTypeProvider() which returns a TypeProvider + // If the custom base type has a namespace (external type), try name+namespace based + // lookup as a fallback. This handles the case where CSharpType equality fails due to + // framework vs non-framework type mismatch (e.g., a CSharpType from Roslyn with + // _type=typeof(T) vs a CSharpType from a model provider with _type=null). if (!string.IsNullOrEmpty(baseType?.Namespace)) { + foreach (var (mapKey, mapValue) in CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap) + { + if (mapValue is ModelProvider mp && + mapKey.Name == baseType.Name && + mapKey.Namespace == baseType.Namespace) + { + // Cache with the custom code's CSharpType for future lookups + CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap[baseType] = mp; + return mp; + } + } return null; } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs new file mode 100644 index 00000000000..1c81da7bd9e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.TypeSpec.Generator.Input; +using Microsoft.TypeSpec.Generator.Primitives; + +namespace Microsoft.TypeSpec.Generator.Providers +{ + /// + /// Represents a model type from an external assembly (system or referenced assembly) that is mapped + /// from an input model type. Unlike which extends , + /// this class extends so it can serve as a + /// for derived models that inherit from system types. + /// + /// This is used when a code generator maps an input model (e.g., an ARM Resource type) to an existing + /// framework type (e.g., ResourceData) rather than generating a new type. + /// + /// + public class SystemObjectModelProvider : ModelProvider + { + private readonly CSharpType _systemType; + + /// + /// Initializes a new instance of . + /// + /// The CSharp type from the external/system assembly. + /// The input model type that this system type replaces. + public SystemObjectModelProvider(CSharpType systemType, InputModelType inputModel) : base(inputModel) + { + _systemType = systemType ?? throw new ArgumentNullException(nameof(systemType)); + } + + /// + /// Gets the underlying system that this provider wraps. + /// + public CSharpType SystemType => _systemType; + + /// + protected override string BuildName() => _systemType.Name; + + /// + protected override string BuildRelativeFilePath() + => throw new InvalidOperationException("This type should not be writing in generation"); + + /// + // _systemType may be null when called from base constructor before field assignment. + protected override string BuildNamespace() => _systemType?.Namespace ?? string.Empty; + } +} From 95caf0f2f029ba8d1c2b1fa761658264f919f98f Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Mon, 2 Mar 2026 17:25:30 +0800 Subject: [PATCH 2/9] Add CrossLanguageDefinitionId and fix BuildName null safety in SystemObjectModelProvider - Added CrossLanguageDefinitionId property (from input model) to support downstream generators that need to check the cross-language definition ID - Fixed BuildName() null safety: _systemType may be null when called from base constructor before field assignment (same pattern as BuildNamespace) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Providers/SystemObjectModelProvider.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs index 1c81da7bd9e..ac5eeb248e5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs @@ -29,6 +29,7 @@ public class SystemObjectModelProvider : ModelProvider public SystemObjectModelProvider(CSharpType systemType, InputModelType inputModel) : base(inputModel) { _systemType = systemType ?? throw new ArgumentNullException(nameof(systemType)); + CrossLanguageDefinitionId = inputModel.CrossLanguageDefinitionId; } /// @@ -36,8 +37,14 @@ public SystemObjectModelProvider(CSharpType systemType, InputModelType inputMode /// public CSharpType SystemType => _systemType; + /// + /// Gets the cross-language definition ID from the input model. + /// + public string CrossLanguageDefinitionId { get; } + /// - protected override string BuildName() => _systemType.Name; + // _systemType may be null when called from base constructor before field assignment. + protected override string BuildName() => _systemType?.Name ?? string.Empty; /// protected override string BuildRelativeFilePath() From 2bf756b660f2cfe273a2a3e986de452a26d9fa86 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Mon, 2 Mar 2026 18:20:40 +0800 Subject: [PATCH 3/9] Add native SystemObjectModelProvider handling for properties, serialization, and constructors - SystemObjectModelProvider now returns empty for BuildProperties, BuildFields, BuildSerializationProviders, BuildConstructors, and null for BuildRawDataField - ModelProvider.BuildProperties skips base properties when base chain includes SystemObjectModelProvider - ModelProvider.BuildRawDataField changed to protected virtual - MrwSerializationTypeDefinition uses virtual (not override) for create/persist methods when base is SystemObjectModelProvider This enables downstream generators (mgmt) to handle framework type bases natively without post-processing visitors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.cs | 8 +++--- .../src/Providers/ModelProvider.cs | 26 ++++++++++++++++++- .../Providers/SystemObjectModelProvider.cs | 26 +++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs index 0cecfe33b29..1ca41296879 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs @@ -66,6 +66,7 @@ public partial class MrwSerializationTypeDefinition : TypeProvider private ConstructorProvider? _serializationConstructor; // Flag to determine if the model should override the serialization methods private readonly bool _shouldOverrideMethods; + private readonly bool _hasSystemObjectModelBase; private readonly Lazy _additionalProperties; private CSharpType RootType => _rootType ??= GetRootModelType(); @@ -91,6 +92,7 @@ public MrwSerializationTypeDefinition(InputModelType inputModel, ModelProvider m _additionalBinaryDataProperty = new(GetAdditionalBinaryDataPropertiesProp); _additionalProperties = new(() => [.. _model.Properties.Where(p => p.IsAdditionalProperties)]); _shouldOverrideMethods = _model.BaseModelProvider != null && !_isStruct; + _hasSystemObjectModelBase = _model.BaseModelProvider is SystemObjectModelProvider; _utf8JsonWriterSnippet = _utf8JsonWriterParameter.As(); _mrwOptionsParameterSnippet = _serializationOptionsParameter.As(); _jsonElementParameterSnippet = _jsonElementDeserializationParam.As(); @@ -482,7 +484,7 @@ internal MethodProvider BuildPersistableModelWriteCoreMethod() ? MethodSignatureModifiers.Private : MethodSignatureModifiers.Protected | MethodSignatureModifiers.Virtual; - if (_shouldOverrideMethods) + if (_shouldOverrideMethods && !_hasSystemObjectModelBase) { modifiers = MethodSignatureModifiers.Protected | MethodSignatureModifiers.Override; } @@ -506,7 +508,7 @@ internal MethodProvider BuildPersistableModelCreateCoreMethod() ? MethodSignatureModifiers.Private : MethodSignatureModifiers.Protected | MethodSignatureModifiers.Virtual; - if (_shouldOverrideMethods) + if (_shouldOverrideMethods && !_hasSystemObjectModelBase) { modifiers = MethodSignatureModifiers.Protected | MethodSignatureModifiers.Override; } @@ -554,7 +556,7 @@ internal MethodProvider BuildJsonModelCreateCoreMethod() ? MethodSignatureModifiers.Private : MethodSignatureModifiers.Protected | MethodSignatureModifiers.Virtual; - if (_shouldOverrideMethods) + if (_shouldOverrideMethods && !_hasSystemObjectModelBase) { modifiers = MethodSignatureModifiers.Protected | MethodSignatureModifiers.Override; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs index aab38e22765..730bf15b3db 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs @@ -555,6 +555,13 @@ protected internal override PropertyProvider[] BuildProperties() var baseProperty = baseProperties.GetValueOrDefault(property.Name); if (baseProperty is not null) { + // If the base chain includes a SystemObjectModelProvider, the framework type + // already defines this property — skip generating it in the derived model. + if (HasSystemObjectModelBase()) + { + continue; + } + if (DomainEqual(baseProperty, property)) { outputProperty.Modifiers |= MethodSignatureModifiers.Override; @@ -592,6 +599,23 @@ private IEnumerable EnumerateBaseModels() } } + /// + /// Checks if any ancestor in the base model chain is a . + /// + private bool HasSystemObjectModelBase() + { + var model = BaseModelProvider; + while (model != null) + { + if (model is SystemObjectModelProvider) + { + return true; + } + model = model.BaseModelProvider; + } + return false; + } + private static bool DomainEqual(InputProperty baseProperty, InputProperty derivedProperty) { if (baseProperty.Type.Name != derivedProperty.Type.Name) @@ -1211,7 +1235,7 @@ private ValueExpression GetConversion(PropertyProvider? property = default, Fiel /// Builds the raw data field for the model to be used for serialization. /// /// The constructed if the model should generate the field. - private FieldProvider? BuildRawDataField() + protected virtual FieldProvider? BuildRawDataField() { // check if there is a raw data field on any of the base models, if so, we do not have to have one here. var baseModelProvider = BaseModelProvider; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs index ac5eeb248e5..8902c850a2f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs @@ -53,5 +53,31 @@ protected override string BuildRelativeFilePath() /// // _systemType may be null when called from base constructor before field assignment. protected override string BuildNamespace() => _systemType?.Namespace ?? string.Empty; + + /// + /// Framework types manage their own properties; no generated properties needed. + /// + protected internal override PropertyProvider[] BuildProperties() => []; + + /// + /// Framework types manage their own fields; no generated fields needed. + /// + protected internal override FieldProvider[] BuildFields() => []; + + /// + /// Framework types have their own serialization; no generated serialization providers needed. + /// + protected override TypeProvider[] BuildSerializationProviders() => []; + + /// + /// Framework types have their own constructors; no generated constructors needed. + /// + protected internal override ConstructorProvider[] BuildConstructors() => []; + + /// + /// Framework types manage their own raw data field. + /// Returns null so derived models create their own. + /// + protected override FieldProvider? BuildRawDataField() => null; } } From 9eba180707abf1bdfff816ae23a678bed7667fe0 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Tue, 3 Mar 2026 09:34:49 +0800 Subject: [PATCH 4/9] Add tests for SystemObjectModelProvider vs SystemObjectTypeProvider Tests demonstrate capabilities that SystemObjectModelProvider provides that are missing from SystemObjectTypeProvider: 1. Type hierarchy: SystemObjectModelProvider extends ModelProvider (not TypeProvider), so it can serve as BaseModelProvider for derived models 2. Property deduplication: derived models skip properties defined in framework base 3. Raw data field: SystemObjectModelProvider returns null, derived models create own 4. Empty members: no generated properties/fields/constructors/serialization 5. Serialization modifiers: correct virtual vs override for 4 serialization methods 6. Name/namespace from system CSharpType Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../SystemObjectModelSerializationTests.cs | 205 +++++++++++ .../SystemObjectModelProviderTests.cs | 330 ++++++++++++++++++ 2 files changed, 535 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SystemObjectModelSerializationTests.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SystemObjectModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SystemObjectModelSerializationTests.cs new file mode 100644 index 00000000000..94648eebfeb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SystemObjectModelSerializationTests.cs @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using Microsoft.TypeSpec.Generator.ClientModel.Providers; +using Microsoft.TypeSpec.Generator.Input; +using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Tests.Common; +using NUnit.Framework; + +namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.MrwSerializationTypeDefinitions +{ + /// + /// Tests that serialization methods use correct modifiers when a model's base is + /// . This validates behavior that would be + /// impossible with (which cannot serve as + /// ). + /// + internal class SystemObjectModelSerializationTests + { + /// + /// Creates a derived model with a SystemObjectModelProvider base and returns its serialization. + /// + private static (ModelProvider Model, MrwSerializationTypeDefinition Serialization) CreateDerivedModelWithSystemBase() + { + var baseProp = InputFactory.Property("Name", InputPrimitiveType.String); + var baseInputModel = InputFactory.Model("Resource", properties: [baseProp]); + var derivedProp = InputFactory.Property("Location", InputPrimitiveType.String); + var derivedInputModel = InputFactory.Model("TrackedResource", properties: [derivedProp], baseModel: baseInputModel); + + // Use typeof(object) as a stand-in framework type. + // SystemObjectModelProvider extracts name/namespace from the CSharpType. + var systemType = new CSharpType(typeof(object)); + var systemBase = new SystemObjectModelProvider(systemType, baseInputModel); + + var generator = MockHelpers.LoadMockGenerator( + inputModels: () => [baseInputModel, derivedInputModel], + createModelCore: (model) => + { + if (model.Name == "Resource") + return systemBase; + return new ModelProvider(model); + }, + createSerializationsCore: (inputType, typeProvider) => + inputType is InputModelType modelType && typeProvider is ModelProvider mp + ? [new MrwSerializationTypeDefinition(modelType, mp)] + : []); + generator.Object.TypeFactory.RootInputModels.Add(derivedInputModel); + generator.Object.TypeFactory.RootOutputModels.Add(derivedInputModel); + + var derived = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(derivedInputModel) as ModelProvider; + Assert.IsNotNull(derived); + Assert.IsInstanceOf(derived!.BaseModelProvider); + + var serializations = derived.SerializationProviders; + Assert.AreEqual(1, serializations.Count); + return (derived, (MrwSerializationTypeDefinition)serializations[0]); + } + + /// + /// Creates a derived model with a regular (non-system) ModelProvider base and returns its serialization. + /// + private static (ModelProvider Model, MrwSerializationTypeDefinition Serialization) CreateDerivedModelWithRegularBase() + { + var baseProp = InputFactory.Property("Name", InputPrimitiveType.String); + var baseInputModel = InputFactory.Model("Resource", properties: [baseProp]); + var derivedProp = InputFactory.Property("Location", InputPrimitiveType.String); + var derivedInputModel = InputFactory.Model("TrackedResource", properties: [derivedProp], baseModel: baseInputModel); + + var generator = MockHelpers.LoadMockGenerator( + inputModels: () => [baseInputModel, derivedInputModel], + createSerializationsCore: (inputType, typeProvider) => + inputType is InputModelType modelType && typeProvider is ModelProvider mp + ? [new MrwSerializationTypeDefinition(modelType, mp)] + : []); + generator.Object.TypeFactory.RootInputModels.Add(derivedInputModel); + generator.Object.TypeFactory.RootOutputModels.Add(derivedInputModel); + + var derived = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(derivedInputModel) as ModelProvider; + Assert.IsNotNull(derived); + Assert.IsNotInstanceOf(derived!.BaseModelProvider); + + var serializations = derived.SerializationProviders; + Assert.AreEqual(1, serializations.Count); + return (derived, (MrwSerializationTypeDefinition)serializations[0]); + } + + // ------------------------------------------------------------------- + // JsonModelWriteCore: always 'override' for both system and regular base + // (the framework base type defines JsonModelWriteCore, so we override it) + // ------------------------------------------------------------------- + + [Test] + public void JsonModelWriteCore_IsOverride_WhenBaseIsSystemObject() + { + var (_, serialization) = CreateDerivedModelWithSystemBase(); + var method = serialization.BuildJsonModelWriteCoreMethod(); + + Assert.IsNotNull(method); + Assert.IsTrue(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Override), + "JsonModelWriteCore should be 'override' even with SystemObjectModelProvider base"); + Assert.IsFalse(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Virtual), + "JsonModelWriteCore should NOT be 'virtual' when base exists"); + } + + [Test] + public void JsonModelWriteCore_IsOverride_WhenBaseIsRegularModel() + { + var (_, serialization) = CreateDerivedModelWithRegularBase(); + var method = serialization.BuildJsonModelWriteCoreMethod(); + + Assert.IsNotNull(method); + Assert.IsTrue(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Override), + "JsonModelWriteCore should be 'override' with regular base too"); + } + + // ------------------------------------------------------------------- + // PersistableModelWriteCore: 'virtual' with system base, 'override' with regular + // (the framework base already implements this; derived model re-introduces it) + // ------------------------------------------------------------------- + + [Test] + public void PersistableModelWriteCore_IsVirtual_WhenBaseIsSystemObject() + { + var (_, serialization) = CreateDerivedModelWithSystemBase(); + var method = serialization.BuildPersistableModelWriteCoreMethod(); + + Assert.IsNotNull(method); + Assert.IsTrue(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Virtual), + "PersistableModelWriteCore should be 'virtual' when base is SystemObjectModelProvider " + + "(framework already has this method; derived re-introduces, not overrides)"); + Assert.IsFalse(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Override), + "PersistableModelWriteCore should NOT be 'override' with SystemObjectModelProvider base"); + } + + [Test] + public void PersistableModelWriteCore_IsOverride_WhenBaseIsRegularModel() + { + var (_, serialization) = CreateDerivedModelWithRegularBase(); + var method = serialization.BuildPersistableModelWriteCoreMethod(); + + Assert.IsNotNull(method); + Assert.IsTrue(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Override), + "PersistableModelWriteCore should be 'override' with regular base model"); + } + + // ------------------------------------------------------------------- + // PersistableModelCreateCore: 'virtual' with system base, 'override' with regular + // ------------------------------------------------------------------- + + [Test] + public void PersistableModelCreateCore_IsVirtual_WhenBaseIsSystemObject() + { + var (_, serialization) = CreateDerivedModelWithSystemBase(); + var method = serialization.BuildPersistableModelCreateCoreMethod(); + + Assert.IsNotNull(method); + Assert.IsTrue(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Virtual), + "PersistableModelCreateCore should be 'virtual' when base is SystemObjectModelProvider"); + Assert.IsFalse(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Override), + "PersistableModelCreateCore should NOT be 'override' with SystemObjectModelProvider base"); + } + + [Test] + public void PersistableModelCreateCore_IsOverride_WhenBaseIsRegularModel() + { + var (_, serialization) = CreateDerivedModelWithRegularBase(); + var method = serialization.BuildPersistableModelCreateCoreMethod(); + + Assert.IsNotNull(method); + Assert.IsTrue(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Override), + "PersistableModelCreateCore should be 'override' with regular base model"); + } + + // ------------------------------------------------------------------- + // JsonModelCreateCore: 'virtual' with system base, 'override' with regular + // ------------------------------------------------------------------- + + [Test] + public void JsonModelCreateCore_IsVirtual_WhenBaseIsSystemObject() + { + var (_, serialization) = CreateDerivedModelWithSystemBase(); + var method = serialization.BuildJsonModelCreateCoreMethod(); + + Assert.IsNotNull(method); + Assert.IsTrue(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Virtual), + "JsonModelCreateCore should be 'virtual' when base is SystemObjectModelProvider"); + Assert.IsFalse(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Override), + "JsonModelCreateCore should NOT be 'override' with SystemObjectModelProvider base"); + } + + [Test] + public void JsonModelCreateCore_IsOverride_WhenBaseIsRegularModel() + { + var (_, serialization) = CreateDerivedModelWithRegularBase(); + var method = serialization.BuildJsonModelCreateCoreMethod(); + + Assert.IsNotNull(method); + Assert.IsTrue(method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Override), + "JsonModelCreateCore should be 'override' with regular base model"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs new file mode 100644 index 00000000000..a426e5ba3aa --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs @@ -0,0 +1,330 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.TypeSpec.Generator.Input; +using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Tests.Common; +using NUnit.Framework; + +namespace Microsoft.TypeSpec.Generator.Tests.Providers +{ + /// + /// Tests for that demonstrate capabilities + /// missing from the existing . + /// + /// extends and cannot + /// serve as a . + /// extends to fill this gap — enabling derived models to inherit + /// from framework/system types while getting proper property dedup, raw data field, and + /// serialization handling. + /// + /// + public class SystemObjectModelProviderTests + { + /// + /// Creates a non-framework CSharpType with the given name and namespace. + /// Uses the internal constructor accessible via InternalsVisibleTo. + /// + private static CSharpType CreateSystemCSharpType(string name, string ns) + => new(name, ns, isValueType: false, isNullable: false, declaringType: null, + args: Array.Empty(), isPublic: true, isStruct: false); + + [SetUp] + public void Setup() + { + MockHelpers.LoadMockGenerator(); + } + + // ------------------------------------------------------------------- + // 1. Type hierarchy: ModelProvider vs TypeProvider + // ------------------------------------------------------------------- + + [Test] + public void SystemObjectModelProvider_IsModelProvider() + { + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + var inputModel = InputFactory.Model("Resource", properties: []); + var provider = new SystemObjectModelProvider(systemType, inputModel); + + Assert.IsInstanceOf(provider); + } + + [Test] + public void SystemObjectTypeProvider_IsNotModelProvider() + { + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + var provider = new SystemObjectTypeProvider(systemType); + + Assert.IsNotInstanceOf(provider); + Assert.IsInstanceOf(provider); + } + + // ------------------------------------------------------------------- + // 2. Can serve as BaseModelProvider for derived models + // (SystemObjectTypeProvider cannot because it's not a ModelProvider) + // ------------------------------------------------------------------- + + [Test] + public void CanServeAsBaseModelProvider() + { + var baseProp = InputFactory.Property("Name", InputPrimitiveType.String); + var baseInputModel = InputFactory.Model("Resource", properties: [baseProp]); + + var derivedProp = InputFactory.Property("Location", InputPrimitiveType.String); + var derivedInputModel = InputFactory.Model("DerivedResource", properties: [derivedProp], baseModel: baseInputModel); + + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + MockHelpers.LoadMockGenerator( + inputModelTypes: [baseInputModel, derivedInputModel], + createModelCore: (model) => + { + if (model.Name == "Resource") + return new SystemObjectModelProvider(systemType, model); + return new ModelProvider(model); + }); + + var derivedProvider = CodeModelGenerator.Instance.TypeFactory.CreateModel(derivedInputModel) as ModelProvider; + Assert.IsNotNull(derivedProvider); + + // The base should be a SystemObjectModelProvider — impossible with SystemObjectTypeProvider + Assert.IsNotNull(derivedProvider!.BaseModelProvider); + Assert.IsInstanceOf(derivedProvider.BaseModelProvider); + } + + // ------------------------------------------------------------------- + // 3. Property deduplication: properties matching framework base are skipped + // ------------------------------------------------------------------- + + [Test] + public void DerivedModel_SkipsPropertiesDefinedInSystemObjectBase() + { + var baseProp = InputFactory.Property("Name", InputPrimitiveType.String); + var baseInputModel = InputFactory.Model("Resource", properties: [baseProp]); + + // Derived re-declares "Name" (same as base) + has its own "Location" + var derivedNameProp = InputFactory.Property("Name", InputPrimitiveType.String); + var derivedLocationProp = InputFactory.Property("Location", InputPrimitiveType.String); + var derivedInputModel = InputFactory.Model( + "TrackedResource", + properties: [derivedNameProp, derivedLocationProp], + baseModel: baseInputModel); + + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + MockHelpers.LoadMockGenerator( + inputModelTypes: [baseInputModel, derivedInputModel], + createModelCore: (model) => + { + if (model.Name == "Resource") + return new SystemObjectModelProvider(systemType, model); + return new ModelProvider(model); + }); + + var derived = CodeModelGenerator.Instance.TypeFactory.CreateModel(derivedInputModel) as ModelProvider; + Assert.IsNotNull(derived); + + // "Name" should be skipped (defined in the framework base) + // Only "Location" should be generated + var propertyNames = derived!.Properties.Select(p => p.Name).ToList(); + Assert.IsFalse(propertyNames.Contains("Name"), + "Property 'Name' should be skipped because it is defined in the SystemObjectModelProvider base"); + Assert.IsTrue(propertyNames.Contains("Location"), + "Property 'Location' should be generated because it is NOT in the base"); + } + + [Test] + public void RegularBaseModel_DoesNotSkipMatchingProperties() + { + // Same setup but with a regular ModelProvider base (not SystemObjectModelProvider) + var baseProp = InputFactory.Property("Name", InputPrimitiveType.String); + var baseInputModel = InputFactory.Model("Resource", properties: [baseProp]); + + var derivedNameProp = InputFactory.Property("Name", InputPrimitiveType.String); + var derivedLocationProp = InputFactory.Property("Location", InputPrimitiveType.String); + var derivedInputModel = InputFactory.Model( + "TrackedResource", + properties: [derivedNameProp, derivedLocationProp], + baseModel: baseInputModel); + + MockHelpers.LoadMockGenerator(inputModelTypes: [baseInputModel, derivedInputModel]); + + var derived = new ModelProvider(derivedInputModel); + + // With a regular base, both properties should be generated (Name as override) + var propertyNames = derived.Properties.Select(p => p.Name).ToList(); + Assert.IsTrue(propertyNames.Contains("Name"), + "Property 'Name' should be generated with override modifier for regular inheritance"); + Assert.IsTrue(propertyNames.Contains("Location")); + } + + // ------------------------------------------------------------------- + // 4. Raw data field: SystemObjectModelProvider returns null, + // so derived models create their own field + // ------------------------------------------------------------------- + + [Test] + public void SystemObjectModelProvider_HasNoRawDataField_InFields() + { + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + var inputModel = InputFactory.Model("Resource", properties: []); + var provider = new SystemObjectModelProvider(systemType, inputModel); + + // SystemObjectModelProvider should have no fields at all (including no raw data field) + Assert.IsEmpty(provider.Fields, + "SystemObjectModelProvider should return no fields — the framework type manages its own raw data"); + } + + [Test] + public void DerivedModel_CreatesOwnRawDataField_WhenBaseIsSystemObject() + { + var baseProp = InputFactory.Property("Name", InputPrimitiveType.String); + var baseInputModel = InputFactory.Model("Resource", properties: [baseProp]); + var derivedProp = InputFactory.Property("Location", InputPrimitiveType.String); + var derivedInputModel = InputFactory.Model("TrackedResource", properties: [derivedProp], baseModel: baseInputModel); + + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + MockHelpers.LoadMockGenerator( + inputModelTypes: [baseInputModel, derivedInputModel], + createModelCore: (model) => + { + if (model.Name == "Resource") + return new SystemObjectModelProvider(systemType, model); + return new ModelProvider(model); + }); + + var derived = CodeModelGenerator.Instance.TypeFactory.CreateModel(derivedInputModel) as ModelProvider; + Assert.IsNotNull(derived); + + // Derived model should have its own raw data field since the system base has none + var rawDataField = derived!.Fields.FirstOrDefault(f => f.Name == "_additionalBinaryDataProperties"); + Assert.IsNotNull(rawDataField, + "Derived model should create its own raw data field when SystemObjectModelProvider base has none"); + } + + // ------------------------------------------------------------------- + // 5. Empty members: SystemObjectModelProvider generates nothing + // (framework type provides everything at runtime) + // ------------------------------------------------------------------- + + [Test] + public void SystemObjectModelProvider_Properties_AreEmpty() + { + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + var prop = InputFactory.Property("Name", InputPrimitiveType.String); + var inputModel = InputFactory.Model("Resource", properties: [prop]); + var provider = new SystemObjectModelProvider(systemType, inputModel); + + Assert.IsEmpty(provider.Properties, + "SystemObjectModelProvider should not generate properties — the framework type already has them"); + } + + [Test] + public void SystemObjectModelProvider_Fields_AreEmpty() + { + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + var inputModel = InputFactory.Model("Resource", properties: []); + var provider = new SystemObjectModelProvider(systemType, inputModel); + + Assert.IsEmpty(provider.Fields, + "SystemObjectModelProvider should not generate fields"); + } + + [Test] + public void SystemObjectModelProvider_Constructors_AreEmpty() + { + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + var inputModel = InputFactory.Model("Resource", properties: []); + var provider = new SystemObjectModelProvider(systemType, inputModel); + + Assert.IsEmpty(provider.Constructors, + "SystemObjectModelProvider should not generate constructors"); + } + + [Test] + public void SystemObjectModelProvider_SerializationProviders_AreEmpty() + { + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + var inputModel = InputFactory.Model("Resource", properties: []); + var provider = new SystemObjectModelProvider(systemType, inputModel); + + Assert.IsEmpty(provider.SerializationProviders, + "SystemObjectModelProvider should not generate serialization providers"); + } + + // ------------------------------------------------------------------- + // 6. Name and namespace come from the system CSharpType + // ------------------------------------------------------------------- + + [Test] + public void Name_ComesFromSystemType_ViaSystemTypeProperty() + { + var systemType = CreateSystemCSharpType("TrackedResourceData", "Azure.ResourceManager.Models"); + var inputModel = InputFactory.Model("TrackedResource", properties: [], access: "internal"); + var provider = new SystemObjectModelProvider(systemType, inputModel); + + // The SystemType property always reflects the original system type + Assert.AreEqual("TrackedResourceData", provider.SystemType.Name); + } + + [Test] + public void Namespace_ComesFromSystemType_ViaSystemTypeProperty() + { + var systemType = CreateSystemCSharpType("TrackedResourceData", "Azure.ResourceManager.Models"); + var inputModel = InputFactory.Model("TrackedResource", properties: [], access: "internal"); + var provider = new SystemObjectModelProvider(systemType, inputModel); + + Assert.AreEqual("Azure.ResourceManager.Models", provider.SystemType.Namespace); + } + + [Test] + public void Name_ComesFromSystemType_WhenTypeNotEarlyCached() + { + // When access is not "public", the ModelProvider constructor doesn't call AddTypeToKeep, + // so Type is not eagerly evaluated and BuildName() is deferred until after _systemType is set. + var systemType = CreateSystemCSharpType("TrackedResourceData", "Azure.ResourceManager.Models"); + var inputModel = InputFactory.Model("TrackedResource", properties: [], access: "internal"); + var provider = new SystemObjectModelProvider(systemType, inputModel); + + Assert.AreEqual("TrackedResourceData", provider.Name); + Assert.AreEqual("Azure.ResourceManager.Models", provider.Type.Namespace); + } + + [Test] + public void CrossLanguageDefinitionId_ComesFromInputModel() + { + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + var inputModel = InputFactory.Model("Resource", properties: []); + var provider = new SystemObjectModelProvider(systemType, inputModel); + + Assert.AreEqual(inputModel.CrossLanguageDefinitionId, provider.CrossLanguageDefinitionId); + } + + // ------------------------------------------------------------------- + // 7. BuildRelativeFilePath throws — system types should not be written + // ------------------------------------------------------------------- + + [Test] + public void BuildRelativeFilePath_Throws() + { + var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); + var inputModel = InputFactory.Model("Resource", properties: []); + var provider = new SystemObjectModelProvider(systemType, inputModel); + + Assert.Throws(() => _ = provider.RelativeFilePath); + } + + // ------------------------------------------------------------------- + // 8. Constructor validation + // ------------------------------------------------------------------- + + [Test] + public void Constructor_ThrowsOnNullSystemType() + { + var inputModel = InputFactory.Model("Resource", properties: []); + Assert.Throws(() => new SystemObjectModelProvider(null!, inputModel)); + } + } +} From 87b2621759c01a2252f45cd09d7340e1a529b9b9 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Tue, 3 Mar 2026 09:43:41 +0800 Subject: [PATCH 5/9] Fix spellcheck: replace 'dedup' with 'deduplication' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../test/Providers/SystemObjectModelProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs index a426e5ba3aa..075442629f1 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs @@ -19,7 +19,7 @@ namespace Microsoft.TypeSpec.Generator.Tests.Providers /// extends and cannot /// serve as a . /// extends to fill this gap — enabling derived models to inherit - /// from framework/system types while getting proper property dedup, raw data field, and + /// from framework/system types while getting proper property deduplication, raw data field, and /// serialization handling. /// /// From 4dcab93d9b532abb130b5155e4b1838c2592c050 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Tue, 3 Mar 2026 12:06:03 +0800 Subject: [PATCH 6/9] Add SetBaseModelProvider, Reset override, InputModel property, and base property dedup from SystemObjectModelProvider chain - SetBaseModelProvider: public method allowing visitors to replace a model's base - Reset override: clears rawDataField, additionalPropertyFields/Properties, fullConstructor - InputModel property: exposes the private _inputModel field for external use - BuildProperties: when base is SystemObjectModelProvider, also includes base properties from its InputModel chain for proper dedup when custom code overrides base type Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs index 730bf15b3db..206a92667f8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs @@ -85,6 +85,11 @@ public ModelProvider(InputModelType inputModel) : base(inputModel) public bool IsUnknownDiscriminatorModel => _inputModel.IsUnknownDiscriminatorModel; + /// + /// Gets the input model type that this provider was created from. + /// + public InputModelType InputModel => _inputModel; + public string? DiscriminatorValue => _inputModel.DiscriminatorValue; public ValueExpression? DiscriminatorValueExpression { get; init; } @@ -169,6 +174,27 @@ private IReadOnlyList BuildDerivedModels() public ModelProvider? BaseModelProvider => _baseModelProvider ??= BuildBaseModelProvider(); + + /// + /// Replaces the base model provider. Use this when a visitor needs to change the base type + /// (e.g., custom code overrides the base to a different framework type). + /// Call after setting to rebuild dependent members. + /// + public void SetBaseModelProvider(ModelProvider? baseModelProvider) + { + _baseModelProvider = baseModelProvider; + } + + /// + public override void Reset() + { + base.Reset(); + _rawDataField = null; + _additionalPropertyFields = null; + _additionalPropertyProperties = null; + _fullConstructor = null; + } + protected FieldProvider? RawDataField => _rawDataField ??= BuildRawDataField(); private List AdditionalPropertyFields => _additionalPropertyFields ??= BuildAdditionalPropertyFields(); private List AdditionalPropertyProperties => _additionalPropertyProperties ??= BuildAdditionalPropertyProperties(); @@ -498,6 +524,22 @@ protected internal override PropertyProvider[] BuildProperties() var propertiesCount = _inputModel.Properties.Count; var properties = new List(propertiesCount + 1); Dictionary baseProperties = EnumerateBaseModels().SelectMany(m => m.Properties).GroupBy(x => x.Name).Select(g => g.First()).ToDictionary(p => p.Name) ?? []; + // When the base model provider is a SystemObjectModelProvider (e.g., from a custom code base type + // override), also include properties from its InputModel chain. This handles the case where the + // spec-defined base differs from the custom code base (e.g., spec says Resource but custom code + // says TrackedResourceData), ensuring all base properties are properly deduplicated. + if (HasSystemObjectModelBase() && BaseModelProvider is SystemObjectModelProvider systemObjBase) + { + var baseInputModel = systemObjBase.InputModel; + while (baseInputModel != null) + { + foreach (var prop in baseInputModel.Properties) + { + baseProperties.TryAdd(prop.Name, prop); + } + baseInputModel = baseInputModel.BaseModel; + } + } // Build a set of serialized names for base discriminator properties to handle cases where // the derived model has a discriminator with a different C# name but the same wire name HashSet baseDiscriminatorSerializedNames = EnumerateBaseModels() From d7bebfa165277e6361c1dd6df1ac8c294f854a32 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Thu, 5 Mar 2026 14:28:05 +0800 Subject: [PATCH 7/9] Fix SystemObjectModelProvider to expose properties and constructors Remove BuildProperties() => [] and BuildConstructors() => [] overrides so that SystemObjectModelProvider builds properties and constructors from its input model. This allows derived models to correctly include base class parameters (e.g., id, name, resourceType, systemData, tags, location) in their constructors and generate proper :base() initializer calls. Without this fix, derived models lose all base class constructor parameters because the empty overrides make the base model invisible to the constructor building logic in ModelProvider.BuildConstructorParameters(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Providers/SystemObjectModelProvider.cs | 10 ---------- .../SystemObjectModelProviderTests.cs | 20 ++++++++++++------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs index 8902c850a2f..83b7098a8d5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs @@ -54,11 +54,6 @@ protected override string BuildRelativeFilePath() // _systemType may be null when called from base constructor before field assignment. protected override string BuildNamespace() => _systemType?.Namespace ?? string.Empty; - /// - /// Framework types manage their own properties; no generated properties needed. - /// - protected internal override PropertyProvider[] BuildProperties() => []; - /// /// Framework types manage their own fields; no generated fields needed. /// @@ -69,11 +64,6 @@ protected override string BuildRelativeFilePath() /// protected override TypeProvider[] BuildSerializationProviders() => []; - /// - /// Framework types have their own constructors; no generated constructors needed. - /// - protected internal override ConstructorProvider[] BuildConstructors() => []; - /// /// Framework types manage their own raw data field. /// Returns null so derived models create their own. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs index 075442629f1..d0e79f9ce9b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs @@ -210,15 +210,18 @@ public void DerivedModel_CreatesOwnRawDataField_WhenBaseIsSystemObject() // ------------------------------------------------------------------- [Test] - public void SystemObjectModelProvider_Properties_AreEmpty() + public void SystemObjectModelProvider_Properties_ComeFromInputModel() { var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); var prop = InputFactory.Property("Name", InputPrimitiveType.String); var inputModel = InputFactory.Model("Resource", properties: [prop]); var provider = new SystemObjectModelProvider(systemType, inputModel); - Assert.IsEmpty(provider.Properties, - "SystemObjectModelProvider should not generate properties — the framework type already has them"); + // Properties are now built from the input model so derived models can see + // base properties for constructor building and property deduplication. + var propertyNames = provider.Properties.Select(p => p.Name).ToList(); + Assert.IsTrue(propertyNames.Contains("Name"), + "SystemObjectModelProvider should expose properties from the input model for derived model constructor building"); } [Test] @@ -233,14 +236,17 @@ public void SystemObjectModelProvider_Fields_AreEmpty() } [Test] - public void SystemObjectModelProvider_Constructors_AreEmpty() + public void SystemObjectModelProvider_Constructors_AreBuiltFromInputModel() { var systemType = CreateSystemCSharpType("ResourceData", "TestFramework"); - var inputModel = InputFactory.Model("Resource", properties: []); + var prop = InputFactory.Property("Name", InputPrimitiveType.String, isRequired: true); + var inputModel = InputFactory.Model("Resource", properties: [prop]); var provider = new SystemObjectModelProvider(systemType, inputModel); - Assert.IsEmpty(provider.Constructors, - "SystemObjectModelProvider should not generate constructors"); + // Constructors are now built from the input model so derived models can use + // BaseModelProvider.FullConstructor.Signature.Parameters for constructor building. + Assert.IsNotEmpty(provider.Constructors, + "SystemObjectModelProvider should build constructors from the input model for derived model constructor building"); } [Test] From 7cc7fc756f00148a80cb41e73ab7b20dd87bc2df Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Thu, 5 Mar 2026 16:10:30 +0800 Subject: [PATCH 8/9] Remove public InputModel property from ModelProvider Access _inputModel directly in BuildProperties() since private members are accessible within the declaring class on any instance. This avoids adding unnecessary public API surface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs index 206a92667f8..6da468b6d14 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs @@ -85,11 +85,6 @@ public ModelProvider(InputModelType inputModel) : base(inputModel) public bool IsUnknownDiscriminatorModel => _inputModel.IsUnknownDiscriminatorModel; - /// - /// Gets the input model type that this provider was created from. - /// - public InputModelType InputModel => _inputModel; - public string? DiscriminatorValue => _inputModel.DiscriminatorValue; public ValueExpression? DiscriminatorValueExpression { get; init; } @@ -530,7 +525,7 @@ protected internal override PropertyProvider[] BuildProperties() // says TrackedResourceData), ensuring all base properties are properly deduplicated. if (HasSystemObjectModelBase() && BaseModelProvider is SystemObjectModelProvider systemObjBase) { - var baseInputModel = systemObjBase.InputModel; + var baseInputModel = systemObjBase._inputModel; while (baseInputModel != null) { foreach (var prop in baseInputModel.Properties) From bb9a9820f83bbba32b04dc728226d96d06736ef6 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Thu, 5 Mar 2026 16:48:51 +0800 Subject: [PATCH 9/9] Replace SetBaseModelProvider with Update overload on ModelProvider Fold base model provider changes into ModelProvider.Update() instead of a separate public SetBaseModelProvider() method. This follows the existing Update() pattern and automatically resets dependent members when the base model changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs index 6da468b6d14..6a62ba76856 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs @@ -171,13 +171,34 @@ public ModelProvider? BaseModelProvider => _baseModelProvider ??= BuildBaseModelProvider(); /// - /// Replaces the base model provider. Use this when a visitor needs to change the base type - /// (e.g., custom code overrides the base to a different framework type). - /// Call after setting to rebuild dependent members. + /// Updates the model provider, optionally replacing the base model provider. + /// When is specified, the model is automatically + /// reset to rebuild all dependent members (constructors, properties, etc.). /// - public void SetBaseModelProvider(ModelProvider? baseModelProvider) + /// The new base model provider to replace the current one. + public void Update( + ModelProvider? baseModelProvider = null, + IEnumerable? methods = null, + IEnumerable? constructors = null, + IEnumerable? properties = null, + IEnumerable? fields = null, + IEnumerable? serializations = null, + IEnumerable? nestedTypes = null, + IEnumerable? attributes = default, + IEnumerable? implements = null, + XmlDocProvider? xmlDocs = null, + TypeSignatureModifiers? modifiers = null, + string? name = null, + string? @namespace = null, + string? relativeFilePath = null, + bool reset = false) { - _baseModelProvider = baseModelProvider; + if (baseModelProvider != null) + { + _baseModelProvider = baseModelProvider; + reset = true; + } + base.Update(methods, constructors, properties, fields, serializations, nestedTypes, attributes, implements, xmlDocs, modifiers, name, @namespace, relativeFilePath, reset); } ///