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

Conditionally filter types from tuples at transpilation time. #38044

Closed
5 tasks done
insidewhy opened this issue Apr 19, 2020 · 6 comments
Closed
5 tasks done

Conditionally filter types from tuples at transpilation time. #38044

insidewhy opened this issue Apr 19, 2020 · 6 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@insidewhy
Copy link

insidewhy commented Apr 19, 2020

Search Terms

tuple, filter, metaprogram

Suggestion

I'd like a better way to filter types from a tuple type (or indeed object type). Currently I can easily make a type not optional (with -?) or make one optional (?) but I can't conditionally remove a type element from a tuple to produce another tuple. Well actually I can, with this crazy metaprogram that uses up so much CPU that it isn't usable in the real world:

type NumberMap = {
  0: 1
  1: 2
  2: 3
  3: 4
  4: 5
  5: 6
  6: 7
  7: 8
  8: 9
  9: 10
  10: 11
  11: 12
  // up to twelve supported
  12: 12
}

// prepending is relatively easy
type Prepend<H, T extends any[]> = ((h: H, ...t: T) => void) extends (
  ...l: infer L
) => void
  ? L
  : never

// appending is possible but expensive and hard, must
// build lists in reverse and reverse the result when done
type Reverse<L extends any[], R extends any[] = []> = {
  0: R
  1: ((...l: L) => void) extends (h: infer H, ...t: infer T) => void
    ? Reverse<T, Prepend<H, R>>
    : never
}[L extends [any, ...any[]] ? 1 : 0]

type Equals<I extends number, L extends number> = I extends L ? 1 : 0

type FilterBoolean<T, R extends any[]> = T extends boolean ? R : Prepend<T, R>

type FilterBooleansNext<
  I extends keyof NumberMap,
  T extends any[],
  R extends any[]
> = T extends []
  ? R
  : {
      0: FilterBooleansNext<NumberMap[I], T, FilterBoolean<T[I], R>>
      1: R
    }[Equals<I, T['length']>]

// append is hard/expensive, so keep prepending and reverse the result
export type FilterBooleans<T extends any[]> = Reverse<
  FilterBooleansNext<0, T, []>
>

Use Cases

I am writing a parser library where you can build parsers by composing operators (each operator being a higher order function). When composing a parser using the sequence operator, the return type of the sequence should be a tuple of the return types of each operator the sequence is composed from. However the return types of "predicate" operators should be excluded from the tuple return type since the predicate operators don't parse any data, they only match or fail to match.

Examples

// need something better than `excluded` here I guess, using it as a placeholder.
type FilterBooleans<T> = { [K in keyof T]: T[K] extends boolean ? excluded : T[K] };

type TupleWithoutBooleans = FilterBooleans<[string, boolean, number]>;
type RecordWithoutBooleans = FilterBooleans<{ s: string, b: boolean, n: number }>;

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@insidewhy insidewhy changed the title Conditionally removing types from tuple/object types. Conditionally filtering types from tuple/object types. Apr 19, 2020
@RyanCavanaugh
Copy link
Member

What does "remove a type from an object" mean?

@RyanCavanaugh
Copy link
Member

I'm guessing you want this

type Step1<T> = { [K in keyof T]: T[K] extends boolean ? never : K }[keyof T];
type FilterBooleans<T> = { [K in Step1<T>]: T[K] }

// { s: string, n: number }
type RecordWithoutBooleans = FilterBooleans<{ s: string, b: boolean, n: number }>;

The corresponding thing for tuples is kind of conceptually weird since it implies some kind of splicing operation?

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Apr 20, 2020
@insidewhy
Copy link
Author

insidewhy commented Apr 21, 2020

@RyanCavanaugh Thanks for the suggestion, I see that works for records but not tuples. If I run:

FilterBooleans<[string, boolean, Date, boolean]>;

I get a type like this (the tuple has been converted into a record):

interface TupleWithoutBooleans {
  4: undefined;
  0: string;
  2: Date;
}

I need it for my parser generator though, so I can build a parser from component functions.

const parseRule = parseSequence(parseRuleName(), notPredicate(parseConstant("<"))

The notPredicate return type shouldn't be in the tuple type returned by the parseRule function.

@insidewhy insidewhy changed the title Conditionally filtering types from tuple/object types. Conditionally filter types from tuples at transpilation time. Jul 19, 2020
@insidewhy
Copy link
Author

@RyanCavanaugh This is labelled "Waiting for feedback" but I'm not sure what feedback you'd like, could you let me know?

BTW in typescript 4.1 I can do this:

type NoBoolean<K, T> = T extends boolean ? never : K

type FilterBooleans<T> = {
  [K in keyof T as NoBoolean<K, T[K]>]: T[K]
}

type Filtered = FilterBooleans<[string, boolean, number, boolean]>

Here type Filtered looks like the tuple I want, only it's a record, with a key 0 of string, a key 2 of number, a length of the constant 4 and all the functions from the array prototype. It would be great if there was a tuple equivalent of what you can already now do with records. Maybe something like:

type FilterBooleans<T> = [
  [K in keyof T as NoBoolean<K, T[K]>]: T[K]
];

(i.e. the same as above but with the { and } changed to [ and ]). Without this, my parser combinator framework needs a lot of manual help from the user.

@insidewhy
Copy link
Author

Closing in favour of #42122

@MartinJohns
Copy link
Contributor

This is labelled "Waiting for feedback" but I'm not sure what feedback you'd like, could you let me know?

@insidewhy: The Awaiting More Feedback label has this description:

This means we'd like to hear from more people who would be helped by this feature

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants