Skip to content

Commit

Permalink
Enhance logic for value type of enum and union as enum for TCGC (#362)
Browse files Browse the repository at this point in the history
  • Loading branch information
tadelesh authored Mar 5, 2024
1 parent 7d71bad commit 2d1b9d4
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 9 deletions.
7 changes: 7 additions & 0 deletions .chronus/changes/enum-value-type-2024-2-5-16-25-35.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@azure-tools/typespec-client-generator-core"
---

enhance logic for value type of enum and union as enum
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,8 @@ export function getSdkTypeBaseHelper<TKind>(
return {
__raw: type,
nullable: false,
deprecation: getDeprecationDetails(context.program, type)?.message,
kind,
deprecation: getDeprecationDetails(context.program, type)?.message,
};
}

Expand Down
53 changes: 45 additions & 8 deletions packages/typespec-client-generator-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
Tuple,
Type,
Union,
UnionVariant,
UsageFlags,
createDiagnosticCollector,
getDiscriminator,
Expand Down Expand Up @@ -86,6 +87,7 @@ import {
isErrorOrChildOfError,
} from "./public-utils.js";

import { UnionEnumVariant } from "../../typespec-azure-core/dist/src/helpers/union-enums.js";
import { TCGCContext } from "./internal-utils.js";

function getAnyType(context: TCGCContext, type: Type): SdkBuiltInType {
Expand Down Expand Up @@ -563,18 +565,51 @@ export function getSdkModelWithDiagnostics(

function getSdkEnumValueType(
context: TCGCContext,
type: EnumMember | StringLiteral | NumericLiteral
values:
| IterableIterator<EnumMember>
| IterableIterator<UnionEnumVariant<string>>
| IterableIterator<UnionEnumVariant<number>>
): SdkBuiltInType {
let kind: "string" | "int32" | "float32" = "string";
if (typeof type.value === "number") {
kind = intOrFloat(type.value);
let type: EnumMember | UnionVariant;
for (const value of values) {
if ((value as EnumMember).kind) {
type = value as EnumMember;
} else {
type = (value as UnionEnumVariant<string> | UnionEnumVariant<number>).type;
}

if (typeof value.value === "number") {
kind = intOrFloat(value.value);
if (kind === "float32") {
break;
}
} else if (typeof value.value === "string") {
kind = "string";
break;
}
}

return {
...getSdkTypeBaseHelper(context, type, kind),
encode: kind,
...getSdkTypeBaseHelper(context, type!, kind!),
encode: kind!,
};
}

function getUnionAsEnumValueType(context: TCGCContext, union: Union): SdkBuiltInType | undefined {
const nonNullOptions = getNonNullOptions(context, union);
for (const option of nonNullOptions) {
if (option.kind === "Union") {
const ret = getUnionAsEnumValueType(context, option);
if (ret) return ret;
} else if (option.kind === "Scalar") {
return getClientType(context, option) as SdkBuiltInType;
}
}

return undefined;
}

export function getSdkEnumValue(
context: TCGCContext,
enumType: SdkEnumType,
Expand All @@ -601,7 +636,7 @@ export function getSdkEnum(context: TCGCContext, type: Enum, operation?: Operati
name: getLibraryName(context, type),
description: docWrapper.description,
details: docWrapper.details,
valueType: getSdkEnumValueType(context, type.members.values().next().value),
valueType: getSdkEnumValueType(context, type.members.values()),
values: [],
isFixed: isFixed(context.program, type),
isFlags: false,
Expand Down Expand Up @@ -651,7 +686,9 @@ function getSdkUnionEnum(context: TCGCContext, type: UnionEnum, operation?: Oper
generatedName: type.union.name ? undefined : getGeneratedName(context, type.union),
description: docWrapper.description,
details: docWrapper.details,
valueType: getSdkEnumValueType(context, type.flattenedMembers.values().next().value),
valueType:
getUnionAsEnumValueType(context, type.union) ??
getSdkEnumValueType(context, type.flattenedMembers.values()),
values: [],
nullable: type.nullable,
isFixed: !type.open,
Expand Down Expand Up @@ -687,7 +724,7 @@ function getKnownValuesEnum(
name: getLibraryName(context, type),
description: docWrapper.description,
details: docWrapper.details,
valueType: getSdkEnumValueType(context, knownValues.members.values().next().value),
valueType: getSdkEnumValueType(context, knownValues.members.values()),
values: [],
isFixed: false,
isFlags: false,
Expand Down
136 changes: 136 additions & 0 deletions packages/typespec-client-generator-core/test/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,118 @@ describe("typespec-client-generator-core: types", () => {
);
});

it("float extensible", async function () {
await runner.compileWithBuiltInService(`
@usage(Usage.input | Usage.output)
@access(Access.public)
enum Floats {
a: 1,
b: 2.1,
c: 3,
}
@usage(Usage.input | Usage.output)
@access(Access.public)
model Test {
prop: Floats
}
`);

const models = getAllModels(runner.context);
strictEqual(models.length, 2);
const sdkType = models.find((x) => x.kind === "enum")! as SdkEnumType;
strictEqual(sdkType.isFixed, false);
strictEqual(sdkType.name, "Floats");
strictEqual(sdkType.valueType.kind, "float32");
const values = sdkType.values;
strictEqual(values.length, 3);
deepEqual(
values.map((x) => x.name),
["a", "b", "c"]
);
deepEqual(
values.map((x) => x.value),
[1, 2.1, 3]
);
});

it("union as enum float type", async function () {
await runner.compileWithBuiltInService(`
@usage(Usage.input | Usage.output)
@access(Access.public)
union Floats {
float,
a: 1,
b: 2,
c: 3,
}
@usage(Usage.input | Usage.output)
@access(Access.public)
model Test {
prop: Floats
}
`);

const models = getAllModels(runner.context);
strictEqual(models.length, 2);
const sdkType = models.find((x) => x.kind === "enum")! as SdkEnumType;
strictEqual(sdkType.isFixed, false);
strictEqual(sdkType.name, "Floats");
strictEqual(sdkType.valueType.kind, "float");
const values = sdkType.values;
strictEqual(values.length, 3);
deepEqual(
values.map((x) => x.name),
["a", "b", "c"]
);
deepEqual(
values.map((x) => x.value),
[1, 2, 3]
);
});

it("union of union as enum float type", async function () {
await runner.compileWithBuiltInService(`
@usage(Usage.input | Usage.output)
@access(Access.public)
union BaseEnum {
int32,
a: 1,
}
@usage(Usage.input | Usage.output)
@access(Access.public)
union ExtendedEnum {
BaseEnum,
b: 2,
c: 3,
}
@usage(Usage.input | Usage.output)
@access(Access.public)
model Test {
prop: ExtendedEnum
}
`);

const models = getAllModels(runner.context);
strictEqual(models.length, 2);
const sdkType = models.find((x) => x.name === "ExtendedEnum")! as SdkEnumType;
strictEqual(sdkType.isFixed, false);
strictEqual(sdkType.valueType.kind, "int32");
const values = sdkType.values;
strictEqual(values.length, 3);
deepEqual(
values.map((x) => x.name),
["a", "b", "c"]
);
deepEqual(
values.map((x) => x.value),
[1, 2, 3]
);
});

it("string fixed", async function () {
const runnerWithCore = await createSdkTestRunner({
librariesToAdd: [AzureCoreTestLibrary],
Expand Down Expand Up @@ -1472,6 +1584,30 @@ describe("typespec-client-generator-core: types", () => {
strictEqual(dogValue.kind, "enumvalue");
});

it("template variable of anonymous union as enum", async () => {
await runner.compileWithBuiltInService(`
interface GetAndSend<Type> {
get(): {
prop: Type;
};
send(prop: Type): void;
}
@route("/string-extensible")
interface StringExtensible extends GetAndSend<string | "b" | "c"> {}
`);
const models = getAllModels(runner.context);
strictEqual(models.length, 3);
const prop = models.find((x) => x.generatedName === "GetResponseProp")! as SdkEnumType;
strictEqual(prop.isFixed, false);
strictEqual(prop.valueType.kind, "string");
const req = models.find((x) => x.generatedName === "SendRequest")! as SdkModelType;
const resp = models.find((x) => x.generatedName === "GetResponse")! as SdkModelType;
strictEqual(req.properties[0].type, prop);
strictEqual(resp.properties[0].type, prop);
});

it("property of anonymous union as enum", async () => {
await runner.compileWithBuiltInService(`
model Pet {
Expand Down

0 comments on commit 2d1b9d4

Please sign in to comment.