diff --git a/.chronus/changes/recover-bad-op-is-2024-3-23-17-46-20.md b/.chronus/changes/recover-bad-op-is-2024-3-23-17-46-20.md new file mode 100644 index 0000000000..553de9ab5d --- /dev/null +++ b/.chronus/changes/recover-bad-op-is-2024-3-23-17-46-20.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: fix +packages: + - "@typespec/compiler" +--- + +Fix compiler crash when using an invalid `is` target in an interface operation template diff --git a/packages/compiler/src/core/checker.ts b/packages/compiler/src/core/checker.ts index 7390a622f3..0a57dd3e69 100644 --- a/packages/compiler/src/core/checker.ts +++ b/packages/compiler/src/core/checker.ts @@ -1807,7 +1807,7 @@ export function createChecker(program: Program): Checker { node: OperationStatementNode, mapper: TypeMapper | undefined, parentInterface?: Interface - ): Operation | ErrorType { + ): Operation { const inInterface = node.parent?.kind === SyntaxKind.InterfaceStatement; const symbol = inInterface ? getSymbolForMember(node) : node.symbol; const links = symbol && getSymbolLinks(symbol); @@ -1819,7 +1819,11 @@ export function createChecker(program: Program): Checker { } if (mapper === undefined && inInterface) { - compilerAssert(parentInterface, "Operation in interface should already have been checked."); + compilerAssert( + parentInterface, + "Operation in interface should already have been checked.", + node.parent + ); } checkTemplateDeclaration(node, mapper); @@ -1837,32 +1841,42 @@ export function createChecker(program: Program): Checker { if (node.signature.kind === SyntaxKind.OperationSignatureReference) { // Attempt to resolve the operation const baseOperation = checkOperationIs(node, node.signature.baseOperation, mapper); - if (!baseOperation) { - return errorType; - } - sourceOperation = baseOperation; - const parameterModelSym = getOrCreateAugmentedSymbolTable(symbol!.metatypeMembers!).get( - "parameters" - )!; - // Reference the same return type and create the parameters type - const clone = initializeClone(baseOperation.parameters, { - properties: createRekeyableMap(), - }); + if (baseOperation) { + sourceOperation = baseOperation; + const parameterModelSym = getOrCreateAugmentedSymbolTable(symbol!.metatypeMembers!).get( + "parameters" + )!; + // Reference the same return type and create the parameters type + const clone = initializeClone(baseOperation.parameters, { + properties: createRekeyableMap(), + }); - clone.properties = createRekeyableMap( - Array.from(baseOperation.parameters.properties.entries()).map(([key, prop]) => [ - key, - cloneTypeForSymbol(getMemberSymbol(parameterModelSym, prop.name)!, prop, { - model: clone, - sourceProperty: prop, - }), - ]) - ); - parameters = finishType(clone); - returnType = baseOperation.returnType; + clone.properties = createRekeyableMap( + Array.from(baseOperation.parameters.properties.entries()).map(([key, prop]) => [ + key, + cloneTypeForSymbol(getMemberSymbol(parameterModelSym, prop.name)!, prop, { + model: clone, + sourceProperty: prop, + }), + ]) + ); + parameters = finishType(clone); + returnType = baseOperation.returnType; - // Copy decorators from the base operation, inserting the base decorators first - decorators = [...baseOperation.decorators]; + // Copy decorators from the base operation, inserting the base decorators first + decorators = [...baseOperation.decorators]; + } else { + // If we can't resolve the signature we return an empty model. + parameters = createAndFinishType({ + kind: "Model", + name: "", + decorators: [], + properties: createRekeyableMap(), + derivedModels: [], + sourceModels: [], + }); + returnType = voidType; + } } else { parameters = getTypeForNode(node.signature.parameters, mapper) as Model; returnType = getTypeForNode(node.signature.returnType, mapper); @@ -4144,19 +4158,17 @@ export function createChecker(program: Program): Checker { for (const opNode of node.operations) { const opType = checkOperation(opNode, mapper, interfaceType); - if (opType.kind === "Operation") { - if (ownMembers.has(opType.name)) { - reportCheckerDiagnostic( - createDiagnostic({ - code: "interface-duplicate", - format: { name: opType.name }, - target: opNode, - }) - ); - continue; - } - ownMembers.set(opType.name, opType); + if (ownMembers.has(opType.name)) { + reportCheckerDiagnostic( + createDiagnostic({ + code: "interface-duplicate", + format: { name: opType.name }, + target: opNode, + }) + ); + continue; } + ownMembers.set(opType.name, opType); } return ownMembers; } diff --git a/packages/compiler/test/checker/operations.test.ts b/packages/compiler/test/checker/operations.test.ts index a8d8a65642..fd04995c92 100644 --- a/packages/compiler/test/checker/operations.test.ts +++ b/packages/compiler/test/checker/operations.test.ts @@ -259,6 +259,37 @@ describe("compiler: operations", () => { ok(gammaTargets.has(newFoo)); }); + // Regression test for https://github.com/microsoft/typespec/issues/3199 + it("produce an empty interface operation in template when op is reference is invalid", async () => { + testHost.addTypeSpecFile( + "main.tsp", + ` + @test op test is IFace.Action<int32>; + + interface IFace { + Action<T> is string; + } + ` + ); + + const [{ test }, diagnostics] = await testHost.compileAndDiagnose("./main.tsp"); + expectDiagnostics(diagnostics, [ + { + code: "is-operation", + message: "Operation can only reuse the signature of another operation.", + }, + { + code: "is-operation", + message: "Operation can only reuse the signature of another operation.", + }, + ]); + strictEqual(test.kind, "Operation"); + strictEqual(test.parameters.name, ""); + strictEqual(test.parameters.properties.size, 0); + strictEqual(test.returnType.kind, "Intrinsic"); + strictEqual((test.returnType as IntrinsicType).name, "void"); + }); + it("emit diagnostic when operation is referencing itself as signature", async () => { testHost.addTypeSpecFile( "main.tsp", @@ -307,10 +338,6 @@ describe("compiler: operations", () => { code: "circular-op-signature", message: "Operation 'foo' recursively references itself.", }, - { - code: "circular-op-signature", - message: "Operation 'bar' recursively references itself.", - }, ]); }); @@ -330,10 +357,6 @@ describe("compiler: operations", () => { code: "circular-op-signature", message: "Operation 'foo' recursively references itself.", }, - { - code: "circular-op-signature", - message: "Operation 'bar' recursively references itself.", - }, ]); });