-
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
Remove the non-null assertion operator #9637
Comments
There are a lot of cases that aren't covered by a simple type predicate, especially when it involves multiple variables whose values are linked (like if one variable is undefined, the other can't be, or if both variables must be defined or undefined at the same time). You might even want to do these guards (and throw errors) in the constructor of a class and use the result of these guards in class methods, without having to cast everytime. And casting can be verbose with complex types too. When I converted some of my code to Consider this example: function test(store?: Store, nodes?: string[]) {
if (!store && nodes || !nodes && store) {
throw new Error("store and nodes must be both defined or undefined");
}
return (nodes || []).map(node => store!.get(node));
} I have a lot of this in my code and I really appreciate being able to use |
Thanks @JabX, I hadn't appreciated those particular limits of the type inference. Although I'm still unsure why the // Adding interface for completeness
interface Store { get: (string) => number }
function test(store?: Store, nodes?: string[]) {
if (!store && nodes || !nodes && store) {
throw new Error("store and nodes must be both defined or undefined");
}
return (nodes || []).map(node => (<Store>store).get(node));
} |
A type assertion instead in that position to a short type name is relatively benign. But consider something like this: let x = foo!.bar.baz!.toString(); which might turn into let x = (<Some.Namespace.Baz>(<{ bar: Something }Foo>foo).bar).baz).toString(); which has several disadvantages:
|
Thanks for responding @RyanCavanaugh. I understand you've closed this issue so I'm not going to press it other than to respond to the disadvantages -
Navigating deeply into a tree structure with nullable fields without intermediate guards really doesn't feel safe to me. I wonder if there's been too much focus on ASTs which atypically contain such guarded deep nested accesses. |
interface Parent {
Child?: {
Name?: string;
}
}
let p: Parent = {}
// without non-null assert
let processParent = (p?: Parent): void => {
if (!p || !p.Child || !p.Child.Name) {
return;
}
console.log(p.Child.Name);
}
// with non-null assert
let processParent2 = (p?: Parent): void => {
console.log(p!.Child!.Name);
} and the js that emmited below var p = {};
// without non-null assert
var processParent = function (p) {
if (!p || !p.Child || !p.Child.Name) {
return;
}
console.log(p.Child.Name);
};
// with non-null assert
var processParent2 = function (p) {
console.log(p.Child.Name);
}; please run at playground on typescriptlang.org, I can still get 'undefined',it seems compiler do nothing, maybe i'm misunderstand the point, need help |
|
need more sample code thx |
Whilst quite hard to search for so I may have missed something, the only justification for the non-null assertion operator I can find is in the design notes #7395 -
So a type predicate would look look like -
Or implicitly -
But am I right in thinking that a type assertion would also work?
If so, this would be my default expectation of how this would work. Adding the new operator means -
!
in the same way.Adding this new operator seems like an extreme step to support an edge case with so many existing solutions. However, maybe I'm missing a killer scenarios where this would be useful?
The text was updated successfully, but these errors were encountered: