-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Optimize and rationalize type-checking for appropriate cases of generic functions with overloads. #47580
Comments
The intent is to have a generic type parameter extend one of the specified types, but have the type parameter not be a union type itself. So for example |
Definitely
This is really a death knell and why we've rejected other sorts of proposals: It's combinatorially explosive to do this, and the explosion occurs in non-pathological code. If you wrote something like function fn1<T extends "A" | "B" | "C">(uno: T) {
function fn2<T extends "A" | "B" | "C">(dos: T) {
function fn3<T extends "A" | "B" | "C">(tres: T) {
return [uno, dos, tres] as const;
}
}
} then the correctly-computed result here involves typechecking the |
Thank you for your reply. I think the onus is on me to explain where I see the equivalence. Observe the following code and it's compilation result:
That shows functional equivalence between Just like Next consider implementation. I strongly suspect the most efficient way to implement So you are right, but I am not exactly wrong to make the functional and implementation analogy. |
I have added to the requirements for being a targeted function
That get's rid of embedded definitions and narrows the range of potential candidates further. Yet it is still sufficient to solve many pernicious problems, e.g., @emilioplatzer 's example code shown in the proposal. Also, I have to point out that your example does not contain generic functions with overloads. The processing does not change in cases of generic functions WITHOUT overloads. Or to put it another way - in the case of the case of a generic functions WITHOUT overloads the default overload is itself - and the proposed processing of that would be identical to the current processing. |
To prevent combinatorial complexity, the following control is imposed -
(Note: The proposal currently has different version of "2." - requiring the generic function be a module top level function, but that is unnecessarily strict.) For an example of how complexity is avoided, consider the following code -
By the above rule number 2, the inner generic functions are not processed per overload.
That wouldn't be much help to the coder in terms of reducing errors, but it also wouldn't
which would type-check the following virtual functions:
The coder could then incorporate the helper function into their code, e.g.:
or even
|
I'm going to close this because there is sufficient overlap with #27808, but it is more simple. |
Suggestion
Optimize and rationalize type-checking for appropriate cases of generic functions with overloads.
🔍 Search Terms
is:issue generic function with overloads optimize type-check
is:issue generic function overloads narrowed type-check
#27808 "extends oneof" generic constraint; allows for narrowing type parameters
List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.
✅ Viability Checklist
My suggestion meets these guidelines:
⭐ Suggestion
Description
A description of the proposed processing in words:
Given a module top level generic function with overloads, the proposed checking result is grossly equivalent to type checking the body of the implementation attached to the declaration of the overload, for each overload, and taking the union of the errors.
A more formal description is given below in More formal description. An illustrative example is given below in Motivating Example.
Targeted Problem:
The driving motivation for this proposal is to allow code in generics function which rely heavily on operator overloading to be written without incurring (seemingly) nuisance type checking errors. (See the code in Use Cases).
Feasability
Two concerns:
the correctness is preserved. There could be fewer errors then in the current type checking behavior,
because the current type-checking behavior includes errros about combinations of types which do not exist
in any of the overloads - but those are false errors.
More formal description
Suppose the overloads have signature types
F_1, ...F_n, ...F_N
whereThen the overloads an implementation can be written as
Then the virtual n_th type-checking pass processes the virtual function
which is the declaration of the n_th overload with the body of the implementation.
The union of warning/errors over all overload passes is the result, with the line numbers
in the implementation body.
📃 Motivating Example
As a toy example, consider an
add
function that overloadsnumber
,bigint
andstring
,but is restricted to adding like types only.
Create defined types to ensure uniform parameter names.
Define overloads.
Define implementation.
proposal is to remove that error.
Thanks to the overloads the call interface type checking is already working correctly.
(This is both the current and desired behavior).
Now we define the proposed type checker behavior in simple terms.
We want each pass (one per overload) to behave as though it were type checking
a function with the signature of the overload and the body of the implementation,
just like the following -
and take the total of all error (in this case, none).
Just to emphasize, this is defining a result - not the mechanics for achieving the result.
A case analysis for a body with
if (typeof ....) ...
conditional branches, is discussed in more detail below.*Would more passes necessarily mean more type-checking time?
Suppose that a single type check pass over a generic function takes time porportional to the number of candidate parameter combinations the type-checker must juggle. Then by filtering out the impossible combinations via individual passes,
the total time could be less, even with more passes.
Use Cases
@emilioplatzer shared the following production code in a comment on another issue, and on playground.
There are several places where type-check errors must be ignored.
If this proposal were enabled, the above code could be rewritten as
and it would pass the type checker without errors.
There is a little more work than the original code to define the types and overloads
at the top, but that also has the advantage of forcing the correct type checking on the calls
to
checkdigit
, which the original code did not have.Other
Relationship to issue #27808
Issue #27808 is titled '"extends oneof" generic constraint; allows for narrowing type parameters'.
My reading of what #27808 intends to achieve is to autogenerate the overloads, possibly virtual overloads,
to enable interface type checking. As such, it is not a requirement for this proposal,
which can use manually written call overloads. Neither is this proposal required for #27808,
because the stated goal there is only to automate interface type checking.
However, both proposals could be mutually beneficial to each other.
Type-checking behavior with conditional blocks
In the implementation body shown above
we would have to use
@ts-ignore
or@ts-expect-error
in order to pass type checking.Suppose instead we prioritize type checking over operator-overloading the
+
, andwrite out all the cases, as follows:
Even then there is a still stubborn error on the returns requiring
as ReturnType<F>
to suppress an error.Additionally, the flow control
doesn't recognise the reduced number of possible cases implied by the overloads,
so it calculates a possible
undefined
return value.Here we solve that by adding
throw never
.How does the implementation fare under the proposed type-checking per-overload?
return r
statement no longer error, so coercion there is not required.throw "never"
is still there. Analysis:The text was updated successfully, but these errors were encountered: