When looking at the sourcecode for a tslint rule, I came across the following statement:
if (node.parent!.kind === ts.SyntaxKind.ObjectLiteralExpression) {
return;
}
Notice the !
operator after node.parent
. Interesting!
I first tried compiling the file locally with my currently installed version of TS (1.5.3). The resulting error pointed to the exact location of the bang:
$ tsc --noImplicitAny memberAccessRule.ts
noPublicModifierRule.ts(57,24): error TS1005: ')' expected.
Next I upgraded to the latest TS (2.1.6), which compiled it without issue. So it seems to be feature of TS 2.x. But the transpilation ignored the bang completely, resulting in the following JS:
if (node.parent.kind === ts.SyntaxKind.ObjectLiteralExpression) {
return;
}
My Google fu has thus far failed me.
What is TS's exclamation mark operator, and how does it work?
!
will bypass this error message, which is something like: "I tell you, it will be there."
That's the non-null assertion operator. It is a way to tell the compiler "this expression cannot be null
or undefined
here, so don't complain about the possibility of it being null
or undefined
." Sometimes the type checker is unable to make that determination itself.
It is explained here:
A new ! post-fix expression operator may be used to assert that its operand is non-null and non-undefined in contexts where the type checker is unable to conclude that fact. Specifically, the operation x! produces a value of the type of x with null and undefined excluded. Similar to type assertions of the forms
I find the use of the term "assert" a bit misleading in that explanation. It is "assert" in the sense that the developer is asserting it, not in the sense that a test is going to be performed. The last line indeed indicates that it results in no JavaScript code being emitted.
Louis' answer is great, but I thought I would try to sum it up succinctly:
The bang operator tells the compiler to temporarily relax the "not null" constraint that it might otherwise demand. It says to the compiler: "As the developer, I know better than you that this variable cannot be null right now".
var
or let
are implicitly initialized to undefined
. Further, class instance properties can be declared as such, so class C { constructor() { this.myVar = undefined; } }
is perfectly legal. Finally, lifecycle hooks are framework dependent; for instance Angular and React implement them differently. So the TS compiler cannot be expected to reason about them.
?
says: this might be null
, which is not true (therefore you can only use !
if you ABSOLUTELY know it's not null
.
?.
return type is nullable (even if it can't ever happen), and you'll have to deal with a nullable type down the road. While if you know null is impossible, !.
fixes the type once and for all.
Non-null assertion operator
With the non-null assertion operator we can tell the compiler explicitly that an expression has value other than null
or undefined
. This is can be useful when the compiler cannot infer the type with certainty but we have more information than the compiler.
Example
TS code
function simpleExample(nullableArg: number | undefined | null) {
const normal: number = nullableArg;
// Compile err:
// Type 'number | null | undefined' is not assignable to type 'number'.
// Type 'undefined' is not assignable to type 'number'.(2322)
const operatorApplied: number = nullableArg!;
// compiles fine because we tell compiler that null | undefined are excluded
}
Compiled JS code
Note that the JS does not know the concept of the Non-null assertion operator since this is a TS feature
"use strict";
function simpleExample(nullableArg) {
const normal = nullableArg;
const operatorApplied = nullableArg;
}
Short Answer
Non-null assertion operator (!) helps the compiler that I'm sure this variable is not a null or undefined variable.
let obj: { field: SampleType } | null | undefined;
... // some code
// the type of sampleVar is SampleType
let sampleVar = obj!.field; // we tell compiler we are sure obj is not null & not undefined so the type of sampleVar is SampleType
My understanding is the !
operator do the same thing like NonNullable
.
let ns: string | null = ''
// ^? let ns: string | null
let s1 = ns!
// ^? let s1: string
let s2 = ns as NonNullable<typeof ns>
// ^? let s2: string
Success story sharing
console.assert()
on the variable in question before appending a!
after it. Because add!
is telling the compiler to ignore the null check, it compiles to noop in javascript. So if you are not sure that the variable is non-null, then better do an explicit assert check.dict.has(key) ? dict.get(key) : 'default';
the TS compiler can't infer that theget
call never returns null/undefined.dict.has(key) ? dict.get(key)! : 'default';
narrows the type correctly.?.
AFAIK came from the C# land. With nullable types, C# got its bang too (pun, of course, intended). Yup, the Sir Tony's invention wroke a serious havoc on the world of procedural programming, and we still cleaning the fallout. Being the sweetest person, he still apologizes for it. Curiously, his major contribs to CS are in automatic reasoning about program correctness (e.g., Hoare logic), applied in static code analysis: he invented both the null and the ways to statically catch it! :)