-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Non-null assertions infringe a responsibility of optional chaining #35025
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
Duplicate of #34875, for context |
For the benefit of anyone else reading this, what's wanted here is for the non-null assertion in |
The non-null assertion operator has never been safe. It can't be, because it's a downcast operator. Now, you can say that an expression of the form
We also can't really do anything about this case const x = a?.b;
const y = x!; without introducing some notion of "No really we can prove it's maybe |
To play devil’s advocate a bit, I suspect it’s reasonably idiomatic to do something like if (foo != null)
doSomething(foo.bar!); // we know bar won’t be null here It would be quite tempting to replace the above with an optional chain, but doing so is not safe because of the I can’t speak to how common the above pattern is, but I would definitely be tempted to do the unsafe refactoring at any rate. Would be nice if the type system could point out the mistake if I went through with it. |
What would you intend to refactor that into? |
doSomething(foo?.bar!); I’ve actually made this mistake a couple of times already; it changes the behavior of the code, but TS typically catches it because now there’s an |
The problem doesn't depend on that usage.
It is wrong understanding. I'm not saying such a poor scenario. I'm explaining a?.(b!) // Expected scope.
(a?.b)! // Actual scope of the current type system. The difference is obvious. You couldn't understand what is explained here is |
Oh, so the request here is for the non-null assertion operator to participate in long short-circuiting along with property accesses, method calls, and function calls. Since the non-null assertion operator is not part of JS, it's up to TS to decide whether or not to support this. Right now we don't. I tend to agree with the current implementation. Still, it does seem a little weird for a supposedly type-system-only assertion to change the runtime behavior: foo?.bar.baz; // foo == null ? undefined : foo.bar.baz
foo?.bar!.baz; // (foo == null ? undefined : foo.bar)!.baz but long short-circuiting seems weird to me anyway, so who knows? |
We can see there that optional chaining affects other operators after.
So I'm saying TS has to do the new decision. The old decision when non-null assertion operator was introduced is not directly applicable here. What decisions does TS make is not restricted. The present problem is TS still doesn't understand the extremely simple difference between |
I note that there is literally no precedent whatsoever for a postfix operator which applies to only part of an expression. |
If there is anything to consider about syntax, it would be document.querySelector('_')?.textContent!!; // string | undefined
document.querySelector('_')?.textContent!!; // string
document.querySelector('_')?.textContent!!; // syntax error I think |
It's not like the language has a large number of postfix operators to choose from. More striking to me is that aside from long short-circuiting, I can't think of other code that is "unparenthesizable" in the sense that the meaning of a sub-expression changes when grouped. (edit: okay, that's mentioned here and As far as I know, But long short-circuiting upends that, because now That feels like precedent for |
Long short-circuiting, while interesting syntactically, is not particularly surprising semantically: it is, for example, how the Maybe monad works. It’s nice to have an operator built into the language that models that for the most common cases (property access and function call) so we can all stop writing boilerplate |
I'm not sure what is really needed when an issue is labeled Needs Proposal because I've never seen accepted proposals nor them driving the work for implementation. |
Now I think const a = ''.match('')?.length ?? null; // number | null
const b = ''.match('')?.length! ?? null; // number | null
const c = ''.match('')?.length!! ?? null; // number
declare function f(a: number): void;
f(''.match('')?.length!); // error
f(''.match('')?.length!!); // ok |
Because @RyanCavanaugh couldn't understand what is the problem, I reexplain it.
In the following case, a responsibility of optional chaining is making a return type
Element | undefined
.In the following case, non-null assertion has broken the safeness made by optional chaining.
It is obvious that optional chaining was not considered when non-null assertion operator was designed. TypeScript has to consider what is the best design and what to do via reconsidering the design of non-null assertion operator.
TypeScript Version: 3.7.x-dev.20191105
Search Terms:
Code
Expected behavior:
a is string | undefined.
Actual behavior:
a is string.
Playground Link:
Related Issues:
The text was updated successfully, but these errors were encountered: