Allow nongeneric string mapping types to exist (#47050)

* Allow nongeneric string mapping types to exist

* Accept baseline

* Recusive membership testing function

* Fix lint

* Add @DanielRosenwasser's comment
This commit is contained in:
Wesley Wigham
2022-06-16 17:02:31 -07:00
committed by GitHub
parent 86d5040031
commit 5c4caafc2a
17 changed files with 1731 additions and 57 deletions

View File

@@ -12182,7 +12182,7 @@ namespace ts {
}
if (t.flags & TypeFlags.StringMapping) {
const constraint = getBaseConstraint((t as StringMappingType).type);
return constraint ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType;
return constraint && constraint !== (t as StringMappingType).type ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType;
}
if (t.flags & TypeFlags.IndexedAccess) {
if (isMappedTypeGenericIndexedAccess(t)) {
@@ -15381,8 +15381,11 @@ namespace ts {
function getStringMappingType(symbol: Symbol, type: Type): Type {
return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) :
isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) :
// Mapping<Mapping<T>> === Mapping<T>
type.flags & TypeFlags.StringMapping && symbol === type.symbol ? type :
isGenericIndexType(type) || isPatternLiteralPlaceholderType(type) ? getStringMappingTypeForGenericType(symbol, isPatternLiteralPlaceholderType(type) && !(type.flags & TypeFlags.StringMapping) ? getTemplateLiteralType(["", ""], [type]) : type) :
type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) :
type.flags & TypeFlags.TemplateLiteral ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) :
type;
}
@@ -15396,6 +15399,16 @@ namespace ts {
return str;
}
function applyTemplateStringMapping(symbol: Symbol, texts: readonly string[], types: readonly Type[]): [texts: readonly string[], types: readonly Type[]] {
switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
case IntrinsicTypeKind.Uppercase: return [texts.map(t => t.toUpperCase()), types.map(t => getStringMappingType(symbol, t))];
case IntrinsicTypeKind.Lowercase: return [texts.map(t => t.toLowerCase()), types.map(t => getStringMappingType(symbol, t))];
case IntrinsicTypeKind.Capitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toUpperCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types];
case IntrinsicTypeKind.Uncapitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toLowerCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types];
}
return [texts, types];
}
function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type {
const id = `${getSymbolId(symbol)},${getTypeId(type)}`;
let result = stringMappingTypes.get(id);
@@ -15651,8 +15664,8 @@ namespace ts {
accessNode;
}
function isPatternLiteralPlaceholderType(type: Type) {
return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt));
function isPatternLiteralPlaceholderType(type: Type): boolean {
return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || !!(type.flags & TypeFlags.StringMapping && isPatternLiteralPlaceholderType((type as StringMappingType).type));
}
function isPatternLiteralType(type: Type) {
@@ -19613,6 +19626,13 @@ namespace ts {
return Ternary.True;
}
}
else if (target.flags & TypeFlags.StringMapping) {
if (!(source.flags & TypeFlags.StringMapping)) {
if (isMemberOfStringMapping(source, target)) {
return Ternary.True;
}
}
}
if (sourceFlags & TypeFlags.TypeVariable) {
// IndexedAccess comparisons are handled above in the `targetFlags & TypeFlage.IndexedAccess` branch
@@ -19657,7 +19677,10 @@ namespace ts {
}
}
else if (sourceFlags & TypeFlags.StringMapping) {
if (targetFlags & TypeFlags.StringMapping && (source as StringMappingType).symbol === (target as StringMappingType).symbol) {
if (targetFlags & TypeFlags.StringMapping) {
if ((source as StringMappingType).symbol !== (target as StringMappingType).symbol) {
return Ternary.False;
}
if (result = isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, RecursionFlags.Both, reportErrors)) {
resetErrorInfo(saveErrorInfo);
return result;
@@ -20611,7 +20634,7 @@ namespace ts {
}
}
return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral);
return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral) || !!(type.flags & TypeFlags.StringMapping);
}
function getExactOptionalUnassignableProperties(source: Type, target: Type) {
@@ -22171,6 +22194,32 @@ namespace ts {
&& (!roundTripOnly || s === pseudoBigIntToString({ negative, base10Value: parsePseudoBigInt(scanner.getTokenValue()) }));
}
function isMemberOfStringMapping(source: Type, target: Type): boolean {
if (target.flags & (TypeFlags.String | TypeFlags.AnyOrUnknown)) {
return true;
}
if (target.flags & TypeFlags.TemplateLiteral) {
return isTypeAssignableTo(source, target);
}
if (target.flags & TypeFlags.StringMapping) {
// We need to see whether applying the same mappings of the target
// onto the source would produce an identical type *and* that
// it's compatible with the inner-most non-string-mapped type.
//
// The intuition here is that if same mappings don't affect the source at all,
// and the source is compatible with the unmapped target, then they must
// still reside in the same domain.
const mappingStack = [];
while (target.flags & TypeFlags.StringMapping) {
mappingStack.unshift(target.symbol);
target = (target as StringMappingType).type;
}
const mappedSource = reduceLeft(mappingStack, (memo, value) => getStringMappingType(value, memo), source);
return mappedSource === source && isMemberOfStringMapping(source, target);
}
return false;
}
function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean {
if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) {
return true;
@@ -22179,7 +22228,8 @@ namespace ts {
const value = (source as StringLiteralType).value;
return !!(target.flags & TypeFlags.Number && isValidNumberString(value, /*roundTripOnly*/ false) ||
target.flags & TypeFlags.BigInt && isValidBigIntString(value, /*roundTripOnly*/ false) ||
target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName);
target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName ||
target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(getStringLiteralType(value), target));
}
if (source.flags & TypeFlags.TemplateLiteral) {
const texts = (source as TemplateLiteralType).texts;