TypeScript: `Result value must be used` check
Hello.
Quite often while working with ImmutableJS data structures and other immutable libraries people forget that values are immutable:
They can accidentally write:
obj.set('name', 'newName');
Instead of:
let newObj = obj.set('name', 'newName');
These errors are hard to discover and they are very annoying. I think that this problem can’t be solved by tslint, so I propose to add some sort of "you must use the result" check into the compiler. But right now I have no idea how I want it to be expressed in the language, so I create this issue mainly to start a discussion.
Rust lang, for example, solves a similar problem with #[must_use] annotation. This is not exact what we want, but it’s a good example and shows that the problem is quite common.
About this issue
- Original URL
- State: open
- Created 8 years ago
- Reactions: 36
- Comments: 28 (9 by maintainers)
Here is another example of a refactoring error that this type of check would prevent:
Consider having an onclick handler:
used like:
Now you refactor, adding some logging:
This refactor is wrong; your buttons now no longer have any effect and your web app is broken due to what’s logically a type error that TypeScript did not help prevent. The correct code has some added
()function calls:Here, one needed to call
f(someArg)()instead off(someArg); the latter returns a function that is then unused.If TypeScript had the ability to warn on unused return values, like Haskell and Rust can do with
-Wall, this mistake would be impossible to make.I think it would be adopted. Developers of good software look forward to features like this which make their programs more resilient. It’s like saying
readonlyis useless because no one uses it.TypeScript also gives you escape hatches:
.d.tswith typestype MakeNodiscard<Fn extends (...args: unknown[]) => unknown> = Fn extends (...args: infer Args) => infer Result ? (...args: Args) => Nodiscard<Result> : never@typesAfter having been burnt by a library updating a method to be const/pure in a new major version, whereas it modified state previously: +1
Calling pure methods without using the return value almost certainly is a bug. So if the library author was able to indicate this in some standardized way (e.g. the
nodiscardas suggested above), TypeScript might’ve been able to prevent this issue from happening to me.A
[[nodiscard]]as in C++ (callee-side) is not as useful as e.g. Haskell’s-Wallcompiler flag (caller-side-determined check):It moves control to the writer of the function, instead of giving control over wardnings to the downstream project that uses them.
In C++, this makes
[[nodiscard]]almost useless, as almost library code uses it. That introduces lots of bugs (e.g. ignoredbool successreturn values). In contrast, those types of bugs do not exist in Haskell, where most users/projects choose to use-Wallto enable stricter warnings. It is also possible to turn these warnings into errors using-Werror, which can be useful in CI.Further, a callee-side annotation would not fix key issues like my example (https://github.com/microsoft/TypeScript/issues/8240#issuecomment-929578147), as there the thing whose return value is ignored is itself a function call, and it’s tough to find a place where you could put the annotation when partially applied functions are involved.
So I think a caller-side check that the user can enable as a warning is much better.
Even if a
[[nodiscard]]callee-side feature was considered valuable, that should exist independent of caller-side warnings.Reopening for consideration, since we have got multiple requests for this.
I think it should be implemented similar to c++
nodiscard, that bothThis certainly got bounced around! I’m quite happy for this to be “just” a decorator and lint, but the --lib typings should be able to benefit from it too. Would declaration merging support adding design time decorators?
@xuhdev I would say he means that any callee side annotation - and he points out that feature is, in C++ - would be useless unless it gets retrofitted onto every library function that needs it, and chances of that are zero. That’s why it is more practical to have a caller-side mechanism so that those of us who care about it can use it when calling all kinds of libraries and be safe.
such decorator would also be useful if we want to return errors (similar to
resultin rust) instead of throwing errors. There was a similar request before, but it was closed since it is, supposedly, a duplicate of other issues: https://github.com/microsoft/TypeScript/issues/38303