Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.MrwSerializat
{
/// <summary>
/// Tests that serialization methods use correct modifiers when a model's base is
/// <see cref="SystemObjectModelProvider"/>. This validates behavior that would be
/// impossible with <see cref="SystemObjectTypeProvider"/> (which cannot serve as
/// <see cref="ModelProvider.BaseModelProvider"/>).
/// <see cref="SystemObjectModelProvider"/>.
/// </summary>
internal class SystemObjectModelSerializationTests
{
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ protected override FormattableString BuildDescription()
private bool _buildingRawDataField;
private List<FieldProvider>? _additionalPropertyFields;
private List<PropertyProvider>? _additionalPropertyProperties;
private ModelProvider? _baseModelProvider;
private ConstructorProvider? _fullConstructor;
internal PropertyProvider? DiscriminatorProperty { get; private set; }
private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? "");
Expand Down Expand Up @@ -124,57 +123,77 @@ private IReadOnlyList<ModelProvider> 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;

/// <inheritdoc/>
public override void Reset()
Expand Down Expand Up @@ -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<FieldProvider> BuildAdditionalPropertyFields()
{
var fields = new List<FieldProvider>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ namespace Microsoft.TypeSpec.Generator.Providers
{
/// <summary>
/// Represents a model type from an external assembly (system or referenced assembly) that is mapped
/// from an input model type. Unlike <see cref="SystemObjectTypeProvider"/> which extends <see cref="TypeProvider"/>,
/// this class extends <see cref="ModelProvider"/> so it can serve as a <see cref="ModelProvider.BaseModelProvider"/>
/// from an input model type. This class can serve as a <see cref="ModelProvider.BaseModelProvider"/>
/// for derived models that inherit from system types.
/// <para>
/// This is used when a code generator maps an input model (e.g., an ARM Resource type) to an existing
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,9 @@
namespace Microsoft.TypeSpec.Generator.Tests.Providers
{
/// <summary>
/// Tests for <see cref="SystemObjectModelProvider"/> that demonstrate capabilities
/// missing from the existing <see cref="SystemObjectTypeProvider"/>.
/// <para>
/// <see cref="SystemObjectTypeProvider"/> extends <see cref="TypeProvider"/> and cannot
/// serve as a <see cref="ModelProvider.BaseModelProvider"/>. <see cref="SystemObjectModelProvider"/>
/// extends <see cref="ModelProvider"/> to fill this gap — enabling derived models to inherit
/// Tests for <see cref="SystemObjectModelProvider"/>, which enables derived models to inherit
/// from framework/system types while getting proper property deduplication, raw data field, and
/// serialization handling.
/// </para>
/// </summary>
public class SystemObjectModelProviderTests
{
Expand Down Expand Up @@ -53,19 +47,8 @@ public void SystemObjectModelProvider_IsModelProvider()
Assert.IsInstanceOf<ModelProvider>(provider);
}

[Test]
public void SystemObjectTypeProvider_IsNotModelProvider()
{
var systemType = CreateSystemCSharpType("ResourceData", "TestFramework");
var provider = new SystemObjectTypeProvider(systemType);

Assert.IsNotInstanceOf<ModelProvider>(provider);
Assert.IsInstanceOf<TypeProvider>(provider);
}

// -------------------------------------------------------------------
// 2. Can serve as BaseModelProvider for derived models
// (SystemObjectTypeProvider cannot because it's not a ModelProvider)
// -------------------------------------------------------------------

[Test]
Expand Down Expand Up @@ -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<SystemObjectModelProvider>(derivedProvider.BaseModelProvider);
}
Expand Down Expand Up @@ -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]
Expand Down
Loading