Last active
October 7, 2024 17:21
-
-
Save webstrand/5a5df9e63d5646f045a8871f11cb5b6f to your computer and use it in GitHub Desktop.
Examples of the <T>(foo: Validator<T>) pattern in typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of `Nonempty` validator which checks that the provided type | |
* has at least one defined property, excluding `{}`. | |
******************************************************************************/ | |
type Nonempty<T extends { [key: string]: any }> = { [P in keyof T]: T }[keyof T]; | |
declare function wantsNonempty<T extends { [key: string]: any }>(x: Nonempty<T>): true; | |
wantsNonempty({ x: 1 }); | |
wantsNonempty({}); // error expected |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of `NonUnion` validator which checks that the provided type | |
* is not a union. Does not check for primitive types such as `string` or | |
* `number`. | |
******************************************************************************/ | |
type NonUnion<T, U extends T = T> = | |
(T extends T ? U extends T ? 0 : 1 : never) extends 0 ? T : never; | |
declare function wantsNonUnion<T extends "foo" | "bar" | "baz">( | |
type: NonUnion<T> | |
): { [P in T]: unknown }; | |
wantsNonUnion("foo"); | |
wantsNonUnion("bar"); | |
wantsNonUnion("foo" as "foo" | "bar"); //error | |
// Note that this example can also be solved by generating an overloaded | |
// function type with overloads for each of "foo" | "bar" | ... | |
// The overloads provide better code-completion suggestions for users and | |
// the error when misusing a union type is not opaque. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of `Literal` validator which checks that the provided type is | |
* a single, definite, literal type. | |
******************************************************************************/ | |
type Literal<T extends string, U extends T = T> = {} extends { [P in T]: unknown } ? never : | |
(T extends T ? U extends T ? 0 : 1 : never) extends 0 ? T : AnyOf<U>; | |
type IsTemplateLiteral<T extends string> = {} extends { [P in T]: unknown } ? true : false; | |
type AnyOf<U> = | |
(U extends U ? (x: () => U) => 0 : never) extends (x: infer I) => 0 | |
? I extends () => infer R | |
? Extract<U, R> | |
: never | |
: never; | |
declare function acceptLiteral<T extends string>(x: Literal<T>): void; | |
declare const a: "foo"; | |
declare const b: "foo" | "bar"; | |
declare const c: string; | |
declare const d: `foo${string}`; | |
acceptLiteral(a) | |
acceptLiteral(b) // Argument of type '"foo" | "bar"' is not assignable to parameter of type '"bar"'. | |
acceptLiteral(c) // Argument of type 'string' is not assignable to parameter of type 'never'. | |
acceptLiteral(d); // Argument of type '`foo${string}`' is not assignable to parameter of type 'never'. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of AnyOf validator that forces non-union generics by picking | |
* one value from the union. | |
******************************************************************************/ | |
type AnyOf<U> = | |
(U extends U ? (x: () => U) => 0 : never) extends (x: infer I) => 0 | |
? I extends () => infer R | |
? U extends R ? U : never | |
: never | |
: never; | |
declare function wantsAnyOf<T>(x: AnyOf<T>): void; | |
wantsAnyOf(1); | |
wantsAnyOf(2); | |
wantsAnyOf({} as 1 | 2 | 3); // error |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of `Exactly` validator which checks that the provided type | |
* does not contain properties in excess of some base type. | |
******************************************************************************/ | |
type Base = { | |
foo: number; | |
bar: number; | |
}; | |
// Base is passed through a conditional to split apart union types, otherwise | |
// exactly breaks on union types. | |
type Exactly<Base extends object, T extends Base> = Base extends Base ? T & { [P in Exclude<keyof T, keyof Base>]?: never } : never; | |
declare function fn<T extends Base>(p: Exactly<Base, T>): void; | |
fn({ foo: 1, bar: 2 }); | |
fn({ foo: 1, bar: 2, baz: 3 }); // error expected |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Validates that a parameter is a literal integer without fractional | |
* component. | |
******************************************************************************/ | |
type IsInteger<T extends number | bigint> = | |
`${T}` extends `${bigint}` | `${bigint}e+${bigint}` | |
? T | |
: { error: `${T} is not an integer` }; | |
declare function getOffset<T extends number | bigint>(i: IsInteger<T>): void; | |
getOffset(1); | |
getOffset(5.5); // error | |
getOffset(-7); | |
getOffset(1e308); | |
getOffset(Infinity); //error | |
getOffset(NaN); //error | |
getIndex(1n); | |
/** | |
* Only permits natural numbers, i.e. positive integers. | |
*/ | |
type IsNatural<T extends number | bigint> = | |
`-${T}` extends `${bigint}` | `${bigint}e+${bigint}` | |
? T | |
: { error: `${T} is not a natural number (zero inclusive)` }; | |
declare function getIndex<T extends number | bigint>(i: IsNatural<T>): void; | |
getIndex(-0) // unfixable :( | |
getIndex(1); | |
getIndex(5.5); // error | |
getIndex(-7); // error | |
getIndex(1e308); | |
getIndex(1e309); //error | |
getIndex(Infinity); //error | |
getIndex(NaN); //error | |
getIndex(1n); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of `PipeValidate` validator which checks that the provided array | |
* type contains a sequence of functions where for each member, the argument | |
* type matches the previous return type. | |
******************************************************************************/ | |
type Pipeable<P = never, R extends unknown = unknown> = (arg: P) => R; | |
/** | |
* A sequence of Pipeable functions. May be invalid, check with | |
* PipeValidate<T>. | |
*/ | |
type Pipe<T extends readonly Pipeable[] = readonly Pipeable[]> = T; | |
/** | |
* Return T matching the mutability of Z. For example if Z is readonly | |
* so will the produced type, and if Z is not readonly, neither will the | |
* produced type. | |
*/ | |
type MatchMutability<T extends readonly unknown[], Z extends readonly unknown[]> = | |
Z extends unknown[] ? [...T] : readonly [...T]; | |
/** | |
* Replace the argument of a pipeable function without changing its name. | |
*/ | |
type ReplaceArg<T extends Pipeable, Replacement> = | |
T extends (...args: infer U) => infer V | |
? (...args: { [P in keyof U]: P extends "0" ? Replacement : U[P] }) => V | |
: (arg: Replacement) => unknown; | |
/** | |
* Validate that every member of some Pipe is compatible with the next. | |
*/ | |
type PipeValidate<T extends Pipe> = | |
T extends readonly [Pipeable<never, infer NextArg>, ...Pipe<infer P>] | |
? _PipeValidate<NextArg, P, [T[0]], T> | |
: MatchMutability<[Pipeable, ...Pipeable[]], T> | |
/** @private */ | |
type _PipeValidate<Arg, T extends Pipe, A extends Pipe, Z extends Pipe> = | |
T extends readonly [Pipeable<Arg, infer NextArg>, ...Pipe<infer P>] | |
? _PipeValidate<NextArg, P, [...A, T[0]], Z> | |
: T extends readonly [] | |
? Z | |
: MatchMutability<[...A, ReplaceArg<T[0], Arg>, ...Pipeable[]], Z>; | |
/** | |
* Obtain the return type of some pipe sequence. | |
*/ | |
type PipeReturnType<T extends Pipe> = | |
T extends readonly [...Pipe, Pipeable<never, infer ReturnType>] | |
? ReturnType | |
: unknown; | |
declare function foo(a: string): number; | |
declare function bar(a: number): symbol; | |
declare function baz(a: symbol): string[]; | |
declare function qux<T>(a: T): T; | |
declare function pipe<T extends Pipe>(...funcs: PipeValidate<T>): (...args: Parameters<T[0]>) => PipeReturnType<T>; | |
pipe(foo, bar); | |
pipe(foo, bar, baz); | |
pipe(foo, (x: number) => ""); | |
pipe(foo, baz, baz); // error | |
pipe(foo, (x) => x) // error, user must specify types | |
pipe(foo, qux, bar); // error, generic functions aren't supported. | |
// Thanks to @keithlayne; a hack to workaround generic functions by fixing their | |
// generic arguments beforehand. | |
function fix<P, R extends unknown>(fn: Pipeable<P, R>) { return fn } | |
pipe(foo, fix<number, number>(qux), bar); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of `IsUnaryFunction` validator which requires that the | |
* provided function type has exactly one argument. | |
******************************************************************************/ | |
type IsUnaryFunction<T extends (...args: any) => any> = Parameters<T> extends [] ? "function must accept exactly one argument" : T; | |
declare function callUnaryFunction<T extends (...args: any) => any>(fn: IsUnaryFunction<T>): void; | |
callUnaryFunction(() => {}); // error expected | |
callUnaryFunction((v: string) => {}); // no error |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of `covary` validator which requires that the provided record | |
* contains property names that match their value's name property value. | |
******************************************************************************/ | |
declare function covary<T extends Record<string, { name: string }>>(map: T & { [P in keyof T]: { name: P } }): void; | |
covary({}); // no error | |
covary({ foo: { name: "foo" }, bar: { name: "bar" } }); // no error | |
covary({ foo: { name: "bar" }, bar: { name: "foo" } }); // error expected |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of `NoUndefined` validator which requires that every property | |
* be defined. Useful for preventing users from passing objects with declared | |
* but undefined properties. Can't be relied upon, though. There are many ways | |
* to accidentally bypass this validator. | |
******************************************************************************/ | |
type SomeOptionalType = { a?: string, b?: number, c: string }; | |
type NoUndefined<T> = { [P in keyof T]: T[P] extends undefined ? never : T[P] } | |
declare function acceptNoUndefined<T extends SomeOptionalType>(prop: NoUndefined<T>): void; | |
acceptNoUndefined({}) // invalid | |
acceptNoUndefined({ c: "" }) // valid | |
acceptNoUndefined({ b: undefined, c: "" }); // invalid | |
acceptNoUndefined({ a: "", c: ""}); // valid |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Validates that a string is a positive decimal of some kind. Does not accept | |
* shorthand ".5" or "5." notation. | |
******************************************************************************/ | |
type ValidatePositiveDecimal<N extends `${number}`, Pass = N> = | |
N extends `${infer U}.${infer V}` | |
? `-${U}.-${V}` extends `${bigint}.${bigint}` ? Pass : never | |
: `-${N}` extends `${bigint}` ? Pass : never; | |
declare function dec<N extends `${number}`>(x: ValidatePositiveDecimal<N>): void; | |
dec("0"); | |
dec("5"); | |
dec("5000"); | |
dec("55.55"); | |
// errors | |
dec(".5"); | |
dec("5."); | |
dec("-5"); | |
dec("-5.-5"); | |
dec("5.-5"); | |
dec("1e32"); | |
dec("-1e22"); | |
dec("0.3e-22"); | |
dec("Infinity"); | |
dec("NaN"); | |
/****************************************************************************** | |
* Validates that a string is a positive decimal followed by some unit of | |
* length. | |
******************************************************************************/ | |
type Units = 'px'|'ch'|'pt'|'cm'|'in'|'em'|''; | |
type Length = `${number}${` ${Exclude<Units, "">}` | `${Units}`}`; | |
type ValidateLength<L extends Length, Pass = L> = | |
L extends `${infer N}${` ${Exclude<Units, "">}` | `${Units}`}` | |
? ValidatePositiveDecimal<N & `${number}`, Pass> | |
: never; | |
declare function len<L extends Length>(x: ValidateLength<L>): void; | |
len("0em"); | |
len("5"); | |
len("5000cm"); | |
len("55.55 pt"); | |
// errors | |
len(".5"); | |
len("5."); | |
len("-5 in"); | |
len("-5.-5"); | |
len("5.-5"); | |
len("1e32"); | |
len("-1e22"); | |
len("0.3e-22"); | |
len("Infinity ch"); | |
len("NaN"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of a validator that only accepts tuple types that contain | |
* every member of some union. Excluding duplicates and unknown types. | |
******************************************************************************/ | |
type CompleteTupleOf<K, T extends readonly unknown[], R extends readonly unknown[] = readonly[], TT extends readonly unknown[] = T> = | |
T extends unknown | |
// check if one or more elements remaining in the tuple? | |
? T extends readonly [infer X extends K, ...infer Y] & { 0: infer XX } | |
? (X extends X ? XX extends X ? 0 : 1 : never) extends 0 | |
? CompleteTupleOf<Exclude<K, X>, Y, readonly [...R, X], TT> | |
: readonly [...R, "error: must not be union", ...K[]] & { invalid: true } | |
// check if the union is empty? | |
: [K] extends [never] | |
// check if there are no excess keys | |
? T extends readonly[] | |
// We must reuse the original binding or inference fails | |
? TT | |
// Prevent excess keys | |
: R & { invalid: true } | |
// union has unsatisfied elements remaining | |
// we intersect `{ invalid: true }` to force unassignability in the face of wildcards like `string` or `number` | |
: readonly [...R, K] & { invalid: true } // K hasn't been emptied, generate an error | |
: T; // provide inference site, unreachable | |
// test cases: | |
type Union = "a" | "b"; | |
declare function completeTuple<T extends Union[]>(accept: CompleteTupleOf<Union, T>): void; | |
completeTuple(null! as Union[]); // error: non-tuples | |
completeTuple(null! as [Union]); // error: elements must be literals | |
completeTuple([]) // error: missing a, b | |
completeTuple(["a"]); // error: missing b | |
completeTuple(["b"]); // error: missing a | |
completeTuple(["a", "b"]); | |
completeTuple(["b", "a"]); | |
completeTuple(["b", "b", "a"]); // error: excess key | |
completeTuple(["b", "a", "a"]); // error: excess key | |
completeTuple(["b", "a", "c"]); // error: unknown excess key | |
// Example Usage: | |
// Typescript can't guess what keys `Object.keys` or `foo in obj` | |
// return at runtime, since typescript only guarantees that know keys exist | |
// but there may be arbitrary unknown (excess) properties on an object. | |
// Thus it types both of the above as `string[]`. | |
// | |
// This causes issues when you're trying to iterate over the keys of an object but | |
// typescript won't stop complaining. You can use `CompleteTupleOf` to fix this: | |
function keyof<T extends {}, K extends readonly [] | readonly ((string & {}) | "" | (number & {}) | 0 | (symbol & {}))[]>(o: T, keys: CompleteTupleOf<keyof T, K>): K { | |
return keys as K | |
} | |
interface Example { | |
foo: string; | |
"baz": string; | |
111: string; | |
} | |
declare const example: Example; | |
for(const k of keyof(example, ["baz", "foo", 111])) { | |
example[k] // no error | |
} | |
declare const myObj: { x: number, y: string }; | |
for(const key of keyof(myObj, ["x", "y"])) { | |
console.log(myObj[key]); // is type-safe and works without error. | |
} | |
// If you add or remove a key from the object type, the compiler will report an error: | |
for(const key of keyof(myObj, ["x", "y", "z"])) {} | |
for(const key of keyof(myObj, ["x"])) {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of `NoDuplicateElements` validator that checks that a tuple | |
* type has no duplicated elements; that every element is distinct at the type | |
* level. | |
******************************************************************************/ | |
type NoDuplicateElements<T extends readonly unknown[], A extends unknown[] = [], Z extends readonly unknown[] = T> = | |
T extends readonly [infer X, ...infer Y] | |
? X extends Y[number] | |
? [...A, X] | |
: NoDuplicateElements<Y, [...A, X], Z> | |
: Z; | |
declare function uniqueArray<T extends unknown[] | []>(accept: NoDuplicateElements<T>): void; | |
declare const a: 1; | |
declare const b: 2; | |
declare const c: 3; | |
uniqueArray([]); | |
uniqueArray([a]); | |
uniqueArray([a,b]); | |
uniqueArray([b,a]); | |
uniqueArray([a,a]); // Argument of type '[1, 1]' is not assignable to parameter of type '[1]'. | |
uniqueArray([b,b]); // Argument of type '[2, 2]' is not assignable to parameter of type '[2]'. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Implementation of a validator for SQL sort strings. Very simple, and | |
* whitespace-sensitive. But it handles auto-completion well and provides | |
* suggestions. | |
******************************************************************************/ | |
type Sort<T> = | |
| `${string & keyof T}` | |
| `${string & keyof T} asc` | |
| `${string & keyof T} ASC` | |
| `${string & keyof T} desc` | |
| `${string & keyof T} DESC`; | |
type ValidateSort<T, U extends string> = | |
U extends `${Sort<T>}${infer V}` ? V extends "" ? U : _ValidateSort<T, V, U extends `${infer W}${V}` ? W : never, U> : Sort<T>; | |
type _ValidateSort<T, U extends string, P extends string, Z extends string> = | |
U extends `, ${Sort<T>}${infer V}` ? V extends "" ? Z : _ValidateSort<T, V, U extends `${infer W}${V}` ? `${P}${W}` : never, Z> : `${P}, ${Sort<T>}`; | |
declare class Sortable<T> { | |
sort<U extends string>(by: ValidateSort<T, U>): void; | |
} | |
declare class SomeEntity { | |
id: unknown; | |
name: unknown; | |
} | |
const sortable = new Sortable<SomeEntity>(); | |
sortable.sort('name'); | |
sortable.sort('name desc, id'); | |
sortable.sort('name desc, id, foo'); // invalid |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Validates parameter names in strings like "foo #{bar} #{baz}". Also provides | |
* autocomplete. | |
******************************************************************************/ | |
type ValidateParams<T extends string, K extends string> = | |
T extends `${infer U}#{${infer V}` | |
? V extends `${infer W}}${infer X}` | |
? W extends K | |
? `${U}#{${W}}${ValidateParams<X, K>}` | |
: `${U}#{${K}}${ValidateParams<X, K>}` | |
: `${U}#{${K}}` | |
: T; | |
declare class Log { | |
set<T>(params: T): { | |
message<U extends string>(msg: ValidateParams<U, keyof T & string>): void | |
} | |
} | |
declare const log: Log; | |
log.set({ | |
post_id: 123, | |
other_prop: 1 | |
}).message('test #{post_id} #{other_prop}'); | |
// type `p` or `o` to start auto-complete, <tab> to select |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/****************************************************************************** | |
* Better inference short-circuiting. Will always infer T, but will instantiate | |
* to "foo". | |
******************************************************************************/ | |
type FastInfer<T> = [T,T|1] extends [never,2] ? T : "foo"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Exact } from "./check"; // https://gist.github.com/webstrand/b0f79ef6ed37839d1432466fe8ddbc1a | |
type StringKeys<T> = | |
T extends T | |
? | `${keyof T & string}` | |
| &`${keyof T & number}` & `${bigint}` // "1e0" does not extend `${number}` & `${bigint}` | |
& ([T] extends [readonly unknown[]] ? 1e309 extends T["length"] ? unknown : keyof T : unknown) // disable `${number}` on finite tuples | |
: never; | |
// Cannot be unified with StringKeys because `${P}${StringKeys<T>}` breaks in certain edge cases. | |
type CandidateStringKeys<T, P extends string> = | |
T extends T | |
? | `${P}${keyof T & string}` | |
| & `${P}${keyof T & number}` & `${P}${bigint}` // "1e0" does not extend `${number}` & `${bigint}` | |
& ([T] extends [readonly unknown[]] ? 1e309 extends T["length"] ? unknown : /* is finite */ never : unknown) // disable `${number}` on finite tuples | |
: never; | |
type PathOf<T, K extends string, P extends string = ""> = | |
K extends `${infer U}.${infer V}` | |
? U extends StringKeys<T> | |
? T extends T ? PathOf<T[U & keyof T], V, `${P}${U}.`> : never | |
: CandidateStringKeys<T, P> | |
: ("" extends K ? unknown : K) extends StringKeys<T> // If K = "" we need to avoid matching `${string}` | |
? `${P}${K}` | |
: CandidateStringKeys<T, P>; | |
// TypeScript does not like "0" extends keyof { 0: ... }, so we have to write out own descend | |
type Descend<T, K extends string> = | |
T extends { [P in K]?: infer X } | |
? X | (T extends { [P in K]: any } ? never : undefined) | |
: T[K & keyof T] | undefined; // index access type of T is to support indexed types | |
type ResolvePath<T, K extends string> = | |
K extends `${infer U}.${infer V}` | |
? T extends T ? ResolvePath<Descend<T, U>, V>: never | |
: T extends T ? Descend<T, K> : never; | |
//////////////////////////////////////////////////////////////////////////////// | |
// How to use: | |
//////////////////////////////////////////////////////////////////////////////// | |
declare const o: Foo; | |
declare function select<O, K extends string>(o: O, x: PathOf<O, K>): ResolvePath<O, K>; | |
select(o, "y"); | |
select(o, "z"); | |
select(o, "0"); | |
select(o, "method"); | |
select(o, "w.n.1"); | |
select(o, "x.z.b.1.0"); | |
//////////////////////////////////////////////////////////////////////////////// | |
// Test cases: | |
//////////////////////////////////////////////////////////////////////////////// | |
type Foo = { | |
w: Bar; | |
x: Foo; | |
y?: Date; | |
z: Baz; | |
0: boolean; | |
method(): void; | |
}; | |
type Bar = { m: Bar.M } | { n: Bar.N } | { o: Bar.O }; | |
declare namespace Bar { | |
type M = { 0: string }; | |
type N = { 1: string }; | |
type O = "o"; | |
} | |
type HMT<T> = { | |
[P in keyof T]: P extends "prime" ? string : Foo | |
}; | |
type Baz = { | |
a: []; | |
b: [1,Foo,"3",4]; | |
c: [1,Foo,"3",4,...string[]]; | |
d: Foo[]; | |
e: { prime: never, [key: string]: Foo }; | |
f: { prime: string, [key: number]: Foo }; | |
g: HMT<{ prime: never, [key: string]: never }>; | |
}; | |
// root | |
Exact<PathOf<Foo, "x">, "x">(true); | |
Exact<PathOf<Foo, "y">, "y">(true); | |
Exact<PathOf<Foo, "z">, "z">(true); | |
Exact<PathOf<Foo, "0">, "0">(true); | |
Assignable<"1", PathOf<Foo, "">>(false); | |
Exact<PathOf<Foo, "method">, "method">(true); | |
Exact<PathOf<Foo, "">, "w" | "x" | "y" | "z" | "0" | "method">(true); | |
Exact(select(o, "w"))<Foo["w"]>(true); | |
Exact(select(o, "x"))<Foo["x"]>(true); | |
Exact(select(o, "y"))<Foo["y"]>(true); | |
Exact(select(o, "z"))<Foo["z"]>(true); | |
Exact(select(o, "0"))<Foo["0"]>(true); | |
// @ts-expect-error | |
select(o, "1") | |
Exact(select(o, "method"))<Foo["method"]>(true); | |
Exact(select(o, "" as "w" | "x" | "y" | "z"))<Foo["w"] | Foo["x"] | Foo["y"] | Foo["z"]>(true); | |
Exact(select(o, "" as "w" | "x" | "y" | "z" | "0" | "method"))<Foo["w"] | Foo["x"] | Foo["y"] | Foo["z"] | Foo["0"] | Foo["method"]>(true); | |
Exact(select(o, "" as `${keyof Foo}`))<Foo[keyof Foo]>(true); | |
// w | |
Exact<PathOf<Foo, "w.m">, "w.m">(true); | |
Exact<PathOf<Foo, "w.n">, "w.n">(true); | |
Exact<PathOf<Foo, "w.o">, "w.o">(true); | |
Exact<PathOf<Foo, "w.">, "w.m" | "w.n" | "w.o">(true); | |
Exact(select(o, "w.m"))<Bar.M | undefined>(true); | |
Exact(select(o, "w.n"))<Bar.N | undefined>(true); | |
Exact(select(o, "w.o"))<Bar.O | undefined>(true); | |
Exact(select(o, "" as "w.m" | "w.n"))<Bar.M | Bar.N | undefined>(true); | |
Exact(select(o, "" as "w.m" | "w.n" | "w.o"))<Bar.M | Bar.N | Bar.O | undefined>(true); | |
Exact(select(o, "" as `w.${StringKeys<Bar>}`))<Bar.M | Bar.N | Bar.O | undefined>(true); | |
// x | |
Exact<PathOf<Foo, "x.w">, "x.w">(true); | |
Exact<PathOf<Foo, "x.x">, "x.x">(true); | |
Exact<PathOf<Foo, "x.y">, "x.y">(true); | |
Exact<PathOf<Foo, "x.z">, "x.z">(true); | |
Exact<PathOf<Foo, "x.0">, "x.0">(true); | |
Assignable<"x.1", PathOf<Foo, "x.">>(false); | |
Exact<PathOf<Foo, "x.method">, "x.method">(true); | |
Exact<PathOf<Foo, "x.">, "x.w" | "x.x" | "x.y" | "x.z" | "x.0" | "x.method">(true); | |
Exact(select(o, "x.w"))<Foo["w"]>(true); | |
Exact(select(o, "x.x"))<Foo["x"]>(true); | |
Exact(select(o, "x.y"))<Foo["y"]>(true); | |
Exact(select(o, "x.z"))<Foo["z"]>(true); | |
Exact(select(o, "x.0"))<Foo["0"]>(true); | |
Exact(select(o, "x.method"))<Foo["method"]>(true); | |
Exact(select(o, "" as "x.w" | "x.x" | "x.y" | "x.z"))<Foo["w"] | Foo["x"] | Foo["y"] | Foo["z"]>(true); | |
Exact(select(o, "" as "x.w" | "x.x" | "x.y" | "x.z" | "x.0" | "x.method"))<Foo["w"] | Foo["x"] | Foo["y"] | Foo["z"] | Foo["0"] | Foo["method"]>(true); | |
Exact(select(o, "" as `x.${keyof Foo}`))<Foo[keyof Foo]>(true); | |
// y | |
Exact<PathOf<Foo, "y.">, `y.${keyof Date & string}`>(true); | |
Exact(select(o, "y.getHours"))<Date["getHours"] | undefined>(true); | |
Exact(select(o, "y.getMinutes"))<Date["getMinutes"] | undefined>(true); | |
Exact(select(o, "" as "y.getSeconds" | "y.getFullYear"))<Date["getSeconds"] | Date["getFullYear"] | undefined>(true); | |
Exact(select(o, "" as `y.${keyof Date & string}`))<Date[keyof Date & string] | undefined>(true); | |
// z | |
Exact<PathOf<Foo, "z.a">, "z.a">(true); | |
Exact<PathOf<Foo, "z.b">, "z.b">(true); | |
Exact<PathOf<Foo, "z.c">, "z.c">(true); | |
Exact<PathOf<Foo, "z.d">, "z.d">(true); | |
Exact<PathOf<Foo, "z.e">, "z.e">(true); | |
Exact<PathOf<Foo, "z.f">, "z.f">(true); | |
Exact<PathOf<Foo, "z.g">, "z.g">(true); | |
Exact<PathOf<Foo, "z.">, "z.a" | "z.b" | "z.c" | "z.d" | "z.e" | "z.f" | "z.g">(true); | |
Exact(select(o, "z.a"))<Baz["a"]>(true); | |
Exact(select(o, "z.b"))<Baz["b"]>(true); | |
Exact(select(o, "z.c"))<Baz["c"]>(true); | |
Exact(select(o, "z.d"))<Baz["d"]>(true); | |
Exact(select(o, "z.e"))<Baz["e"]>(true); | |
Exact(select(o, "z.f"))<Baz["f"]>(true); | |
Exact(select(o, "z.g"))<Baz["g"]>(true); | |
// @ts-expect-error | |
select(o, "z.zzzz"); | |
Exact(select(o, "" as "z.a" | "z.b"))<Baz["a"] | Baz["b"]>(true); | |
Exact(select(o, "" as "z.a" | "z.b" | "z.c" | "z.d" | "z.e" | "z.f" | "z.g"))<Baz["a"] | Baz["b"] | Baz["c"] | Baz["d"] | Baz["e"] | Baz["f"] | Baz["g"]>(true); | |
Exact(select(o, "" as `z.${keyof Baz}`))<Baz[keyof Baz]>(true); | |
// z.a | |
Assignable<"z.a.0", PathOf<Foo, "z.a.0">>(false); | |
Assignable<"z.a.1", PathOf<Foo, "z.a.1">>(false); | |
Assignable<"z.a.1e0", PathOf<Foo, "z.a.">>(false); | |
Exact<PathOf<Foo, "z.a.length">, `z.a.length`>(true); | |
Exact<PathOf<Foo, "z.a.slice">, `z.a.slice`>(true); | |
Exact<PathOf<Foo, "z.a.">, `z.a.${keyof Array<any> & string}`>(true); | |
// @ts-expect-error | |
select(o, "z.a.0"); | |
// @ts-expect-error | |
select(o, "z.a.1"); | |
// @ts-expect-error | |
select(o, "z.a.1e0"); | |
Exact(select(o, "z.a.length"))<Foo["z"]["a"]["length"]>(true); | |
Exact(select(o, "z.a.slice"))<Foo["z"]["a"]["slice"]>(true); | |
Exact(select(o, "" as "z.a.slice" | "z.a.length"))<Foo["z"]["a"]["slice"] | Foo["z"]["a"]["length"]>(true); | |
Exact(select(o, "" as `z.a.${keyof Array<any> & string}`))<Foo["z"]["a"][keyof Foo["z"]["a"] & string]>(true); | |
// z.b | |
Exact<PathOf<Foo, "z.b.0">, "z.b.0">(true); | |
Exact<PathOf<Foo, "z.b.1">, "z.b.1">(true); | |
Exact<PathOf<Foo, "z.b.2">, "z.b.2">(true); | |
Exact<PathOf<Foo, "z.b.3">, "z.b.3">(true); | |
Assignable<"z.b.4", PathOf<Foo, "z.b.4">>(false); | |
Assignable<"z.b.1e0", PathOf<Foo, "z.b.">>(false); | |
Exact<PathOf<Foo, "z.b.">, "z.b.0" | "z.b.1" | "z.b.2" | "z.b.3" | `z.b.${keyof Array<any> & string}`>(true); | |
Exact(select(o, "z.b.0"))<Foo["z"]["b"][0]>(true); | |
Exact(select(o, "z.b.1"))<Foo["z"]["b"][1]>(true); | |
Exact(select(o, "z.b.2"))<Foo["z"]["b"][2]>(true); | |
Exact(select(o, "z.b.3"))<Foo["z"]["b"][3]>(true); | |
// @ts-expect-error | |
select(o, "z.b.4"); | |
Exact(select(o, "z.b.length"))<Foo["z"]["b"]["length"]>(true); | |
Exact(select(o, "z.b.slice"))<Foo["z"]["b"]["slice"]>(true); | |
Exact(select(o, "" as "z.b.slice" | "z.b.length"))<Foo["z"]["b"]["slice"] | Foo["z"]["b"]["length"]>(true); | |
Exact(select(o, "" as `z.b.${keyof Foo["z"]["b"] & string}`))<Foo["z"]["b"][keyof Foo["z"]["b"] & string]>(true); | |
// z.c | |
Exact<PathOf<Foo, "z.c">, "z.c">(true); | |
Exact<PathOf<Foo, "z.c.0">, "z.c.0">(true); | |
Exact<PathOf<Foo, "z.c.1">, "z.c.1">(true); | |
Exact<PathOf<Foo, "z.c.2">, "z.c.2">(true); | |
Exact<PathOf<Foo, "z.c.3">, "z.c.3">(true); | |
Exact<PathOf<Foo, "z.c.99999">, "z.c.99999">(true); | |
Assignable<"z.c.1e0", PathOf<Foo, "z.c.">>(false); | |
Exact<PathOf<Foo, "z.c.">,"z.c.0" | "z.c.1" | "z.c.2" | "z.c.3" | `z.c.${keyof Array<any> & string}` | `z.c.${number}` & `z.c.${bigint}`>(true); | |
Exact(select(o, "z.c.0"))<Foo["z"]["c"][0]>(true); | |
Exact(select(o, "z.c.1"))<Foo["z"]["c"][1]>(true); | |
Exact(select(o, "z.c.2"))<Foo["z"]["c"][2]>(true); | |
Exact(select(o, "z.c.3"))<Foo["z"]["c"][3]>(true); | |
Exact(select(o, "z.c.4"))<Foo["z"]["c"][number | "0" | "1" | "2" | "3"] | undefined>(true); // less than ideal, but sufficient. | |
Exact(select(o, "z.c.length"))<Foo["z"]["c"]["length"]>(true); | |
Exact(select(o, "z.c.slice"))<Foo["z"]["c"]["slice"]>(true); | |
Exact(select(o, "" as "z.c.slice" | "z.c.length"))<Foo["z"]["c"]["slice"] | Foo["z"]["c"]["length"]>(true); | |
Exact(select(o, "" as `z.c.${keyof Foo["z"]["c"] & string}`))<Foo["z"]["c"][keyof Foo["z"]["c"] & string]>(true); | |
// z.d | |
Exact<PathOf<Foo, "z.d">, "z.d">(true); | |
Exact<PathOf<Foo, "z.d.0">, "z.d.0">(true); | |
Exact<PathOf<Foo, "z.d.1">, "z.d.1">(true); | |
Exact<PathOf<Foo, "z.d.99999">, "z.d.99999">(true); | |
Assignable<"z.d.1e0", PathOf<Foo, "z.d.">>(false); | |
Exact<PathOf<Foo, "z.d.">, `z.d.${keyof Array<any> & string}` | `z.d.${number}` & `z.d.${bigint}`>(true); | |
Exact(select(o, "z.d.0"))<Foo["z"]["d"][0] | undefined>(true); | |
Exact(select(o, "z.d.1"))<Foo["z"]["d"][1] | undefined>(true); | |
Exact(select(o, "z.d.2"))<Foo["z"]["d"][2] | undefined>(true); | |
Exact(select(o, "z.d.99999"))<Foo["z"]["d"][99999] | undefined>(true); | |
Exact(select(o, "z.d.length"))<Foo["z"]["d"]["length"]>(true); | |
Exact(select(o, "z.d.slice"))<Foo["z"]["d"]["slice"]>(true); | |
Exact(select(o, "" as "z.d.slice" | "z.d.length"))<Foo["z"]["d"]["slice"] | Foo["z"]["d"]["length"]>(true); | |
Exact(select(o, "" as `z.d.${keyof Foo["z"]["d"] & string}`))<Foo["z"]["d"][keyof Foo["z"]["d"] & string]>(true); | |
// z.e | |
Exact<PathOf<Foo, "z.e">, "z.e">(true); | |
Exact<PathOf<Foo, "z.e.prime">, "z.e.prime">(true); | |
Exact<PathOf<Foo, "z.e.0">, "z.e.0">(true); | |
Exact<PathOf<Foo, "z.e.1">, "z.e.1">(true); | |
Exact<PathOf<Foo, "z.e.99999">, "z.e.99999">(true); | |
Exact<PathOf<Foo, "z.e.foo">, "z.e.foo">(true); | |
Exact<PathOf<Foo, "z.e.1e0">, "z.e.1e0">(true); | |
Exact<PathOf<Foo, "z.e.">, `z.e.${string}` | (`z.e.${number}` & `z.e.${bigint}`)>(true); | |
Exact(select(o, "z.e.prime"), "" as never, true); | |
Exact(select(o, "z.e.0"))<Foo["z"]["e"][0] | undefined>(true); | |
Exact(select(o, "z.e.1"))<Foo["z"]["e"][1] | undefined>(true); | |
Exact(select(o, "z.e.2"))<Foo["z"]["e"][2] | undefined>(true); | |
Exact(select(o, "z.e.1e1"))<Foo["z"]["e"][1e1] | undefined>(true); | |
Exact(select(o, "z.e.99999"))<Foo["z"]["e"][99999] | undefined>(true); | |
Exact(select(o, "z.e.foo"))<Foo["z"]["e"]["foo"] | undefined>(true); | |
Exact(select(o, "z.e.1e0"))<Foo["z"]["e"]["1e0"] | undefined>(true); | |
// broken until 4.3 | |
// Exact(select(o, "" as `z.e.${keyof Foo["z"]["e"] & string}`))<Foo["z"]["e"][keyof Foo["z"]["e"] & string]>(true); | |
// z.f | |
Exact<PathOf<Foo, "z.f">, "z.f">(true); | |
Exact<PathOf<Foo, "z.f.prime">, "z.f.prime">(true); | |
Exact<PathOf<Foo, "z.f.0">, "z.f.0">(true); | |
Exact<PathOf<Foo, "z.f.1">, "z.f.1">(true); | |
Exact<PathOf<Foo, "z.f.99999">, "z.f.99999">(true); | |
Assignable<"z.f.foo", PathOf<Foo, "z.f.foo">>(false); | |
Exact<PathOf<Foo, "z.f.">, `z.f.prime` | (`z.f.${number}` & `z.f.${bigint}`)>(true); | |
Exact(select(o, "z.f.prime"))<Foo["z"]["f"]["prime"]>(true); | |
Exact(select(o, "z.f.prime.length"))<Foo["z"]["f"]["prime"]["length"]>(true); | |
Exact(select(o, "z.f.0"))<Foo["z"]["f"][0] | undefined>(true); | |
Exact(select(o, "z.f.1"))<Foo["z"]["f"][1] | undefined>(true); | |
Exact(select(o, "z.f.2"))<Foo["z"]["f"][2] | undefined>(true); | |
Exact(select(o, "z.f.99999"))<Foo["z"]["f"][99999] | undefined>(true); | |
// @ts-expect-error | |
select(o, "z.f.foo"); | |
Exact(select(o, "" as `z.f.${keyof Foo["z"]["f"] & string}`))<Foo["z"]["f"][keyof Foo["z"]["f"] & string]>(true); | |
// z.g | |
Exact<PathOf<Foo, "z.g">, "z.g">(true); | |
Exact<PathOf<Foo, "z.g.prime">, "z.g.prime">(true); | |
Exact<PathOf<Foo, "z.g.prime.length">, "z.g.prime.length">(true); | |
Exact<PathOf<Foo, "z.g.0">, "z.g.0">(true); | |
Exact<PathOf<Foo, "z.g.1">, "z.g.1">(true); | |
Exact<PathOf<Foo, "z.g.99999">, "z.g.99999">(true); | |
Exact<PathOf<Foo, "z.g.foo">, "z.g.foo">(true); | |
Exact<PathOf<Foo, "z.g.1e0">, "z.g.1e0">(true); | |
Exact<PathOf<Foo, "z.g.">, `z.g.${string}` | (`z.g.${number}` & `z.g.${bigint}`)>(true); | |
Exact(select(o, "z.g.prime"))<Foo["z"]["g"]["prime"]>(true); | |
Exact(select(o, "z.g.prime.length"))<Foo["z"]["g"]["prime"]["length"]>(true); | |
Exact(select(o, "z.g.0"))<Foo["z"]["g"][0] | undefined>(true); | |
Exact(select(o, "z.g.1"))<Foo["z"]["g"][1] | undefined>(true); | |
Exact(select(o, "z.g.2"))<Foo["z"]["g"][2] | undefined>(true); | |
Exact(select(o, "z.g.1e1"))<Foo["z"]["g"][1e1] | undefined>(true); | |
Exact(select(o, "z.g.99999"))<Foo["z"]["g"][99999] | undefined>(true); | |
Exact(select(o, "z.g.foo"))<Foo["z"]["g"]["foo"] | undefined>(true); | |
Exact(select(o, "z.g.1e0"))<Foo["z"]["g"]["1e0"] | undefined>(true); | |
// broken until 4.3 | |
// Exact(select(o, "" as `z.g.${keyof Foo["z"]["g"] & string}`))<Foo["z"]["g"][keyof Foo["z"]["g"] & string]>(true); | |
// z.a.* | |
// has no elements | |
// z.b.* | |
Exact<PathOf<Foo, "z.b.0">, "z.b.0">(true); | |
Exact<PathOf<Foo, "z.b.0.toString">, `z.b.0.toString`>(true); | |
Assignable<`z.b.0.foo`, PathOf<Foo, "z.b.0.foo">>(false); | |
Exact<PathOf<Foo, "z.b.0.">, `z.b.0.${keyof Number & string}`>(true); | |
Exact<PathOf<Foo, "z.b.1">, "z.b.1">(true); | |
Exact<PathOf<Foo, "z.b.1.x.y">, "z.b.1.x.y">(true); | |
Exact<PathOf<Foo, "z.b.1.">, "z.b.1.w" | "z.b.1.x" | "z.b.1.y" | "z.b.1.z" | "z.b.1.0" | "z.b.1.method">(true); | |
Exact<PathOf<Foo, "z.b.2">, "z.b.2">(true); | |
Exact<PathOf<Foo, "z.b.2.trim">, "z.b.2.trim">(true); | |
Exact<PathOf<Foo, "z.b.2.">, `z.b.2.${keyof String & string}` | `z.b.2.${number}` & `z.b.2.${bigint}`>(true); | |
Assignable<"z.b.4.toString", PathOf<Foo, "z.b.4.toString">>(false); | |
// z.c.* | |
Exact<PathOf<Foo, "z.c.0">, "z.c.0">(true); | |
Exact<PathOf<Foo, "z.c.0.toString">, "z.c.0.toString">(true); | |
Exact<PathOf<Foo, "z.c.0.">, `z.c.0.${keyof Number & string}`>(true); | |
Exact<PathOf<Foo, "z.c.1">, "z.c.1">(true); | |
Exact<PathOf<Foo, "z.c.1.x.y">, "z.c.1.x.y">(true); | |
Exact<PathOf<Foo, "z.c.1.">, "z.c.1.w" | "z.c.1.x" | "z.c.1.y" | "z.c.1.z" | "z.c.1.0" | "z.c.1.method">(true); | |
Exact<PathOf<Foo, "z.c.2">, "z.c.2">(true); | |
Exact<PathOf<Foo, "z.c.2.trim">, "z.c.2.trim">(true); | |
Exact<PathOf<Foo, "z.c.2.">, `z.c.2.${keyof String & string}` | `z.c.2.${number}` & `z.c.2.${bigint}`>(true); | |
Exact<PathOf<Foo, "z.c.4">, "z.c.4">(true); | |
Exact<PathOf<Foo, "z.c.4.length">, "z.c.4.length">(true); | |
ExactL<PathOf<Foo, "z.c.4.">, | |
| ("z.c.4.w" | "z.c.4.x" | "z.c.4.y" | "z.c.4.z" | "z.c.4.0" | "z.c.4.method") | |
| `z.c.4.${keyof Number & string}` | |
| `z.c.4.${keyof String & string}` | |
| `z.c.4.${number}` & `z.c.4.${bigint}` | |
>(true); // less than ideal, but sufficient. | |
// z.d.* | |
Exact<PathOf<Foo, "z.d.0">, "z.d.0">(true); | |
Assignable<"z.d.0.foo.y", PathOf<Foo, "z.d.0.foo.y">>(false); | |
Exact<PathOf<Foo, "z.d.0.x.y">, "z.d.0.x.y">(true); | |
Exact<PathOf<Foo, "z.d.0.">, "z.d.0.w" | "z.d.0.x" | "z.d.0.y" | "z.d.0.z" | "z.d.0.0" | "z.d.0.method">(true); | |
Exact<PathOf<Foo, "z.d.1">, "z.d.1">(true); | |
Exact<PathOf<Foo, "z.d.1.x.y">, "z.d.1.x.y">(true); | |
Exact<PathOf<Foo, "z.d.1.">, "z.d.1.w" | "z.d.1.x" | "z.d.1.y" | "z.d.1.z" | "z.d.1.0" | "z.d.1.method">(true); | |
Exact<PathOf<Foo, "z.d.99999">, "z.d.99999">(true); | |
Exact<PathOf<Foo, "z.d.99999.x.y">, "z.d.99999.x.y">(true); | |
Exact<PathOf<Foo, "z.d.99999.">, "z.d.99999.w" | "z.d.99999.x" | "z.d.99999.y" | "z.d.99999.z" | "z.d.99999.0" | "z.d.99999.method">(true); | |
// z.e.* | |
Exact<PathOf<Foo, "z.e.prime">, "z.e.prime">(true); | |
Assignable<"z.e.prime.length", PathOf<Foo, "z.e.prime.length">>(false); | |
Exact<PathOf<Foo, "z.e.prime.">, never>(true); // booooo | |
Exact<PathOf<Foo, "z.e.0">, "z.e.0">(true); | |
Exact<PathOf<Foo, "z.e.0.x.y">, "z.e.0.x.y">(true); | |
Exact<PathOf<Foo, "z.e.0.">, "z.e.0.w" | "z.e.0.x" | "z.e.0.y" | "z.e.0.z" | "z.e.0.0" | "z.e.0.method">(true); | |
Exact<PathOf<Foo, "z.e.1e1">, "z.e.1e1">(true); | |
Exact<PathOf<Foo, "z.e.1e1.x.y">, "z.e.1e1.x.y">(true); | |
Exact<PathOf<Foo, "z.e.1e1.">,"z.e.1e1.w" | "z.e.1e1.x" | "z.e.1e1.y" | "z.e.1e1.z" | "z.e.1e1.0" | "z.e.1e1.method">(true); | |
Exact<PathOf<Foo, "z.e.foo">, "z.e.foo">(true); | |
Exact<PathOf<Foo, "z.e.foo.x.y">, "z.e.foo.x.y">(true); | |
Exact<PathOf<Foo, "z.e.foo.">, "z.e.foo.w" | "z.e.foo.x" | "z.e.foo.y" | "z.e.foo.z" | "z.e.foo.0" | "z.e.foo.method">(true); | |
// z.f.* | |
Exact<PathOf<Foo, "z.f.prime">, "z.f.prime">(true); | |
Exact<PathOf<Foo, "z.f.prime.length">, "z.f.prime.length">(true); | |
Assignable<"z.f.prime.foo", PathOf<Foo, "z.f.prime.foo">>(false); | |
Exact<PathOf<Foo, "z.f.prime.">, `z.f.prime.${keyof string & string}` | `z.f.prime.${number}` & `z.f.prime.${bigint}`>(true); | |
Exact<PathOf<Foo, "z.f.0">, "z.f.0">(true); | |
Exact<PathOf<Foo, "z.f.0.x.y">, "z.f.0.x.y">(true); | |
Assignable<"z.f.0.foo", PathOf<Foo, "z.f.0.foo">>(false); | |
ExactL<PathOf<Foo, "z.f.0.">, "z.f.0.w" | "z.f.0.x" | "z.f.0.y" | "z.f.0.z" | "z.f.0.0" | "z.f.0.method">(true); | |
// z.g.* | |
Exact<PathOf<Foo, "z.g.prime">, "z.g.prime">(true); | |
Exact<PathOf<Foo, "z.g.prime.length">, "z.g.prime.length">(true); | |
Assignable<"z.g.prime.foo", PathOf<Foo, "z.g.prime.foo">>(false); | |
Exact<PathOf<Foo, "z.f.prime.">, `z.f.prime.${keyof string & string}` | `z.f.prime.${number}` & `z.f.prime.${bigint}`>(true); | |
Exact<PathOf<Foo, "z.g.foo">, "z.g.foo">(true); | |
Exact<PathOf<Foo, "z.g.foo.x.y">, "z.g.foo.x.y">(true); | |
Assignable<"z.g.foo.foo", PathOf<Foo, "z.g.foo.foo">>(false); | |
Exact<PathOf<Foo, "z.g.foo.">, "z.g.foo.w" | "z.g.foo.x" | "z.g.foo.y" | "z.g.foo.z" | "z.g.foo.0" | "z.g.foo.method">(true) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A type validator that removes excess properties to prevent excess properties in a | |
// literal assigned to a generic. Does _not_ work for non-object literal assignment, | |
// since the excess properties are simply removed. | |
type ExactLiteral<Basis, T> = T extends Function ? T : { | |
[P in keyof T as P extends keyof Basis ? P : never]: Exact<Basis[P & keyof Basis], T[P]> | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type PseudoDateString = `${bigint}${bigint}${bigint}${number}-${bigint}${bigint}-${bigint}${bigint}`; | |
declare const DateString: unique symbol; | |
type DateString = PseudoDateString & { [DateString]: true }; | |
function validateDateString<T extends PseudoDateString>(s: ValidateDateString<T>): s is typeof s & DateString { | |
return true | |
} | |
const x = "2020-04-14"; | |
if(validateDateString(x)) { | |
const y: DateString = x // perfect | |
} | |
// IMPLEMENTATION: | |
type DateDigits<Length extends 30 | 31> = { | |
"0": "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9", | |
"1": "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9", | |
"2": "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9", | |
"3": 31 extends Length ? "0" | "1" : "0", | |
}; | |
type MonthDateDigits = { | |
"0": { | |
"1": DateDigits<31>, | |
"2": DateDigits<30>, // historical | |
"3": DateDigits<31>, | |
"4": DateDigits<30>, | |
"5": DateDigits<31>, | |
"6": DateDigits<30>, | |
"7": DateDigits<31>, | |
"8": DateDigits<31>, | |
"9": DateDigits<30>, | |
}, | |
"1": { | |
"0": DateDigits<31>, | |
"1": DateDigits<30>, | |
"2": DateDigits<31>, | |
}, | |
}; | |
type ValidateDateString<T0 extends string> = | |
T0 extends `${infer Y extends `${bigint}${bigint}${bigint}${number}`}-${infer M1 extends `${bigint}`}${infer M2 extends `${bigint}`}-${infer D1 extends `${bigint}`}${infer D2 extends `${bigint}`}` | |
? Y extends `0${infer YY extends `${bigint}${bigint}${bigint}${number}`}` ? `${YY}-${M1}${M2}-${D1}${D2}` | |
: M1 extends keyof MonthDateDigits | |
? M2 extends keyof MonthDateDigits[M1] | |
? D1 extends keyof MonthDateDigits[M1][M2] | |
? D2 extends MonthDateDigits[M1][M2][D1] | |
? T0 | |
: `${Y}-${M1}${M2}-${D1}${MonthDateDigits[M1][M2][D1] & string}` | |
: `${Y}-${M1}${M2}-${keyof MonthDateDigits[M1][M2] & string}${D2}` | |
: `${Y}-${M1}${keyof MonthDateDigits[M1] & string}-${D1}${D2}` | |
: `${Y}-${keyof MonthDateDigits}${M2}-${D1}${D2}` | |
: PseudoDateString; | |
// TESTING: | |
declare function ok<T extends string>(x: ValidateDateString<T>): void; | |
declare function err<T extends string>(x: ValidateDateString<T> extends T ? never : T): void; | |
ok("0000-01-01"); | |
ok("0001-01-01"); | |
ok("0001-12-31"); | |
ok("9999-12-31"); | |
ok("99999999-12-31"); | |
ok("10000001-01-01"); | |
// we don't check for incorrect month lengths | |
ok("1712-02-30"); // this is real | |
ok("2001-02-29"); // not a leap year | |
// no invalid days | |
err("0000-01-00"); | |
err("0000-00-01"); | |
err("0000-13-01"); | |
err("0000-01-32"); | |
err("00000-01-01"); | |
err("00001-01-01"); | |
err("09999-01-01"); | |
err("099999999-12-31"); | |
err("010000001-01-01"); | |
// no ${numbers} | |
err("099999999.1-12-31"); | |
err("099999999e+1-12-31"); | |
// no excess zero-padding | |
err("0000-1-01"); | |
err("0000-100-1"); | |
err("0000-010-1"); | |
err("0000-001-1"); | |
err("0000-01-1"); | |
err("0000-01-100"); | |
err("0000-01-010"); | |
err("0000-01-001"); | |
// no incorrect days | |
err("0000-02-31"); | |
err("0000-11-31"); | |
err("0000-06-31"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** Turn a union into an intersection */ | |
type U2I<U> = | |
(U extends U ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never | |
/** Unique type used to halt recursion in U2T */ | |
declare const Stop: unique symbol; | |
/** Unique type used to halt recursion in U2T */ | |
type Stop = typeof Stop; | |
/** | |
* Convert a union into a tuple. | |
*/ | |
type U2T<U> = _U2T< | |
& (() => Stop) // stop-word | |
& U2I<U extends U ? () => U : never> | |
>; | |
type _U2T<B extends () => unknown, A extends unknown[] = [], E = B extends (() => infer C) ? C : never> = E extends Stop ? A : _U2T<(() => E) & B, [E, ...A]>; | |
/** | |
* A validator which picks the first type that matches T out of the union U. This can sometimes be helpful to exclude excess properties introduced by other members of the union. | |
*/ | |
type OneOf<T extends U, U> = [T] extends [never] ? T : _OneOf<T, U>; | |
type _OneOf<T extends U, U, Candidates extends U[] = U2T<U>> = Candidates extends [infer C, ...(infer RemainingCandidates extends U[])] ? [T] extends [C] ? C : _OneOf<T, U, RemainingCandidates> : "no matches found!?"; | |
type JF2 = `JF2:${string}`; | |
type Types = | |
| { url: string; fetch: (url: string) => Promise<JF2> } // More complex types must come before simpler ones. | |
| { url: string } | |
| { jf2: JF2 } | |
| undefined; | |
declare function component<T extends Types>(props: OneOf<T, Types>): void | |
component({url: "x", jf2: `JF2:x`}) // error | |
component({url: "x", fetch: () => {throw "y"}}) | |
component({url: "x" }) | |
component(undefined) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment