Skip to content

Commit

Permalink
Reduce type instantiations in e.literal (#924)
Browse files Browse the repository at this point in the history
TypeScript 5.4 introduced a pretty significant regression in type performance
with the `e.literal` expression. The changes here are meant to be no-op
performance improvements.
  • Loading branch information
scotttrinh committed Apr 3, 2024
1 parent 497ff51 commit 80cd914
Show file tree
Hide file tree
Showing 20 changed files with 201 additions and 154 deletions.
2 changes: 1 addition & 1 deletion integration-tests/legacy/package.json
Expand Up @@ -16,7 +16,7 @@
"jest": "^29.5.0",
"superjson": "^1.12.4",
"ts-jest": "^29.1.0",
"typescript": "^5.2.2"
"typescript": "^5.4.3"
},
"dependencies": {}
}
103 changes: 85 additions & 18 deletions integration-tests/lts/bench.ts
@@ -1,43 +1,100 @@
import { bench } from "@arktype/attest";

import e from "./dbschema/edgeql-js";
import { type BaseTypeToTsType } from "./dbschema/edgeql-js/typesystem";

bench("scalar literal", () => {
bench("BaseTypeToTsType: scalar", () => {
const lit = e.int32(42);
return {} as BaseTypeToTsType<typeof lit>;
}).types([596, "instantiations"]);

bench("e.literal: scalar", () => {
const lit = e.literal(e.int32, 42);
return {} as typeof lit;
}).types([755, "instantiations"]);

bench("e.int32: scalar", () => {
const lit = e.int32(42);
return {} as typeof lit;
}).types([556, "instantiations"]);

bench("e.str: scalar", () => {
const lit = e.str("abcd");
return {} as typeof lit;
}).types([555, "instantiations"]);
}).types([894, "instantiations"]);

bench("array literal", () => {
bench("BaseTypeToTsType: array literal", () => {
const lit = e.array([e.str("abcd")]);
return {} as BaseTypeToTsType<typeof lit>;
}).types([2394, "instantiations"]);

bench("e.literal: array literal", () => {
const lit = e.literal(e.array(e.str), ["abcd"]);
return {} as typeof lit;
}).types([2407, "instantiations"]);
}).types([1980, "instantiations"]);

bench("e.array: array literal", () => {
const lit = e.array([e.str("abcd")]);
return {} as typeof lit;
}).types([2367, "instantiations"]);

bench("named tuple literal", () => {
bench("e.literal: named tuple literal", () => {
const lit = e.literal(e.tuple({ str: e.str }), {
str: "asdf",
});
return {} as typeof lit;
}).types([11597, "instantiations"]);
}).types([10765, "instantiations"]);

bench("e.tuple: named tuple literal", () => {
const lit = e.tuple({ str: e.str("asdf") });
return {} as typeof lit;
}).types([7564, "instantiations"]);

bench("e.literal: tuple literal", () => {
const lit = e.literal(e.tuple([e.str, e.int32]), ["asdf", 42]);
return {} as typeof lit;
}).types([4670, "instantiations"]);

bench("e.tuple: tuple literal", () => {
const lit = e.tuple([e.str("asdf"), e.int32(42)]);
return {} as typeof lit;
}).types([4836, "instantiations"]);

bench("e.literal: array of tuples", () => {
const lit = e.literal(e.array(e.tuple([e.str, e.int32])), [
["asdf", 42],
["qwer", 43],
]);
return {} as typeof lit;
}).types([5664, "instantiations"]);

bench("e.array: array of tuples", () => {
const lit = e.array([
e.tuple([e.str("asdf"), e.int32(42)]),
e.tuple([e.str("qwer"), e.int32(43)]),
]);
return {} as typeof lit;
}).types([20582, "instantiations"]);

bench("base type: array", () => {
const baseType = e.array(e.str);
return {} as typeof baseType;
}).types([348, "instantiations"]);
}).types([351, "instantiations"]);

bench("base type: named tuple", () => {
const baseType = e.tuple({ str: e.str });
return {} as typeof baseType;
}).types([2160, "instantiations"]);
}).types([3564, "instantiations"]);

bench("select: scalar", () => {
const query = e.select(e.int32(42));
return {} as typeof query;
}).types([1155, "instantiations"]);
}).types([1173, "instantiations"]);

bench("select: free object", () => {
const query = e.select({ meaning: e.int32(42) });
return {} as typeof query;
}).types([2012, "instantiations"]);
}).types([2027, "instantiations"]);

bench("select: id only", () => {
const query = e.select(e.User, () => ({ id: true }));
Expand All @@ -49,7 +106,7 @@ bench("select: filtered", () => {
filter_single: { id: e.uuid("123") },
}));
return {} as typeof query;
}).types([5019, "instantiations"]);
}).types([5100, "instantiations"]);

bench("select: nested", () => {
const user = e.select(e.User, () => ({
Expand All @@ -58,7 +115,7 @@ bench("select: nested", () => {
const query = e.select(user, () => ({ id: true }));

return {} as typeof query;
}).types([6037, "instantiations"]);
}).types([6116, "instantiations"]);

bench("select: complex", () => {
const query = e.select(e.Movie, () => ({
Expand All @@ -70,7 +127,7 @@ bench("select: complex", () => {
}),
}));
return {} as typeof query;
}).types([6342, "instantiations"]);
}).types([6352, "instantiations"]);

bench("select: with filter", () => {
const query = e.select(e.Hero, (hero) => ({
Expand All @@ -82,7 +139,7 @@ bench("select: with filter", () => {
filter_single: e.op(hero.name, "=", "Peter Parker"),
}));
return {} as typeof query;
}).types([6289, "instantiations"]);
}).types([6428, "instantiations"]);

bench("select: with order", () => {
const query = e.select(e.Hero, (hero) => ({
Expand All @@ -95,7 +152,7 @@ bench("select: with order", () => {
filter_single: e.op(hero.name, "=", "Peter Parker"),
}));
return {} as typeof query;
}).types([6624, "instantiations"]);
}).types([6765, "instantiations"]);

bench("select: with limit", () => {
const query = e.select(e.Hero, (hero) => ({
Expand All @@ -108,7 +165,7 @@ bench("select: with limit", () => {
filter_single: e.op(hero.name, "=", "Peter Parker"),
}));
return {} as typeof query;
}).types([6352, "instantiations"]);
}).types([6490, "instantiations"]);

bench("select: with offset", () => {
const query = e.select(e.Hero, (hero) => ({
Expand All @@ -121,7 +178,7 @@ bench("select: with offset", () => {
filter_single: e.op(hero.name, "=", "Peter Parker"),
}));
return {} as typeof query;
}).types([6391, "instantiations"]);
}).types([6533, "instantiations"]);

bench("params select", () => {
const query = e.params({ name: e.str }, (params) =>
Expand All @@ -135,4 +192,14 @@ bench("params select", () => {
}))
);
return {} as typeof query;
}).types([11865, "instantiations"]);
}).types([11290, "instantiations"]);

bench("e.op: str = str", () => {
const op = e.op(e.str("a"), "=", e.str("b"));
return {} as typeof op;
}).types([1854, "instantiations"]);

bench("e.op: str ilike str", () => {
const op = e.op(e.str("a"), "ilike", e.str("b"));
return {} as typeof op;
}).types([51413, "instantiations"]);
2 changes: 1 addition & 1 deletion integration-tests/lts/package.json
Expand Up @@ -24,7 +24,7 @@
"jest": "^29.5.0",
"superjson": "^1.12.4",
"ts-jest": "^29.1.0",
"typescript": "^5.2.2"
"typescript": "^5.4.3"
},
"dependencies": {}
}
12 changes: 9 additions & 3 deletions integration-tests/lts/select.test.ts
Expand Up @@ -1162,7 +1162,9 @@ SELECT __scope_0_defaultPerson {
name: h.name,
otherHeros: e.select(e.Hero, (h2) => ({
name: true,
names: e.op(h.name, "++", h2.name),
name_one: h.name,
name_two: h2.name,
names_match: e.op(h.name, "=", h2.name),
order_by: h2.name,
})),
order_by: h.name,
Expand All @@ -1176,7 +1178,9 @@ SELECT __scope_0_defaultPerson {
name: h.name,
otherHeros: heros.map((h2) => ({
name: h2.name,
names: h.name + h2.name,
name_one: h.name,
name_two: h2.name,
names_match: h.name === h2.name,
})),
}));

