Repository navigation
Bad type checking for GenericType<boolean> vs GenericType<false> #33833
Description
TypeScript Version: 3.7.0-dev.20191006
Search Terms: covariance, subtype, inference, value type (... honestly I have no idea what the right term for "false when used as a type" is, it's... probably not this though).
Code:
class Grid<T> {
constructor(width: number, height: number, defaultFill: T) { }
// some function which either behaves as a fill or an in-place equivalent of map, for example
fillWith(valueOrCallback: T | ((current: T, x: number, y: number, idx: number) => T)) {}
}
function takesBoolGrid(grid: Grid<boolean>) { }
// This causes a type error
takesBoolGrid(new Grid(16, 16, false));
// But this doesn't
let g = new Grid(16, 16, false);
takesBoolGrid(g);Expected behavior: The code above should typecheck. Or, at the very least, both cases should fail, instead of assigning to an intermediate variable fixing the issue.
Actual behavior: I get an error like the following on the first call to takesBoolGrid:
Argument of type 'Grid<false>' is not assignable to parameter of type 'Grid<boolean>'.
Types of property 'fillWith' are incompatible.
Type '(valueOrCallback: false | ((current: false, x: number, y: number, idx: number) => false)) => void' is not assignable to type '(valueOrCallback: boolean | ((current: boolean, x: number, y: number, idx: number) => boolean)) => void'.
Types of parameters 'valueOrCallback' and 'valueOrCallback' are incompatible.
Type 'boolean | ((current: boolean, x: number, y: number, idx: number) => boolean)' is not assignable to type 'false | ((current: false, x: number, y: number, idx: number) => false)'.
Type 'true' is not assignable to type 'false | ((current: false, x: number, y: number, idx: number) => false)'.(2345)
Related Issues: I looked, but sadly my search terms quickly brought me to a bunch of issues that assume more familiarity with tsc (and type system jargon, probably) than I have. (This is probably because the only terminology I have for describing this problem is, well, type system jargon that I only barely can say I understand).
Some notes that don't fit above which might help, possibly: this code used to work in like, typescript 2.something (I could probably find out which version it was on Monday if it would help).
I updated the code to the new typescript, and got this error, and assumed it was just typescript getting updated with some stricter behavior around variance. I complained online, and was asked to produce an example, and it turned out that the issue was weirder than I had though, as:
- It only errored if the fillWith function was present
- It didn't error if I assign the Grid to an intermediate variable.
Point 2 seems weird, so I decided to file a bug.
Note that (IMO fairly unsurprisingly) the expression takesBoolGrid(new Grid<boolean>(16, 16, false)) type checks fine, e.g. if the type is listed explicitly, no intermediate variable is needed. So I guess it's some kind of type inference thing too? Who knows... (hopefully someone here!)
Activity
jack-williams commentedon Oct 6, 2019
Could well be caused by #29478, and this may be a duplicate of #30390.
It seems that the argument type in the signature of
takesBoolGridis contextually typing the argument which prevents widening offalse. The problem is thatGridis invariant inT, so restricting tofalserather than widening to typebooleanmakes the types unrelated.Not really sure how best to fix this. I wonder if variance markers can be used to control widening: a boolean type that instantiates an invariant type parameter should not produce a non-widening contextual type.
xiaoxiangmoe commentedon Oct 8, 2019
I think both cases should fail. Because
Gridis invariant inT.thomcc commentedon Oct 13, 2019
ZHAO Jin-Xiang (@xiaoxiangmoe) Ideally the type would be inferred as
Grid<boolean>and notGrid<false>here.ahejlsberg commentedon Oct 16, 2019
This is a duplicate of #30390.
typescript-bot commentedon Oct 19, 2019
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.