microsoft / microsoft/typespec

Unify semantic property matching across generated and custom model bases

Open
#11,765 1 comment 1 reaction 0 assignees View on GitHub
emitter:client:csharp feature
Dominant language
Java
Stars
5.9k
Forks
394
Avg merge
1d 23h
Merged PRs (30d)
104

Description

## Description

MTG has multiple paths for deciding whether a property on the current model is already represented by a base model. Those paths currently use different matching rules. While fixing #11727, we identified that custom CLR base reconciliation should align with the normal generated-model inheritance behavior rather than introduce another independent policy.

## Current behavior on `main`

### Current TypeSpec model compared with its generated base model

`ModelProvider.BuildProperties` walks `EnumerateBaseModelProviders()` and builds a map of the base models’ `InputModelProperty` instances keyed by raw `InputModelProperty.Name`.

For a property declared by the current model:

- A discriminator is skipped when a base discriminator matches either the raw property name or serialized/wire name.
- A non-discriminator is matched to a base property by raw input name.
- `DomainEqual` then compares:
- `InputType.Name`;
- `IsRequired`; and
- whether the type is wrapped in `InputNullableType`.
- A domain-equal property is emitted as `override`.
- A non-domain-equal property is emitted as `new` with separate backing behavior.
- `ShouldSkipDerivedModelProperties` can cause a base property name to suppress generation in the derived model.

This path operates primarily on `InputModelProperty` metadata and does not share a general semantic property-identity abstraction.

### Current model compared with a custom/effective CLR base

`TypeProvider.FilterCustomizedProperties` uses a different mechanism:

- It collects property and field names from custom code and the effective custom CLR base hierarchy.
- It also collects `OriginalName` values supplied by `CodeGenMember`.
- It applies the existing identifier/acronym normalization.
- It suppresses a generated property when its generated C# name appears in that name set.

This path is name-based and does not use `DomainEqual`, wire identity, requiredness, nullability, visibility, accessor capabilities, or constructor participation.

## Problem

The two inheritance paths can disagree about whether a base member represents a current-model property:

- normal generated TypeSpec base: raw input name plus `DomainEqual`;
- custom/effective CLR base: generated or `CodeGenMember` name only;
- discriminator handling: raw and wire-name checks in a separate path.

This inconsistency makes custom-base reconciliation difficult and can also produce incorrect behavior in ordinary generated model inheritance. Improving only the custom CLR path would add another special case rather than fixing the underlying property identity and compatibility model.

## Compatibility dimensions to consider

- Raw TypeSpec name, generated C# name, `CodeGenMember` original name, and serialized/wire name.
- Property/field type compatibility, including requiredness and nullability.
- Nullable reference annotations from Roslyn (`string` versus `string?`), which are currently not preserved by all `ITypeSymbol` to `CSharpType` conversions.
- Required API visibility: public TypeSpec properties should not be satisfied by inherited members that are only protected/internal.
- Getter presence and accessibility for output and serialization use.
- Setter/field writability for input and deserialization use.
- Assembly-aware accessibility for `internal`, `protected internal`, and `private protected` members.
- Readonly and const fields.
- Whether an effective `ModelProvider` base actually propagates constructor parameters, including `SystemObjectModelProvider` with `ShouldUseFullConstructorInDerivedTypes` disabled.
- Internal discriminator properties and other non-public generated members, whose required visibility differs from normal public model properties.
- `ShouldSkipDerivedModelProperties` and framework/system model mappings.

## Expected direction

Introduce a shared property identity and compatibility helper used by both:

1. `ModelProvider.BuildProperties` when comparing properties on the current TypeSpec model with its normal generated base hierarchy.
2. Custom/effective CLR base reconciliation and `TypeProvider.FilterCustomizedProperties`.

The helper should separate two decisions:

1. **Identity:** do these members represent the same wire/model property?
2. **Compatibility:** can the inherited member fully satisfy the current model property for its API, constructor, and serialization usage?

The result may need to distinguish outcomes such as no match, inherited/satisfied, override-compatible, and name collision requiring a new member. Discriminator handling should use the same identity rules rather than remain a separate raw-name check.

The normal generated-base path should define the canonical behavior first; custom CLR bases should then reuse that behavior with CLR member metadata adapted into the same comparison model.

## Context

Follow-up to #11727 and PR #11752. That PR intentionally only restores disconnected TypeSpec-base properties and passes them through the existing custom-member matching behavior from `main`. This issue tracks unifying and improving the matching policy for both normal generated bases and custom CLR bases.

- by copilot

Contributor guide

Open the contributing guide

Research direction

Start with ModelProvider.BuildProperties and TypeProvider.FilterCustomizedProperties, then trace InputModelProperty matching, DomainEqual, discriminator checks, and the custom-member metadata described in the issue. Use #11727 and PR #11752 for context. Done means both generated-base and custom/effective CLR-base paths use one identity and compatibility policy covering the stated API, constructor, and serialization cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
devtools
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.