diff --git a/common/changes/@typespec/compiler/fix-interface-op-instance-cache_2023-04-18-21-50.json b/common/changes/@typespec/compiler/fix-interface-op-instance-cache_2023-04-18-21-50.json new file mode 100644 index 00000000000..2c9dc1c469a --- /dev/null +++ b/common/changes/@typespec/compiler/fix-interface-op-instance-cache_2023-04-18-21-50.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@typespec/compiler", + "comment": "Fix: Issue with templated operations in templated interface would get cached only by keying on the operation template args.", + "type": "none" + } + ], + "packageName": "@typespec/compiler" +} \ No newline at end of file diff --git a/packages/compiler/core/checker.ts b/packages/compiler/core/checker.ts index bc923adbaa9..d19681ef982 100644 --- a/packages/compiler/core/checker.ts +++ b/packages/compiler/core/checker.ts @@ -1146,18 +1146,13 @@ export function createChecker(program: Program): Checker { ); } } - const cached = symbolLinks.instantiations?.get(args); + const mapper = createTypeMapper(params, args, parentMapper); + const cached = symbolLinks.instantiations?.get(mapper.args); if (cached) { return cached; } if (instantiateTempalates) { - return instantiateTemplate( - symbolLinks.instantiations, - templateNode, - params, - args, - parentMapper - ); + return instantiateTemplate(symbolLinks.instantiations, templateNode, params, mapper); } else { return errorType; } @@ -1175,13 +1170,11 @@ export function createChecker(program: Program): Checker { instantiations: TypeInstantiationMap, templateNode: TemplateableNode, params: TemplateParameter[], - args: Type[], - parentMapper: TypeMapper | undefined + mapper: TypeMapper ): Type { - const mapper = createTypeMapper(params, args, parentMapper); const type = getTypeForNode(templateNode, mapper); - if (!instantiations.get(args)) { - instantiations.set(args, type); + if (!instantiations.get(mapper.args)) { + instantiations.set(mapper.args, type); } if (type.kind === "Model") { type.templateNode = templateNode; @@ -5041,7 +5034,7 @@ function createTypeMapper( return { partial: false, - args, + args: [...(parentMapper?.args ?? []), ...args], getMappedType: (type: TemplateParameter) => { return map.get(type) ?? type; }, diff --git a/packages/compiler/test/checker/interface.test.ts b/packages/compiler/test/checker/interface.test.ts index a1885e6b255..4880675ce76 100644 --- a/packages/compiler/test/checker/interface.test.ts +++ b/packages/compiler/test/checker/interface.test.ts @@ -1,4 +1,4 @@ -import { deepStrictEqual, ok, strictEqual } from "assert"; +import { deepStrictEqual, notStrictEqual, ok, strictEqual } from "assert"; import { isTemplateDeclaration } from "../../core/type-utils.js"; import { Interface, Model, Operation, Type } from "../../core/types.js"; import { @@ -368,6 +368,29 @@ describe("compiler: interfaces", () => { strictEqual(a.type, b.type); }); + it("templated interface with different args but templated operations with the same arg shouldn't be the same", async () => { + const { Index } = (await runner.compile(` + @test interface Foo { + @test bar(input: A): B; + } + + alias MyFoo8 = Foo; + alias MyFoo16 = Foo; + @test model Index { + a: MyFoo8.bar; + b: MyFoo16.bar; + } + `)) as { + Index: Model; + }; + const a = Index.properties.get("a"); + const b = Index.properties.get("b"); + ok(a); + ok(b); + + notStrictEqual(a.type, b.type); + }); + it("can extend an interface with templated operations", async () => { const { Foo, myBar: bar } = (await runner.compile(` interface Base {