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