From 71fe87ce0ff71e775689aaa304a5780fdf0d5ca8 Mon Sep 17 00:00:00 2001 From: tadelesh Date: Tue, 6 May 2025 18:35:52 +0800 Subject: [PATCH 1/4] add cache for array and dictionary types --- .../src/context.ts | 3 ++ .../src/interfaces.ts | 1 + .../src/types.ts | 54 ++++++++++--------- .../test/types/array.test.ts | 30 +++++++++++ .../test/types/dictionary.test.ts | 38 +++++++++++++ 5 files changed, 102 insertions(+), 24 deletions(-) create mode 100644 packages/typespec-client-generator-core/test/types/dictionary.test.ts diff --git a/packages/typespec-client-generator-core/src/context.ts b/packages/typespec-client-generator-core/src/context.ts index 6f6c261128..e218ddaab6 100644 --- a/packages/typespec-client-generator-core/src/context.ts +++ b/packages/typespec-client-generator-core/src/context.ts @@ -19,7 +19,9 @@ import { defaultDecoratorsAllowList } from "./configs.js"; import { handleClientExamples } from "./example.js"; import { getKnownScalars, + SdkArrayType, SdkContext, + SdkDictionaryType, SdkEnumType, SdkHttpOperation, SdkModelPropertyType, @@ -56,6 +58,7 @@ export function createTCGCContext(program: Program, emitterName?: string): TCGCC Type, SdkModelType | SdkEnumType | SdkUnionType | SdkNullableType >(), + __arrayDictionaryCache: new Map(), __modelPropertyCache: new Map(), __generatedNames: new Map(), __httpOperationCache: new Map(), diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index 159d6a84aa..7e2e70a323 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -54,6 +54,7 @@ export interface TCGCContext { flattenUnionAsEnum?: boolean; __referencedTypeCache: Map; + __arrayDictionaryCache: Map; __modelPropertyCache: Map; __generatedNames: Map; __httpOperationCache: Map; diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 05f663588f..f0bfaac15a 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -437,32 +437,38 @@ export function getSdkArrayOrDictWithDiagnostics( // if model with both indexer and properties or name should be a model with additional properties if (type.indexer !== undefined && type.properties.size === 0) { if (!isNeverType(type.indexer.key)) { - const valueType = diagnostics.pipe( - getClientTypeWithDiagnostics(context, type.indexer.value!, operation), - ); - const name = type.indexer.key.name; - if (name === "string" && type.name === "Record") { - // model MyModel is Record<> {} should be model with additional properties - if (type.sourceModel?.kind === "Model" && type.sourceModel?.name === "Record") { - return diagnostics.wrap(undefined); + let sdkType = context.__arrayDictionaryCache.get(type); + if (!sdkType) { + const valueType = diagnostics.pipe( + getClientTypeWithDiagnostics(context, type.indexer.value!, operation), + ); + const name = type.indexer.key.name; + if (name === "string" && type.name === "Record") { + // model MyModel is Record<> {} should be model with additional properties + if (type.sourceModel?.kind === "Model" && type.sourceModel?.name === "Record") { + return diagnostics.wrap(undefined); + } else { + // other cases are dict + sdkType = { + ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "dict")), + keyType: diagnostics.pipe( + getClientTypeWithDiagnostics(context, type.indexer.key, operation), + ), + valueType: valueType, + }; + } + } else if (name === "integer") { + // only array's index key name is integer + sdkType = { + ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "array")), + name: getLibraryName(context, type), + valueType: valueType, + crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, type, operation), + }; } - // other cases are dict - return diagnostics.wrap({ - ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "dict")), - keyType: diagnostics.pipe( - getClientTypeWithDiagnostics(context, type.indexer.key, operation), - ), - valueType: valueType, - }); - } else if (name === "integer") { - // only array's index key name is integer - return diagnostics.wrap({ - ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "array")), - name: getLibraryName(context, type), - valueType: valueType, - crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, type, operation), - }); + context.__arrayDictionaryCache.set(type, sdkType!); } + return diagnostics.wrap(sdkType); } } return diagnostics.wrap(undefined); diff --git a/packages/typespec-client-generator-core/test/types/array.test.ts b/packages/typespec-client-generator-core/test/types/array.test.ts index 7386c11560..8faee13e21 100644 --- a/packages/typespec-client-generator-core/test/types/array.test.ts +++ b/packages/typespec-client-generator-core/test/types/array.test.ts @@ -91,3 +91,33 @@ it("alias of EmbeddingVector", async () => { strictEqual(property.type.crossLanguageDefinitionId, "Azure.Core.EmbeddingVector"); strictEqual(property.type.valueType.kind, "int32"); }); + +it("same type's array come to same type", async () => { + await runner.compile(` + @service + namespace TestClient { + model Test { + prop: string; + } + + model TestArray { + prop1: Test[]; + prop2: Test[]; + prop3: string[]; + prop4: string[]; + } + + op get(): TestArray; + } + `); + const testArrayModel = runner.context.sdkPackage.models[0]; + strictEqual(testArrayModel.kind, "model"); + strictEqual(testArrayModel.name, "TestArray"); + strictEqual(testArrayModel.properties.length, 4); + const prop1 = testArrayModel.properties[0]; + const prop2 = testArrayModel.properties[1]; + const prop3 = testArrayModel.properties[2]; + const prop4 = testArrayModel.properties[3]; + strictEqual(prop1.type, prop2.type); + strictEqual(prop3.type, prop4.type); +}); diff --git a/packages/typespec-client-generator-core/test/types/dictionary.test.ts b/packages/typespec-client-generator-core/test/types/dictionary.test.ts new file mode 100644 index 0000000000..7b83d83c46 --- /dev/null +++ b/packages/typespec-client-generator-core/test/types/dictionary.test.ts @@ -0,0 +1,38 @@ +import { strictEqual } from "assert"; +import { beforeEach, it } from "vitest"; +import { SdkTestRunner, createSdkTestRunner } from "../test-host.js"; + +let runner: SdkTestRunner; + +beforeEach(async () => { + runner = await createSdkTestRunner({ emitterName: "@azure-tools/typespec-java" }); +}); + +it("same type's dictionary come to same type", async () => { + await runner.compile(` + @service + namespace TestClient { + model Test { + prop: string; + } + + model TestDictionary { + prop1: Record; + prop2: Record; + prop3: Record; + prop4: Record; + } + op get(): TestDictionary; + } + `); + const testArrayModel = runner.context.sdkPackage.models[0]; + strictEqual(testArrayModel.kind, "model"); + strictEqual(testArrayModel.name, "TestDictionary"); + strictEqual(testArrayModel.properties.length, 4); + const prop1 = testArrayModel.properties[0]; + const prop2 = testArrayModel.properties[1]; + const prop3 = testArrayModel.properties[2]; + const prop4 = testArrayModel.properties[3]; + strictEqual(prop1.type, prop2.type); + strictEqual(prop3.type, prop4.type); +}); From c44e9a6346017342bee17a4e880f8188b30c8e6c Mon Sep 17 00:00:00 2001 From: tadelesh Date: Tue, 6 May 2025 18:37:56 +0800 Subject: [PATCH 2/4] changeset --- .chronus/changes/tcgc-cache_array_map-2025-4-6-18-37-49.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/tcgc-cache_array_map-2025-4-6-18-37-49.md diff --git a/.chronus/changes/tcgc-cache_array_map-2025-4-6-18-37-49.md b/.chronus/changes/tcgc-cache_array_map-2025-4-6-18-37-49.md new file mode 100644 index 0000000000..d167b4c6ac --- /dev/null +++ b/.chronus/changes/tcgc-cache_array_map-2025-4-6-18-37-49.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +Convert to same type for array or dictionary of same type. \ No newline at end of file From 3be0d7e0640c533f07d8f53b2fe255d1f3bccfee Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 6 May 2025 15:41:13 -0700 Subject: [PATCH 3/4] add tests for list of dict and dict of list --- .../test/types/array.test.ts | 12 +++++++++++- .../test/types/dictionary.test.ts | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/typespec-client-generator-core/test/types/array.test.ts b/packages/typespec-client-generator-core/test/types/array.test.ts index 8faee13e21..005cc928b3 100644 --- a/packages/typespec-client-generator-core/test/types/array.test.ts +++ b/packages/typespec-client-generator-core/test/types/array.test.ts @@ -105,6 +105,10 @@ it("same type's array come to same type", async () => { prop2: Test[]; prop3: string[]; prop4: string[]; + prop5: Test[][]; + prop6: Test[][]; + prop7: Record[]; + prop8: Record[]; } op get(): TestArray; @@ -113,11 +117,17 @@ it("same type's array come to same type", async () => { const testArrayModel = runner.context.sdkPackage.models[0]; strictEqual(testArrayModel.kind, "model"); strictEqual(testArrayModel.name, "TestArray"); - strictEqual(testArrayModel.properties.length, 4); + strictEqual(testArrayModel.properties.length, 8); const prop1 = testArrayModel.properties[0]; const prop2 = testArrayModel.properties[1]; const prop3 = testArrayModel.properties[2]; const prop4 = testArrayModel.properties[3]; + const prop5 = testArrayModel.properties[4]; + const prop6 = testArrayModel.properties[5]; + const prop7 = testArrayModel.properties[6]; + const prop8 = testArrayModel.properties[7]; strictEqual(prop1.type, prop2.type); strictEqual(prop3.type, prop4.type); + strictEqual(prop5.type, prop6.type); + strictEqual(prop7.type, prop8.type); }); diff --git a/packages/typespec-client-generator-core/test/types/dictionary.test.ts b/packages/typespec-client-generator-core/test/types/dictionary.test.ts index 7b83d83c46..4251db84e4 100644 --- a/packages/typespec-client-generator-core/test/types/dictionary.test.ts +++ b/packages/typespec-client-generator-core/test/types/dictionary.test.ts @@ -21,6 +21,8 @@ it("same type's dictionary come to same type", async () => { prop2: Record; prop3: Record; prop4: Record; + prop5: Record; + prop6: Record; } op get(): TestDictionary; } @@ -28,11 +30,14 @@ it("same type's dictionary come to same type", async () => { const testArrayModel = runner.context.sdkPackage.models[0]; strictEqual(testArrayModel.kind, "model"); strictEqual(testArrayModel.name, "TestDictionary"); - strictEqual(testArrayModel.properties.length, 4); + strictEqual(testArrayModel.properties.length, 6); const prop1 = testArrayModel.properties[0]; const prop2 = testArrayModel.properties[1]; const prop3 = testArrayModel.properties[2]; const prop4 = testArrayModel.properties[3]; + const prop5 = testArrayModel.properties[4]; + const prop6 = testArrayModel.properties[5]; strictEqual(prop1.type, prop2.type); strictEqual(prop3.type, prop4.type); + strictEqual(prop5.type, prop6.type); }); From 432b53644489494bd45315ec146dfe12acdd9bbb Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 6 May 2025 15:44:03 -0700 Subject: [PATCH 4/4] add more tests --- .../test/types/array.test.ts | 7 ++++++- .../test/types/dictionary.test.ts | 12 +++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/typespec-client-generator-core/test/types/array.test.ts b/packages/typespec-client-generator-core/test/types/array.test.ts index 005cc928b3..b3254c5f16 100644 --- a/packages/typespec-client-generator-core/test/types/array.test.ts +++ b/packages/typespec-client-generator-core/test/types/array.test.ts @@ -109,6 +109,8 @@ it("same type's array come to same type", async () => { prop6: Test[][]; prop7: Record[]; prop8: Record[]; + prop9: Record>[]; + prop10: Record>[]; } op get(): TestArray; @@ -117,7 +119,7 @@ it("same type's array come to same type", async () => { const testArrayModel = runner.context.sdkPackage.models[0]; strictEqual(testArrayModel.kind, "model"); strictEqual(testArrayModel.name, "TestArray"); - strictEqual(testArrayModel.properties.length, 8); + strictEqual(testArrayModel.properties.length, 10); const prop1 = testArrayModel.properties[0]; const prop2 = testArrayModel.properties[1]; const prop3 = testArrayModel.properties[2]; @@ -126,8 +128,11 @@ it("same type's array come to same type", async () => { const prop6 = testArrayModel.properties[5]; const prop7 = testArrayModel.properties[6]; const prop8 = testArrayModel.properties[7]; + const prop9 = testArrayModel.properties[8]; + const prop10 = testArrayModel.properties[9]; strictEqual(prop1.type, prop2.type); strictEqual(prop3.type, prop4.type); strictEqual(prop5.type, prop6.type); strictEqual(prop7.type, prop8.type); + strictEqual(prop9.type, prop10.type); }); diff --git a/packages/typespec-client-generator-core/test/types/dictionary.test.ts b/packages/typespec-client-generator-core/test/types/dictionary.test.ts index 4251db84e4..0922b441e5 100644 --- a/packages/typespec-client-generator-core/test/types/dictionary.test.ts +++ b/packages/typespec-client-generator-core/test/types/dictionary.test.ts @@ -23,6 +23,10 @@ it("same type's dictionary come to same type", async () => { prop4: Record; prop5: Record; prop6: Record; + prop7: Record>; + prop8: Record>; + prop9: Record; + prop10: Record; } op get(): TestDictionary; } @@ -30,14 +34,20 @@ it("same type's dictionary come to same type", async () => { const testArrayModel = runner.context.sdkPackage.models[0]; strictEqual(testArrayModel.kind, "model"); strictEqual(testArrayModel.name, "TestDictionary"); - strictEqual(testArrayModel.properties.length, 6); + strictEqual(testArrayModel.properties.length, 10); const prop1 = testArrayModel.properties[0]; const prop2 = testArrayModel.properties[1]; const prop3 = testArrayModel.properties[2]; const prop4 = testArrayModel.properties[3]; const prop5 = testArrayModel.properties[4]; const prop6 = testArrayModel.properties[5]; + const prop7 = testArrayModel.properties[6]; + const prop8 = testArrayModel.properties[7]; + const prop9 = testArrayModel.properties[8]; + const prop10 = testArrayModel.properties[9]; strictEqual(prop1.type, prop2.type); strictEqual(prop3.type, prop4.type); strictEqual(prop5.type, prop6.type); + strictEqual(prop7.type, prop8.type); + strictEqual(prop9.type, prop10.type); });