Skip to content
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

Non-null assertion operator and the typeof operator #17370

Closed
bradzacher opened this issue Jul 24, 2017 · 24 comments
Closed

Non-null assertion operator and the typeof operator #17370

bradzacher opened this issue Jul 24, 2017 · 24 comments
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@bradzacher
Copy link
Contributor

TypeScript Version: 2.4.1

Code

// strictNullChecks: true

let x : string | undefined
let y = x!

type t1 = typeof x!
type t2 = typeof y

Expected behavior:
the t1 statement should create a type of type string.
the t2 statement should create a type of type string.

Actual behavior:
the t1 statement errors with ';' semicolon expected.
the t2 statement creates a type of type string.

There appears to be no way to achieve this without defining a separate variable, or by type guarding to remove the undefined / null.

With strict null checks on, this is especially troublesome in the case of generics with extends clauses, i.e.

class Foo {}
class Bar<T extends Foo> {}

const x : Foo | undefined

const y = new Bar<typeof x>() // throws an exception because typeof x === Foo | undefined != Foo

It would be great if the non-null assertion operator worked natively in typeof statements, or even if you could use brackets to resolve the assertion before the typeof.

@jcalz
Copy link
Contributor

jcalz commented Jul 24, 2017

I think you're asking for #6606. For now, workarounds involve forcing the type checker to evaluate the type you care about while not causing much extra work at runtime:

class Foo {}
class Bar<T extends Foo> {}
declare const x : Foo | undefined
if (false as true && (typeof x !== 'undefined')) {
    var definedX = x; // assignment never happens at runtime
}
const y = new Bar<typeof definedX>()

@bradzacher
Copy link
Contributor Author

i mean, yeah #6606 would definitely solve this as it's a pretty all encompassing proposition...

It would be nice if there was a better error message to signify that the error is specifically that the expression isn't supported, rather than a 'semicolon expected' error.

@KiaraGrouwstra
Copy link
Contributor

A potential alternative here would be for the compiler to expose ! on the type level. I'm not yet aware of outstanding proposals for that though.

@Igorbek
Copy link
Contributor

Igorbek commented Jul 24, 2017

@tycho01 back to the #6606 discussion, is it going to be too many type-level operators, isn't? that was one of my points, we'll never get same expressiveness.

@KiaraGrouwstra
Copy link
Contributor

KiaraGrouwstra commented Jul 24, 2017

I'd be interested to get their take on it. Evidently they didn't feel the expression level had too many operators to add that !.
Personally my ideal situation would be to keep the two completely on-par. But yeah, the TS team may well be considerably more conservative.

Edit: to get to your point, yeah, if we are to be stuck with a type level not on par with the expression level, that admittedly goes to validate the added value of your expression-first #6606 variant.

@RyanCavanaugh RyanCavanaugh added In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Jul 24, 2017
@RyanCavanaugh
Copy link
Member

We can expand the grammar here bit by bit without going full-blast with #6606. This incremental change seems pretty reasonable

@bradzacher
Copy link
Contributor Author

it would be great as (right now) there's no other way to scrub null/undefined at the expression level; making it more difficult to work with the strictNullChecks option at times.

@Igorbek
Copy link
Contributor

Igorbek commented Jul 24, 2017

@RyanCavanaugh Hm, I'd prefer full-blast (you might have already got this 😄) as it would cover much more cases at once.

@KiaraGrouwstra
Copy link
Contributor

@bradzacher: I think you meant type level. :)

@KiaraGrouwstra
Copy link
Contributor

@RyanCavanaugh: on #6606, does the reluctance stem from perceived complexity of the implementation as you proposed it (link)? Because afaik, the proposal would not necessarily* bring breaking changes.

*: ignoring the keyword priority discussion raised by Igorbek for the expression-based proposal.

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jul 25, 2017

#6606 is accepting PRs and anyone can jump in, but we're not ready to commit team resources to it yet. The prioritization is based on a cost/benefit analysis: adding ! to the grammar here is probably a ~1/2-day work item (for someone on the team) but that other proposal is probably a 5-day or more work item (due to likely unknown design aspects that would only become apparent during implementation) plus a lot of ongoing complexity maintenance cost.

@Igorbek
Copy link
Contributor

Igorbek commented Jul 25, 2017

@RyanCavanaugh is #6606 accepting PRs even in expression-based syntax?

@KiaraGrouwstra
Copy link
Contributor

On-topic, with 6606 a type-level ! alternative could look like this:

class Foo {}
// expression level function application today, to test:
declare function assert<T>(v: T | null | undefined): T;
let a: Foo | undefined;
let b = assert(a); // Foo
// v type level function 'application' with #6606:
type Assert<T> = (<U>(v: U | null | undefined) => U)(T);
let x: Assert<Foo | undefined>; // Foo

@bradzacher
Copy link
Contributor Author

from the looks of it - typescript doesn't let you have self invoking functions like that within a type declaration.

[ts] ';' expected.
[ts] Cannot find name 'T'.

@KiaraGrouwstra
Copy link
Contributor

@bradzacher: yeah, the function 'application' there is not possible yet. Either flavor of the 6606 proposal could enable something similar to that.

@KiaraGrouwstra
Copy link
Contributor

@RyanCavanaugh is #6606 accepting PRs even in expression-based syntax?

since we're just about down to aesthetics... welcome to typeof fn(1 as any as MyType)!

@KiaraGrouwstra
Copy link
Contributor

This topic came up in the recent design meeting at #17621 from the duplicate issue #14366.

@KiaraGrouwstra
Copy link
Contributor

I made some initial progress at #17948, but still broken.

@KiaraGrouwstra
Copy link
Contributor

@Igorbek:

@tycho01 back to the #6606 discussion, is it going to be too many type-level operators, isn't? that was one of my points, we'll never get same expressiveness.

Seems they reconsidered in favor of your conclusion:

We have discussed this in a few design meetings now, and do not feel that adding a new type operator is the right way to go.
Type operators come with a maintainability cost, as well as learning cost by adding a new concept to the language.
An alternative here is to always remove null|undefined in type positions. this allows the use case in #14366 to follow as expected.

@KiaraGrouwstra
Copy link
Contributor

Working example at 4d14c9f.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 2, 2018

This should be doable today with NonNullable type.

@bradzacher
Copy link
Contributor Author

@mhegazy it should be yeah! 👍

I would have liked the operator for it for consistency with non-type def syntax, but I understand and agree with the design decisions.

I see it was merged a few weeks ago. I'd assume that'll be part of the next minor version (2.8??).
do you know when that is being released?

@mhegazy
Copy link
Contributor

mhegazy commented Mar 8, 2018

do you know when that is being released?

TypeScript 2.8 should be available by the end of March.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 9, 2018

Closing as fixed by NonNullable type.

@mhegazy mhegazy closed this as completed Mar 9, 2018
@mhegazy mhegazy added Fixed A PR has been merged for this issue and removed In Discussion Not yet reached consensus labels Mar 9, 2018
@microsoft microsoft locked and limited conversation to collaborators Jul 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants