You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
API: getTypeAtLocation of node inside non-null assertion returns apparent type of TypeParameter with nullable constraint #21317
Expected behavior: checker.getTypeAtLocation(node) where node is the Identifier param1 or param2 always returns a ts.TypeParameter.
Hovering param2 in both locations shows (parameter) param2: U extends string
Hovering param1 in both locations shows (parameter) param1: T extends string | undefined
Actual behavior:
result of checker.getTypeAtLocation(node):
on both uses of param2 returns ts.TypeParameter
on the first use of param1 returns ts.TypeParameter
on the second use of param1 (passing the Identifier as argument, not the NonNullExpression) returns ts.UnionType (string | undefined)
Hovering param2 in both locations shows (parameter) param2: U extends string
Hovering the first use of param1 shows (parameter) param1: T extends string | undefined
Hovering the second use of param1 shows (parameter) param1: string | undefined
The result of param1! is type string which is not valid according to #20974.
This change was introduced in #20995 as a bugfix. Looking at the baseline changes this might even be intended.
IMO this is pretty inconsistent if it only applies to TypeParameters with nullable constraint.
According to the reasoning in #20974 (comment) this new behavior is invalid.
And it's a breaking change for API users.
Playground Link: Playground doesn't use the correct nightly
Looking at the baseline changes this might even be intended.
Yes, it is intended. We wanted to allow people to assert nonullness of type variables, since otherwise you can perform no meaningful operations on them. The nonull operator is effectively a shorthand cast, so it losing a bit of information was deemed acceptable until such time as we can represent it completely using a negated type.
Klaus Meinhardt (@ajafff) This is likely still labeled as a bug because we haven't gotten a chance to talk about it in the office and have it retriaged yet 😉
The type before the assertion is going to be the constraint (string | undefined, because it's an apparent position thanks to the assertion), and then after the assertion is applied it should just be string. This isn't an API issue, we legitimately interpret the type at that position as just the constraint now, that way the bang after it functions somewhat.
Let me share my use case:
I want to detect if a variable may be used before being assigned. Until #20221 is implemented, I need a workaround. One part of that workaround is checking if the type before the assertion is exactly the same as the declared type of the variable.
Now that I get the apparent / constrained type, I can no longer distinguish the following:
functionfn<Textendsstring|undefined>(param: T){letfoo: T;letbar: T|undefined;if(Boolean()){foo=bar=param;}foo!;// expected 'foo' to have type 'T', got 'string | undefined'bar!;// expected 'bar' to have type 'T | undefined', got `string | undefined`}
Now there is no way for me to know that banging foo (foo!) is necessary to avoid a compile error while the assertion in bar! is redundant.
we legitimately interpret the type at that position as just the constraint now, that way the bang after it functions somewhat.
Can't you just do that getConstrainedType magic inside checkNonNullAssertion? That way the API still returns what I requested.
Now that conditional types have landed, we're probably doing to change the assertion operator to narrow to NonNullable<T>, rather than the overeager constraint things we're doing in nightly. Just a heads-up.
Activity
Add test for #21317
weswigham commentedon Jan 23, 2018
Yes, it is intended. We wanted to allow people to assert nonullness of type variables, since otherwise you can perform no meaningful operations on them. The nonull operator is effectively a shorthand cast, so it losing a bit of information was deemed acceptable until such time as we can represent it completely using a negated type.
ajafff commentedon Jan 23, 2018
Thank you for the explanation. This actually makes sense.
This is still labeled as bug because the API returns the wrong type?
weswigham commentedon Jan 24, 2018
Klaus Meinhardt (@ajafff) This is likely still labeled as a bug because we haven't gotten a chance to talk about it in the office and have it retriaged yet 😉
ajafff commentedon Jan 24, 2018
Continuing discussion from #21349 here:
Let me share my use case:
I want to detect if a variable may be used before being assigned. Until #20221 is implemented, I need a workaround. One part of that workaround is checking if the type before the assertion is exactly the same as the declared type of the variable.
Now that I get the apparent / constrained type, I can no longer distinguish the following:
Now there is no way for me to know that banging foo (
foo!) is necessary to avoid a compile error while the assertion inbar!is redundant.Can't you just do that
getConstrainedTypemagic insidecheckNonNullAssertion? That way the API still returns what I requested.weswigham commentedon Feb 14, 2018
Now that conditional types have landed, we're probably doing to change the assertion operator to narrow to
NonNullable<T>, rather than the overeager constraint things we're doing in nightly. Just a heads-up.