-
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
Nullish coalescing should always include the type of the right operand #36393
Comments
This is probably related to or a duplicate of #13778. With const counts: Record<string, number | undefined> = {};
for (const elt of elts) {
counts[elt] = (counts[elt] ?? "zero"); // error now
counts[elt] = (counts[elt] ?? 0) + 1; // okay
} With |
It seems like there could be some "rightward-precedence" for this kind of thing (I'm not sure about the right terminology to describe what I mean). What I mean is basically this:
I think the algorithm would roughly be something like
string ?? (string | null) ?? string[]
// Take first pair...
=> (NonNull<string> | (string | null)) ?? string[]
// Simplify the types...
=> (string | null) ?? string[]
// Recursively take first pair...
=> NonNull<string | null> | string[]
// Simplify the types...
=> string | string[]
// Done! |
|
I just ran into this myself, but I'd say this is working as intended. Rephrasing what @jcalz found, the example in the issue description is demonstrating an example of a const counts: Record<string, number>;
// strictNullChecks: true
counts.foo // evaluated as (number)
// strictNullChecks: false
counts.foo // evaluated as (number | null | undefined) Under strict mode, @typescript-eslint/no-unnecessary-condition does catch TS's short-circuiting behavior when computing the type and will yell at you: const elts = ['foo', 'bar', 'spam', 'spam', 'foo', 'eggs'];
const counts: Record<string, number> = {};
for (const elt of elts) {
counts[elt] = counts[elt] ?? 'zero'; /*
^^^^^^^^^^^
Unnecessary conditional, expected left-hand side of `??` operator to be possibly null or undefined. [eslint] */
counts[elt] += 1;
} If you explicitly widen the type to include const elts = ['foo', 'bar', 'spam', 'spam', 'foo', 'eggs'];
const counts: Record<string, number> = {};
for (const elt of elts) {
counts[elt] = (counts[elt] as number | null | undefined) ?? 'zero'; /*
^^^^^^^^^^^
Type 'number | "zero"' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'. [ts] */
counts[elt] += 1;
} Given that a linter is capable of pointing out the potentially unexpected behavior, I don't know if there's anything that needs to be fixed on TS's end. |
I respectfully disagree (obviously - I created the issue 😉). Using too-loose |
I think the following might be related to this issue: function auto<T1, T2>(value: T1, fallback?: T2) {
return value ?? fallback;
}
// Incorrectly typed to "unknown" even though the right hand path of `??` will never be taken in this case.
const f1 = auto(42);
function forced<T1, T2>(value: T1, fallback?: T2): T1 extends undefined | null ? NonNullable<T1> | T2 : T1 {
return value ?? fallback as any;
}
// Correctly typed to "number".
const f2 = forced(42); This can be important if your system relies on fallback values. With the current typing of From my point of view, it seems like the typing of |
For the record, this: let foo: string = "";
foo ??= 123; has the "expected" behavior. |
It seems to be resolved by turning on noUncheckedIndexedAccess config. |
Search Terms:
Code
A toy example would be something like this.
This one is obviously fine since
"bar"
is always truthy.However, this becomes a little bit problematic when you consider the idiom of having
Record
objects and checking their truthiness before using them.Expected behavior:
An error should be raised.
Actual behavior:
Curiously, an error is not raised in strict mode but is raised in un-strict mode.
$ ./node_modules/.bin/tsc --strict ./foo.ts # No error, exits 0 and emits JS.
Playground Link:
Playground Link
Toggling the
strictNullChecks
config option will show the issue.Related Issues:
The text was updated successfully, but these errors were encountered: