diff --git a/.chronus/changes/fix-ice-serialize-value-as-json-custom-scalar-2026-2-25-20-0-0.md b/.chronus/changes/fix-ice-serialize-value-as-json-custom-scalar-2026-2-25-20-0-0.md new file mode 100644 index 00000000000..25affad8bba --- /dev/null +++ b/.chronus/changes/fix-ice-serialize-value-as-json-custom-scalar-2026-2-25-20-0-0.md @@ -0,0 +1,9 @@ +--- +changeKind: fix +packages: + - "@typespec/compiler" +--- + +Fix crash when using custom scalar initializer in examples or default values + [API] Fix crash in `serializeValueAsJson` when a custom scalar initializer has no recognized constructor (e.g. `S.i()` with no args). Now returns `undefined` instead of crashing. + diff --git a/packages/compiler/src/lib/examples.ts b/packages/compiler/src/lib/examples.ts index 9516fd638a6..90d61d0b278 100644 --- a/packages/compiler/src/lib/examples.ts +++ b/packages/compiler/src/lib/examples.ts @@ -229,7 +229,7 @@ function serializeScalarValueAsJson( const result = resolveKnownScalar(program, value.scalar); if (result === undefined) { - return serializeValueAsJson(program, value.value.args[0], value.value.args[0].type); + return undefined; } encodeAs = encodeAs ?? result.encodeAs; diff --git a/packages/compiler/test/decorators/examples.test.ts b/packages/compiler/test/decorators/examples.test.ts index 141e05655cf..b6d75bf81ec 100644 --- a/packages/compiler/test/decorators/examples.test.ts +++ b/packages/compiler/test/decorators/examples.test.ts @@ -130,6 +130,28 @@ describe("@example", () => { code: "unassignable", }); }); + + it("returns undefined for custom scalar with no-argument initializer", async () => { + const { program, examples, target } = await getExamplesFor(` + @example(test.i()) + @test scalar test { + init i(); + } + `); + expect(examples).toHaveLength(1); + expect(serializeValueAsJson(program, examples[0].value, target)).toBeUndefined(); + }); + + it("returns undefined for custom scalar with string-argument initializer", async () => { + const { program, examples, target } = await getExamplesFor(` + @example(test.name("Shorty")) + @test scalar test { + init name(value: string); + } + `); + expect(examples).toHaveLength(1); + expect(serializeValueAsJson(program, examples[0].value, target)).toBeUndefined(); + }); }); describe("enum", () => { diff --git a/packages/openapi3/test/models.test.ts b/packages/openapi3/test/models.test.ts index 7ce314dec71..d88c53d8773 100644 --- a/packages/openapi3/test/models.test.ts +++ b/packages/openapi3/test/models.test.ts @@ -262,7 +262,7 @@ worksFor(supportedVersions, ({ diagnoseOpenApiFor, oapiForModel, openApiFor }) = }); }); - it("scalar used as a default value", async () => { + it("scalar with unknown constructor used as a default value produces no default and no diagnostic", async () => { const res = await oapiForModel( "Pet", ` @@ -272,7 +272,35 @@ worksFor(supportedVersions, ({ diagnoseOpenApiFor, oapiForModel, openApiFor }) = `, ); - expect(res.schemas.Pet.properties.name.default).toEqual("Shorty"); + expect(res.schemas.Pet.properties.name.default).toBeUndefined(); + }); + + it("scalar with no-argument initializer used as a default value does not crash", async () => { + const res = await oapiForModel( + "M", + ` + scalar S { init i(); } + + model M { p: S = S.i(); } + `, + ); + + expect(res.schemas.M.properties.p.default).toBeUndefined(); + }); + + it("known scalar constructors used as default values produce correct defaults", async () => { + const res = await oapiForModel( + "Foo", + ` + model Foo { + int32Prop: int32 = int32(12); + stringProp: string = string("this is the string value"); + } + `, + ); + + expect(res.schemas.Foo.properties.int32Prop.default).toEqual(12); + expect(res.schemas.Foo.properties.stringProp.default).toEqual("this is the string value"); }); it("encode know scalar as a default value", async () => {