Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More specific TemplateStringsArray type for tagged templates #49552

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

rbuckton
Copy link
Member

@rbuckton rbuckton commented Jun 15, 2022

This introduces a more specific type over TemplateStingsArray when invoking a tagged template. This would then allow developers to interpolate the literal types provided to the tagged template invocation, along with the provided arguments:

type Interpolate<T extends readonly string[], A extends any[], R extends string = ''> =
    T extends readonly [infer TH extends string, ...infer TT extends readonly string[]] ?
        A extends [infer AH extends string, ...infer AT extends string[]] ?
            Interpolate<TT, AT, `${R}${TH}${AH}`> :
            Interpolate<TT, [], `${R}${TH}`> :
        R;

declare function interp<
  T extends TemplateStringsArray,
  A extends string[]
>(strs: T, ...args: A): Interpolate<T, A>;

const a = interp`a${"b"}c`; // "abc"
const b = interp`1${2}3`; // "123"

The type we manufacture for the tagged template invocation is an instantiation of TemplateStringsArrayOf<Cooked, Raw>:

type TemplateStringsArrayOf<
  Cooked extends readonly string[],
  Raw extends readonly string[]
> =
  Cooked & { readonly raw: Raw };

This would then allow you to perform custom interpolation against either the "cooked" values (where escapes are normalized), and "raw" values (where escapes are preserved):

declare function raw<
  T extends TemplateStringsArray,
  A extends string[]
>(strs: T, ...args: A): Interpolate<T["raw"], A>;

const a = raw`a\n${"b"}\nc`; // "a\\nb\\nc"

In theory, more complex interpolation could also be achieved, but that is an exercise left to the reader:

type QueryResult<T extends readonly string[], A extends any[]> =
   // some type that parses T and interpolates A...
  todo;

declare function query<T extends TemplateStringsArray, A extends any[]>(strs: T, ...args: A): QueryResult<T, A>;

interface Person { firstName: string, lastName: string }
declare const people: Iterable<Person>;

const result = query`
  for x in ${people}
  where x.firstName === ${"Bob"}
  select x.lastName
`;

result; // Iterable<string>

To support interpolation over a TemplateStringsArray, I also needed to make a change to inferFromObjectTypes. Prior to this change you could not infer to tuple element positions if the source type was an intersection of a tuple and another type:

type X<T extends any[]> = T extends [infer H, ...infer _] ? H : never;

// before
type T1 = X<[1, 2]>; // 1 (ok)
type T2 = X<[1, 2] & { foo: true }>; // never (😞)

However, this becomes a problem when trying to pick apart a generic TemplateStringsArrayOf type. To address this, this PR makes a small change in how we perform this inference. Nothing changes when a source type is already an array or tuple type, but if the source type is instead an intersection of an array or tuple type and another type, we will also now permit tuple element inference as long as the intersected types do not shadow tuple-specific properties (i.e., numeric indexers, numeric-stringed properties, or the "length" property):

type X<T extends any[]> = T extends [infer H, ...infer _] ? H : never;

// after
type T1 = X<[1, 2]>; // 1 (ok)
type T2 = X<[1, 2] & { foo: true }>; // 1 (ok)

Fixes #33304
Fixes #31422
Related #29432
Related #16551

