-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Typecheck passes for incorrect assignment #21868
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
Comments
Huh, optimizing typechecking for the "Array.forEach()" case is kind of weird. In any well-typed language those types are clearly different.
This is indeed how the signature for the "forEach" should have looked like. Now consider this example:
Behavior is very inconsistent. |
Check this example as well. Again, different behavior:
|
The logic from the FAQ entry is not correct: Indeed, signatures are read from the caller's perspective. But the caller in this case is not the "forEach" method, but the one that calls "forEach" itself. It just happens that "forEach" will provide all three optional arguments to its callback. The caller of "forEach" may consume arbitrary number of them, or even none. |
This is working as intended and the wording in the FAQ is indeed correct. In your examples above, it is safe to treat a |
Sad to hear. Obviously wrong from my perspective. It is safe to add arguments, not remove. |
Following your logic "you can safely ignore arguments you aren't interested in" basically means - it is safe to ignore mandatory argument for function. Does not make sense for me. |
There are dozens of issues where people have argued about this (start at #17868); we don't need another. This is the intended behavior. |
It is a very strange way of running a language business - compromise type safety in return of some ad-hoc convenience for few methods of the built-in lib. If you have some experience with any language with proper type systems (Haskell, Idris) then It is very strange way to think about the type The callbacks for native methods can be defined as sum types instead:
Now this is a "it-was-always-like-that-and-we-will-not-change-it-because-it-may-break-something" thing and If I'm correct many people made their reputation, advocating for the current state of things. But it does not become correct because of that. Its obviously wrong. |
An optional or required parameter is from the perspective of the caller. |
You're begging the question. Ignored extra arguments are both common and safe in JavaScript; it's only "unsafe" if you think ignoring extra arguments is unsafe, and it isn't except by your own fiat. It makes no sense to require everyone writing a calling-back function to write out N + 1 explicit overloads if they supply N arguments. |
Everyone writing a calling back function and willing to make it flexible in terms of arguments will make them optional, thats all. In fact, an optional arguments is exactly equivalent of the sum type above. But a hole in the typechecker still remains: |
This is covered in the FAQ! You should read it! https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-functions-with-fewer-parameters-assignable-to-functions-that-take-more-parameters |
The fact that something is in the FAQ does not make it automatically correct. Let me explain. The type that represent a function with one required argument, that is a string and return number:
The type that represent a function with one optional argument, that is a string and return number:
The value of
This is how the question mark Now lets check the
The signature is actually incorrect, since it is valid for the caller of the "forEach" method, to provide a callback without arguments, only 1 argument, 2 args, or all 3 args. So, the correct signature should look like:
We are only interested in the type of the
Now, lets examine what will be the type of the callback fn, if we specify all arguments as optional:
Lets apply the same resolving rule using the sum type for the last argument:
We've indicated in the types, that since the Applying the rule again 2 times and we get exactly the same signature as we defined previously:
What we did, we proved that:
Which by transitivity of the equality means If the intention is, that caller of the method with the callback can not omit any of the callback's arguments, then those should not be marked as optional. That's all, no need to compromise type-safety. |
You're just arguing that optionality should have a different definition from the definition that it does have. The language not having the definition you wanted to have doesn't change the definition, or make it wrong. I can argue that a meter should be 10cm, because then 1cm = 0.1m, which is clearly safe, whereas 1cm = 0.01m is unsafe. The fact that a book says 1m = 100cm is irrelevant because a correct definition of the meter is 1m = 10cm. You can tell it's unsafe because 5cm should be 0.5m but it's not. Please change science. |
Also worth nothing that if optional means "callee may ignore", then there's no syntax for "caller might not provide"! |
No, I'm arguing, that I would not talk about the callbacks at all, if it wouldn't been mentioned in the FAQ, as the reason why the code above is considered valid. It has been artificially made valid, to solve the "callbacks problem". That was a bad way of solving that problem, especially that it only applies to built-in methods. The better solution would be to use sum types as I mentioned (which is equivalent to marking arguments as optional). |
OK, let me try this: Why do you think it should be invalid? The circular argument of "because it is unsafe" should be avoided -- as much as possible, unsafe things are indeed made invalid. It's odd to say "let's not talk about callbacks"; in what other scenario would you alias a function? |
Following your logic, why not make the Types express some contracts ("invariants") about the code, that user has intention to maintain. If I declare |
Because
You say you want to disallow unsafe assignments. In your world, this code is legal: // Callee may have zero or one arguments
const some: ((arg?: string) => string) = arg => arg.toUpperCase();
// When invoking a function, you don't need to provide optional arguments (by definition)
some(); and it crashes. What's "safe" about this? How do you imagine changing the definition of "optional" in this world? |
This discussion goes to nowhere very quickly. Thanks for explaining the current quality standards of the typescript typechecker. |
I'm here with actual code examples showing why it works the way it does. Why this attitude? |
Hello,
This code typechecks fine:
const some : ((arg : string) => boolean) = () => true
In the same time it looks obviously incorrect. Since the "arg" in the type definition is not optional. So the function w/o argument is assigned to the variable, which has a type of function with 1 argument.
I'd understand if this would typechecks (it does)
const some1 : ((arg? : string) => boolean) = () => true
but not the above.
I've tried running it with --strictFunctionTypes and it typechecks as well.
The text was updated successfully, but these errors were encountered: