diff --git a/packages/graphql/src/lib/scalar-mappings.ts b/packages/graphql/src/lib/scalar-mappings.ts index f4cf21cedaa..4f47b585c9b 100644 --- a/packages/graphql/src/lib/scalar-mappings.ts +++ b/packages/graphql/src/lib/scalar-mappings.ts @@ -157,6 +157,25 @@ export function isStdScalar(tk: Typekit, scalar: Scalar): boolean { return tk.scalar.getStdBase(scalar) === scalar; } +/** + * TypeSpec std scalar names that map directly to GraphQL built-in scalar types: + * string → String, boolean → Boolean, int32 → Int, float32/float64 → Float. + * + * @see https://spec.graphql.org/September2025/#sec-Scalars.Built-in-Scalars + */ +const GRAPHQL_BUILTIN_SCALAR_NAMES = new Set([ + "string", "boolean", "int32", "float32", "float64", +]); + +/** + * Check whether a scalar IS a GraphQL built-in type (String, Boolean, Int, Float). + * Uses identity-based check via typekit to avoid false positives from user-defined + * scalars that happen to share the same name in a different namespace. + */ +export function isGraphQLBuiltinScalar(tk: Typekit, scalar: Scalar): boolean { + return isStdScalar(tk, scalar) && GRAPHQL_BUILTIN_SCALAR_NAMES.has(scalar.name); +} + /** * Get the GraphQL custom scalar mapping for a scalar via its standard library ancestor. * diff --git a/packages/graphql/src/mutation-engine/mutations/scalar.ts b/packages/graphql/src/mutation-engine/mutations/scalar.ts index 57661b8fc2d..f82e42a8db6 100644 --- a/packages/graphql/src/mutation-engine/mutations/scalar.ts +++ b/packages/graphql/src/mutation-engine/mutations/scalar.ts @@ -7,7 +7,7 @@ import { type SimpleMutations, } from "@typespec/mutator-framework"; import { reportDiagnostic } from "../../lib.js"; -import { getScalarMapping, isStdScalar } from "../../lib/scalar-mappings.js"; +import { getScalarMapping, isGraphQLBuiltinScalar, isStdScalar } from "../../lib/scalar-mappings.js"; import { getSpecifiedBy, setSpecifiedByUrl } from "../../lib/specified-by.js"; import { sanitizeNameForGraphQL } from "../../lib/type-utils.js"; @@ -61,7 +61,7 @@ export class GraphQLScalarMutation extends SimpleScalarMutation { scalar.name = mapping.graphqlName; diff --git a/packages/graphql/test/mutation-engine/graphql-mutation-engine.test.ts b/packages/graphql/test/mutation-engine/graphql-mutation-engine.test.ts index ff2859e7a3d..070a433115c 100644 --- a/packages/graphql/test/mutation-engine/graphql-mutation-engine.test.ts +++ b/packages/graphql/test/mutation-engine/graphql-mutation-engine.test.ts @@ -326,6 +326,60 @@ describe("GraphQL Mutation Engine - Scalars", () => { expect(mutation.mutatedType.name).toBe("ID"); }); + it("does not rename builtin std scalars even when they inherit a mapping", async () => { + // float32 inherits a mapping via float → numeric → "Numeric", but it's a + // GraphQL builtin (maps to Float) and must never be renamed. + const { M } = await tester.compile( + t.code`model ${t.model("M")} { value: float32; }`, + ); + + const engine = createTestEngine(tester.program); + const float32Scalar = M.properties.get("value")!.type; + expect(float32Scalar.kind).toBe("Scalar"); + const mutation = engine.mutateScalar(float32Scalar as any); + + expect(mutation.mutatedType.name).toBe("float32"); + }); + + it("does not rename float64 builtin scalar", async () => { + const { M } = await tester.compile( + t.code`model ${t.model("M")} { value: float64; }`, + ); + + const engine = createTestEngine(tester.program); + const float64Scalar = M.properties.get("value")!.type; + expect(float64Scalar.kind).toBe("Scalar"); + const mutation = engine.mutateScalar(float64Scalar as any); + + expect(mutation.mutatedType.name).toBe("float64"); + }); + + it("does not rename int32 builtin scalar", async () => { + const { M } = await tester.compile( + t.code`model ${t.model("M")} { count: int32; }`, + ); + + const engine = createTestEngine(tester.program); + const int32Scalar = M.properties.get("count")!.type; + expect(int32Scalar.kind).toBe("Scalar"); + const mutation = engine.mutateScalar(int32Scalar as any); + + expect(mutation.mutatedType.name).toBe("int32"); + }); + + it("still renames mapped non-builtin std scalars like int64", async () => { + const { M } = await tester.compile( + t.code`model ${t.model("M")} { big: int64; }`, + ); + + const engine = createTestEngine(tester.program); + const int64Scalar = M.properties.get("big")!.type; + expect(int64Scalar.kind).toBe("Scalar"); + const mutation = engine.mutateScalar(int64Scalar as any); + + expect(mutation.mutatedType.name).toBe("Long"); + }); + it("warns when user-defined scalar collides with GraphQL built-in name", async () => { const { Float } = await tester.compile( t.code`scalar ${t.scalar("Float")} extends string;`,