Allow distinct string enum members with identical property names to form unions in mapped types (#39101)

This commit is contained in:
Andrew Branch 2020-06-29 10:08:17 -07:00 committed by GitHub
parent 3853bf5273
commit 58ed610ef1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 307 additions and 15 deletions

View File

@ -10253,22 +10253,32 @@ namespace ts {
// Otherwise, for type string create a string index signature.
if (isTypeUsableAsPropertyName(t)) {
const propName = getPropertyNameFromType(t);
const modifiersProp = getPropertyOfType(modifiersType, propName);
const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional ||
!(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional);
const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly ||
!(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersProp && isReadonlySymbol(modifiersProp));
const stripOptional = strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional;
const prop = <MappedSymbol>createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName,
CheckFlags.Mapped | (isReadonly ? CheckFlags.Readonly : 0) | (stripOptional ? CheckFlags.StripOptional : 0));
prop.mappedType = type;
prop.mapper = templateMapper;
if (modifiersProp) {
prop.syntheticOrigin = modifiersProp;
prop.declarations = modifiersProp.declarations;
// String enum members from separate enums with identical values
// are distinct types with the same property name. Make the resulting
// property symbol's name type be the union of those enum member types.
const existingProp = members.get(propName) as MappedSymbol | undefined;
if (existingProp) {
existingProp.nameType = getUnionType([existingProp.nameType!, t]);
existingProp.mapper = appendTypeMapping(type.mapper, typeParameter, existingProp.nameType);
}
else {
const modifiersProp = getPropertyOfType(modifiersType, propName);
const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional ||
!(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional);
const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly ||
!(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersProp && isReadonlySymbol(modifiersProp));
const stripOptional = strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional;
const prop = <MappedSymbol>createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName,
CheckFlags.Mapped | (isReadonly ? CheckFlags.Readonly : 0) | (stripOptional ? CheckFlags.StripOptional : 0));
prop.mappedType = type;
if (modifiersProp) {
prop.syntheticOrigin = modifiersProp;
prop.declarations = modifiersProp.declarations;
}
prop.nameType = t;
prop.mapper = templateMapper;
members.set(propName, prop);
}
prop.nameType = t;
members.set(propName, prop);
}
else if (t.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.Enum)) {
const propType = instantiateType(templateType, templateMapper);

View File

@ -0,0 +1,59 @@
//// [mappedTypeOverlappingStringEnumKeys.ts]
// #37859
enum TerrestrialAnimalTypes {
CAT = "cat",
DOG = "dog"
};
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 CatMap = {
[V in AnimalTypes]: Extract<Cats, { type: V }>[]
};
const catMap: CatMap = {
cat: [
{ type: TerrestrialAnimalTypes.CAT, address: "" },
{ type: AlienAnimalTypes.CAT, planet: "" }
],
dog: [] as never[]
};
//// [mappedTypeOverlappingStringEnumKeys.js]
// #37859
var TerrestrialAnimalTypes;
(function (TerrestrialAnimalTypes) {
TerrestrialAnimalTypes["CAT"] = "cat";
TerrestrialAnimalTypes["DOG"] = "dog";
})(TerrestrialAnimalTypes || (TerrestrialAnimalTypes = {}));
;
var AlienAnimalTypes;
(function (AlienAnimalTypes) {
AlienAnimalTypes["CAT"] = "cat";
})(AlienAnimalTypes || (AlienAnimalTypes = {}));
;
var catMap = {
cat: [
{ type: TerrestrialAnimalTypes.CAT, address: "" },
{ type: AlienAnimalTypes.CAT, planet: "" }
],
dog: []
};

View File

@ -0,0 +1,96 @@
=== tests/cases/conformance/types/mapped/mappedTypeOverlappingStringEnumKeys.ts ===
// #37859
enum TerrestrialAnimalTypes {
>TerrestrialAnimalTypes : Symbol(TerrestrialAnimalTypes, Decl(mappedTypeOverlappingStringEnumKeys.ts, 0, 0))
CAT = "cat",
>CAT : Symbol(TerrestrialAnimalTypes.CAT, Decl(mappedTypeOverlappingStringEnumKeys.ts, 2, 29))
DOG = "dog"
>DOG : Symbol(TerrestrialAnimalTypes.DOG, Decl(mappedTypeOverlappingStringEnumKeys.ts, 3, 14))
};
enum AlienAnimalTypes {
>AlienAnimalTypes : Symbol(AlienAnimalTypes, Decl(mappedTypeOverlappingStringEnumKeys.ts, 5, 2))
CAT = "cat",
>CAT : Symbol(AlienAnimalTypes.CAT, Decl(mappedTypeOverlappingStringEnumKeys.ts, 7, 23))
};
type AnimalTypes = TerrestrialAnimalTypes | AlienAnimalTypes;
>AnimalTypes : Symbol(AnimalTypes, Decl(mappedTypeOverlappingStringEnumKeys.ts, 9, 2))
>TerrestrialAnimalTypes : Symbol(TerrestrialAnimalTypes, Decl(mappedTypeOverlappingStringEnumKeys.ts, 0, 0))
>AlienAnimalTypes : Symbol(AlienAnimalTypes, Decl(mappedTypeOverlappingStringEnumKeys.ts, 5, 2))
interface TerrestrialCat {
>TerrestrialCat : Symbol(TerrestrialCat, Decl(mappedTypeOverlappingStringEnumKeys.ts, 11, 61))
type: TerrestrialAnimalTypes.CAT;
>type : Symbol(TerrestrialCat.type, Decl(mappedTypeOverlappingStringEnumKeys.ts, 13, 26))
>TerrestrialAnimalTypes : Symbol(TerrestrialAnimalTypes, Decl(mappedTypeOverlappingStringEnumKeys.ts, 0, 0))
>CAT : Symbol(TerrestrialAnimalTypes.CAT, Decl(mappedTypeOverlappingStringEnumKeys.ts, 2, 29))
address: string;
>address : Symbol(TerrestrialCat.address, Decl(mappedTypeOverlappingStringEnumKeys.ts, 14, 35))
}
interface AlienCat {
>AlienCat : Symbol(AlienCat, Decl(mappedTypeOverlappingStringEnumKeys.ts, 16, 1))
type: AlienAnimalTypes.CAT
>type : Symbol(AlienCat.type, Decl(mappedTypeOverlappingStringEnumKeys.ts, 18, 20))
>AlienAnimalTypes : Symbol(AlienAnimalTypes, Decl(mappedTypeOverlappingStringEnumKeys.ts, 5, 2))
>CAT : Symbol(AlienAnimalTypes.CAT, Decl(mappedTypeOverlappingStringEnumKeys.ts, 7, 23))
planet: string;
>planet : Symbol(AlienCat.planet, Decl(mappedTypeOverlappingStringEnumKeys.ts, 19, 28))
}
type Cats = TerrestrialCat | AlienCat;
>Cats : Symbol(Cats, Decl(mappedTypeOverlappingStringEnumKeys.ts, 21, 1))
>TerrestrialCat : Symbol(TerrestrialCat, Decl(mappedTypeOverlappingStringEnumKeys.ts, 11, 61))
>AlienCat : Symbol(AlienCat, Decl(mappedTypeOverlappingStringEnumKeys.ts, 16, 1))
type CatMap = {
>CatMap : Symbol(CatMap, Decl(mappedTypeOverlappingStringEnumKeys.ts, 23, 38))
[V in AnimalTypes]: Extract<Cats, { type: V }>[]
>V : Symbol(V, Decl(mappedTypeOverlappingStringEnumKeys.ts, 26, 3))
>AnimalTypes : Symbol(AnimalTypes, Decl(mappedTypeOverlappingStringEnumKeys.ts, 9, 2))
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
>Cats : Symbol(Cats, Decl(mappedTypeOverlappingStringEnumKeys.ts, 21, 1))
>type : Symbol(type, Decl(mappedTypeOverlappingStringEnumKeys.ts, 26, 37))
>V : Symbol(V, Decl(mappedTypeOverlappingStringEnumKeys.ts, 26, 3))
};
const catMap: CatMap = {
>catMap : Symbol(catMap, Decl(mappedTypeOverlappingStringEnumKeys.ts, 29, 5))
>CatMap : Symbol(CatMap, Decl(mappedTypeOverlappingStringEnumKeys.ts, 23, 38))
cat: [
>cat : Symbol(cat, Decl(mappedTypeOverlappingStringEnumKeys.ts, 29, 24))
{ type: TerrestrialAnimalTypes.CAT, address: "" },
>type : Symbol(type, Decl(mappedTypeOverlappingStringEnumKeys.ts, 31, 5))
>TerrestrialAnimalTypes.CAT : Symbol(TerrestrialAnimalTypes.CAT, Decl(mappedTypeOverlappingStringEnumKeys.ts, 2, 29))
>TerrestrialAnimalTypes : Symbol(TerrestrialAnimalTypes, Decl(mappedTypeOverlappingStringEnumKeys.ts, 0, 0))
>CAT : Symbol(TerrestrialAnimalTypes.CAT, Decl(mappedTypeOverlappingStringEnumKeys.ts, 2, 29))
>address : Symbol(address, Decl(mappedTypeOverlappingStringEnumKeys.ts, 31, 39))
{ type: AlienAnimalTypes.CAT, planet: "" }
>type : Symbol(type, Decl(mappedTypeOverlappingStringEnumKeys.ts, 32, 5))
>AlienAnimalTypes.CAT : Symbol(AlienAnimalTypes.CAT, Decl(mappedTypeOverlappingStringEnumKeys.ts, 7, 23))
>AlienAnimalTypes : Symbol(AlienAnimalTypes, Decl(mappedTypeOverlappingStringEnumKeys.ts, 5, 2))
>CAT : Symbol(AlienAnimalTypes.CAT, Decl(mappedTypeOverlappingStringEnumKeys.ts, 7, 23))
>planet : Symbol(planet, Decl(mappedTypeOverlappingStringEnumKeys.ts, 32, 33))
],
dog: [] as never[]
>dog : Symbol(dog, Decl(mappedTypeOverlappingStringEnumKeys.ts, 33, 4))
};

View File

@ -0,0 +1,91 @@
=== tests/cases/conformance/types/mapped/mappedTypeOverlappingStringEnumKeys.ts ===
// #37859
enum TerrestrialAnimalTypes {
>TerrestrialAnimalTypes : TerrestrialAnimalTypes
CAT = "cat",
>CAT : TerrestrialAnimalTypes.CAT
>"cat" : "cat"
DOG = "dog"
>DOG : TerrestrialAnimalTypes.DOG
>"dog" : "dog"
};
enum AlienAnimalTypes {
>AlienAnimalTypes : AlienAnimalTypes
CAT = "cat",
>CAT : AlienAnimalTypes.CAT
>"cat" : "cat"
};
type AnimalTypes = TerrestrialAnimalTypes | AlienAnimalTypes;
>AnimalTypes : AnimalTypes
interface TerrestrialCat {
type: TerrestrialAnimalTypes.CAT;
>type : TerrestrialAnimalTypes.CAT
>TerrestrialAnimalTypes : any
address: string;
>address : string
}
interface AlienCat {
type: AlienAnimalTypes.CAT
>type : AlienAnimalTypes
>AlienAnimalTypes : any
planet: string;
>planet : string
}
type Cats = TerrestrialCat | AlienCat;
>Cats : Cats
type CatMap = {
>CatMap : CatMap
[V in AnimalTypes]: Extract<Cats, { type: V }>[]
>type : V
};
const catMap: CatMap = {
>catMap : CatMap
>{ cat: [ { type: TerrestrialAnimalTypes.CAT, address: "" }, { type: AlienAnimalTypes.CAT, planet: "" } ], dog: [] as never[]} : { cat: ({ type: TerrestrialAnimalTypes.CAT; address: string; } | { type: AlienAnimalTypes.CAT; planet: string; })[]; dog: never[]; }
cat: [
>cat : ({ type: TerrestrialAnimalTypes.CAT; address: string; } | { type: AlienAnimalTypes.CAT; planet: string; })[]
>[ { type: TerrestrialAnimalTypes.CAT, address: "" }, { type: AlienAnimalTypes.CAT, planet: "" } ] : ({ type: TerrestrialAnimalTypes.CAT; address: string; } | { type: AlienAnimalTypes.CAT; planet: string; })[]
{ type: TerrestrialAnimalTypes.CAT, address: "" },
>{ type: TerrestrialAnimalTypes.CAT, address: "" } : { type: TerrestrialAnimalTypes.CAT; address: string; }
>type : TerrestrialAnimalTypes.CAT
>TerrestrialAnimalTypes.CAT : TerrestrialAnimalTypes.CAT
>TerrestrialAnimalTypes : typeof TerrestrialAnimalTypes
>CAT : TerrestrialAnimalTypes.CAT
>address : string
>"" : ""
{ type: AlienAnimalTypes.CAT, planet: "" }
>{ type: AlienAnimalTypes.CAT, planet: "" } : { type: AlienAnimalTypes.CAT; planet: string; }
>type : AlienAnimalTypes.CAT
>AlienAnimalTypes.CAT : AlienAnimalTypes
>AlienAnimalTypes : typeof AlienAnimalTypes
>CAT : AlienAnimalTypes
>planet : string
>"" : ""
],
dog: [] as never[]
>dog : never[]
>[] as never[] : never[]
>[] : undefined[]
};

View File

@ -0,0 +1,36 @@
// #37859
enum TerrestrialAnimalTypes {
CAT = "cat",
DOG = "dog"
};
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 CatMap = {
[V in AnimalTypes]: Extract<Cats, { type: V }>[]
};
const catMap: CatMap = {
cat: [
{ type: TerrestrialAnimalTypes.CAT, address: "" },
{ type: AlienAnimalTypes.CAT, planet: "" }
],
dog: [] as never[]
};