• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import { Debug, DeprecationOptions, hasProperty, UnionToIntersection } from "./_namespaces/ts";
2
3// The following are deprecations for the public API. Deprecated exports are removed from the compiler itself
4// and compatible implementations are added here, along with an appropriate deprecation warning using
5// the `@deprecated` JSDoc tag as well as the `Debug.deprecate` API.
6//
7// Deprecations fall into one of three categories:
8//
9// - "soft" - Soft deprecations are indicated with the `@deprecated` JSDoc Tag.
10// - "warn" - Warning deprecations are indicated with the `@deprecated` JSDoc Tag and a diagnostic message (assuming a compatible host).
11// - "error" - Error deprecations are either indicated with the `@deprecated` JSDoc tag and will throw a `TypeError` when invoked, or removed from the API entirely.
12//
13// Once we have determined enough time has passed after a deprecation has been marked as `"warn"` or `"error"`, it will be removed from the public API.
14
15/**
16 * Defines a list of overloads by ordinal
17 *
18 * @internal
19 */
20export type OverloadDefinitions = { readonly [P in number]: (...args: any[]) => any; };
21
22/** A function that returns the ordinal of the overload that matches the provided arguments */
23type OverloadBinder<T extends OverloadDefinitions> = (args: OverloadParameters<T>) => OverloadKeys<T> | undefined;
24
25/**
26 * Extracts the ordinals from an set of overload definitions.
27 *
28 * @internal
29 */
30export type OverloadKeys<T extends OverloadDefinitions> = Extract<keyof T, number>;
31
32/**
33 * Extracts a union of the potential parameter lists for each overload.
34 *
35 * @internal
36 */
37export type OverloadParameters<T extends OverloadDefinitions> = Parameters<{ [P in OverloadKeys<T>]: T[P]; }[OverloadKeys<T>]>;
38
39// NOTE: the following doesn't work in TS 4.4 (the current LKG in main), so we have to use UnionToIntersection for now
40// type OverloadFunction<T extends OverloadDefinitions, R extends ((...args: any[]) => any)[] = [], O = unknown> =
41//     R["length"] extends keyof T ? OverloadFunction<T, [...R, T[R["length"]]], O & T[R["length"]]> :
42//     unknown extends O ? never : O;
43/**
44 * Constructs an intersection of each overload in a set of overload definitions.
45 *
46 * @internal
47 */
48export type OverloadFunction<T extends OverloadDefinitions> = UnionToIntersection<T[keyof T]>;
49
50/**
51 * Maps each ordinal in a set of overload definitions to a function that can be used to bind its arguments.
52 *
53 * @internal
54 */
55export type OverloadBinders<T extends OverloadDefinitions> = { [P in OverloadKeys<T>]: (args: OverloadParameters<T>) => boolean | undefined; };
56
57/**
58 * Defines deprecations for specific overloads by ordinal.
59 *
60 * @internal
61 */
62export type OverloadDeprecations<T extends OverloadDefinitions> = { [P in OverloadKeys<T>]?: DeprecationOptions; };
63
64/** @internal */
65export function createOverload<T extends OverloadDefinitions>(name: string, overloads: T, binder: OverloadBinders<T>, deprecations?: OverloadDeprecations<T>) {
66    Object.defineProperty(call, "name", { ...Object.getOwnPropertyDescriptor(call, "name"), value: name });
67
68    if (deprecations) {
69        for (const key of Object.keys(deprecations)) {
70            const index = +key as (keyof T & number);
71            if (!isNaN(index) && hasProperty(overloads, `${index}`)) {
72                overloads[index] = Debug.deprecate(overloads[index], { ...deprecations[index], name });
73            }
74        }
75    }
76
77    const bind = createBinder(overloads, binder);
78    return call as OverloadFunction<T>;
79
80    function call(...args: OverloadParameters<T>) {
81        const index = bind(args);
82        const fn = index !== undefined ? overloads[index] : undefined;
83        if (typeof fn === "function") {
84            return fn(...args);
85        }
86        throw new TypeError("Invalid arguments");
87    }
88}
89
90function createBinder<T extends OverloadDefinitions>(overloads: T, binder: OverloadBinders<T>): OverloadBinder<T> {
91    return args => {
92        for (let i = 0; hasProperty(overloads, `${i}`) && hasProperty(binder, `${i}`); i++) {
93            const fn = binder[i];
94            if (fn(args)) {
95                return i as OverloadKeys<T>;
96            }
97        }
98    };
99}
100
101/** @internal */
102export interface OverloadBuilder {
103    overload<T extends OverloadDefinitions>(overloads: T): BindableOverloadBuilder<T>;
104}
105
106/** @internal */
107export interface BindableOverloadBuilder<T extends OverloadDefinitions> {
108    bind(binder: OverloadBinders<T>): BoundOverloadBuilder<T>;
109}
110
111/** @internal */
112export interface FinishableOverloadBuilder<T extends OverloadDefinitions> {
113    finish(): OverloadFunction<T>;
114}
115
116/** @internal */
117export interface BoundOverloadBuilder<T extends OverloadDefinitions> extends FinishableOverloadBuilder<T> {
118    deprecate(deprecations: OverloadDeprecations<T>): FinishableOverloadBuilder<T>;
119}
120
121// NOTE: We only use this "builder" because we don't infer correctly when calling `createOverload` directly in < TS 4.7,
122//       but lib is currently at TS 4.4. We can switch to directly calling `createOverload` when we update LKG in main.
123
124/** @internal */
125export function buildOverload(name: string): OverloadBuilder {
126    return {
127        overload: overloads => ({
128            bind: binder => ({
129                finish: () => createOverload(name, overloads, binder),
130                deprecate: deprecations => ({
131                    finish: () => createOverload(name, overloads, binder, deprecations)
132                })
133            })
134        })
135    };
136}