Expand Down Expand Up @@ -1377,9 +1381,11 @@ SELECT __scope_0_defaultPerson {

const result = await query.run(client);

type Result = typeof result;

tc.assert<
tc.IsExact<
typeof result,
Result,
{ xy: { a: string | null; b: number | null } | null }[]
>
>(true);
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/nightly/package.json
Expand Up @@ -16,7 +16,7 @@
"jest": "^29.5.0",
"superjson": "^1.12.4",
"ts-jest": "^29.1.0",
"typescript": "^5.2.2"
"typescript": "^5.4.3"
},
"dependencies": {}
}
2 changes: 1 addition & 1 deletion integration-tests/stable/package.json
Expand Up @@ -16,7 +16,7 @@
"jest": "^29.5.0",
"superjson": "^1.12.4",
"ts-jest": "^29.1.0",
"typescript": "^5.2.2"
"typescript": "^5.4.3"
},
"dependencies": {}
}
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -15,7 +15,7 @@
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"tslint-plugin-prettier": "^2.3.0",
"typescript": "^5.2.2"
"typescript": "^5.4.2"
},
"scripts": {
"lint": "tslint 'packages/*/src/**/*.ts'",
Expand Down
2 changes: 1 addition & 1 deletion packages/auth-core/package.json
Expand Up @@ -37,7 +37,7 @@
"edgedb": "^1.4.0",
"jest": "29.5.0",
"ts-jest": "29.1.0",
"typescript": "^5.2.2"
"typescript": "^5.4.3"
},
"peerDependencies": {
"edgedb": "^1.3.6"
Expand Down
2 changes: 1 addition & 1 deletion packages/auth-express/package.json
Expand Up @@ -31,7 +31,7 @@
"@types/node": "^20.8.4",
"edgedb": "^1.3.6",
"express": "^4.18.2",
"typescript": "^5.2.2"
"typescript": "^5.4.3"
},
"peerDependencies": {
"cookie-parser": "^1.4.6",
Expand Down
8 changes: 4 additions & 4 deletions packages/auth-nextjs/package.json
Expand Up @@ -27,13 +27,13 @@
"@types/react": "^18.2.42",
"edgedb": "^1.3.6",
"next": "13.5.6",
"typescript": "^5.2.2",
"react": "^18.2.0"
"react": "^18.2.0",
"typescript": "^5.4.3"
},
"peerDependencies": {
"edgedb": "^1.3.6",
"react": "^18.2.0",
"next": ">=13.5.6 <15.0.0"
"next": ">=13.5.6 <15.0.0",
"react": "^18.2.0"
},
"dependencies": {
"@edgedb/auth-core": "0.2.0-beta.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/auth-remix/package.json
Expand Up @@ -21,7 +21,7 @@
"devDependencies": {
"@types/node": "^20.8.4",
"edgedb": "^1.4.0",
"typescript": "^5.2.2"
"typescript": "^5.4.3"
},
"peerDependencies": {
"edgedb": "^1.3.6"
Expand Down
4 changes: 2 additions & 2 deletions packages/auth-sveltekit/package.json
Expand Up @@ -19,11 +19,11 @@
"build": "tsc --project tsconfig.json"
},
"devDependencies": {
"@sveltejs/kit": "^2.0.0",
"@types/node": "^20.8.4",
"edgedb": "^1.3.6",
"typescript": "^5.2.2",
"svelte": "^4.2.7",
"@sveltejs/kit": "^2.0.0",
"typescript": "^5.4.3",
"vite": "^5.0.3"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/create/package.json
Expand Up @@ -23,7 +23,7 @@
"@types/debug": "^4.1.12",
"@types/node": "^20.10.4",
"tsx": "^4.6.2",
"typescript": "^5.2.2"
"typescript": "^5.4.3"
},
"dependencies": {
"@clack/prompts": "^0.7.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/driver/package.json
Expand Up @@ -34,7 +34,7 @@
"jest-environment-jsdom": "^29.5.0",
"ts-jest": "29.1.0",
"tsx": "^3.12.7",
"typescript": "^5.2.2"
"typescript": "^5.4.3"
},
"scripts": {
"typecheck": "tsc --project tsconfig.json --noEmit",
Expand Down
2 changes: 1 addition & 1 deletion packages/generate/package.json
Expand Up @@ -31,7 +31,7 @@
"jest": "^29.5.0",
"superjson": "^1.12.4",
"ts-jest": "^29.1.0",
"typescript": "^5.2.2"
"typescript": "^5.4.3"
},
"dependencies": {},
"scripts": {
Expand Down
11 changes: 6 additions & 5 deletions packages/generate/src/syntax/literal.ts
Expand Up @@ -27,16 +27,17 @@ export type $expr_Literal<Type extends BaseType = BaseType> = Expression<{
__value__: any;
}>;

export function literal<T extends BaseType>(
type: T,
value: BaseTypeToTsType<T>
): $expr_Literal<T> {
export function literal<
T extends BaseType,
TsType extends BaseTypeToTsType<T> = BaseTypeToTsType<T>,
ExprType extends $expr_Literal<T> = $expr_Literal<T>
>(type: T, value: TsType): ExprType {
return $expressionify({
__element__: type,
__cardinality__: Cardinality.One,
__kind__: ExpressionKind.Literal,
__value__: value,
}) as any;
}) as ExprType;
}

export const $nameMapping = new Map<string, string>([
Expand Down
4 changes: 2 additions & 2 deletions packages/generate/src/syntax/path.ts
Expand Up @@ -77,9 +77,9 @@ export type $pathify<
// Parent extends PathParent | null = null
> = Root extends ObjectTypeSet
? ObjectTypeSet extends Root
? {} // Root is literally ObjectTypeSet
? unknown // Root is literally ObjectTypeSet
: pathifyPointers<Root> & pathifyShape<Root> & $linkPropify<Root>
: {}; // pathify does nothing on non-object types
: unknown; // pathify does nothing on non-object types

export type pathifyPointers<
Root extends ObjectTypeSet
Expand Down

0 comments on commit 80cd914

Please sign in to comment.