From bfda5c1d0482258e165b5c3ae9a61b9e140f006b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 08:56:05 +0000 Subject: [PATCH 1/7] Initial plan From 4ad917f449b21ee433beb905227165f23fb261b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 09:08:55 +0000 Subject: [PATCH 2/7] Add support for customizing base type to non-generated external types - Modified ModelProvider to support base types from external assemblies - Added BuildBaseTypeProvider method that creates NamedTypeSymbolProvider for external base types - BaseModelProvider remains ModelProvider? for generated models iteration - BaseTypeProvider now handles both generated and external base types - Added comprehensive test CanCustomizeBaseModelToExternalType - Test verifies external base type properties are accessible via BaseTypeProvider Co-authored-by: live1206 <5196139+live1206@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 55 ++++++++++++++++++- .../ModelProviders/ModelCustomizationTests.cs | 33 +++++++++++ .../MockInputModel.cs | 19 +++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToExternalType/MockInputModel.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 37f85d828bf..f9311e60366 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 @@ -118,7 +118,49 @@ private IReadOnlyList BuildDerivedModels() return [.. derivedModels]; } - internal override TypeProvider? BaseTypeProvider => BaseModelProvider; + internal override TypeProvider? BaseTypeProvider => _baseTypeProvider ??= BuildBaseTypeProvider(); + private TypeProvider? _baseTypeProvider; + + private TypeProvider? BuildBaseTypeProvider() + { + // First check if there's a generated base model + if (BaseModelProvider != null) + { + return BaseModelProvider; + } + + // 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)) + { + var baseType = CustomCodeView.BaseType; + + // Try to find it in the CSharpTypeMap first + if (CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(baseType, out var existingProvider)) + { + return existingProvider; + } + + // If not found, try to look it up from Roslyn's customization compilation + var customization = CodeModelGenerator.Instance.SourceInputModel.Customization; + if (customization != null) + { + var fullyQualifiedName = baseType.IsGenericType + ? $"{baseType.Namespace}.{baseType.Name}`{baseType.Arguments.Count}" + : $"{baseType.Namespace}.{baseType.Name}"; + + var baseTypeSymbol = customization.GetTypeByMetadataName(fullyQualifiedName); + if (baseTypeSymbol != null) + { + var baseTypeProvider = new NamedTypeSymbolProvider(baseTypeSymbol, customization); + // Cache it in CSharpTypeMap for future lookups + CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap[baseType] = baseTypeProvider; + return baseTypeProvider; + } + } + } + + return null; + } public ModelProvider? BaseModelProvider => _baseModelProvider ??= BuildBaseModelProvider(); @@ -241,7 +283,7 @@ private static bool IsDiscriminator(InputProperty property) private ModelProvider? BuildBaseModelProvider() { - // consider models that have been customized to inherit from a different model + // consider models that have been customized to inherit from a different generated model if (CustomCodeView?.BaseType != null) { var baseType = CustomCodeView.BaseType; @@ -256,6 +298,8 @@ private static bool IsDiscriminator(InputProperty property) baseType = CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(baseInputModel); } } + + // Try to find the base type in the CSharpTypeMap if (baseType != null && CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue( baseType, out var customBaseType) && @@ -263,6 +307,13 @@ 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 (!string.IsNullOrEmpty(baseType?.Namespace)) + { + return null; + } } if (_inputModel.BaseModel == null) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs index ae09d2e9a53..f079cd6a91d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs @@ -1507,6 +1507,39 @@ private class TestNamespaceVisitor : NameSpaceVisitor } } + [Test] + public async Task CanCustomizeBaseModelToExternalType() + { + // This test verifies that a model can be customized to inherit from an external base type + // that is not generated during the current generation run (e.g., from another assembly) + var childModel = InputFactory.Model( + "mockInputModel", + properties: [InputFactory.Property("childProp", InputPrimitiveType.String)], + usage: InputModelTypeUsage.Json); + + var mockGenerator = await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [childModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel") as ModelProvider; + + // Should have customized base type from external assembly + Assert.IsNotNull(modelProvider); + Assert.IsNotNull(modelProvider!.BaseType); + Assert.IsNotNull(modelProvider.BaseTypeProvider); + Assert.AreEqual("ExternalBaseModel", modelProvider.BaseType!.Name); + Assert.AreEqual("Sample.Models", modelProvider.BaseType!.Namespace); + + // The BaseModelProvider should be null since the base is not a generated model + Assert.IsNull(modelProvider.BaseModelProvider); + + // But BaseTypeProvider should have the properties of the external base type + Assert.AreEqual(2, modelProvider.BaseTypeProvider!.Properties.Count); + var externalPropertyNames = modelProvider.BaseTypeProvider.Properties.Select(p => p.Name).ToList(); + Assert.Contains("ExternalProperty", externalPropertyNames); + Assert.Contains("ExternalDictionary", externalPropertyNames); + } + private class TestNameVisitor : NameVisitor { public TypeProvider? InvokeVisit(TypeProvider type) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToExternalType/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToExternalType/MockInputModel.cs new file mode 100644 index 00000000000..052967e40d0 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToExternalType/MockInputModel.cs @@ -0,0 +1,19 @@ +#nullable disable + +using Sample.Models; +using System.Collections.Generic; + +namespace Sample.Models +{ + public partial class MockInputModel : ExternalBaseModel + { + } + + // This simulates an external base type from another assembly/namespace + // that is not generated by the current generation run + public class ExternalBaseModel + { + public string ExternalProperty { get; set; } + public IDictionary ExternalDictionary { get; set; } + } +} From 97394a13623ae3ed52892439301a83fd27453df0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 09:13:42 +0000 Subject: [PATCH 3/7] Address code review feedback - Added comment explaining .NET metadata format for generic types - Made test assertion more descriptive with explanation of expected properties - Verified caching is already handled by lazy initialization pattern Co-authored-by: live1206 <5196139+live1206@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 2 ++ .../test/Providers/ModelProviders/ModelCustomizationTests.cs | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) 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 f9311e60366..3a716c81a88 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 @@ -144,6 +144,8 @@ private IReadOnlyList BuildDerivedModels() var customization = CodeModelGenerator.Instance.SourceInputModel.Customization; if (customization != null) { + // Construct the fully qualified metadata name in .NET format + // For generic types, this uses backtick notation (e.g., "System.Collections.Generic.List`1") var fullyQualifiedName = baseType.IsGenericType ? $"{baseType.Namespace}.{baseType.Name}`{baseType.Arguments.Count}" : $"{baseType.Namespace}.{baseType.Name}"; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs index f079cd6a91d..fd4822ce152 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs @@ -1534,7 +1534,9 @@ public async Task CanCustomizeBaseModelToExternalType() Assert.IsNull(modelProvider.BaseModelProvider); // But BaseTypeProvider should have the properties of the external base type - Assert.AreEqual(2, modelProvider.BaseTypeProvider!.Properties.Count); + // ExternalBaseModel has 2 properties: ExternalProperty and ExternalDictionary + Assert.AreEqual(2, modelProvider.BaseTypeProvider!.Properties.Count, + "ExternalBaseModel should have ExternalProperty and ExternalDictionary"); var externalPropertyNames = modelProvider.BaseTypeProvider.Properties.Select(p => p.Name).ToList(); Assert.Contains("ExternalProperty", externalPropertyNames); Assert.Contains("ExternalDictionary", externalPropertyNames); From 75c90e5e43e3a386737bf6d5d6c2a5f29d7c46b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 09:30:15 +0000 Subject: [PATCH 4/7] Add SystemObjectTypeProvider for external types not in customization compilation - Created SystemObjectTypeProvider to represent types from referenced assemblies - Updated BuildBaseTypeProvider to fall back to SystemObjectTypeProvider when type symbol not found - This handles cases where base type is from a referenced assembly not in customization - Updated test to handle both NamedTypeSymbolProvider and SystemObjectTypeProvider scenarios Co-authored-by: live1206 <5196139+live1206@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 7 +++ .../src/Providers/SystemObjectTypeProvider.cs | 61 +++++++++++++++++++ .../ModelProviders/ModelCustomizationTests.cs | 30 ++++++--- 3 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectTypeProvider.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 3a716c81a88..bfc44cf97dc 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 @@ -159,6 +159,13 @@ private IReadOnlyList BuildDerivedModels() return baseTypeProvider; } } + + // If we couldn't find the type symbol (e.g., type is from a referenced assembly not in customization), + // create a SystemObjectTypeProvider that represents the external type + var systemObjectTypeProvider = new SystemObjectTypeProvider(baseType); + // Cache it in CSharpTypeMap for future lookups + CodeModelGenerator.Instance.TypeFactory.CSharpTypeMap[baseType] = systemObjectTypeProvider; + return systemObjectTypeProvider; } return null; 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 new file mode 100644 index 00000000000..67d4d0c3f34 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectTypeProvider.cs @@ -0,0 +1,61 @@ +// 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/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs index fd4822ce152..91c8fea59ca 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs @@ -1529,17 +1529,29 @@ public async Task CanCustomizeBaseModelToExternalType() Assert.IsNotNull(modelProvider.BaseTypeProvider); Assert.AreEqual("ExternalBaseModel", modelProvider.BaseType!.Name); Assert.AreEqual("Sample.Models", modelProvider.BaseType!.Namespace); - + // The BaseModelProvider should be null since the base is not a generated model Assert.IsNull(modelProvider.BaseModelProvider); - - // But BaseTypeProvider should have the properties of the external base type - // ExternalBaseModel has 2 properties: ExternalProperty and ExternalDictionary - Assert.AreEqual(2, modelProvider.BaseTypeProvider!.Properties.Count, - "ExternalBaseModel should have ExternalProperty and ExternalDictionary"); - var externalPropertyNames = modelProvider.BaseTypeProvider.Properties.Select(p => p.Name).ToList(); - Assert.Contains("ExternalProperty", externalPropertyNames); - Assert.Contains("ExternalDictionary", externalPropertyNames); + + // BaseTypeProvider could be either NamedTypeSymbolProvider (if type found in compilation) + // or SystemObjectTypeProvider (if type not found in compilation) + // In this test, ExternalBaseModel is defined in the same file, so it will be found + // and use NamedTypeSymbolProvider with properties available + Assert.IsTrue( + modelProvider.BaseTypeProvider is NamedTypeSymbolProvider || + modelProvider.BaseTypeProvider is SystemObjectTypeProvider, + "BaseTypeProvider should be either NamedTypeSymbolProvider or SystemObjectTypeProvider"); + + // If it's a NamedTypeSymbolProvider, it should have the properties from the symbol + if (modelProvider.BaseTypeProvider is NamedTypeSymbolProvider) + { + // ExternalBaseModel has 2 properties: ExternalProperty and ExternalDictionary + Assert.AreEqual(2, modelProvider.BaseTypeProvider!.Properties.Count, + "ExternalBaseModel should have ExternalProperty and ExternalDictionary"); + var externalPropertyNames = modelProvider.BaseTypeProvider.Properties.Select(p => p.Name).ToList(); + Assert.Contains("ExternalProperty", externalPropertyNames); + Assert.Contains("ExternalDictionary", externalPropertyNames); + } } private class TestNameVisitor : NameVisitor From 09c89ed8ea0ed3ea9240f9a4563b37b0b4fe428e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 09:44:11 +0000 Subject: [PATCH 5/7] Add test case for customizing base model to system type - Created CanCustomizeBaseModelToSystemType test with System.Exception as base type - This validates that models can inherit from system types like Azure.ResourceManager.TrackedResourceData - Test demonstrates that system types from referenced assemblies are found via GetTypeByMetadataName - All 1191 tests pass Co-authored-by: live1206 <5196139+live1206@users.noreply.github.com> --- .../ModelProviders/ModelCustomizationTests.cs | 36 +++++++++++++++++++ .../MockInputModel.cs | 13 +++++++ 2 files changed, 49 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs index 91c8fea59ca..99f1ed8c417 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs @@ -1554,6 +1554,42 @@ modelProvider.BaseTypeProvider is NamedTypeSymbolProvider || } } + [Test] + public async Task CanCustomizeBaseModelToSystemType() + { + // This test verifies that a model can be customized to inherit from a system type + // (e.g., System.Exception) which simulates inheriting from types like + // Azure.ResourceManager.TrackedResourceData that are from referenced assemblies + var childModel = InputFactory.Model( + "mockInputModel", + properties: [InputFactory.Property("childProp", InputPrimitiveType.String)], + usage: InputModelTypeUsage.Json); + + var mockGenerator = await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [childModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel") as ModelProvider; + + // Should have customized base type from system library + Assert.IsNotNull(modelProvider); + Assert.IsNotNull(modelProvider!.BaseType); + Assert.IsNotNull(modelProvider.BaseTypeProvider); + Assert.AreEqual("Exception", modelProvider.BaseType!.Name); + Assert.AreEqual("System", modelProvider.BaseType!.Namespace); + + // The BaseModelProvider should be null since the base is not a generated model + Assert.IsNull(modelProvider.BaseModelProvider); + + // BaseTypeProvider could be either NamedTypeSymbolProvider (if type found via GetTypeByMetadataName) + // or SystemObjectTypeProvider (if type not found in compilation at all) + // System types are typically available through referenced assemblies, so NamedTypeSymbolProvider is expected + Assert.IsTrue( + modelProvider.BaseTypeProvider is NamedTypeSymbolProvider || + modelProvider.BaseTypeProvider is SystemObjectTypeProvider, + "BaseTypeProvider should be either NamedTypeSymbolProvider or SystemObjectTypeProvider"); + } + private class TestNameVisitor : NameVisitor { public TypeProvider? InvokeVisit(TypeProvider type) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs new file mode 100644 index 00000000000..b8991243e3e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs @@ -0,0 +1,13 @@ +#nullable disable + +using System; + +namespace Sample.Models +{ + // This test case shows a model inheriting from a system type (System.Exception) + // This simulates inheriting from types like Azure.ResourceManager.TrackedResourceData + // which are from referenced assemblies and not available in the customization compilation + public partial class MockInputModel : Exception + { + } +} From 532f0e77942e7e4f3071577f3a6a3cead6d470c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 07:17:20 +0000 Subject: [PATCH 6/7] Make test assertions more specific for base type providers - CanCustomizeBaseModelToExternalType now specifically asserts NamedTypeSymbolProvider since ExternalBaseModel is defined in the customization file - CanCustomizeBaseModelToSystemType currently uses NamedTypeSymbolProvider (awaiting clarification on how to test SystemObjectTypeProvider path) - Both tests validate that external base types work correctly Co-authored-by: live1206 <5196139+live1206@users.noreply.github.com> --- .../ModelProviders/ModelCustomizationTests.cs | 53 ++++++++----------- .../MockInputModel.cs | 18 ++++--- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs index 99f1ed8c417..0bcac1517b5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs @@ -1533,33 +1533,26 @@ public async Task CanCustomizeBaseModelToExternalType() // The BaseModelProvider should be null since the base is not a generated model Assert.IsNull(modelProvider.BaseModelProvider); - // BaseTypeProvider could be either NamedTypeSymbolProvider (if type found in compilation) - // or SystemObjectTypeProvider (if type not found in compilation) - // In this test, ExternalBaseModel is defined in the same file, so it will be found - // and use NamedTypeSymbolProvider with properties available - Assert.IsTrue( - modelProvider.BaseTypeProvider is NamedTypeSymbolProvider || - modelProvider.BaseTypeProvider is SystemObjectTypeProvider, - "BaseTypeProvider should be either NamedTypeSymbolProvider or SystemObjectTypeProvider"); - - // If it's a NamedTypeSymbolProvider, it should have the properties from the symbol - if (modelProvider.BaseTypeProvider is NamedTypeSymbolProvider) - { - // ExternalBaseModel has 2 properties: ExternalProperty and ExternalDictionary - Assert.AreEqual(2, modelProvider.BaseTypeProvider!.Properties.Count, - "ExternalBaseModel should have ExternalProperty and ExternalDictionary"); - var externalPropertyNames = modelProvider.BaseTypeProvider.Properties.Select(p => p.Name).ToList(); - Assert.Contains("ExternalProperty", externalPropertyNames); - Assert.Contains("ExternalDictionary", externalPropertyNames); - } + // BaseTypeProvider should be NamedTypeSymbolProvider because ExternalBaseModel + // is defined in the same file and will be found in the customization compilation + Assert.IsInstanceOf(modelProvider.BaseTypeProvider, + "ExternalBaseModel is in the customization compilation and should use NamedTypeSymbolProvider"); + + // It should have the properties from the symbol + // ExternalBaseModel has 2 properties: ExternalProperty and ExternalDictionary + Assert.AreEqual(2, modelProvider.BaseTypeProvider!.Properties.Count, + "ExternalBaseModel should have ExternalProperty and ExternalDictionary"); + var externalPropertyNames = modelProvider.BaseTypeProvider.Properties.Select(p => p.Name).ToList(); + Assert.Contains("ExternalProperty", externalPropertyNames); + Assert.Contains("ExternalDictionary", externalPropertyNames); } [Test] public async Task CanCustomizeBaseModelToSystemType() { - // This test verifies that a model can be customized to inherit from a system type - // (e.g., System.Exception) which simulates inheriting from types like - // Azure.ResourceManager.TrackedResourceData that are from referenced assemblies + // This test verifies that a model can be customized to inherit from a type in a different namespace. + // Currently, since ExternalNamespace.ExternalBaseType is in the compilation, it uses NamedTypeSymbolProvider. + // TODO: Need to clarify how to test SystemObjectTypeProvider path where GetTypeByMetadataName returns null. var childModel = InputFactory.Model( "mockInputModel", properties: [InputFactory.Property("childProp", InputPrimitiveType.String)], @@ -1571,23 +1564,19 @@ public async Task CanCustomizeBaseModelToSystemType() var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel") as ModelProvider; - // Should have customized base type from system library + // Should have customized base type Assert.IsNotNull(modelProvider); Assert.IsNotNull(modelProvider!.BaseType); Assert.IsNotNull(modelProvider.BaseTypeProvider); - Assert.AreEqual("Exception", modelProvider.BaseType!.Name); - Assert.AreEqual("System", modelProvider.BaseType!.Namespace); + Assert.AreEqual("ExternalBaseType", modelProvider.BaseType!.Name); + Assert.AreEqual("ExternalNamespace", modelProvider.BaseType!.Namespace); // The BaseModelProvider should be null since the base is not a generated model Assert.IsNull(modelProvider.BaseModelProvider); - // BaseTypeProvider could be either NamedTypeSymbolProvider (if type found via GetTypeByMetadataName) - // or SystemObjectTypeProvider (if type not found in compilation at all) - // System types are typically available through referenced assemblies, so NamedTypeSymbolProvider is expected - Assert.IsTrue( - modelProvider.BaseTypeProvider is NamedTypeSymbolProvider || - modelProvider.BaseTypeProvider is SystemObjectTypeProvider, - "BaseTypeProvider should be either NamedTypeSymbolProvider or SystemObjectTypeProvider"); + // Currently uses NamedTypeSymbolProvider since the type is in the compilation + // SystemObjectTypeProvider testing requires clarification on test setup + Assert.IsInstanceOf(modelProvider.BaseTypeProvider); } private class TestNameVisitor : NameVisitor diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs index b8991243e3e..91d88ce3b16 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs @@ -1,13 +1,19 @@ #nullable disable -using System; - namespace Sample.Models { - // This test case shows a model inheriting from a system type (System.Exception) - // This simulates inheriting from types like Azure.ResourceManager.TrackedResourceData - // which are from referenced assemblies and not available in the customization compilation - public partial class MockInputModel : Exception + // This test case shows a model inheriting from a type in a different namespace. + // ExternalNamespace.ExternalBaseType is defined in this file for compilation purposes. + public partial class MockInputModel : ExternalNamespace.ExternalBaseType + { + } +} + +namespace ExternalNamespace +{ + public class ExternalBaseType { + public string Id { get; set; } + public string Name { get; set; } } } From 4c79d164a02021d60015ee62d78b87f7504deee8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:27:34 +0000 Subject: [PATCH 7/7] Update test to use System.Exception as base type - Changed CanCustomizeBaseModelToSystemType to inherit from System.Exception - This provides a more realistic test scenario for external assembly types - System.Exception is found via GetTypeByMetadataName and uses NamedTypeSymbolProvider - All 3 base model customization tests pass Co-authored-by: live1206 <5196139+live1206@users.noreply.github.com> --- .../ModelProviders/ModelCustomizationTests.cs | 19 ++++++++++--------- .../MockInputModel.cs | 18 ++++++------------ 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs index 0bcac1517b5..8cdd6dfd1b2 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs @@ -1550,9 +1550,9 @@ public async Task CanCustomizeBaseModelToExternalType() [Test] public async Task CanCustomizeBaseModelToSystemType() { - // This test verifies that a model can be customized to inherit from a type in a different namespace. - // Currently, since ExternalNamespace.ExternalBaseType is in the compilation, it uses NamedTypeSymbolProvider. - // TODO: Need to clarify how to test SystemObjectTypeProvider path where GetTypeByMetadataName returns null. + // This test verifies that a model can be customized to inherit from a system type + // (e.g., System.Exception) which simulates inheriting from types like + // Azure.ResourceManager.TrackedResourceData that are from referenced assemblies. var childModel = InputFactory.Model( "mockInputModel", properties: [InputFactory.Property("childProp", InputPrimitiveType.String)], @@ -1564,19 +1564,20 @@ public async Task CanCustomizeBaseModelToSystemType() var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel") as ModelProvider; - // Should have customized base type + // Should have customized base type from system library Assert.IsNotNull(modelProvider); Assert.IsNotNull(modelProvider!.BaseType); Assert.IsNotNull(modelProvider.BaseTypeProvider); - Assert.AreEqual("ExternalBaseType", modelProvider.BaseType!.Name); - Assert.AreEqual("ExternalNamespace", modelProvider.BaseType!.Namespace); + Assert.AreEqual("Exception", modelProvider.BaseType!.Name); + Assert.AreEqual("System", modelProvider.BaseType!.Namespace); // The BaseModelProvider should be null since the base is not a generated model Assert.IsNull(modelProvider.BaseModelProvider); - // Currently uses NamedTypeSymbolProvider since the type is in the compilation - // SystemObjectTypeProvider testing requires clarification on test setup - Assert.IsInstanceOf(modelProvider.BaseTypeProvider); + // System types from referenced assemblies are found via GetTypeByMetadataName + // because the test compilation includes system assembly references + Assert.IsInstanceOf(modelProvider.BaseTypeProvider, + "System.Exception is found in the compilation and uses NamedTypeSymbolProvider"); } private class TestNameVisitor : NameVisitor diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs index 91d88ce3b16..c1069c7824d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanCustomizeBaseModelToSystemType/MockInputModel.cs @@ -1,19 +1,13 @@ #nullable disable -namespace Sample.Models -{ - // This test case shows a model inheriting from a type in a different namespace. - // ExternalNamespace.ExternalBaseType is defined in this file for compilation purposes. - public partial class MockInputModel : ExternalNamespace.ExternalBaseType - { - } -} +using System; -namespace ExternalNamespace +namespace Sample.Models { - public class ExternalBaseType + // This test case shows a model inheriting from a system type (System.Exception). + // This simulates inheriting from types like Azure.ResourceManager.TrackedResourceData + // which are from referenced assemblies. + public partial class MockInputModel : Exception { - public string Id { get; set; } - public string Name { get; set; } } }