Skip to content

Handle custom external model bases#11183

Open
live1206 wants to merge 21 commits into
microsoft:mainfrom
live1206:fix/system-object-custom-base-mrw
Open

Handle custom external model bases#11183
live1206 wants to merge 21 commits into
microsoft:mainfrom
live1206:fix/system-object-custom-base-mrw

Conversation

@live1206

@live1206 live1206 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Remove the synthetic custom-base SystemObjectModelProvider path and keep custom C# bases represented through existing type metadata.
  • Update MRW serialization so models with custom C# bases derive serialization behavior from the base type hierarchy:
    • emit JsonModelWriteCore as an override when the custom base already defines it;
    • keep create-core return types rooted at the meaningful base type, such as ResourceData;
    • only override create-core methods when the base actually participates in that method chain.
  • Tighten XML serialization override detection so XmlModelWriteCore only overrides when the base exposes a compatible, accessible, non-sealed XML core method:
    • use XML-specific detection instead of the JSON MRW override predicate;
    • account for generated base custom replacements that filter out generated methods;
    • reject wrong-signature, non-virtual, protected/public, sealed, reflected external, and shadowed ancestor XML core methods.
  • Preserve existing BaseModelProvider behavior for real input-model inheritance and external @alternateType model mappings.
  • Add coverage for canonical duplicate wire-path filtering from custom/system bases while preserving non-canonical same-wire SDK properties.

Validation

  • dotnet test ./generator/Microsoft.TypeSpec.Generator.ClientModel/test/Microsoft.TypeSpec.Generator.ClientModel.Tests.csproj --no-restore from packages/http-client-csharp
  • dotnet test ./generator/Microsoft.TypeSpec.Generator/test/Microsoft.TypeSpec.Generator.Tests.csproj --no-restore from packages/http-client-csharp
  • Validated from azure-sdk-for-net with temporary local project references: full Azure SDK mgmt generator tests passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added the emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp label Jul 7, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/http-client-csharp@11183

commit: c4a19ce

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

No changes needing a change description found.

live1206 and others added 6 commits July 7, 2026 06:54
Use the system object model provider's base model chain for inheritance and expose customization-only properties through a TypeProvider hook instead of storing inherited properties separately.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Populate synthetic system object input models with referenced type properties so existing ModelProvider inheritance handles inherited property filtering. Skip constructor forwarding for synthetic custom-base providers so external constructor surfaces are not generated into derived models.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cover canonical generated duplicate removal when a custom system object base exposes the same wire path through a differently named property.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Stop storing a base model provider on SystemObjectModelProvider. Seed the CSharpTypeMap for external base types and let the existing BaseType/BuildBaseModelProvider path resolve inheritance normally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the ModelProvider-local custom base helper and centralize synthetic SystemObjectModelProvider creation in TypeFactory. ModelProvider now only asks TypeFactory to create/register a model provider for a custom external base, then uses the existing BaseType/CSharpTypeMap lookup path for inheritance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
live1206 added a commit to live1206/azure-sdk-for-net that referenced this pull request Jul 7, 2026
The root cause is fixed in microsoft/typespec#11183, so this SDK PR should not carry generator changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
live1206 and others added 11 commits July 7, 2026 09:44
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@live1206 live1206 marked this pull request as ready for review July 8, 2026 07:01
@live1206

live1206 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

No diff in regen: Azure/azure-sdk-for-net#60807

live1206 and others added 2 commits July 9, 2026 07:40
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refines how the C# generator models and serializes types that derive from custom/external base classes, removing reliance on a synthetic base-provider path and instead using existing type metadata and hierarchy-aware detection (especially for MRW XML serialization). It also adds test coverage around external base chains and canonical wire-path deduplication.