@typescript-bot typescript-bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Jun 15, 2022
@rbuckton rbuckton marked this pull request as ready for review June 15, 2022 06:13
src/compiler/checker.ts Outdated Show resolved Hide resolved
let constituents: Type[] | undefined;
for (const constituent of (type as IntersectionType).types) {
if (isArrayOrTupleType(constituent)) {
arrayOrTuple = constituent;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this always just picks the last array or tuple type, making it order-dependent which tuple is picked, which seems bad. We should either have tuple-intersecting behavior or return undefined when there are multiple arrays/tuples, IMO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are already order dependent for this case: Playground link

Copy link
Member Author

@rbuckton rbuckton Jun 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think intersecting the tuples is viable here, as it introduces far too much complexity. I'm leaning towards what appears to be the current behavior in this case (i.e., picking the right-most), though another viable alternative would be to pick neither if there is more than one (since they would overlap).

Copy link
Member

@weswigham weswigham Jun 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Picking neither if there's more than one is often what we do in other situations, and order-dependence in inference is bad anywhere it crops up, since it means inference results can shift with file ordering, which is a pain in the butt bug to track down when it's reported later. Our existing left-preferring behavior among multiple matching (mutually subtyped) inferences is already a big "this really should be fixed"; I'd much prefer to bring the cases that introduce order dependence down if possible.

Copy link
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like most of this change, it's not too big, either; but there's some intersection-order-dependence in the inference code I think we should remove, and a debugger statement that obviously needs to go. 😄

src/compiler/checker.ts Outdated Show resolved Hide resolved
@rbuckton
Copy link
Member Author

I tinkered with an example of a tail-recursive parser for jest.each here, though it can probably be drastically simplified.

@rbuckton
Copy link
Member Author

Here's a much simpler version of Jest's each:

type IDStart =
    | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
    | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
    | "$" | "_"
    ;

type IDPart =
    | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
    | IDStart
    ;

type Trim<S extends string, Chars extends string = " " | "\t" | "\v" | "\n"> =
    S extends (Chars extends "" ? S : never) ? S :
    S extends `${Chars}${infer R}` ? Trim<R, Chars> :
    S extends `${infer R}${Chars}` ? Trim<R, Chars> :
    S;

type ParseHeaders<S extends string> =
    S extends "|" ? [] :
    S extends `${infer H}|${infer T}` ? [Trim<H>, ...ParseHeaders<T>] :
    [Trim<S>];

type CheckHeader<TIn extends string, TWork extends string = TIn, Valid extends IDPart = IDStart> =
    TWork extends `${infer Ch}${infer R}` ?
        Ch extends Valid ? CheckHeader<TIn, R, IDPart> :
            JestEachParseError<`Invalid identifier: '${TIn}'`> :
    TIn extends '' ? JestEachParseError<`Identifier expected.`> :
        TIn;

type CheckHeaders<TIn extends string[], TWork extends string[] = TIn> =
    TWork extends [infer TH extends string, ...infer TT extends string[]] ?
        CheckHeader<TH> extends infer E extends JestEachParseError<any> ? E :
            CheckHeaders<TIn, TT> :
        TIn;

type ParseRows<A extends any[], S extends readonly string[], Row extends any[] = [], Rows extends any[][] = []> =
    [A, S] extends [[infer AH, ...infer AT], readonly [infer TH extends string, ...infer TT extends string[]]] ?
        Trim<TH, " " | "\t" | "\v"> extends "|" ? ParseRows<AT, TT, [...Row, AH], Rows> :
        Trim<TH, " " | "\t" | "\v"> extends "\n" | "" ? ParseRows<AT, TT, [], [...Rows, [...Row, AH]]> :
            JestEachParseError<`Unexpected character: '${Trim<TH>}'`> :
    [A, S] extends [[], readonly []] ? Rows :
        JestEachParseError<`Mismatched elements`>;

type JestEachArgument<Headers extends string[] | JestEachParseError<any>, Rows extends any[][] | JestEachParseError<any>> =
    Headers extends string[] ?
        Rows extends any[][] ?
            {
                [P1 in keyof Rows]: {
                    [P2 in keyof Headers as P2 extends `${number}` ? Headers[P2] : never]:
                        P2 extends keyof Rows[P1] ? Rows[P1][P2] : undefined;
                };
            }[number] :
            Rows :
        Headers;

type JestEachFunction<Arg> =
    Arg extends JestEachParseError<any> ? Arg :
    (name: string, cb: (arg: Arg) => void, timeout?: number) => void;

declare const JestEachParseError: unique symbol;

interface JestEachParseError<Message extends string> {
    [JestEachParseError]: Message;
}

type JestEach<T extends readonly string[], A extends any[]> =
    T extends readonly [infer TH extends string, ...infer TT extends readonly string[]] ?
        JestEachFunction<JestEachArgument<CheckHeaders<ParseHeaders<TH>>, ParseRows<A, TT>>> :
        JestEachParseError<`Mismatched elements`>;

declare function each<T extends readonly string[], A extends (string | symbol | number | bigint | boolean | null | undefined | object)[]>(strs: T, ...args: A): JestEach<T, A>;

// jest
it.each`
    foo    | bar
    ${"a"} | ${"b"}
    ${"a"} | ${"c"}
`("test", ({ foo, bar }) => {
//           ^? any
//                ^? any
});

each`
    foo    | bar
    ${"a"} | ${"b"}
    ${"c"} | ${"d"}
`("test", ({ foo, bar }) => {
//           ^? "a" | "c"
//                ^? "b" | "d"
});

Copy link
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer the order dependence removed from inference, and I'd love to see that .each example in the test baselines (it probably exercises a bunch that we have minimal coverage of in concert, and would be useful to know we don't regress it), but I'm willing to approve this now, even if I'd like to see a bit more.

@rbuckton
Copy link
Member Author

There is no longer any order dependence. The behavior for inferring from an intersection of two tuple types now falls back to pre-existing behavior.

@rbuckton
Copy link
Member Author

Also, I've added a test for Jest's each based on the example above.

@rbuckton
Copy link
Member Author

@DanielRosenwasser: Are we comfortable taking this for 4.8 or would you rather wait?

@rbuckton
Copy link
Member Author

@typescript-bot perf test
@typescript-bot run dt
@typescript-bot test this
@typescript-bot user test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 17, 2022

Heya @rbuckton, I've started to run the parallelized Definitely Typed test suite on this PR at f8752ad. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 17, 2022

Heya @rbuckton, I've started to run the perf test suite on this PR at f8752ad. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 17, 2022

Heya @rbuckton, I've started to run the diff-based user code test suite on this PR at f8752ad. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 17, 2022

Heya @rbuckton, I've started to run the extended test suite on this PR at f8752ad. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

Heya @rbuckton, I've run the RWC suite on this PR - assuming you're on the TS core team, you can view the resulting diff here.

@typescript-bot
Copy link
Collaborator

@rbuckton
Great news! no new errors were found between main..refs/pull/49552/merge

@rbuckton
Copy link
Member Author

@typescript-bot perf test

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 17, 2022

Heya @rbuckton, I've started to run the perf test suite on this PR at e079811. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

@rbuckton
The results of the perf run you requested are in!

Here they are:

Comparison Report - main..49552

Metric main 49552 Delta Best Worst
Angular - node (v10.16.3, x64)
Memory used 359,557k (± 0.02%) 359,644k (± 0.02%) +87k (+ 0.02%) 359,533k 359,779k
Parse Time 2.11s (± 0.63%) 2.11s (± 0.41%) -0.00s (- 0.05%) 2.10s 2.14s
Bind Time 0.91s (± 1.05%) 0.90s (± 0.78%) -0.00s (- 0.44%) 0.89s 0.92s
Check Time 5.95s (± 0.43%) 5.99s (± 0.29%) +0.04s (+ 0.71%) 5.96s 6.04s
Emit Time 6.11s (± 0.77%) 6.14s (± 0.65%) +0.03s (+ 0.49%) 6.08s 6.25s
Total Time 15.07s (± 0.45%) 15.14s (± 0.27%) +0.07s (+ 0.47%) 15.07s 15.24s
Compiler-Unions - node (v10.16.3, x64)
Memory used 206,384k (± 0.04%) 206,445k (± 0.05%) +61k (+ 0.03%) 206,241k 206,661k
Parse Time 0.84s (± 0.89%) 0.84s (± 1.19%) +0.00s (+ 0.00%) 0.82s 0.87s
Bind Time 0.53s (± 1.59%) 0.53s (± 0.84%) +0.00s (+ 0.19%) 0.52s 0.54s
Check Time 8.12s (± 1.07%) 8.18s (± 0.52%) +0.06s (+ 0.73%) 8.09s 8.26s
Emit Time 2.51s (± 1.05%) 2.50s (± 0.69%) -0.00s (- 0.08%) 2.47s 2.54s
Total Time 11.99s (± 0.85%) 12.05s (± 0.44%) +0.06s (+ 0.48%) 11.95s 12.18s
Monaco - node (v10.16.3, x64)
Memory used 343,744k (± 0.02%) 343,785k (± 0.01%) +41k (+ 0.01%) 343,720k 343,859k
Parse Time 1.59s (± 0.76%) 1.59s (± 0.76%) +0.01s (+ 0.50%) 1.56s 1.62s
Bind Time 0.77s (± 1.01%) 0.77s (± 0.67%) 0.00s ( 0.00%) 0.76s 0.78s
Check Time 5.98s (± 0.64%) 5.95s (± 0.44%) -0.03s (- 0.45%) 5.89s 6.00s
Emit Time 3.24s (± 0.85%) 3.26s (± 0.73%) +0.02s (+ 0.77%) 3.21s 3.34s
Total Time 11.57s (± 0.54%) 11.58s (± 0.26%) +0.00s (+ 0.03%) 11.50s 11.64s
TFS - node (v10.16.3, x64)
Memory used 305,120k (± 0.02%) 305,158k (± 0.03%) +38k (+ 0.01%) 304,964k 305,420k
Parse Time 1.29s (± 0.78%) 1.29s (± 0.53%) +0.00s (+ 0.00%) 1.28s 1.31s
Bind Time 0.73s (± 0.92%) 0.73s (± 0.65%) +0.00s (+ 0.00%) 0.72s 0.74s
Check Time 5.41s (± 0.59%) 5.38s (± 0.60%) -0.02s (- 0.44%) 5.31s 5.45s
Emit Time 3.40s (± 1.03%) 3.44s (± 0.68%) +0.04s (+ 1.12%) 3.39s 3.50s
Total Time 10.83s (± 0.53%) 10.84s (± 0.28%) +0.01s (+ 0.12%) 10.77s 10.91s
material-ui - node (v10.16.3, x64)
Memory used 469,379k (± 0.01%) 471,939k (± 0.01%) +2,560k (+ 0.55%) 471,751k 472,084k
Parse Time 1.85s (± 0.77%) 1.84s (± 0.58%) -0.00s (- 0.22%) 1.82s 1.87s
Bind Time 0.69s (± 1.34%) 0.69s (± 1.34%) +0.00s (+ 0.00%) 0.67s 0.71s
Check Time 14.51s (± 0.73%) 14.62s (± 0.71%) +0.12s (+ 0.82%) 14.40s 14.85s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 17.04s (± 0.66%) 17.16s (± 0.58%) +0.12s (+ 0.68%) 16.94s 17.36s
xstate - node (v10.16.3, x64)
Memory used 584,715k (± 1.67%) 578,260k (± 0.02%) -6,455k (- 1.10%) 578,002k 578,446k
Parse Time 2.64s (± 0.40%) 2.63s (± 0.44%) -0.01s (- 0.27%) 2.61s 2.66s
Bind Time 1.05s (± 0.96%) 1.04s (± 0.97%) -0.00s (- 0.10%) 1.03s 1.07s
Check Time 1.55s (± 0.65%) 1.55s (± 0.62%) +0.00s (+ 0.19%) 1.52s 1.56s
Emit Time 0.07s (± 3.14%) 0.07s (± 0.00%) -0.00s (- 1.41%) 0.07s 0.07s
Total Time 5.29s (± 0.36%) 5.29s (± 0.26%) -0.01s (- 0.13%) 5.25s 5.31s
Angular - node (v12.1.0, x64)
Memory used 337,240k (± 0.02%) 337,315k (± 0.02%) +75k (+ 0.02%) 337,212k 337,429k
Parse Time 2.10s (± 0.66%) 2.10s (± 0.59%) +0.00s (+ 0.10%) 2.07s 2.13s
Bind Time 0.86s (± 1.16%) 0.86s (± 1.27%) +0.00s (+ 0.23%) 0.84s 0.90s
Check Time 5.76s (± 0.60%) 5.77s (± 0.56%) +0.01s (+ 0.21%) 5.70s 5.83s
Emit Time 6.36s (± 0.68%) 6.38s (± 0.68%) +0.02s (+ 0.31%) 6.25s 6.49s
Total Time 15.08s (± 0.30%) 15.11s (± 0.45%) +0.04s (+ 0.25%) 14.96s 15.26s
Compiler-Unions - node (v12.1.0, x64)
Memory used 194,005k (± 0.14%) 193,964k (± 0.16%) -41k (- 0.02%) 193,010k 194,297k
Parse Time 0.83s (± 1.21%) 0.84s (± 1.50%) +0.01s (+ 1.09%) 0.81s 0.86s
Bind Time 0.54s (± 0.67%) 0.55s (± 0.62%) +0.00s (+ 0.55%) 0.54s 0.55s
Check Time 7.63s (± 0.92%) 7.58s (± 0.78%) -0.05s (- 0.68%) 7.48s 7.72s
Emit Time 2.51s (± 0.78%) 2.54s (± 0.96%) +0.03s (+ 1.15%) 2.48s 2.61s
Total Time 11.51s (± 0.68%) 11.50s (± 0.72%) -0.02s (- 0.15%) 11.37s 11.74s
Monaco - node (v12.1.0, x64)
Memory used 326,866k (± 0.02%) 326,850k (± 0.02%) -16k (- 0.00%) 326,646k 326,984k
Parse Time 1.56s (± 0.69%) 1.56s (± 1.00%) -0.00s (- 0.26%) 1.53s 1.59s
Bind Time 0.76s (± 1.02%) 0.76s (± 0.45%) -0.00s (- 0.53%) 0.75s 0.76s
Check Time 5.76s (± 0.66%) 5.78s (± 0.39%) +0.01s (+ 0.23%) 5.71s 5.81s
Emit Time 3.28s (± 0.48%) 3.28s (± 0.52%) -0.00s (- 0.12%) 3.24s 3.31s
Total Time 11.37s (± 0.49%) 11.37s (± 0.41%) +0.00s (+ 0.03%) 11.25s 11.46s
TFS - node (v12.1.0, x64)
Memory used 289,745k (± 0.02%) 289,675k (± 0.07%) -70k (- 0.02%) 288,847k 289,941k
Parse Time 1.31s (± 1.22%) 1.29s (± 0.74%) -0.01s (- 0.84%) 1.28s 1.32s
Bind Time 0.75s (± 1.01%) 0.76s (± 0.65%) +0.00s (+ 0.13%) 0.75s 0.77s
Check Time 5.33s (± 0.62%) 5.32s (± 0.52%) -0.01s (- 0.15%) 5.24s 5.39s
Emit Time 3.50s (± 0.68%) 3.48s (± 0.76%) -0.02s (- 0.63%) 3.43s 3.54s
Total Time 10.88s (± 0.50%) 10.85s (± 0.36%) -0.03s (- 0.30%) 10.77s 10.95s
material-ui - node (v12.1.0, x64)
Memory used 448,352k (± 0.02%) 450,817k (± 0.06%) +2,466k (+ 0.55%) 449,731k 451,099k
Parse Time 1.85s (± 0.44%) 1.84s (± 0.56%) -0.01s (- 0.59%) 1.82s 1.87s
Bind Time 0.68s (± 0.98%) 0.68s (± 0.65%) -0.00s (- 0.15%) 0.67s 0.69s
Check Time 13.10s (± 0.73%) 13.10s (± 0.62%) -0.00s (- 0.03%) 12.94s 13.27s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 15.64s (± 0.64%) 15.62s (± 0.53%) -0.02s (- 0.10%) 15.44s 15.79s
xstate - node (v12.1.0, x64)
Memory used 546,965k (± 1.32%) 546,968k (± 1.31%) +3k (+ 0.00%) 543,492k 575,977k
Parse Time 2.59s (± 0.38%) 2.58s (± 0.50%) -0.01s (- 0.50%) 2.55s 2.60s
Bind Time 1.04s (± 1.11%) 1.03s (± 1.03%) -0.00s (- 0.29%) 1.01s 1.06s
Check Time 1.50s (± 0.83%) 1.49s (± 0.78%) -0.00s (- 0.20%) 1.47s 1.52s
Emit Time 0.07s (± 0.00%) 0.07s (± 0.00%) 0.00s ( 0.00%) 0.07s 0.07s
Total Time 5.20s (± 0.32%) 5.17s (± 0.51%) -0.02s (- 0.44%) 5.11s 5.22s
Angular - node (v14.15.1, x64)
Memory used 335,338k (± 0.01%) 335,372k (± 0.01%) +35k (+ 0.01%) 335,286k 335,431k
Parse Time 2.08s (± 0.63%) 2.07s (± 0.69%) -0.01s (- 0.38%) 2.04s 2.11s
Bind Time 0.90s (± 0.84%) 0.91s (± 1.18%) +0.01s (+ 0.77%) 0.90s 0.95s
Check Time 5.74s (± 0.37%) 5.76s (± 0.44%) +0.02s (+ 0.35%) 5.67s 5.79s
Emit Time 6.40s (± 0.61%) 6.42s (± 0.88%) +0.02s (+ 0.38%) 6.31s 6.58s
Total Time 15.12s (± 0.32%) 15.16s (± 0.47%) +0.04s (+ 0.26%) 15.02s 15.35s
Compiler-Unions - node (v14.15.1, x64)
Memory used 192,627k (± 0.02%) 192,648k (± 0.01%) +21k (+ 0.01%) 192,598k 192,702k
Parse Time 0.84s (± 0.87%) 0.84s (± 0.90%) +0.01s (+ 0.84%) 0.83s 0.87s
Bind Time 0.58s (± 0.69%) 0.58s (± 1.01%) -0.00s (- 0.69%) 0.57s 0.59s
Check Time 7.66s (± 0.60%) 7.68s (± 0.65%) +0.02s (+ 0.30%) 7.56s 7.77s
Emit Time 2.50s (± 0.59%) 2.51s (± 0.43%) +0.01s (+ 0.44%) 2.49s 2.54s
Total Time 11.58s (± 0.44%) 11.61s (± 0.41%) +0.04s (+ 0.33%) 11.49s 11.68s
Monaco - node (v14.15.1, x64)
Memory used 325,577k (± 0.01%) 325,588k (± 0.01%) +11k (+ 0.00%) 325,537k 325,638k
Parse Time 1.57s (± 0.47%) 1.57s (± 0.60%) -0.00s (- 0.19%) 1.56s 1.60s
Bind Time 0.79s (± 0.98%) 0.79s (± 0.89%) +0.00s (+ 0.25%) 0.78s 0.81s
Check Time 5.66s (± 0.41%) 5.67s (± 0.71%) +0.01s (+ 0.11%) 5.61s 5.76s
Emit Time 3.36s (± 0.75%) 3.35s (± 0.65%) -0.01s (- 0.36%) 3.30s 3.38s
Total Time 11.38s (± 0.37%) 11.38s (± 0.55%) -0.01s (- 0.05%) 11.27s 11.54s
TFS - node (v14.15.1, x64)
Memory used 288,764k (± 0.01%) 288,771k (± 0.01%) +8k (+ 0.00%) 288,720k 288,851k
Parse Time 1.32s (± 1.02%) 1.32s (± 1.28%) +0.00s (+ 0.23%) 1.29s 1.36s
Bind Time 0.76s (± 0.91%) 0.76s (± 1.41%) +0.00s (+ 0.53%) 0.74s 0.79s
Check Time 5.32s (± 0.48%) 5.32s (± 0.64%) -0.00s (- 0.02%) 5.26s 5.41s
Emit Time 3.64s (± 1.91%) 3.62s (± 1.69%) -0.01s (- 0.41%) 3.46s 3.68s
Total Time 11.03s (± 0.72%) 11.02s (± 0.72%) -0.00s (- 0.05%) 10.79s 11.13s
material-ui - node (v14.15.1, x64)
Memory used 446,630k (± 0.00%) 449,208k (± 0.00%) +2,578k (+ 0.58%) 449,175k 449,237k
Parse Time 1.90s (± 0.50%) 1.89s (± 0.44%) -0.02s (- 1.00%) 1.87s 1.90s
Bind Time 0.73s (± 1.27%) 0.73s (± 0.88%) +0.00s (+ 0.55%) 0.71s 0.74s
Check Time 13.25s (± 0.92%) 13.26s (± 0.72%) +0.01s (+ 0.10%) 13.04s 13.51s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 15.88s (± 0.80%) 15.88s (± 0.60%) -0.00s (- 0.00%) 15.67s 16.13s
xstate - node (v14.15.1, x64)
Memory used 541,401k (± 0.00%) 541,456k (± 0.00%) +55k (+ 0.01%) 541,393k 541,500k
Parse Time 2.62s (± 0.52%) 2.62s (± 0.32%) -0.00s (- 0.15%) 2.60s 2.64s
Bind Time 1.18s (± 0.80%) 1.18s (± 0.92%) +0.01s (+ 0.42%) 1.15s 1.20s
Check Time 1.53s (± 0.48%) 1.54s (± 0.31%) +0.01s (+ 0.59%) 1.53s 1.55s
Emit Time 0.07s (± 3.14%) 0.07s (± 4.66%) +0.00s (+ 2.82%) 0.07s 0.08s
Total Time 5.41s (± 0.32%) 5.41s (± 0.36%) +0.00s (+ 0.07%) 5.36s 5.45s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-210-generic
Architecturex64
Available Memory16 GB
Available Memory1 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v10.16.3, x64)
  • node (v12.1.0, x64)
  • node (v14.15.1, x64)
