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

I'm confused. If you want to assert that something is non-nullish - and get an exception otherwise - you can already do this either with Object(x) or x.literallyAnything, both of which throw for nullish values. Why do we need new syntax for this?

Object(null) and Object(undefined) returns {}, so those wouldn't do it.

You would have to do maybeNullish.literallyAnything, which works, but you wouldn't be able to use it inline with other actions, for example, a nullish assertion operator would let you do this:

return f(g()!)

The equivalent today would have to be this:

const result = g()
result.literallyAnything
return f(result)

Or, if you actually care about readability, something like this:

const result = g()
if (result != null) throw new Error()
return f(result)

Alternatively, I'm just realizing that if the throw-as-expression proposal ever goes through, then this would be possible.

return f(g() ?? throw new Error())

This is where .literallyAnything would need to be .valueOf(). That would give you back the value for those that support it, which are basically all non-nullish values except those that have it overridden or don't inherit from Object.prototype. That then brings us back to mhofman's example of assertNonNull:

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

which works for all non-nullish being called as a function using the original Object implementation of valueOf().

That implementation doesn't actually work right either (if you want to utilize its return value). It'll automatically turn primitives into objects, which could have unexpected consequences on your code.

> const assertNonNull = Function.prototype.call.bind(Object.prototype.valueOf);
> const data = [false, false]
> data.every(x => x)
false
> data.every(x => assertNonNull(x))
true
1 Like

Yes, the goal of any type assertion is to pass the original value through if the check passes.

My assertNonNull didn't attempt to faithfully do that, sorry for the confusion. It was meant as an example of the engine performing a non-null check and throwing if not.

As for the stack trace observation, yes indeed a syntax based type assertion would obviously not add a stack entry, and I assume a built-in assert predicate would avoid this as well. However I'll be pedantic and point out that stack traces are not yet part of the spec.

I just want to point out that this concept exists in Dart language. It's called Null Safety and it tells the IDE to treat the variable as if it were not null. If it is null, this is a problem, so an error is thrown. Not quite like the proposal, but close enough.

exactly the same as this proposal

Yes exactly the same as this proposal, I came from Swift which refers to this as force unwrapping and I expected the same behaviour in TS

When I learnt it didn't behave this way I asked mfxb9a4 if he knew more which lead to this question

Maybe not.

I'm not an expert in Dart, but to my knowledge, it's not a check. An error is thrown only if a null value is assigned to a variable that cannot be null. If the non-null assertion were to be assed to JS, there would have to be a check that looks for null values.

In JS, it says "throw if null." In Dart, it says "treat value as if it isn't null."

With GitHub - tc39/proposal-throw-expressions: Proposal for ECMAScript 'throw' expressions this syntax can more-verbosely be achieved with

(val ?? throw new Error());

If it helps, I was just sketching out the problem space and the shape of a solution. :wink:

I was explicitly not proposing any particular syntax, and almost made it deliberately ugly just to emphasize that.

The driving reason for me is that I want stack traces to be in more helpful places. I've already encountered more than my fair share of state bugs where the stack trace was a red herring, and it's always because the data is generated in one location and processed in another.

Having an easy way to check my assumptions when I generate the data would be far more valuable at avoiding bugs than having code throw in a location far away from where source of the bug actually is.

It's also why I want a slightly more generalized typeof/instanceof-aware form and not just this limited version.

1 Like