diff --git a/.chronus/changes/is-error-model-2024-1-29-0-44-17.md b/.chronus/changes/is-error-model-2024-1-29-0-44-17.md new file mode 100644 index 0000000000..9ba3ef2faa --- /dev/null +++ b/.chronus/changes/is-error-model-2024-1-29-0-44-17.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@typespec/compiler" +--- + +Any subtype of an error(marked with `@error`) is now an error. diff --git a/packages/compiler/src/lib/decorators.ts b/packages/compiler/src/lib/decorators.ts index baa0b680c8..29866cd033 100644 --- a/packages/compiler/src/lib/decorators.ts +++ b/packages/compiler/src/lib/decorators.ts @@ -350,16 +350,28 @@ const errorKey = createStateSymbol("error"); /** * `@error` decorator marks a model as an error type. - * - * `@error` can only be specified on a model. + * Any derived models (using extends) will also be seen as error types. */ export function $error(context: DecoratorContext, entity: Model) { validateDecoratorUniqueOnNode(context, entity, $error); context.program.stateSet(errorKey).add(entity); } +/** + * Check if the type is an error model or a descendant of an error model. + */ export function isErrorModel(program: Program, target: Type): boolean { - return program.stateSet(errorKey).has(target); + if (target.kind !== "Model") { + return false; + } + let current: Model | undefined = target; + while (current) { + if (program.stateSet(errorKey).has(current)) { + return true; + } + current = current.baseModel; + } + return false; } // -- @format decorator --------------------- diff --git a/packages/compiler/test/decorators/decorators.test.ts b/packages/compiler/test/decorators/decorators.test.ts index e889a767e4..01758ad734 100644 --- a/packages/compiler/test/decorators/decorators.test.ts +++ b/packages/compiler/test/decorators/decorators.test.ts @@ -387,6 +387,16 @@ describe("compiler: built-in decorators", () => { ok(isErrorModel(runner.program, A), "isError should be true"); }); + it("applies @error on derived models", async () => { + const { B, C } = await runner.compile(` + @error model A { } + @test model B extends A { } + @test model C extends B { } + `); + ok(isErrorModel(runner.program, B), "isError should be true"); + ok(isErrorModel(runner.program, C), "isError should be true"); + }); + it("emit diagnostic if error is not applied to a model", async () => { const diagnostics = await runner.diagnose(` @error