-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Reject functions with not enough parameters on strict mode #17868
Comments
So you would be okay with all your callbacks to function squareAll(nums: number[]) {
return nums.map((v, _index, _arr) => v ** 2);
} |
I understand it's inconvenient for some native functions, but if that's the price to pay for detecting mistakes, I'm absolutely OK with it. The trigger of this request was indeed a hidden mistake that took a while to catch up due to TypeScript being too tolerant. Besides, for custom functions, you can always define optional parameters, which will be very useful in callbacks. In fact, perhaps this is the way all callbacks of native functions accepting them should be declared. |
On top of what @inad9300 has said, you're not obligated to rewrite all your |
This isn't what an optional parameter means! If strictly enforced (which we don't because everyone gets it wrong when writing declaration files), an optional parameter in a callback position means "I (the caller) might not provide this parameter", not "You (the callee) are free to ignore this parameter".
Of course. The line of discussion here is about whether anyone would turn this flag on in the first place if it caused massive headaches for all built-in array functions. Our hypothesis is "no". |
In my case the answer would be yes. I would turn this flag on to ensure my callbacks are correctly defined and called instead of silently failing. |
Can you show an example where a callback was "silently failing" ? @inad9300 same question - seeing the actual bug you had would be very informative |
This is not best place to pose questions, but since it is relevant for the discussion... Given the following code:
What do the optional parameters mean here? As I see it, they should mean that I, the caller of Edit Added missing name for the callback argument. |
What you've typed represents a function that acts like this function each<T>(list: T[], cb: (item?: T, index?: number, listReference?: T[]) => void) {
// ...
cb();
cb(list[0]);
cb(list[1], 1);
// etc
} |
OK. It's a bit confusing, but I think I understand. Now, what does it mean for the provider of the callback to declare its parameters as optional? It's completely useless and ignored, as I can see quickly in the playground.
|
The first one should be illegal given the definition of The second one is a "correct" callback for a declared callback type which has optional parameters. |
A function has an interface and a function with fewer arguments can implement that same interface so long as the types of the arguments it provides are assignable type F = (key: string, value: string) => object; An example implementation of const f: F = (key, value) => ({[key]: value}); Another implementation of const g: F = key => f(key, key); Why is this a problem? Trying to enforce that a provided callback binds each possible argument feels like trying to depend on the implementation of an interface. |
Thanks for the clarification, @RyanCavanaugh. In my opinion, the separation between normal and strict modes should allow to overcome this kind of history-provoked limitations. That bug should definitely be fixed in strict mode. @aluanhaddad, for me, it is precisely when I see functions as interfaces when it makes the less sense. If you accept |
I don't understand the claim that Again I'd like to see the root-cause bugs here to understand how you had a bug caused by wrong function arity. The only case I can think of is something like writing a comparator function, where it's somewhat implausible that you could write a correct 1-arity function for |
Of course in the current system As for the issue @antoniovazquezblanco and me had, we have been trying to reproduce it, without success unfortunately. Our scenario is a bit complex and we simply didn't manage to return things to the state they were in when we had the problem. But your example is very good already, and I'm certain there are many analogous cases. If you are type CompareFn<T> = (a: T, b: T) => -1 | 0 | 1 And you better don't pass to it a function that takes only one argument, because it is not going to work. And all the people using Actually, [1, 2, 3].map(() => 42) Please, don't hesitate to refute my arguments. I might be still missing the point, and I'm happy to be proven wrong. |
The rational here is simple, a callback that declares less parameters than its caller is the same as a callback that declares the parameters and never uses them. @inad9300 can you share an example where having a callback with less parameters than the declaration would be considered a bug. |
I'm sorry, but didn't I just provide two examples of that? (One as a continuation of @RyanCavanaugh's case). Iif in strict mode, passing |
So is Moreover, |
A zero-parameter Even It's not even clear to me how this rule helps you in that case. If you forgot to use the parameters that should have been there in the function body, how is declaring them going to help? Under this system you're going to routinely have to write a bunch of stand-in We could imagine some special syntax for "This parameter must be declared for this callback", but I think that'd be useful for |
@mhegazy, what you are saying sounds to me like not checking a function's use against its declaration because the function is not guaranteed to be correct anyway, so why bother. I think you are mixing what are two very different things: type checks and executable code. Getting the types right is only a prerequisite to getting the code right. I understand you don't want to accept every suggestion you get, nor to pollute the compiler's flag set. At the end of the day, there are many users to satisfy, and you are the real experts, so you decide. All I'm asking is for you to take the proposal into consideration, not just spit off counterarguments without having spend proper time to consider. Maybe it should not be under the |
We've been explaining this behavior to users for five years now. We understand why it works the way it does, and we understand why there isn't a flag. If you think it's just that we haven't thought about it, you are very much mistaken. |
@RyanCavanaugh, I think the first part of your comment is a too-narrowed view. Of course it is unlikely that you fail in simple cases, but once you start to have more complex ones, where (e.g.) you pass one of a set of callbacks dynamically, it is then when you need a compiler to back you up the most. Edit Besides, I would argue that using The second part is a totally valid one. It would imply clutter. I personally value correctness over everything else. But again, I'm not the one to take the decision. Other opinions would be helpful to better evaluate the cost vs. value. |
@RyanCavanaugh I should have imagined you have already thought about this many times. I'm sorry to have insinuated otherwise. Regardless of when, I just wanted to argue in favor of taking the proper time to consider suggestions in general. |
This also impacts assignability. async function get(url: string) {
const response = await fetch(url, {method: "GET"});
return response.json();
}
const endpoints = [
"api/users",
"api/products",
"api/orders"
];
const [users, products, orders] = await Promise.all(endpoints.map(get)); That code is perfectly fine and making it an error would not only be inconvenient but it would severely limit code reuse. |
@inad9300 the main goal of TypeScript is to increase developer productivity; this means we have to base many of our decisions on usability. As noted by @RyanCavanaugh and @DanielRosenwasser this issue has been discussed in the past, and JS coding patterns were clear in favor of the current behavior. |
OK, got it. I have never seen TypeScript as a productivity tool, to be honest, but rather as a help to write correct programs as your code gets bigger and bigger (hence "JavaScript that scales"). Someone should have pointed out the existence of previous discussions in the beginning, linking to them, in order to minimize the time I make you waste. |
You found the FAQ which you linked to. There is a search tool which can lead you to all sorts of conversation on the topic, including #13043, #16871, #9825, and #9765 which all seem to be in this same space. I am not sure why it is incumbent on others to point these out to you. |
Let me rephrase: I would have appreciated –and think it would have been on the benefit of everyone– that someone had kindly pointed out to existing issues discussing the topic. Thanks for pointing out to some. |
+1 |
As is well explained in the FAQ, "functions with fewer parameters [are] assignable to functions that take more parameters."
This is fine to ease the transition from JavaScript, but limits TypeScript's ability to detect errors at compile time. A compiler flag to switch on this check would be an amazing step towards safer code.
This option may also be enabled by the "strict" flag.
The text was updated successfully, but these errors were encountered: