TypeScript Version: 3.8.3
Search Terms:
Expected behavior:
CatMap should have type
type CatMap = {
cat: TerrestrialCat[] | AlienCat[];
}
Actual behavior:
CatMap has type
type CatMap = {
cat: AlienCat[];
}
Related Issues:
Code
enum TerrestrialAnimalTypes {
CAT = "cat",
};
enum AlienAnimalTypes {
CAT = "cat",
};
type AnimalTypes = TerrestrialAnimalTypes | AlienAnimalTypes;
interface TerrestrialCat {
type: TerrestrialAnimalTypes.CAT;
address: string;
}
interface AlienCat {
type: AlienAnimalTypes.CAT
planet: string;
}
type Cats = TerrestrialCat | AlienCat;
// type A = TerrestrialCat | AlienCat
type A = Extract<Cats, { type: "cat" }>;
/*
* type CatMap = {
* cat: AlienCat[];
* }
*/
type CatMap = {
[V in AnimalTypes]: Extract<Cats, { type: V }>[]
};
Output
"use strict";
var AnimalTypes;
(function (AnimalTypes) {
AnimalTypes["CAT"] = "cat";
})(AnimalTypes || (AnimalTypes = {}));
;
Compiler Options
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"useDefineForClassFields": false,
"alwaysStrict": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"downlevelIteration": false,
"noEmitHelpers": false,
"noLib": false,
"noStrictGenericChecks": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"esModuleInterop": true,
"preserveConstEnums": false,
"removeComments": false,
"skipLibCheck": false,
"checkJs": false,
"allowJs": false,
"declaration": true,
"experimentalDecorators": false,
"emitDecoratorMetadata": false,
"target": "ES2017",
"module": "ESNext"
}
}
Playground Link: Provided
@andrewbranch I'm not 100% convinced this is a bug, but it's possible
I... guess this is a bug? When we resolve mapped type members from a union constraint type like we have here, we build up a symbol table of members, with string keys corresponding to the constituents of the union. Since both TerrestrialAnimalTypes.CAT and AlienAnimalTypes.CAT have the property name 'cat', we do create two separate member symbols, but then we simply (accidentally?) overwrite the first with the second in the table.
(The bit that happens in Extract is _not_ a bug; the assignability of identically-valued but separately-declared string enums is well-defined.)
It鈥檚 only a few lines of code to check for an existing property by the same name and fix up its nameType and mapper such that it produces a union type, and no existing tests break. I _think_ that鈥檚 reasonable and the result looks like desirable behavior.
Here鈥檚 a smaller example to consider:
enum TerrestrialAnimalTypes { CAT = "cat" };
enum AlienAnimalTypes { CAT = "cat" };
type T = { [K in TerrestrialAnimalTypes | AlienAnimalTypes]: K };
Today, T is { cat: AlienAnimalTypes }. In my branch, T is { cat: AlienAnimalTypes | TerrestrialAnimalTypes }. It feels slightly paradoxical that K could itself instantiate to a union, but I can鈥檛 see anything wrong with it.
Most helpful comment
I... guess this is a bug? When we resolve mapped type members from a union constraint type like we have here, we build up a symbol table of members, with string keys corresponding to the constituents of the union. Since both
TerrestrialAnimalTypes.CATandAlienAnimalTypes.CAThave the property name'cat', we do create two separate member symbols, but then we simply (accidentally?) overwrite the first with the second in the table.(The bit that happens in
Extractis _not_ a bug; the assignability of identically-valued but separately-declared string enums is well-defined.)It鈥檚 only a few lines of code to check for an existing property by the same name and fix up its
nameTypeandmappersuch that it produces a union type, and no existing tests break. I _think_ that鈥檚 reasonable and the result looks like desirable behavior.Here鈥檚 a smaller example to consider:
Today,
Tis{ cat: AlienAnimalTypes }. In my branch,Tis{ cat: AlienAnimalTypes | TerrestrialAnimalTypes }. It feels slightly paradoxical thatKcould itself instantiate to a union, but I can鈥檛 see anything wrong with it.