Skip to content
Open
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
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-client-generator-core"
---

Convert inline-discriminated unions with explicit nominal model hierarchies to polymorphic SDK models by default. Set `convertDiscriminatedUnionsToModels: false` in `createSdkContext` to preserve union representations. Ordinary unions and object-envelope unions retain their existing behavior.
4 changes: 4 additions & 0 deletions packages/typespec-client-generator-core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export function createTCGCContext(

previewStringRegex: /-preview$/,
disableUsageAccessPropagationToBase: false,
convertDiscriminatedUnionsToModels: true,
generateProtocolMethods: true,
generateConvenienceMethods: true,
__referencedTypeCache: new Map<
Expand Down Expand Up @@ -173,6 +174,8 @@ export interface CreateSdkContextOptions {
exportTCGCoutput?: boolean; // this flag is for emitter to export TCGC output as yaml file
flattenUnionAsEnum?: boolean; // this flag is for emitter to decide whether tcgc should flatten union as enum
enableLegacyHierarchyBuilding?: boolean; // this flag is for emitter to decide whether tcgc should respect the `@hierarchyBuilding` decorator
/** Convert eligible inline-discriminated unions to model hierarchies. Default: true. */
convertDiscriminatedUnionsToModels?: boolean;
}

export async function createSdkContext<
Expand Down Expand Up @@ -238,6 +241,7 @@ export async function createSdkContext<
disableUsageAccessPropagationToBase: options?.disableUsageAccessPropagationToBase ?? false,
flattenUnionAsEnum: options?.flattenUnionAsEnum ?? true,
enableLegacyHierarchyBuilding: options?.enableLegacyHierarchyBuilding ?? true,
convertDiscriminatedUnionsToModels: options?.convertDiscriminatedUnionsToModels ?? true,
};

if (context.options["examples-dir"]) {
Expand Down
14 changes: 12 additions & 2 deletions packages/typespec-client-generator-core/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,12 @@ export function getUsage(context: TCGCContext, entity: Model | Enum | Union): Us
switch (entity.kind) {
case "Union":
const type = getSdkUnion(context, entity);
if (type.kind === "enum" || type.kind === "union" || type.kind === "nullable") {
if (
type.kind === "model" ||
type.kind === "enum" ||
type.kind === "union" ||
type.kind === "nullable"
) {
return type.usage;
}
return UsageFlags.None;
Expand Down Expand Up @@ -771,7 +776,12 @@ export function getAccess(
return getSdkEnum(context, entity).access;
case "Union": {
const type = getSdkUnion(context, entity);
if (type.kind === "enum" || type.kind === "union" || type.kind === "nullable") {
if (
type.kind === "model" ||
type.kind === "enum" ||
type.kind === "union" ||
type.kind === "nullable"
) {
return type.access;
}
return "public";
Expand Down
Comment thread
tadelesh marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
This diagnostic is issued when an inline-discriminated union's discriminator property or tag mappings conflict with another union or an existing model discriminator in the same hierarchy.

## Impact

- **Area:** SDK union type conversion. The union resolves to its declared base SDK model, but incompatible discriminator metadata prevents generating a valid polymorphic hierarchy. TCGC reports an error instead of overwriting existing discriminator metadata.
- **Not affected:** The TypeSpec union declaration, service routes, and wire payloads are unchanged.

## ❌ Incorrect Usage

```typespec
@service
namespace Example;

model PetBase {
name: string;
}

model Cat extends PetBase {
meows: boolean;
}

@discriminated(#{ envelope: "none" })
union Pet extends PetBase {
cat: Cat,
feline: Cat, // one model cannot have two polymorphic discriminator values
}

op get(): Pet;
```

## Diagnostic Message

TCGC reports:

```text
Discriminated union 'Pet' has conflicting discriminator metadata for its base model hierarchy. Use consistent discriminator properties and tag mappings, or set `convertDiscriminatedUnionsToModels` to `false` to retain union representations.
```

## ✅ How to Fix

Use consistent discriminator properties and tag mappings throughout the shared hierarchy. Give each alternative with a different wire tag its own model.

```typespec
@service
namespace Example;

model PetBase {
name: string;
}

model Cat extends PetBase {
meows: boolean;
}

model Dog extends PetBase {
barks: boolean;
}

@discriminated(#{ envelope: "none" })
union Pet extends PetBase {
cat: Cat,
dog: Dog,
}

op get(): Pet;
```

Emitters that intentionally use native discriminated unions can set `convertDiscriminatedUnionsToModels: false` in `createSdkContext` instead of requesting polymorphic conversion.

## Suppression

This diagnostic should not be suppressed. Align the hierarchy's discriminator metadata, or disable conversion when the emitter intentionally retains discriminated unions.
2 changes: 2 additions & 0 deletions packages/typespec-client-generator-core/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ export interface TCGCContext {
disableUsageAccessPropagationToBase: boolean;
flattenUnionAsEnum?: boolean;
enableLegacyHierarchyBuilding?: boolean;
convertDiscriminatedUnionsToModels?: boolean;

__discriminatedUnionModels?: Map<Union, Model>;
__referencedTypeCache: Map<Type, SdkModelType | SdkEnumType | SdkUnionType | SdkNullableType>;
__arrayDictionaryCache: Map<Type, SdkDictionaryType | SdkArrayType>;
__methodParameterCache: Map<ModelProperty, SdkMethodParameter>;
Expand Down
7 changes: 7 additions & 0 deletions packages/typespec-client-generator-core/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ function doc(code: string) {
export const $lib = createTypeSpecLibrary({
name: "@azure-tools/typespec-client-generator-core",
diagnostics: {
"union-model-conversion-conflict": {
...doc("union-model-conversion-conflict"),
severity: "error",
messages: {
default: paramMessage`Discriminated union '${"unionName"}' has conflicting discriminator metadata for its base model hierarchy. Use consistent discriminator properties and tag mappings, or set \`convertDiscriminatedUnionsToModels\` to \`false\` to retain union representations.`,
},
},
"union-null": {
...doc("union-null"),
severity: "warning",
Expand Down
Loading
Loading