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
Maybe there's something we're misunderstanding about {}, but it doesn't seem like simply removing the falsy values from {} should result in never, right?
Is the proposal to always revert back to the declared type in a false branch when narrowing on truthiness?
I think the bug is that {} is incorrectly (IMO) considered "always truthy" which seems wrong to me as you can assign 0 or "" to it just fine.
So today:
letx: string= ...;if(!x){// x is still `string`}lety: number= ...;if(!y){// y is still `number`}
To the extent that ^^^ is the current behavior, I think {} should be treated the same, so, yes, I think {} should not be narrowed out in the false branches.
Ideally, since we have literally types now, I think all false branches should be something like originalType & (0 | "" | undefined | null | false), so x in above would become "" and y would become 0, but that probably requires some machinery that is not in the system today.
Nevertheless, I think a better approach here is to use object | string | number | boolean instead of {} (I am assuming here that is what you originally intended)
constx: object|string|number|boolean=Symbol();// does not type checkconstx: {}=Symbol();// type checks
It seems problematic for us to need to maintain a union of all of the possible primitive types ourselves.
From my perspective, there are a few different things we might be trying to express with the core types:
"indexable" (you are allowed to use [] or .x at runtime)
anything other than null or undefined
hard to reliably express in TypeScript without enumerating a list of the primitive types
"nullish"
null | undefined
| void?
"falsy"
null | undefined | false | 0 | ""
"truthy"
anything not falsy
afaict impossible to express in TypeScript
We typically try to wrangle these types when we start new projects in TypeScript, and we started to formalize our own usage in ts-std. Our core.ts file has the types we use most often that aren't already in TypeScript: ts-std/src/core.ts.
We are happy to use whatever unions work to express the most common concepts, and got the idea for ts-std's unknown type from a very helpful discussion with Mohamed Hegazy (@mhegazy) early on in our TypeScript work.
We're happy to use {} to mean "not nullish", or happy to use a sanctioned union to mean that, but we really would like a canonical answer or implementation from the TypeScript team 😄
symbol is another primitive type. i would say {} is overly general in many cases. if i were starting over i would be more explicit, i.e. type primitive = number | string | boolean | symbol | undefined | null | void | object to mean {} | null | undefined.
I would like to fix this issue too, but will need to see what breaks. i am sure some one somewhere depends on this behavior inadvertently.
We agree that there is a bug with narrowing in {}. Specifically, just as (! operation) & string = string and (! operation) & number = number, (! operation) & {} should = {}.
Mohamed Hegazy (@mhegazy) proposed that we should just try to avoid using {}, and instead enumerate the types that {} is expressing. However, the list of those types is not permanently bounded. For example, symbol was recently introduced by JavaScript, which now must be included in such a list.
It seems that there are two options:
Fix the bug in {} and say that it means "the union of all not-null, not-undefined types"
Introduce a new built-in type that means "the union of all not-null, not-undefined types" that doesn't have the bug.
It is worth noting that JavaScript is adding a new primitive type (BigInt) that has typeof bigint === 'bigint' and has ToBoolean(0n) === false, so there will be additional types that are subject to this problem over time.
unknown still includes null and undefined, though; so it'd be nice if there was a simple way to refer to object | string | boolean | number | symbol | bigint etc.
Activity
DanielRosenwasser commentedon Sep 1, 2017
Is the proposal to always revert back to the declared type in a false branch when narrowing on truthiness?
DanielRosenwasser commentedon Sep 1, 2017
Or perhaps to just not narrow in the false branch?
wycats commentedon Sep 1, 2017
Maybe there's something we're misunderstanding about
{}, but it doesn't seem like simply removing the falsy values from{}should result innever, right?chancancode commentedon Sep 1, 2017
I think the bug is that
{}is incorrectly (IMO) considered "always truthy" which seems wrong to me as you can assign0or""to it just fine.So today:
To the extent that ^^^ is the current behavior, I think
{}should be treated the same, so, yes, I think{}should not be narrowed out in the false branches.Ideally, since we have literally types now, I think all false branches should be something like
originalType & (0 | "" | undefined | null | false), soxin above would become""andywould become0, but that probably requires some machinery that is not in the system today.mhegazy commentedon Sep 5, 2017
Technically you are correct in the case of
{}.Nevertheless, I think a better approach here is to use
object | string | number | booleaninstead of{}(I am assuming here that is what you originally intended)wycats commentedon Sep 5, 2017
Mohamed Hegazy (@mhegazy) at minimum, your union is missing
symbol.It seems problematic for us to need to maintain a union of all of the possible primitive types ourselves.
From my perspective, there are a few different things we might be trying to express with the core types:
[]or.xat runtime)nullorundefinednull | undefined| void?null | undefined | false | 0 | ""We typically try to wrangle these types when we start new projects in TypeScript, and we started to formalize our own usage in ts-std. Our
core.tsfile has the types we use most often that aren't already in TypeScript: ts-std/src/core.ts.We are happy to use whatever unions work to express the most common concepts, and got the idea for
ts-std'sunknowntype from a very helpful discussion with Mohamed Hegazy (@mhegazy) early on in our TypeScript work.We're happy to use
{}to mean "not nullish", or happy to use a sanctioned union to mean that, but we really would like a canonical answer or implementation from the TypeScript team 😄mhegazy commentedon Sep 5, 2017
symbolis another primitive type. i would say{}is overly general in many cases. if i were starting over i would be more explicit, i.e.type primitive = number | string | boolean | symbol | undefined | null | void | objectto mean{} | null | undefined.I would like to fix this issue too, but will need to see what breaks. i am sure some one somewhere depends on this behavior inadvertently.
DanielRosenwasser commentedon Sep 7, 2017
Side note: the list of falsy types needs to include:
NaN.chancancode commentedon Sep 7, 2017
Daniel Rosenwasser (@DanielRosenwasser) that is correct. However, as far as I can tell, NaN is not a literal type in TypeScript. Is there a trick?
mhegazy commentedon Sep 7, 2017
NaNis not a type. the valueNaNis of typenumber. andNaNis funny from a typing perspective, so let's not go there right now..wycats commentedon Sep 7, 2017
Sure, let's leave
NaNout for the time being.To recap:
We agree that there is a bug with narrowing in
{}. Specifically, just as(! operation) & string = stringand(! operation) & number = number,(! operation) & {}should= {}.Mohamed Hegazy (@mhegazy) proposed that we should just try to avoid using
{}, and instead enumerate the types that{}is expressing. However, the list of those types is not permanently bounded. For example,symbolwas recently introduced by JavaScript, which now must be included in such a list.It seems that there are two options:
{}and say that it means "the union of all not-null, not-undefined types"It is worth noting that JavaScript is adding a new primitive type (BigInt) that has
typeof bigint === 'bigint'and hasToBoolean(0n) === false, so there will be additional types that are subject to this problem over time.Fix `unknown` as per microsoft/TypeScript#18196
DanielRosenwasser commentedon Sep 14, 2018
Hey Godfrey Chan (@chancancode) and Yehuda Katz (@wycats), now that
unknownexists, I'd like to know whether you still end up running into this scenario.ljharb commentedon Sep 14, 2018
unknownstill includesnullandundefined, though; so it'd be nice if there was a simple way to refer toobject | string | boolean | number | symbol | bigintetc.