Skip to content

Commit 7ccbbfc

Browse files
committed
Preserve literal types only when contextual type has literals of same kind
1 parent e934c30 commit 7ccbbfc

1 file changed

Lines changed: 25 additions & 11 deletions

File tree

src/compiler/checker.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10680,7 +10680,7 @@ namespace ts {
1068010680
}
1068110681

1068210682
function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type) {
10683-
if (!isLiteralLikeContextualType(contextualType)) {
10683+
if (!isLiteralOfContextualType(type, contextualType)) {
1068410684
type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type));
1068510685
}
1068610686
return type;
@@ -18901,19 +18901,33 @@ namespace ts {
1890118901
isTypeAssertion(declaration.initializer) ? type : getWidenedLiteralType(type);
1890218902
}
1890318903

18904-
function isLiteralLikeContextualType(contextualType: Type) {
18904+
function isLiteralOfContextualType(candidateType: Type, contextualType: Type): boolean {
1890518905
if (contextualType) {
18906+
if (contextualType.flags & TypeFlags.Union && !(contextualType.flags & TypeFlags.Boolean)) {
18907+
// If the contextual type is a union containing both of the 'true' and 'false' types we
18908+
// don't consider it a literal context for boolean literals.
18909+
const types = (<UnionType>contextualType).types;
18910+
return some(types, t =>
18911+
!(t.flags & TypeFlags.BooleanLiteral && containsType(types, trueType) && containsType(types, falseType)) &&
18912+
isLiteralOfContextualType(candidateType, t));
18913+
}
1890618914
if (contextualType.flags & TypeFlags.TypeVariable) {
18915+
// If the contextual type is a type variable constrained to a primitive type, consider
18916+
// this a literal context for literals of that primitive type. For example, given a
18917+
// type parameter 'T extends string', infer string literal types for T.
1890718918
const constraint = getBaseConstraintOfType(contextualType) || emptyObjectType;
18908-
// If the type parameter is constrained to the base primitive type we're checking for,
18909-
// consider this a literal context. For example, given a type parameter 'T extends string',
18910-
// this causes us to infer string literal types for T.
18911-
if (constraint.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.Boolean | TypeFlags.Enum | TypeFlags.ESSymbol)) {
18912-
return true;
18913-
}
18914-
contextualType = constraint;
18915-
}
18916-
return maybeTypeOfKind(contextualType, (TypeFlags.Literal | TypeFlags.Index | TypeFlags.UniqueESSymbol));
18919+
return constraint.flags & TypeFlags.String && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
18920+
constraint.flags & TypeFlags.Number && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
18921+
constraint.flags & TypeFlags.Boolean && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) ||
18922+
constraint.flags & TypeFlags.ESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) ||
18923+
isLiteralOfContextualType(candidateType, constraint);
18924+
}
18925+
// If the contextual type is a literal of a particular primitive type, we consider this a
18926+
// literal context for all literals of that primitive type.
18927+
return contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
18928+
contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
18929+
contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) ||
18930+
contextualType.flags & TypeFlags.UniqueESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol);
1891718931
}
1891818932
return false;
1891918933
}

0 commit comments

Comments
 (0)