Skip to content

Commit

Permalink
Fix $infer for e.shape (#1044)
Browse files Browse the repository at this point in the history
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<typeof someShape>` 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.
  • Loading branch information
scotttrinh authored Jun 20, 2024
1 parent c63404d commit 936f952
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 20 deletions.
37 changes: 36 additions & 1 deletion integration-tests/lts/select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof baseShape>;
tc.assert<
Expand All @@ -1366,12 +1373,40 @@ SELECT __scope_0_defaultPerson {
>
>(true);

type CharacterShapeType = $infer<typeof characterShape>;
tc.assert<tc.IsExact<CharacterShapeType, { name: string; id: string }[]>>(
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<typeof query>;

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);
Expand Down
4 changes: 1 addition & 3 deletions packages/generate/src/syntax/external.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { TypeSet, setToTsType } from "./typesystem";

export { literal } from "./literal";
export {} from "./path";
export { set } from "./set";
Expand Down Expand Up @@ -27,4 +25,4 @@ export { optional, params } from "./params";
export { detached } from "./detached";
export {} from "./toEdgeQL";

export type $infer<A extends TypeSet> = setToTsType<A>;
export type { setToTsType as $infer } from "./typesystem";
16 changes: 4 additions & 12 deletions packages/generate/src/syntax/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -849,25 +849,17 @@ function $shape<
Expr extends ObjectTypeExpression,
Element extends Expr["__element__"],
Shape extends objectTypeToSelectShape<Element> & SelectModifiers<Element>,
SelectCard extends ComputeSelectCardinality<Expr, Modifiers>,
SelectShape extends normaliseShape<Shape, SelectModifierNames>,
Scope extends $scopify<Element> &
$linkPropify<{
[k in keyof Expr]: k extends "__cardinality__"
? Cardinality.One
: Expr[k];
}>,
Modifiers extends UnknownSelectModifiers = Pick<Shape, SelectModifierNames>,
>(
expr: Expr,
_shape: (scope: Scope) => Readonly<Shape>,
): ((scope: unknown) => Readonly<Shape>) &
TypeSet<
ObjectType<Element["__name__"], Element["__pointers__"], SelectShape>,
SelectCard
>;
function $shape(_a: unknown, b: (...args: any) => any) {
return b;
_expr: Expr,
shape: (scope: Scope) => Readonly<Shape>,
): (scope: Scope) => Readonly<Shape> {
return shape;
}
export { $shape as shape };

Expand Down
58 changes: 54 additions & 4 deletions packages/generate/src/syntax/typesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -724,10 +731,53 @@ export type BaseTypeToTsType<
? computeObjectShape<Type["__pointers__"], Type["__shape__"]>
: never;

export type setToTsType<Set extends TypeSet> = computeTsType<
Set["__element__"],
Set["__cardinality__"]
>;
type shapeFnToTsType<ShapeFn> = ShapeFn extends (
scope: infer Scope,
) => Readonly<infer Shape>
? Scope extends $scopify<ObjectType>
? Shape extends objectTypeToSelectShape<Scope["__element__"]> &
SelectModifiers<Scope["__element__"]>
? computeTsType<
ObjectType<
Scope["__element__"]["__name__"],
Scope["__element__"]["__pointers__"],
normaliseShape<Shape>
>,
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<Shape, SelectModifierNames>
>
>
: never
: never
: never;

type portableShapeToTsType<PortableShape> = PortableShape extends (
expr: infer Expr extends ObjectTypeExpression,
shape: (scope: unknown) => Readonly<infer Shape>,
) => (scope: unknown) => void
? Shape extends objectTypeToSelectShape<Expr["__element__"]> &
SelectModifiers<Expr["__element__"]>
? computeTsType<
ObjectType<
Expr["__element__"]["__name__"],
Expr["__element__"]["__pointers__"],
normaliseShape<Shape>
>,
ComputeSelectCardinality<Expr, Pick<Shape, SelectModifierNames>>
>
: never
: shapeFnToTsType<PortableShape>;

export type setToTsType<Set> = Set extends TypeSet
? computeTsType<Set["__element__"], Set["__cardinality__"]>
: portableShapeToTsType<Set>;

export type computeTsTypeCard<T, C extends Cardinality> = Cardinality extends C
? unknown
Expand Down

0 comments on commit 936f952

Please sign in to comment.