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.
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>()
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.
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.
@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.
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.
We can expand the grammar here bit by bit without going full-blast with #6606. This incremental change seems pretty reasonable
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.
@RyanCavanaugh Hm, I'd prefer full-blast (you might have already got this 馃槃) as it would cover much more cases at once.
@bradzacher: I think you meant type level. :)
@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.
! 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.@RyanCavanaugh is #6606 accepting PRs even in expression-based syntax?
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
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'.
@bradzacher: yeah, the function 'application' there is not possible yet. Either flavor of the 6606 proposal could enable something similar to that.
@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)!
This topic came up in the recent design meeting at #17621 from the duplicate issue #14366.
I made some initial progress at #17948, but still broken.
@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.
This should be doable today with NonNullable type.
@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?
do you know when that is being released?
Closing as fixed by NonNullable type.
Most helpful comment
Closing as fixed by
NonNullabletype.