Changes:

  • Extend model/provider plumbing to better represent external/custom base type chains (base type + constructor-parameter propagation behavior).
  • Improve MRW serialization override decisions (JSON root return type discovery; XML override detection with signature/accessibility/overridability checks).
  • Add tests + test data for external base behaviors and canonical duplicate wire-path filtering.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs Adds coverage for representing an external base chain via type metadata without duplicating inherited properties.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanFilterCanonicalDuplicateWirePathFromSystemObjectModelProviderBase/MockInputModel.cs Adds test customization source defining a framework-like base (ResourceData) and a derived partial model.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs Adds test ensuring canonical duplicate wire-path properties are filtered when a system/custom base already maps that wire path.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Writers/CodeWriter.cs Adds sealed emission support for method/property signatures.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs Enhances customized-member filtering to consider canonical duplicate wire paths (not just names).
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/SystemObjectModelProvider.cs Adds base-type propagation and derived-constructor parameter behavior control for wrapped system types.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/NamedTypeSymbolProvider.cs Adds interface list materialization and propagates sealed into method signature modifiers.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs Gates base-constructor parameter propagation based on the base provider’s preference.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/MethodSignatureModifiers.cs Adds Sealed to the modifier bitflags.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationCustomizationTests.cs Adds extensive XML override-detection tests across multiple base scenarios.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationCustomizationTests/XmlModelWriteCoreOverridesGeneratedBaseWithCustomReplacement/BaseModel.cs Adds test data for a generated base with a compatible virtual XML core method.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationCustomizationTests/XmlModelWriteCoreDoesNotOverrideProtectedCustomExternalBase/MockInputModel.cs Adds test data for a protected XML base method that must not be overridden by an internal method.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationCustomizationTests/XmlModelWriteCoreDoesNotOverrideJsonOnlyCustomBase/MockInputModel.cs Adds test data for a JSON-only base to ensure XML methods are not overridden incorrectly.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationCustomizationTests/XmlModelWriteCoreDoesNotOverrideGeneratedBaseWithSealedCustomMethod/BaseModel.cs Adds test data ensuring a sealed override in a customized base prevents derived overrides.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationCustomizationTests/XmlModelWriteCoreDoesNotOverrideGeneratedBaseWithProtectedCustomMethod/BaseModel.cs Adds test data ensuring protected custom methods don’t trigger internal overrides.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationCustomizationTests/XmlModelWriteCoreDoesNotOverrideGeneratedBaseWithNonVirtualCustomMethod/BaseModel.cs Adds test data ensuring non-virtual methods don’t trigger overrides.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SystemObjectModelSerializationTests.cs Adjusts system-base MRW tests to explicitly model interface implementation via a specialized provider.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs Reworks XML override detection to be XML-specific and hierarchy-aware.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs Improves MRW override decisions and root-type discovery for models with custom/external bases.

Comment on lines +45 to +46
protected internal override CSharpType[] BuildImplements()
=> [.. _namedTypeSymbol.Interfaces.Select(i => i.GetCSharpType())];
Comment on lines 30 to +48
public SystemObjectModelProvider(CSharpType systemType, InputModelType inputModel) : base(inputModel)
{
_systemType = systemType ?? throw new ArgumentNullException(nameof(systemType));
CrossLanguageDefinitionId = inputModel.CrossLanguageDefinitionId;

// The base ModelProvider constructor can evaluate Type before _systemType is assigned.
// Clear those cached values so Name/Namespace/BaseType are rebuilt from the wrapped type.
Reset();
}

public SystemObjectModelProvider(
CSharpType systemType,
InputModelType inputModel,
bool skipDerivedConstructorParameters)
: this(systemType, inputModel)
{
_skipDerivedConstructorParameters = skipDerivedConstructorParameters;
Reset();
}
Comment on lines +82 to +86
private bool IsXmlModelWriteCoreMethod(MethodProvider method)
=> IsXmlModelWriteCoreSignature(method) &&
method.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal) &&
IsOverridable(method.Signature.Modifiers);

@@ -74,10 +74,10 @@ public partial class MrwSerializationTypeDefinition : TypeProvider
private ConstructorProvider? _serializationConstructor;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we can avoid all of these changes by just changing the accessibility of BaseTypeProvider:

public TypeProvider? BaseTypeProvider => BaseTypeProviderTypeProvider;
private protected virtual TypeProvider? BaseTypeProviderTypeProvider  => null;

Then in this file we can add a helper:

private bool BaseDeclaresOverridable(string methodName)
{
    for (var provider = _model.BaseTypeProvider; provider is not null; provider = provider.BaseTypeProvider)
    {
        // Prefer the custom view so hand-authored replacements/hooks win over generated members.
        var source = provider.CustomCodeView ?? provider;
        var method = source.Methods.FirstOrDefault(m => m.Signature.Name == methodName);
        if (method is null)
        {
            continue; // not declared at this level; keep walking up
        }

        var mods = method.Signature.Modifiers;
        return !mods.HasFlag(MethodSignatureModifiers.Sealed)
            && (mods.HasFlag(MethodSignatureModifiers.Virtual)
                || mods.HasFlag(MethodSignatureModifiers.Override)
                || mods.HasFlag(MethodSignatureModifiers.Abstract));
    }

    return false;
}

Will this help simplify the proposed changes or am I missing something ?

Partial = 8192,
Abstract = 16384
Abstract = 16384,
Sealed = 32768

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return property.Name == wirePath.ToIdentifierName();
}

private static bool TryGetWirePath(PropertyProvider property, out string wirePath)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants