Non-null assertion operator (aka Swift's force unwrap)

I would like to have an assertion operator similar to typescript's non-null assertion operator. Any expression with a bang at the end should throw if it is null or undefined.

foo()! // throws if returns null or undefined

Are there any existing proposals?

Would be similar to Swift's "force unwrap" Force unwrapping - a free Hacking with Swift tutorial

1 Like

Similar conversation here: Nullish unary operator `?` but for doing a boolean check instead of an assertion.

I would say that's quite different. That proposal is about checking if a value is nullish. This proposal is about asserting and throwing if a value is nullish like you have in swift with force unwrap.

This would make the TypeScript syntax valid JavaScript. I actually like that idea, as it'd increase type inference and compatibility with JSDoc, without incurring the more significant runtime and character cost of a dynamic assert type check.

Yes it is different, I agree. I tried to use the word “similar” to link the two threads, not to suggest that this is a duplicate. I could have been clearer.

I think it’s valuable to consider each idea in the context of similar ones. Maybe the language only needs one and not the other, or maybe if both ideas are added to the language we should make sure that it’s clear which one is which.

—-

I do like this idea, as someone who has helped a few people learn TypeScript, I have found this TS syntax stands out less than the other parts of TS - leading people to think it is JS syntax.

I wonder if it would be even more useful if it wasn’t an operator as such, and could be used in other non-expression positions.

obj!.prop // not super useful here as .prop would throw anyway

fun2(fun()!) // more useful here, unless fun2 is already doing a check 

function f(a = assert(false)) { // only catches undefined
}

function f(a!) { // ! here could catch both
}

const { a!, b!, c} = g(); // assert in a destructure 
3 Likes

Indeed, the two proposal seems related, but accomplish quite different goals.

The non-nullish ? operator aims to give another syntactic sugar to handle nullish values, in the same way than the nullish coalescing ?? or the optional chaining ?. This gives programmers an additional tool they can use wherever they want.

On the other hand, the non-null ! operator gives a syntactic sugar for a particular condition (checking nullish value) of specific situation (assertion). This can indeed be useful, but assertion still doesn't have a native way to be done in the language. I think it would be better to first have a general solution for assertion (like using an assert keyword or something), before trying to implement syntactic sugar for a sub case of it.

Also, thanks to the non-nullish ? operator general purpose, it could be used in the case of assertion too, maybe removing that need of syntactic sugar for the case of nullish values, and also working with any assertion libraries. For example let's take the case of node assert function, expecting a boolean as its parameter and checking if it is truthy. Here, treating the case of nullish value becomes dead simple:

// assert that `myVar` is not nullish
assert(?myVar)
1 Like

Would you be able to expand on that? The language does have throw new TypeError e.g:

arr.map(null) //TypeError

Great points.

I get the impression the typescript ethos is to create type comments rather transpile to JS code. However, in this case, it would be way more useful if foo()! actually threw rather than just assumed types.

I'm not really sure about what you mean. Indeed javascript can throw errors using the throw keyword, but it does not have yet a native way to do assertions. What I mean by assertion is a predicate function (or keyword) that throw when it is given a falsy value.

So yes, technically you can create your own assertion function using the throw keyword, but then this assertion is not native to the language, it belongs to userland code.

For example, I would love to see something like this assertion idea go through. It would allow you to do something like TypeError.assert(someCondition). Many languages have some sort of assert function or statement. It would be nice if JavaScript does as well.

I guess someone could utilize the fact that null! will always throw an assertion error, and with a bit of hacking, emulate assertions as follows:

function operateOnArray(array) {
  Array.isArray(array) || null!
  ...
}

But, it would be preferable to just provide a native assert function.

function operateOnArray(array) {
  TypeError.assert(Array.isArray(array))
  ...
}

I think an assertion lib should be separate from this. Some standardized assertion predicates would be great, but I wouldn't want to entangle them.

As @aclaymore points out, a non-null assertion character could be used in non-expression places.

A non-null assertion function can basically be built from the language today, without an explicit throw or type check (if you ignore the return value):

const assertNonNull = Function.prototype.call.bind(Object.prototype.valueOf);
3 Likes

I could see value in a non-null assertion operator as a way to enforce assumptions a lot more easily than if (value == null) throw new TypeError("..."). And the lighter weight would make it nice for internal type assertions, where that boilerplate gets old fast, making it much more compelling to not actually check types and just trust they're correct.

I want to like @theScottyJam's idea of an Error.assert, but I have concerns about the boilerplate becoming high enough to not actually fix the issue I mentioned about error assertion boilerplate.

There are three classes of lightweight assertions I'd like:

  • Non-nullish (the value! as proposed here)
  • typeof (something like value is "type")
  • instanceof (something like value is Type)

Each of these would simply throw a TypeError if the check fails and pass value through if it succeeds. (Obviously not tied to any particular syntax - I just want it concise enough to not become burdensome.) And of course, you could use it both as an expression and as a statement. As for language precedent, most major JVM languages' cast operators work this way as the JVM's checkcast instruction likewise works this way, just throwing a ClassCastException instead due to differing error idioms.

1 Like

Interesting, I hadn't thought of other type assertions, but that makes sense. I would however prefer the TypeScript as type assertions keyword.
however, right now I see 3 major deviations:

  • primitive assertions would be done through the string result value of typeof, instead of the non-quoted "primitive type" that TypeScript uses. Worse, a quoted string is a valid type in TS, it's a narrowed string type of a specific value.
const tsNum = getNum() as number; // `number` isn't valid JS
// vs
const jsNum = getNum() as 'number'; // TypeScript consider this to be the `'number'` subtype of string
  • the right hand side would have to be an expression in JavaScript, which isn't currently supported in TypeScript
  • TypeScript supports other types (e.g. any or never) and syntax for them (e.g. Map<Foo>) which aren't valid JavaScript.

If we could end up making the typescript assertions a superset of JavaScript ones, then it might be possible. It'd be a breaking change for typescript though:

  • expressions in the right hand side would have to resolve the type of the expression and consider it as the "class" of.
  • val as 'number' for primitive assertion would be supported
  • val as type 'stringValue', which would be removed at compilation, for explicit type only assertion, and provide an upgrade path for existing TS code.

One assertion that is unclear in JacaScript would be val as null. On one hand it'd be a way to provide a "null assertion" which doesn't have its own typeof, on the other it opens the door to the right hand being an expression that evaluates to null, and that not being in itself a TypeError.

You're proposing we add this "as" type assertion, have TypeScript adopt the semantics via some breaking change, and let legacy TypeScript code switch to using "as type"?

Why not choose a different operator from the start that doesn't conflict with TypeScript's as, which we use for runtime type assertions, and let TypeScript add typing behavior to this new operator, and let legacy code uses TypeScript's existing "as" operator?

Correct, because as is a keyword reserved in places where an identifier is not expected (which would be the case here), and it has the right meaning. There are no other reserved keyword that would work.

It would also be compatible for most existing usages in TypeScript. TS could have heuristics that infer from the invalid usages where no confusion is possible, and issue a warning, or error if there is a confusion. This also wouldn't be the first breaking change that JavaScript changes has on TypeScript.

Depending on how as is specced, TS could also add a runtime shim to make usages like as number be valid. E.g. if it was following instanceof semantics of checking Symbol.hasInstance, you could add a global const number = { [Symbol.hasInstance](value) { return typeof value === 'number'; } } to make 42 as number work.

How is this better than

export function assert(predicate, message) {
  if (!predicate) {
    const error = new Error(message)
    error.name = 'AssertionError'
    throw error
  }
}

I agree best to leave assertions discussions out of this discussion. But to be a hypocrite, the main benefit of a native assertion function is that it would preserve call stacks. (Not sure if there is a call stack workaround?)

I think another common use case is duck type checking, ie check that an object has a property but I guess that can be covered by

value?.foo!

All that said, just foo! is a great start.

Maybe rather than "non-null operator", we might want "non-falsy operator", as then it would be easy to cover the latter two with

(e instanceof Error)!
(typeof x === 'number')!

Another option, if we're not trying to constrain ourselves to the way TypeScript does its types, is to rely on pattern matching syntax for the assertion. It's an idea I brought up over here (but with a different purpose in mind). The syntax would basically be this (I tweaked the syntax here to make it similar to the syntax we've already been discussing)

const user2 = user as { name } // works
const user2 = user as { xyz } // throw an error

The RHS is a pattern-matching expression from the pattern matching proposal. If the language also provides some default matchers, it would not be hard to allow null assertions as well.

const user2 = null as Types.notNullish // throws an error
const user2 = user as Types.notNullish // works

Things like instanceof could easily be supported as well, as I explained over there (which I can elaborate on in this thread, if there's interest in going this direction).

Thus, there would be only one fairly simple piece of syntax to provide assertions for all three of these categories - non-nullish, typeof, and instanceof, along with other stuff like object shape.

Wouldn't this fail because null is nullish and not notNullish?

Whoops, you're right. I changed it from "Types.nullish" to "Types.notNullish", but forgot to update the comments. I've fixed it now.

1 Like