Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/compiler"
---

Fixed compiler crash when a decorator is called with a function argument that contains an invalid reference. The decorator is now correctly skipped and an error diagnostic is reported instead of passing the raw return type to the decorator implementation.
8 changes: 7 additions & 1 deletion packages/compiler/src/core/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5822,6 +5822,12 @@ export function createChecker(program: Program, resolver: NameResolver): Checker
const fnCtx = createFunctionContext(program, node);

if (!canCall) {
// If arguments were not satisfied and we are not in a template declaration,
// return errorType so that the invalid result propagates as an error and
// prevents downstream usage (e.g., decorator application with wrong argument types).
if (!satisfied && !ctx.hasFlags(CheckFlags.InTemplateDeclaration)) {
return errorType;
}
return getDefaultFunctionResult(target.returnType);
}

Expand Down Expand Up @@ -5995,7 +6001,7 @@ export function createChecker(program: Program, resolver: NameResolver): Checker
constraint: param.type,
});

if (!checkedArg) {
if (!checkedArg || (isType(checkedArg) && isErrorType(checkedArg))) {
satisfied = false;
continue;
}
Expand Down
32 changes: 32 additions & 0 deletions packages/compiler/test/checker/decorators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,38 @@ describe("compiler: checker: decorators", () => {
expectDecoratorNotCalled();
});

it("does not call decorator when augment decorator fn argument has an invalid reference", async () => {
const FnTester = Tester.files({
"test.js": mockFile.js({
$flags: {},
$testDec: (...args: any[]) => (calledArgs = args),
$functions: {
"": {
myFn: (_ctx: any, target: any) => target,
},
},
}),
})
.import("./test.js")
.using("TypeSpec.Reflection");

const diagnostics = await FnTester.diagnose(`
#suppress "experimental-feature" "testing"
extern fn myFn(target: Reflection.Operation): Reflection.Operation;
extern dec testDec(target: unknown, override: Reflection.Operation);

op test(): void;

@@testDec(test, myFn(invalidref));
`);

expectDiagnostics(diagnostics, {
code: "invalid-ref",
message: /Unknown identifier invalidref/,
});
expectDecoratorNotCalled();
});

// Regresssion test for https://github.com/microsoft/typespec/issues/3211
it("augmenting a template model property before a decorator declaration resolve the declaration correctly", async () => {
await UsageTester.compile(`
Expand Down
10 changes: 5 additions & 5 deletions packages/compiler/test/checker/functions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,9 @@ describe("usage", () => {

expectNotCalled();

// In this case, we did not call the function, so we expect the constraint.
// In this case, we did not call the function due to argument errors, so we expect errorType.
strictEqual(t.kind, "Intrinsic");
strictEqual(t.name, "unknown");
strictEqual(t.name, "ErrorType");
});

it("errors if argument type mismatch (value)", async () => {
Expand Down Expand Up @@ -780,9 +780,9 @@ describe("specific type constraints", () => {
]);

strictEqual(receivedType, undefined);
// Since we didn't call the function, we get back the constraint type, which is Reflection.Model.
strictEqual(t.kind, "Model");
strictEqual(t.name, "Model");
// Since we didn't call the function due to argument errors, we get back errorType.
strictEqual(t.kind, "Intrinsic");
strictEqual(t.name, "ErrorType");
});
});

Expand Down
Loading