Skip to content

Commit

Permalink
Use specific type-level marker to e.shape (#1048)
Browse files Browse the repository at this point in the history
Introduces a type-level marker to the return value of the $shape
function. This marker is used solely for type inference to carry
element, cardinality, and shape information and is not present at
runtime.

We now detect this specific construct in the `setToTsType` (which is the
actual type we alias as `$infer`) and just short-circuit to calculating
the object shape as if the shape was wrapped in a select.
  • Loading branch information
scotttrinh authored Jun 24, 2024
1 parent 252b0ab commit 9bb1a27
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 66 deletions.
11 changes: 5 additions & 6 deletions integration-tests/lts/select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as $ from "../../packages/generate/src/syntax/reflection";

import e, { type $infer } from "./dbschema/edgeql-js";
import { setupTests, teardownTests, tc, type TestData } from "./setupTeardown";

let client: edgedb.Client;
let data: TestData;

Expand Down Expand Up @@ -1358,8 +1359,8 @@ SELECT __scope_0_defaultPerson {
name: true,
id: true,
}));
const heroShape = e.shape(e.Hero, () => ({
villains: true,
const personShape = e.shape(e.Person, () => ({
name: true,
}));
const villainShape = e.shape(e.Villain, () => ({
nemesis: true,
Expand Down Expand Up @@ -1422,17 +1423,15 @@ SELECT __scope_0_defaultPerson {

const cast = e.select(query, () => ({ characters: true }));
const freeObjWithShape = e.select({
heros: e.select(cast.characters.is(e.Hero), (h) => {
return heroShape(h);
}),
heros: e.select(cast.characters.is(e.Hero), personShape),
villains: e.select(cast.characters.is(e.Villain), villainShape),
});
type FreeObjWithShape = $infer<typeof freeObjWithShape>;
tc.assert<
tc.IsExact<
FreeObjWithShape,
{
heros: { villains: { id: string }[] }[];
heros: { name: string }[];
villains: { nemesis: { id: string } | null }[];
}
>
Expand Down
27 changes: 19 additions & 8 deletions packages/generate/src/syntax/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import type {
ExclusiveTuple,
orLiteralValue,
EnumType,
assert_single,
} from "./typesystem";

import {
Expand Down Expand Up @@ -846,26 +845,38 @@ export const $existingScopes = new Set<
Expression<TypeSet<BaseType, Cardinality>>
>();

const shapeSymbol = Symbol("portableShape");

export interface $Shape<
Element extends ObjectType,
SelectShape,
Card extends Cardinality = Cardinality.One,
> {
[shapeSymbol]: {
__element__: Element;
__cardinality__: Card;
__shape__: SelectShape;
};
}

function $shape<
Expr extends ObjectTypeExpression,
Element extends Expr["__element__"],
Shape extends objectTypeToSelectShape<Element> & SelectModifiers<Element>,
SelectCard extends ComputeSelectCardinality<
Expr,
Pick<Shape, SelectModifierNames>
>,
Scope extends $scopify<Element> &
$linkPropify<{
[k in keyof Expr]: k extends "__cardinality__"
? Cardinality.One
: Expr[k];
}>,
ElementOfAnyShape extends Omit<Element, "__shape__"> & { __shape__: any },
>(
_expr: Expr,
shape: (scope: Scope) => Readonly<Shape>,
): (
scope: Omit<Scope, "__element__" | "assert_single"> & {
__element__: ElementOfAnyShape;
assert_single(): assert_single<ElementOfAnyShape, Cardinality.AtMostOne>;
},
) => Readonly<Shape>;
): ((scope: unknown) => Readonly<Shape>) & $Shape<Element, Shape, SelectCard>;
function $shape(_a: unknown, b: (...args: any) => any) {
return b;
}
Expand Down
62 changes: 10 additions & 52 deletions packages/generate/src/syntax/typesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,7 @@ 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";
import type { $Shape, normaliseShape } from "./select";

//////////////////
// BASETYPE
Expand Down Expand Up @@ -732,53 +726,17 @@ export type BaseTypeToTsType<
? computeObjectShape<Type["__pointers__"], Type["__shape__"]>
: never;

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>
>
export type setToTsType<Set> =
Set extends $Shape<infer Element, infer Shape, infer Card>
? Shape extends object
? computeTsTypeCard<
computeObjectShape<Element["__pointers__"], normaliseShape<Shape>>,
Card
>
: 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>;
: Set extends TypeSet
? computeTsType<Set["__element__"], Set["__cardinality__"]>
: never;

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

0 comments on commit 9bb1a27

Please sign in to comment.