From 936f952898684ccc47bdf2660f6af60bfca7a5d3 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Thu, 20 Jun 2024 13:11:36 -0400 Subject: [PATCH] Fix `$infer` for `e.shape` (#1044) We were "faking" a `TypeSet` as part of the return type of `e.shape` to allow using `$infer` on `e.shape` expressions, but it turns out that caused the recent change to allow setting properties on a shape to a `TypeSet` as long as it agrees with the cardinality of the pointer to break. There are some lingering issues here that we should clean up, such as making the cardinality of `$infer` something reasonable rather than trying to infer it from the shape since that will depend on it's actual usage. In practice, most people want the `Cardinality.ONE` version (so, without `null` and not an array) so perhaps we can change this in a break version in the future. --- integration-tests/lts/select.test.ts | 37 +++++++++++++- packages/generate/src/syntax/external.ts | 4 +- packages/generate/src/syntax/select.ts | 16 ++---- packages/generate/src/syntax/typesystem.ts | 58 ++++++++++++++++++++-- 4 files changed, 95 insertions(+), 20 deletions(-) diff --git a/integration-tests/lts/select.test.ts b/integration-tests/lts/select.test.ts index b575b1159..8c47e175e 100644 --- a/integration-tests/lts/select.test.ts +++ b/integration-tests/lts/select.test.ts @@ -1354,6 +1354,13 @@ SELECT __scope_0_defaultPerson { rating: true, filter_single: e.op(movie.title, "=", "The Avengers"), })); + const characterShape = e.shape(e.Person, () => ({ + name: true, + id: true, + })); + const profileShape = e.shape(e.Profile, () => ({ + slug: true, + })); type ShapeType = $infer; tc.assert< @@ -1366,12 +1373,40 @@ SELECT __scope_0_defaultPerson { > >(true); + type CharacterShapeType = $infer; + tc.assert>( + true, + ); + const query = e.select(e.Movie, (m) => { return { ...baseShape(m), - characters: { name: true }, + characters: characterShape, + profile: profileShape, }; }); + assert.equal( + (query.__element__.__shape__.profile as any).__cardinality__, + $.Cardinality.AtMostOne, + ); + type Q = $infer; + + tc.assert< + tc.IsExact< + Q, + { + title: string; + rating: number | null; + characters: { + id: string; + name: string; + }[]; + profile: { + slug: string | null; + } | null; + } | null + > + >(true); const result = await query.run(client); assert.ok(result); diff --git a/packages/generate/src/syntax/external.ts b/packages/generate/src/syntax/external.ts index 4656a2bff..01ccfc53f 100644 --- a/packages/generate/src/syntax/external.ts +++ b/packages/generate/src/syntax/external.ts @@ -1,5 +1,3 @@ -import type { TypeSet, setToTsType } from "./typesystem"; - export { literal } from "./literal"; export {} from "./path"; export { set } from "./set"; @@ -27,4 +25,4 @@ export { optional, params } from "./params"; export { detached } from "./detached"; export {} from "./toEdgeQL"; -export type $infer = setToTsType; +export type { setToTsType as $infer } from "./typesystem"; diff --git a/packages/generate/src/syntax/select.ts b/packages/generate/src/syntax/select.ts index 5a8d29f91..bf2f092d9 100644 --- a/packages/generate/src/syntax/select.ts +++ b/packages/generate/src/syntax/select.ts @@ -849,25 +849,17 @@ function $shape< Expr extends ObjectTypeExpression, Element extends Expr["__element__"], Shape extends objectTypeToSelectShape & SelectModifiers, - SelectCard extends ComputeSelectCardinality, - SelectShape extends normaliseShape, Scope extends $scopify & $linkPropify<{ [k in keyof Expr]: k extends "__cardinality__" ? Cardinality.One : Expr[k]; }>, - Modifiers extends UnknownSelectModifiers = Pick, >( - expr: Expr, - _shape: (scope: Scope) => Readonly, -): ((scope: unknown) => Readonly) & - TypeSet< - ObjectType, - SelectCard - >; -function $shape(_a: unknown, b: (...args: any) => any) { - return b; + _expr: Expr, + shape: (scope: Scope) => Readonly, +): (scope: Scope) => Readonly { + return shape; } export { $shape as shape }; diff --git a/packages/generate/src/syntax/typesystem.ts b/packages/generate/src/syntax/typesystem.ts index 21ac4347a..09f21e739 100644 --- a/packages/generate/src/syntax/typesystem.ts +++ b/packages/generate/src/syntax/typesystem.ts @@ -10,6 +10,13 @@ import type { import { TypeKind } from "edgedb/dist/reflection/index"; import type { cardutil } from "./cardinality"; import type { Range, MultiRange } from "edgedb"; +import type { + ComputeSelectCardinality, + SelectModifierNames, + SelectModifiers, + normaliseShape, + objectTypeToSelectShape, +} from "./select"; ////////////////// // BASETYPE @@ -724,10 +731,53 @@ export type BaseTypeToTsType< ? computeObjectShape : never; -export type setToTsType = computeTsType< - Set["__element__"], - Set["__cardinality__"] ->; +type shapeFnToTsType = ShapeFn extends ( + scope: infer Scope, +) => Readonly + ? Scope extends $scopify + ? Shape extends objectTypeToSelectShape & + SelectModifiers + ? computeTsType< + ObjectType< + Scope["__element__"]["__name__"], + Scope["__element__"]["__pointers__"], + normaliseShape + >, + ComputeSelectCardinality< + { + __element__: Scope["__element__"]; + // TODO: Drop this explicit cardinality in a breaking change version + // A previous implementation of this acted this way, so we need to + // keep it for compatibility. + __cardinality__: Cardinality.Many; + }, + Pick + > + > + : never + : never + : never; + +type portableShapeToTsType = PortableShape extends ( + expr: infer Expr extends ObjectTypeExpression, + shape: (scope: unknown) => Readonly, +) => (scope: unknown) => void + ? Shape extends objectTypeToSelectShape & + SelectModifiers + ? computeTsType< + ObjectType< + Expr["__element__"]["__name__"], + Expr["__element__"]["__pointers__"], + normaliseShape + >, + ComputeSelectCardinality> + > + : never + : shapeFnToTsType; + +export type setToTsType = Set extends TypeSet + ? computeTsType + : portableShapeToTsType; export type computeTsTypeCard = Cardinality extends C ? unknown