-
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
add a flag to disable () => void being subtype of () => a #8584
Comments
Would we also ban function calls in expression statements unless those functions are e.g. I imagine you have this problem function doSomething(): Promise<number>;
// BUG
doSomething(); |
yes, you are right, expression statements other than |
wish there was a flag that would require the |
unhandled all in all TypeScript is clearly favoring OOP over FP being yet another mainstream languages with a low entry level |
There are a lot of functions with infrequently-useful return values. They can't be declared as returning Whether a return value is "ignorable" or not isn't just determined by its type, either. People ignore the number var x = 3.14;
Math.floor(x);
console.log(x); // "Why isn't it 3?" ;) So to properly detect bugs involving ignoring return values, the type system would have to distinguish between ignorable and unignorable return values, perhaps by intersection with declare function setTimeout(...): number & void; // ignorable
declare function floor(...): number; // not ignorable It's an interesting idea, but one the TypeScript people would probably reject. |
if you are (like us) working on a serious UI heavy application with a possibility to cancel any long running opertaion you must never ignore it for a jquery powered home page, sure go ahead believe me the hassle of being exact about ignoring the return value is nothing compared to the benefits of being able to catch more bugs at compile time, if you value the quality and your time this all comes down to an old idea of making typescript stricter to ones who needs it (minority): #274 |
is this the same as #8240? |
it's about rephrasing the last line in 3.11.3 Subtypes and Supertypes Instad:
Should be:
|
I will bring it up for discussion. though I do nothing think we should be adding flags to change the behavior of the type system, unless it is a transitional phase (e.g. noImplictAny, strictNullChecks, etc..). |
Some points from discussion
|
As I already said in #8240, this issue is must have to simplify work with all immutable data structures and immutable operations (
instead of just
And it's pretty hard to find the error in the large codebase. And it's also very annoying. |
This kind of thing is also useful for scenarios where you want to disambiguate overloads. As an example, mocha's |
On another issue @Aleksey-Bykov raised the use-case: /** @must_be_used */
function fn() { return {}; }
[].map(fn); // <-- a problem or not?
Redux store Are there cases where the must-use-ness of the result depends on the argument types? So a rough design would be:
What about passing must-use function types as an argument? declare function makeFoo(): must_use Foo;
declare function useFooMaker(maker: () => Foo): void;
useFooMaker(makeFoo()); // Is this an error? How do we know `useFooMaker()` actually uses `Foo`? Do we care? I would probably say if it's declared as having to return a type, it's safe to assume it's actually used (otherwise why not |
there is no definition of pure, so it's hard to talk about what it is
but if we try: #7770 |
The callback is impure, which makes the call impure, but |
Allowing certain functions to insist on return value consumption would be valuable for greater error/exception type safety, and is a nice feature of Rust. For instance, for any function that is used primarily for its side effects, you currently can only choose between:
|
The current behaviour of treating Imho TypeScript should help me by showing an error and not confuse me by allowing this. I was almost pushing code with this wrong assumption that would have been very hard to track down. |
Two days back I also opened a issue similar to this #29173 Also library author can make a type itself as non discard-able , so that any function returning that type is automatically non discard-able , without any annotation (thing like promises, observable , error codes) also see https://en.cppreference.com/w/cpp/language/attributes/nodiscard for c++ syntax and behaviour. |
Adding to the discussion, this is especially annoying when working with lazy constructs such as When interoperating with callback-based API, you constantly risk to introduce subtle bugs: // dummy declaration of Task
interface Task<A> { run(): Promise<A> };
declare function blippy(f: () => void);
declare function command(param: number): Task<string>;
blippy(() => command(42)); // nothing is being run, because `Task` is lazy
blippy(() => command(42).run()); // correct way of doing this |
There should be a new strict compiler option which does either of these:
|
I came here from another issue which was discussing an option to mark a function such that it's return type cannot be unused. I'm unable to understand whether this issue is actually about it, so please forgive me if it isn't. A useful scenario for the above mentioned feature is while using redux-thunk. We can create a thunk like subscribeToPlan(), but we might forget to actually dispatch it, leading to an error. This is usually not a problem when we create new thunks because we'll find that the new feature is not working as expected, but if we're migrating from a library which uses another convention to something like a thunk, we might miss a place if there are a lot of changes. If there was a way to mark a thunk such that it has to be used, we'd be able to catch them at compile time. |
We've been spending hours dealing with bugs that would been caught with this check. as @simonbuchan had pointed out, this language feature is available in a lot of languages, and some even have it turned on by default. e.g. Swift Would really like to see TypeScript adding support to this as well. Also, if anyone knows if there is any workaround currently? Either being eslint, tslint or anything that can help mitigate this? |
F# has this turned on by default for all functions as well. |
Instead of global flag could this be a new utility type than can be applied where we want? function compute(myNumber: readonly number): SideEffect<number>;
function mutate(myNumber: SideEffect<number>): void; With Invalidvar x = 3.14;
Math.floor(readonly x); // WARN: "math.floor() do not modify the input but returns it, did you mean to use the result?" var x = {foo: 'bar'};
x = modify(x: SideEffect<object>): any; // WARN: "compute() modifies the input but you reassigned the results to the original value, did you mean to check the input?" var x = {foo: 'bar'};
modify(x: SideEffect<object>): any; // WARN: "compute() modifies the input but you never reuse it, did you mean to pass it by reference?" Validvar x = 3.14;
x = Math.floor(readonly x); var x: SideEffect<object> = {foo: 'bar'};
modify(x: SideEffect<object>): any; |
I was directed here from #8240 ("Result value must be used") and I'm not sure why that one was closed and locked in favor of this issue, since both seem to be about different concepts. This issue (coming from #8581) started out with a global compiler flag to disable assigning a non-void return type to a void return type, which (as RyanCavanaugh and mhegazy already pointed out in #8581/#8240) runs against the established understanding of assignability - it is indeed perfectly valid to ignore the result of many function calls, especially most existing JavaScript APIs. #8240 is about marking individual functions, for example like bodinsamuel suggested, which presumably wouldn't require "function attributes" so it would be worth reconsidering.
Please clarify whether this issue is about the global flag or per-function flag, and if the former, please reopen #8240 as it is a different and reasonable feature, while this issue is clearly controversial due to its intrusive nature. |
Considering this again, I think the solution to OP is straightforward: If you want a function that truly returns nothing, that type is if you want a function whose return type you pledge to ignore, that type is For "result must be used", I guess we need a new issue; these two have become too interlinked. Feel free to open. |
Actually, on second thought, "result must be used" is exactly the same as "pure" (or has such substantial overlap that it's not really needed to have both); #7770 |
@RyanCavanaugh I'm not sure I quite understand: are you suggesting that if the original code were: declare function useCallback(f: () => void);
declare function callback() => a;
useCallback(callback); // no error, a is ignored by useCallback that it is changed to: declare function useCallback(f: () => undefined);
declare function callback() => a;
useCallback(callback); // error here? The problem here is that it's changing
It's not really the best description for this issue, but I can't really agree with merging with pure either. Here are some DOM examples of very-non-pure, but very-must-use functions: // different results each time, but no use except for result:
Date.now();
crypto.randomBytes();
// even after unwrapping promise, could return failure response
fetch();
// locks the stream as a side-effect, but also useless without using the reader
stream.getReader(); And in practice the changes for pure would be largely about checking pure function bodies, while must-use is about checking calls to must-use results, at least from it's approach in other languages. If you like I could put this threads must-use specific detail into a fresh issue? |
@RyanCavanaugh can you reopen either #8240 or #29173 for return value must be used feature |
Or either of those, I suppose! |
Reopened #8240 |
@RyanCavanaugh (psst, you actually only unlocked it, you didn't actually reopen it) |
Oops, thanks! |
sometimes assuming that the result of a function might be ignored isn't safe, consider adding a flag that would disable such assumptions
followup of #8581
The text was updated successfully, but these errors were encountered: