diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs
index 79b77b510c9..123253b91ff 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs
@@ -1183,7 +1183,7 @@ public DuplicateDerivedProvider(InputModelType inputModelType, ModelProvider bas
_baseProvider = baseProvider;
}
- protected override ModelProvider? BuildBaseModelProvider() => _baseProvider;
+ internal override TypeProvider? BaseTypeProvider => _baseProvider;
protected override string BuildName() => "DerivedModel";
}
@@ -1196,7 +1196,7 @@ public CyclicBaseProvider(InputModelType inputModelType, bool includeFrameworkPr
public ModelProvider? BaseProvider { get; set; }
- protected override ModelProvider? BuildBaseModelProvider() => BaseProvider;
+ internal override TypeProvider? BaseTypeProvider => BaseProvider;
}
public class FrameworkTypeImplementingOtherFrameworkType : ComplexFrameworkType
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs
index 2c54d7968ba..56e937b4885 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs
@@ -320,7 +320,10 @@ public async Task CanCustomizeBaseType(bool declareBaseTypeInCustomCode, bool ca
inputModels: () => [inputModel, baseModel],
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(declareBaseTypeInCustomCode.ToString()));
- var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel");
+ var modelProvider = (ModelProvider)mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel");
+
+ Assert.IsNotNull(modelProvider.BaseTypeProvider);
+ Assert.AreSame(modelProvider.BaseModelProvider, modelProvider.BaseTypeProvider);
// should not have the additionalProperties dictionary
Assert.AreEqual(0, modelProvider.Fields.Count);
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
index fcc90582416..7b875f323e2 100644
--- 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
@@ -16,9 +16,7 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.MrwSerializat
{
///
/// 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
{
@@ -358,7 +356,7 @@ private class DelayedBaseModelProvider(InputModelType inputModel) : ModelProvide
{
public ModelProvider? BaseModel { get; set; }
- protected override ModelProvider? BuildBaseModelProvider() => BaseModel;
+ internal override TypeProvider? BaseTypeProvider => BaseModel;
protected override CSharpType? BuildBaseType() => BaseModel?.Type;
}
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 9f28c6bc482..1d0abe379a8 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
@@ -62,7 +62,6 @@ protected override FormattableString BuildDescription()
private bool _buildingRawDataField;
private List? _additionalPropertyFields;
private List? _additionalPropertyProperties;
- private ModelProvider? _baseModelProvider;
private ConstructorProvider? _fullConstructor;
internal PropertyProvider? DiscriminatorProperty { get; private set; }
private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? "");
@@ -124,57 +123,77 @@ private IReadOnlyList BuildDerivedModels()
return [.. derivedModels];
}
- internal override TypeProvider? BaseTypeProvider => _baseTypeProvider ??= BuildBaseTypeProvider();
+ internal override TypeProvider? BaseTypeProvider => _baseTypeProvider ??= ResolveBaseTypeProvider();
private TypeProvider? _baseTypeProvider;
- private TypeProvider? BuildBaseTypeProvider()
+ private TypeProvider? ResolveBaseTypeProvider()
{
- // First check if there's a generated base model
- if (BaseModelProvider != null)
+ var baseType = BaseType;
+ if (baseType is null)
+ {
+ return null;
+ }
+
+ var hasExistingProvider = CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(baseType, out var existingProvider);
+ if (existingProvider is ModelProvider)
{
- return BaseModelProvider;
+ return existingProvider;
}
- // If there's a custom base type that's not a generated model, create a provider for it
- if (CustomCodeView?.BaseType != null && !string.IsNullOrEmpty(CustomCodeView.BaseType.Namespace))
+ if (CustomCodeView?.BaseType is null)
{
- var baseType = CustomCodeView.BaseType;
+ return existingProvider;
+ }
- // Try to find it in the CSharpTypeMap first
- if (CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(baseType, out var existingProvider))
+ // Prefer a mapped model over a symbol provider for a custom base that names a generated model.
+ var matchingModel = CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap
+ .FirstOrDefault(entry =>
+ entry.Value is ModelProvider &&
+ entry.Key.Name == baseType.Name &&
+ entry.Key.Namespace == baseType.Namespace)
+ .Value;
+ if (matchingModel is null)
+ {
+ // Ensure visitor-renamed input models have been registered before falling back to a symbol provider.
+ foreach (var model in CodeModelGenerator.Instance.InputLibrary.InputNamespace.Models)
{
- return existingProvider;
+ CodeModelGenerator.Instance.TypeFactory.CreateModel(model);
}
- // Try to find the type in the customization compilation. Referenced assemblies are
- // included so custom bases from framework or external packages are represented by
- // normal symbol-backed providers.
- var baseTypeProvider = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCustomization(
- baseType.Namespace,
- baseType.Name,
- baseType.DeclaringType?.Name,
- includeReferencedAssemblies: true);
+ matchingModel = CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap
+ .FirstOrDefault(entry =>
+ entry.Value is ModelProvider &&
+ entry.Key.Name == baseType.Name &&
+ entry.Key.Namespace == baseType.Namespace)
+ .Value;
+ }
+
+ if (matchingModel is not null)
+ {
+ CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap[baseType] = matchingModel;
+ return matchingModel;
+ }
- if (baseTypeProvider != null)
- {
- // Cache it in CSharpTypeMap for future lookups
- CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap[baseType] = baseTypeProvider;
- return baseTypeProvider;
- }
+ if (hasExistingProvider || string.IsNullOrEmpty(baseType.Namespace))
+ {
+ return existingProvider;
+ }
+
+ var matchingProvider = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCustomization(
+ baseType.Namespace,
+ baseType.Name,
+ baseType.DeclaringType?.Name,
+ includeReferencedAssemblies: true);
- // If we couldn't find the type symbol, create a SystemObjectTypeProvider that
- // represents the external type without member metadata.
- var systemObjectTypeProvider = new SystemObjectTypeProvider(baseType);
- // Cache it in CSharpTypeMap for future lookups
- CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap[baseType] = systemObjectTypeProvider;
- return systemObjectTypeProvider;
+ if (matchingProvider is not null)
+ {
+ CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap[baseType] = matchingProvider;
}
- return null;
+ return matchingProvider;
}
- public ModelProvider? BaseModelProvider
- => _baseModelProvider ??= BuildBaseModelProvider();
+ public ModelProvider? BaseModelProvider => BaseTypeProvider as ModelProvider;
///
public override void Reset()
@@ -384,37 +403,6 @@ private static bool IsDiscriminator(InputProperty property)
return property is InputModelProperty modelProperty && modelProperty.IsDiscriminator;
}
- protected virtual ModelProvider? BuildBaseModelProvider()
- {
- var baseType = BaseType;
- if (baseType is null)
- {
- return null;
- }
-
- if (CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(baseType, out var provider)
- && provider is ModelProvider modelProvider)
- {
- return modelProvider;
- }
-
- if (CustomCodeView?.BaseType != null && !string.IsNullOrEmpty(baseType.Namespace))
- {
- foreach (var (mapKey, mapValue) in CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap)
- {
- if (mapValue is ModelProvider model
- && mapKey.Name == baseType.Name
- && mapKey.Namespace == baseType.Namespace)
- {
- CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap[baseType] = model;
- return model;
- }
- }
- }
-
- return null;
- }
-
private List BuildAdditionalPropertyFields()
{
var fields = new List();
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 8708932e11e..327f4f0496b 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
@@ -9,8 +9,7 @@ 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
+ /// from an input model type. This class 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
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectTypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectTypeProvider.cs
deleted file mode 100644
index 67d4d0c3f34..00000000000
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectTypeProvider.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using Microsoft.TypeSpec.Generator.Primitives;
-using Microsoft.TypeSpec.Generator.Statements;
-
-namespace Microsoft.TypeSpec.Generator.Providers
-{
- ///
- /// Represents a type from an external assembly (system or referenced assembly) that is not part of the current generation.
- /// This provider is used when a generated model inherits from a type that exists in a referenced assembly
- /// but doesn't have a Roslyn type symbol available in the customization compilation.
- ///
- internal sealed class SystemObjectTypeProvider : TypeProvider
- {
- private readonly CSharpType _type;
-
- public SystemObjectTypeProvider(CSharpType type)
- {
- _type = type ?? throw new ArgumentNullException(nameof(type));
-
- if (string.IsNullOrEmpty(_type.Namespace))
- {
- throw new ArgumentException("Type must have a namespace", nameof(type));
- }
- }
-
- private protected sealed override TypeProvider? BuildCustomCodeView(string? generatedTypeName = default, string? generatedTypeNamespace = default) => null;
- private protected sealed override TypeProvider? BuildLastContractView(string? generatedTypeName = default, string? generatedTypeNamespace = default) => null;
-
- protected override string BuildRelativeFilePath() => throw new InvalidOperationException("This type should not be writing in generation");
-
- protected override string BuildName() => _type.Name;
-
- protected override string BuildNamespace() => _type.Namespace;
-
- protected override IReadOnlyList BuildAttributes() => [];
-
- protected override CSharpType? BuildBaseType() => _type.BaseType;
-
- protected override TypeSignatureModifiers BuildDeclarationModifiers()
- {
- // Default to public class since we don't have symbol information
- return TypeSignatureModifiers.Public | TypeSignatureModifiers.Class;
- }
-
- protected internal override FieldProvider[] BuildFields() => [];
-
- protected internal override PropertyProvider[] BuildProperties() => [];
-
- protected internal override ConstructorProvider[] BuildConstructors() => [];
-
- protected internal override MethodProvider[] BuildMethods() => [];
-
- protected override bool GetIsEnum() => false;
-
- protected override CSharpType BuildEnumUnderlyingType() => throw new InvalidOperationException("This type is not an enum");
- }
-}
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs
index 07065943b7b..d6c021422f7 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs
@@ -180,7 +180,7 @@ protected internal TypeFactory()
}
// Add sentinel before construction to prevent re-entrant creation of the same model
- // (e.g., when BuildBaseModelProvider triggers CreateModel for all input models).
+ // (e.g., when base type resolution triggers CreateModel for all input models).
InputTypeToModelProvider[model] = null;
// A model marked as external maps to a type that already exists in a framework or
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs
index 9f9945a2360..43a45fb2efa 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs
@@ -503,6 +503,7 @@ public void OverridingBuildBaseType_AutoResolvesBaseModelProviderToNullForNonMod
Assert.IsNotNull(actualDerived);
Assert.AreEqual(nonModelTypeProvider.Type, actualDerived!.BaseType);
+ Assert.AreSame(nonModelTypeProvider, actualDerived.BaseTypeProvider);
Assert.IsNull(actualDerived.BaseModelProvider);
}
@@ -565,7 +566,7 @@ public CyclicBaseModelProvider(InputModelType inputModel) : base(inputModel)
public ModelProvider? CyclicBase { get; set; }
- protected override ModelProvider? BuildBaseModelProvider() => CyclicBase;
+ internal override TypeProvider? BaseTypeProvider => CyclicBase;
}
[Test]
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 e5179daaa47..2eed4b7890e 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
@@ -13,15 +13,9 @@
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
+ /// Tests for , which enables derived models to inherit
/// from framework/system types while getting proper property deduplication, raw data field, and
/// serialization handling.
- ///
///
public class SystemObjectModelProviderTests
{
@@ -53,19 +47,8 @@ public void SystemObjectModelProvider_IsModelProvider()
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]
@@ -93,7 +76,6 @@ public void CanServeAsBaseModelProvider()
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);
}
@@ -385,8 +367,7 @@ public void Constructor_ThrowsOnNullSystemType()
// -------------------------------------------------------------------
// 9. A derived discriminated model forwards its discriminator value to a
- // SystemObjectModelProvider base constructor. This is impossible with
- // SystemObjectTypeProvider because it cannot serve as a BaseModelProvider.
+ // SystemObjectModelProvider base constructor.
// -------------------------------------------------------------------
[Test]