Skip to content

Commit a45df5c

Browse files
live1206Copilot
andcommitted
Filter members inherited from custom base types
Avoid emitting duplicate generated properties or fields when custom partial code changes a model base type to a type that already exposes those members. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f692a94 commit a45df5c

4 files changed

Lines changed: 88 additions & 9 deletions

File tree

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Reflection;
8+
using Microsoft.TypeSpec.Generator.Expressions;
69
using Microsoft.TypeSpec.Generator.Primitives;
710
using Microsoft.TypeSpec.Generator.Statements;
811

@@ -48,7 +51,23 @@ protected override TypeSignatureModifiers BuildDeclarationModifiers()
4851

4952
protected internal override FieldProvider[] BuildFields() => [];
5053

51-
protected internal override PropertyProvider[] BuildProperties() => [];
54+
protected internal override PropertyProvider[] BuildProperties()
55+
{
56+
if (!_type.IsFrameworkType)
57+
{
58+
return [];
59+
}
60+
61+
return [.. _type.FrameworkType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
62+
.Where(property => property.GetMethod is not null && !property.GetMethod.IsStatic)
63+
.Select(property => new PropertyProvider(
64+
null,
65+
MethodSignatureModifiers.Public,
66+
new CSharpType(property.PropertyType),
67+
property.Name,
68+
new AutoPropertyBody(property.SetMethod is not null && property.SetMethod.IsPublic),
69+
this))];
70+
}
5271

5372
protected internal override ConstructorProvider[] BuildConstructors() => [];
5473

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,18 @@ private IReadOnlyList<PropertyProvider> BuildAllCustomProperties()
6464
var allCustomProperties = CustomCodeView?.Properties != null
6565
? new List<PropertyProvider>(CustomCodeView.Properties)
6666
: [];
67-
var baseTypeCustomCodeView = BaseTypeProvider?.CustomCodeView;
67+
var baseTypeProvider = BaseTypeProvider;
68+
var visited = new HashSet<TypeProvider>();
6869

6970
// add all custom properties from base types
70-
while (baseTypeCustomCodeView != null)
71+
while (baseTypeProvider != null && visited.Add(baseTypeProvider))
7172
{
72-
allCustomProperties.AddRange(baseTypeCustomCodeView.Properties);
73-
baseTypeCustomCodeView = baseTypeCustomCodeView.BaseTypeProvider?.CustomCodeView;
73+
allCustomProperties.AddRange(baseTypeProvider.Properties);
74+
if (baseTypeProvider.CustomCodeView is { } customCodeView)
75+
{
76+
allCustomProperties.AddRange(customCodeView.Properties);
77+
}
78+
baseTypeProvider = baseTypeProvider.BaseTypeProvider;
7479
}
7580

7681
return allCustomProperties;
@@ -81,13 +86,18 @@ private IReadOnlyList<FieldProvider> BuildAllCustomFields()
8186
var allCustomFields = CustomCodeView?.Fields != null
8287
? new List<FieldProvider>(CustomCodeView.Fields)
8388
: [];
84-
var baseTypeCustomCodeView = BaseTypeProvider?.CustomCodeView;
89+
var baseTypeProvider = BaseTypeProvider;
90+
var visited = new HashSet<TypeProvider>();
8591

8692
// add all custom fields from base types
87-
while (baseTypeCustomCodeView != null)
93+
while (baseTypeProvider != null && visited.Add(baseTypeProvider))
8894
{
89-
allCustomFields.AddRange(baseTypeCustomCodeView.Fields);
90-
baseTypeCustomCodeView = baseTypeCustomCodeView.BaseTypeProvider?.CustomCodeView;
95+
allCustomFields.AddRange(baseTypeProvider.Fields);
96+
if (baseTypeProvider.CustomCodeView is { } customCodeView)
97+
{
98+
allCustomFields.AddRange(customCodeView.Fields);
99+
}
100+
baseTypeProvider = baseTypeProvider.BaseTypeProvider;
91101
}
92102

93103
return allCustomFields;

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,46 @@ public async Task CanCustomizeBaseModelToSystemType()
17711771
"System.Exception is from a referenced assembly and should use SystemObjectTypeProvider");
17721772
}
17731773

1774+
[Test]
1775+
public async Task CanCustomizeSpecBaseModelToSystemType()
1776+
{
1777+
// This verifies that a custom partial base type wins even when the input model
1778+
// has a TypeSpec base model. Otherwise the generated partial would keep the
1779+
// TypeSpec base and conflict with the custom partial declaration.
1780+
var specBaseModel = InputFactory.Model(
1781+
"specBaseModel",
1782+
properties: [InputFactory.Property("specBaseProp", InputPrimitiveType.String)],
1783+
usage: InputModelTypeUsage.Json);
1784+
var childModel = InputFactory.Model(
1785+
"mockInputModel",
1786+
properties: [
1787+
InputFactory.Property("message", InputPrimitiveType.String),
1788+
InputFactory.Property("childProp", InputPrimitiveType.String),
1789+
],
1790+
baseModel: specBaseModel,
1791+
usage: InputModelTypeUsage.Json);
1792+
1793+
var mockGenerator = await MockHelpers.LoadMockGeneratorAsync(
1794+
inputModelTypes: [childModel, specBaseModel],
1795+
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
1796+
1797+
var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel") as ModelProvider;
1798+
1799+
Assert.IsNotNull(modelProvider);
1800+
Assert.IsNotNull(modelProvider!.BaseType);
1801+
Assert.AreEqual("Exception", modelProvider.BaseType!.Name);
1802+
Assert.AreEqual("System", modelProvider.BaseType!.Namespace);
1803+
Assert.IsNull(modelProvider.BaseModelProvider, "The TypeSpec base model should not be used when custom code declares a system base type.");
1804+
Assert.IsInstanceOf<SystemObjectTypeProvider>(modelProvider.BaseTypeProvider);
1805+
Assert.That(modelProvider.Properties.Select(p => p.Name), Does.Not.Contain("Message"));
1806+
Assert.That(modelProvider.Properties.Select(p => p.Name), Does.Contain("ChildProp"));
1807+
1808+
var modelContent = new TypeProviderWriter(modelProvider).Write().Content;
1809+
Assert.That(modelContent, Does.Contain("public partial class MockInputModel : global::System.Exception"));
1810+
Assert.That(modelContent, Does.Not.Contain("SpecBaseModel"));
1811+
Assert.That(modelContent, Does.Not.Contain("public string Message"));
1812+
}
1813+
17741814
[Test]
17751815
public async Task CanReadPropertyAttributes()
17761816
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#nullable disable
2+
3+
using System;
4+
5+
namespace Sample.Models
6+
{
7+
public partial class MockInputModel : Exception
8+
{
9+
}
10+
}

0 commit comments

Comments
 (0)