Scenarios
  • Angular - node (v10.16.3, x64)
  • Angular - node (v12.1.0, x64)
  • Angular - node (v14.15.1, x64)
  • Compiler-Unions - node (v10.16.3, x64)
  • Compiler-Unions - node (v12.1.0, x64)
  • Compiler-Unions - node (v14.15.1, x64)
  • Monaco - node (v10.16.3, x64)
  • Monaco - node (v12.1.0, x64)
  • Monaco - node (v14.15.1, x64)
  • TFS - node (v10.16.3, x64)
  • TFS - node (v12.1.0, x64)
  • TFS - node (v14.15.1, x64)
  • material-ui - node (v10.16.3, x64)
  • material-ui - node (v12.1.0, x64)
  • material-ui - node (v14.15.1, x64)
  • xstate - node (v10.16.3, x64)
  • xstate - node (v12.1.0, x64)
  • xstate - node (v14.15.1, x64)
Benchmark Name Iterations
Current 49552 10
Baseline main 10

Developer Information:

Download Benchmark

@trusktr
Copy link
Contributor

trusktr commented Jun 28, 2022

reference playground

@MartinJohns
Copy link
Contributor

This is a feature I've been waiting for a long time, but I forgot why. 😄

@harrysolovay
Copy link

Hi, this PR looks fantastic (I've been hoping for this feature for a long time)! Wondering what the status is?

@SieR-VR
Copy link

SieR-VR commented Oct 28, 2022

Is there any progress for this feature?

@mzinner
Copy link

mzinner commented Nov 3, 2022

I hope the pending reviews can be done soon so we will see this released soon. This would open the door to a lot of interesting things.

@rbuckton
Copy link
Member Author

rbuckton commented Nov 4, 2022

This PR is currently on hold. The main use cases for this feature require a fair amount of complexity that is likely to run any implementation against the recursion depth limiter. Such types are expensive to compute and check, and often result in degraded performance in an editor. It's unlikely that we will ship a feature that introduces this much complexity without first making significant improvements in overall type checking performance.

I do plan to keep this PR alive and will periodically update it, but I do not foresee it shipping in the near future.

@jantimon
Copy link

jantimon commented Nov 10, 2022

There are also use cases which require no recursions.
One example is the generation of GraphQL types from .ts files as described here:
https://the-guild.dev/graphql/codegen/plugins/presets/gql-tag-operations-preset

The idea is to generate return types for queries based on your written code.
For example:

import { gql } from "gql.generated"; // <--- gqls types are generated from all .ts files
const userQuery = const USER_QUERY = gql(`
  query User($id: String!) {
    user(id: $id) {
      id
      name
    }
  }
`);
execute(userQuery, "12") // <--- typed - will return Promise<{user: { id: string, name: string } }>

However for graphql fragments we would need to use template literals to merge the return types of the query and fragments:

import { gql } from "gql.generated";
const fragment = gql`a fragment query`;
const userQuery = gql`a graphql query { ${fragment} }`

@sandersn sandersn added the Experiment A fork with an experimental idea which might not make it into master label Feb 28, 2023
@NWYLZW
Copy link

NWYLZW commented Dec 27, 2023

Another year has passed, and version 5.x has now been released. This version has optimized the performance of comparison. Is it possible to consider introducing this feature in the new version? This helps us a lot, thank you very much for your work.

@easrng
Copy link

easrng commented Feb 26, 2024

i wrote a working type level parser for my library's html template string DSL so it can be typesafe but it's useless without this PR. JSX isn't an option due to lack of ability to cache static parts with default tooling. i'd love to see this merged.

@aradalvand
Copy link

@rbuckton Any updates? Aren't v5's perf enhancements sufficient now?

@rbuckton
Copy link
Member Author

rbuckton commented Mar 8, 2024

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Mar 8, 2024

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
pack this ✅ Started

@typescript-bot
Copy link
Collaborator

typescript-bot commented Mar 8, 2024

Hey @rbuckton, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/160237/artifacts?artifactName=tgz&fileId=C4F63B683D4A1D20951F7242BAF3E88934805D10EB48C11D866934D3E86690CF02&fileName=/typescript-4.8.0-insiders.20240308.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

@rbuckton
Copy link
Member Author

rbuckton commented Mar 8, 2024

@rbuckton Any updates? Aren't v5's perf enhancements sufficient now?

No, unfortunately. The performance concerns we have are related to the complex conditional types and inference that are necessary to "parse" a string literal type, and that still isn't very efficient at the moment.

@ssalbdivad
Copy link

@rbuckton Has ArkType not proven it can be done efficiently at this point?

Even with the overhead of type-level string parsing (including syntactic and semantic validation), types are often 3-10x less expensive than equivalent Zod.

It would be a shame not to ship what would be an awesome feature just because it is possible to use inefficiently.

@porsager
Copy link

porsager commented Aug 8, 2024

We need this merged yesterday ❤️ Typescript users are harassing me into changing my library APIs to be different just because they can't do what this PR would allow them.

Why not let the resposibility of making fast implementations, and avoiding slow ones be up to the users?

@ariofrio
Copy link

ariofrio commented Aug 23, 2024

This PR is currently on hold. The main use cases for this feature require a fair amount of complexity that is likely to run any implementation against the recursion depth limiter. Such types are expensive to compute and check, and often result in degraded performance in an editor. It's unlikely that we will ship a feature that introduces this much complexity without first making significant improvements in overall type checking performance.

I do plan to keep this PR alive and will periodically update it, but I do not foresee it shipping in the near future.

@rbuckton Any updates? Aren't v5's perf enhancements sufficient now?

No, unfortunately. The performance concerns we have are related to the complex conditional types and inference that are necessary to "parse" a string literal type, and that still isn't very efficient at the moment.

FWIW, @jantimon's use case, as well as a similar use case I'm working on regarding SQL queries, do not make use of conditional types or literal type parsing. If I understand him correctly, the use of literal types we have in mind boils down to using external tools to generate overloads from user code, like so:

function sql(strings: readonly ["select ", " + 1"], parameter: number): number;
function sql(strings: readonly ["select ", " || ' world!'"], parameter: string): string;
...

No literal parsing required. I'd argue that tools like this will be the main use cases in the near future, since users will avoid libraries that do complex parsing within TypeScript because they are too slow.

@porsager
Copy link

What about those of us who just wants a way to map types against a tagged template literal function starting with specific words?

@ariofrio
Copy link

ariofrio commented Aug 23, 2024

What about those of us who just wants a way to map types against a tagged template literal function starting with specific words?

It sounds like that would be supported by this PR. And simple enough that it wouldn't run into TypeScript performance constraints, and so, under my theory, be adopted widely by users and be one of "the main use cases", unlike more complex use cases that may need to wait for a more performant TypeScript to gain adoption.

My point is that performance is an important part of DX, and this is something that library authors and users already have to take into account. For example, uom for Rust is an awesome library, but it is not as widely used as it could be because it has quite slow compile times because it (ab)uses the type and macro systems.

There are plenty of use cases for this feature that improve DX and reliability without sacrificing editor performance by limiting the amount of work done in the type checker.

Is there anything would-be-users of this feature could do to help move it along?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team Experiment A fork with an experimental idea which might not make it into master For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
None yet