Skip to content

Commit 7d4a187

Browse files
committed
Track freshness of string and numeric literals
1 parent 95c3ecc commit 7d4a187

2 files changed

Lines changed: 82 additions & 27 deletions

File tree

src/compiler/checker.ts

Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3838,6 +3838,14 @@ namespace ts {
38383838
return true;
38393839
}
38403840

3841+
function createEnumLiteralType(symbol: Symbol, baseType: EnumType, text: string) {
3842+
const type = <EnumLiteralType>createType(TypeFlags.EnumLiteral);
3843+
type.symbol = symbol;
3844+
type.baseType = <EnumType & UnionType>baseType;
3845+
type.text = text;
3846+
return type;
3847+
}
3848+
38413849
function getDeclaredTypeOfEnum(symbol: Symbol): Type {
38423850
const links = getSymbolLinks(symbol);
38433851
if (!links.declaredType) {
@@ -3853,10 +3861,7 @@ namespace ts {
38533861
const memberSymbol = getSymbolOfNode(member);
38543862
const value = getEnumMemberValue(member);
38553863
if (!memberTypes[value]) {
3856-
const memberType = memberTypes[value] = <EnumLiteralType>createType(TypeFlags.EnumLiteral);
3857-
memberType.symbol = memberSymbol;
3858-
memberType.baseType = <EnumType & UnionType>enumType;
3859-
memberType.text = "" + value;
3864+
const memberType = memberTypes[value] = createEnumLiteralType(memberSymbol, enumType, "" + value);
38603865
memberTypeList.push(memberType);
38613866
}
38623867
}
@@ -5335,6 +5340,7 @@ namespace ts {
53355340
containsUndefined?: boolean;
53365341
containsNull?: boolean;
53375342
containsNonWideningType?: boolean;
5343+
containsStringOrNumberLiteral?: boolean;
53385344
}
53395345

53405346
function binarySearchTypes(types: Type[], type: Type): number {
@@ -5374,6 +5380,7 @@ namespace ts {
53745380
if (!(type.flags & TypeFlags.ContainsWideningType)) typeSet.containsNonWideningType = true;
53755381
}
53765382
else if (!(type.flags & TypeFlags.Never)) {
5383+
if (type.flags & TypeFlags.StringOrNumberLiteral) typeSet.containsStringOrNumberLiteral = true;
53775384
const len = typeSet.length;
53785385
const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearchTypes(typeSet, type);
53795386
if (index < 0) {
@@ -5420,6 +5427,19 @@ namespace ts {
54205427
}
54215428
}
54225429

5430+
function removeFreshLiteralTypes(types: Type[]) {
5431+
let i = types.length;
5432+
while (i > 1) {
5433+
i--;
5434+
const t = types[i];
5435+
if (t.flags & TypeFlags.StringOrNumberLiteral && t.flags & TypeFlags.FreshLiteral) {
5436+
if (types[i - 1] === (<LiteralType>t).regularType) {
5437+
orderedRemoveItemAt(types, i);
5438+
}
5439+
}
5440+
}
5441+
}
5442+
54235443
// We sort and deduplicate the constituent types based on object identity. If the subtypeReduction
54245444
// flag is specified we also reduce the constituent type set to only include types that aren't subtypes
54255445
// of other types. Subtype reduction is expensive for large union types and is possible only when union
@@ -5442,6 +5462,9 @@ namespace ts {
54425462
if (subtypeReduction) {
54435463
removeSubtypes(typeSet);
54445464
}
5465+
else if (typeSet.containsStringOrNumberLiteral) {
5466+
removeFreshLiteralTypes(typeSet);
5467+
}
54455468
if (typeSet.length === 0) {
54465469
return typeSet.containsNull ? typeSet.containsNonWideningType ? nullType : nullWideningType :
54475470
typeSet.containsUndefined ? typeSet.containsNonWideningType ? undefinedType : undefinedWideningType :
@@ -5549,10 +5572,21 @@ namespace ts {
55495572

55505573
function createLiteralType(flags: TypeFlags, text: string) {
55515574
const type = <LiteralType>createType(flags);
5552-
type.text = text;
5575+
const freshType = <LiteralType>createType(flags | TypeFlags.FreshLiteral);
5576+
type.text = freshType.text = text;
5577+
type.freshType = freshType;
5578+
freshType.regularType = type;
55535579
return type;
55545580
}
55555581

5582+
function getFreshTypeOfLiteralType(type: Type) {
5583+
return type.flags & TypeFlags.StringOrNumberLiteral && !(type.flags & TypeFlags.FreshLiteral) ? (<LiteralType>type).freshType : type;
5584+
}
5585+
5586+
function getRegularTypeOfLiteralType(type: Type) {
5587+
return type.flags & TypeFlags.StringOrNumberLiteral && type.flags & TypeFlags.FreshLiteral ? (<LiteralType>type).regularType : type;
5588+
}
5589+
55565590
function getLiteralTypeForText(flags: TypeFlags, text: string) {
55575591
const map = flags & TypeFlags.StringLiteral ? stringLiteralTypes : numericLiteralTypes;
55585592
return map[text] || (map[text] = createLiteralType(flags, text));
@@ -5561,7 +5595,7 @@ namespace ts {
55615595
function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type {
55625596
const links = getNodeLinks(node);
55635597
if (!links.resolvedType) {
5564-
links.resolvedType = checkExpression(node.literal);
5598+
links.resolvedType = getRegularTypeOfLiteralType(checkExpression(node.literal));
55655599
}
55665600
return links.resolvedType;
55675601
}
@@ -6273,7 +6307,7 @@ namespace ts {
62736307
if ((source.flags & TypeFlags.Number | source.flags & TypeFlags.NumberLiteral) && target.flags & TypeFlags.EnumLike) return true;
62746308
if (source.flags & TypeFlags.EnumLiteral &&
62756309
target.flags & TypeFlags.EnumLiteral &&
6276-
(<LiteralType>source).text === (<LiteralType>target).text &&
6310+
(<EnumLiteralType>source).text === (<EnumLiteralType>target).text &&
62776311
isEnumTypeRelatedTo((<EnumLiteralType>source).baseType, (<EnumLiteralType>target).baseType, errorReporter)) {
62786312
return true;
62796313
}
@@ -6287,6 +6321,12 @@ namespace ts {
62876321
}
62886322

62896323
function isTypeRelatedTo(source: Type, target: Type, relation: Map<RelationComparisonResult>) {
6324+
if (source.flags & TypeFlags.Literal && source.flags & TypeFlags.FreshLiteral) {
6325+
source = (<LiteralType>source).regularType;
6326+
}
6327+
if (target.flags & TypeFlags.Literal && target.flags & TypeFlags.FreshLiteral) {
6328+
target = (<LiteralType>target).regularType;
6329+
}
62906330
if (source === target || relation !== identityRelation && isSimpleTypeRelatedTo(source, target, relation)) {
62916331
return true;
62926332
}
@@ -6384,6 +6424,12 @@ namespace ts {
63846424
// Ternary.False if they are not related.
63856425
function isRelatedTo(source: Type, target: Type, reportErrors?: boolean, headMessage?: DiagnosticMessage): Ternary {
63866426
let result: Ternary;
6427+
if (source.flags & TypeFlags.Literal && source.flags & TypeFlags.FreshLiteral) {
6428+
source = (<LiteralType>source).regularType;
6429+
}
6430+
if (target.flags & TypeFlags.Literal && target.flags & TypeFlags.FreshLiteral) {
6431+
target = (<LiteralType>target).regularType;
6432+
}
63876433
// both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases
63886434
if (source === target) return Ternary.True;
63896435

@@ -6393,7 +6439,7 @@ namespace ts {
63936439

63946440
if (isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;
63956441

6396-
if (source.flags & TypeFlags.FreshObjectLiteral) {
6442+
if (source.flags & TypeFlags.ObjectLiteral && source.flags & TypeFlags.FreshLiteral) {
63976443
if (hasExcessProperties(<FreshObjectLiteralType>source, target, reportErrors)) {
63986444
if (reportErrors) {
63996445
reportRelationError(headMessage, source, target);
@@ -7323,8 +7369,8 @@ namespace ts {
73237369
// no flags for all other types (including non-falsy literal types).
73247370
function getFalsyFlags(type: Type): TypeFlags {
73257371
return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((<UnionType>type).types) :
7326-
type.flags & TypeFlags.StringLiteral ? type === emptyStringType ? TypeFlags.StringLiteral : 0 :
7327-
type.flags & TypeFlags.NumberLiteral ? type === zeroType ? TypeFlags.NumberLiteral : 0 :
7372+
type.flags & TypeFlags.StringLiteral ? (<LiteralType>type).text === "" ? TypeFlags.StringLiteral : 0 :
7373+
type.flags & TypeFlags.NumberLiteral ? (<LiteralType>type).text === "0" ? TypeFlags.NumberLiteral : 0 :
73287374
type.flags & TypeFlags.BooleanLiteral ? type === falseType ? TypeFlags.BooleanLiteral : 0 :
73297375
type.flags & TypeFlags.PossiblyFalsy;
73307376
}
@@ -7391,7 +7437,7 @@ namespace ts {
73917437
* Leave signatures alone since they are not subject to the check.
73927438
*/
73937439
function getRegularTypeOfObjectLiteral(type: Type): Type {
7394-
if (!(type.flags & TypeFlags.FreshObjectLiteral)) {
7440+
if (!(type.flags & TypeFlags.ObjectLiteral && type.flags & TypeFlags.FreshLiteral)) {
73957441
return type;
73967442
}
73977443
const regularType = (<FreshObjectLiteralType>type).regularType;
@@ -7407,7 +7453,7 @@ namespace ts {
74077453
resolved.constructSignatures,
74087454
resolved.stringIndexInfo,
74097455
resolved.numberIndexInfo);
7410-
regularNew.flags = resolved.flags & ~TypeFlags.FreshObjectLiteral;
7456+
regularNew.flags = resolved.flags & ~TypeFlags.FreshLiteral;
74117457
(<FreshObjectLiteralType>type).regularType = regularNew;
74127458
return regularNew;
74137459
}
@@ -8109,14 +8155,14 @@ namespace ts {
81098155
}
81108156
if (flags & TypeFlags.StringLiteral) {
81118157
return strictNullChecks ?
8112-
type === emptyStringType ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts :
8113-
type === emptyStringType ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts;
8158+
(<LiteralType>type).text === "" ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts :
8159+
(<LiteralType>type).text === "" ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts;
81148160
}
81158161
if (flags & (TypeFlags.Number | TypeFlags.Enum)) {
81168162
return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts;
81178163
}
81188164
if (flags & (TypeFlags.NumberLiteral | TypeFlags.EnumLiteral)) {
8119-
const isZero = type === zeroType || type.flags & TypeFlags.EnumLiteral && (<LiteralType>type).text === "0";
8165+
const isZero = (<LiteralType>type).text === "0";
81208166
return strictNullChecks ?
81218167
isZero ? TypeFacts.ZeroStrictFacts : TypeFacts.NonZeroStrictFacts :
81228168
isZero ? TypeFacts.ZeroFacts : TypeFacts.NonZeroFacts;
@@ -8289,7 +8335,7 @@ namespace ts {
82898335

82908336
function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) {
82918337
if (clause.kind === SyntaxKind.CaseClause) {
8292-
const caseType = checkExpression((<CaseClause>clause).expression);
8338+
const caseType = getRegularTypeOfLiteralType(checkExpression((<CaseClause>clause).expression));
82938339
return isUnitType(caseType) ? caseType : undefined;
82948340
}
82958341
return neverType;
@@ -8670,7 +8716,11 @@ namespace ts {
86708716
const narrowedType = filterType(type, t => areTypesComparable(t, valueType));
86718717
return narrowedType.flags & TypeFlags.Never ? type : narrowedType;
86728718
}
8673-
return isUnitType(valueType) ? filterType(type, t => t !== valueType) : type;
8719+
if (isUnitType(valueType)) {
8720+
const regularType = getRegularTypeOfLiteralType(valueType);
8721+
return filterType(type, t => getRegularTypeOfLiteralType(t) !== regularType);
8722+
}
8723+
return type;
86748724
}
86758725

86768726
function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type {
@@ -8715,7 +8765,7 @@ namespace ts {
87158765
if (!hasDefaultClause) {
87168766
return caseType;
87178767
}
8718-
const defaultType = filterType(type, t => !(isUnitType(t) && contains(switchTypes, t)));
8768+
const defaultType = filterType(type, t => !(isUnitType(t) && contains(switchTypes, getRegularTypeOfLiteralType(t))));
87198769
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
87208770
}
87218771

@@ -10289,7 +10339,7 @@ namespace ts {
1028910339
const stringIndexInfo = hasComputedStringProperty ? getObjectLiteralIndexInfo(node, propertiesArray, IndexKind.String) : undefined;
1029010340
const numberIndexInfo = hasComputedNumberProperty ? getObjectLiteralIndexInfo(node, propertiesArray, IndexKind.Number) : undefined;
1029110341
const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
10292-
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshObjectLiteral;
10342+
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshLiteral;
1029310343
result.flags |= TypeFlags.ObjectLiteral | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | (typeFlags & TypeFlags.PropagatingFlags) | (patternWithComputedProperties ? TypeFlags.ObjectLiteralPatternWithComputedProperties : 0);
1029410344
if (inDestructuringPattern) {
1029510345
result.pattern = node;
@@ -13682,12 +13732,13 @@ namespace ts {
1368213732
}
1368313733
switch (node.kind) {
1368413734
case SyntaxKind.StringLiteral:
13685-
return getLiteralTypeForText(TypeFlags.StringLiteral, (<LiteralExpression>node).text);
13735+
return getFreshTypeOfLiteralType(getLiteralTypeForText(TypeFlags.StringLiteral, (<LiteralExpression>node).text));
1368613736
case SyntaxKind.NumericLiteral:
13687-
return getLiteralTypeForText(TypeFlags.NumberLiteral, (<LiteralExpression>node).text);
13737+
return getFreshTypeOfLiteralType(getLiteralTypeForText(TypeFlags.NumberLiteral, (<LiteralExpression>node).text));
1368813738
case SyntaxKind.TrueKeyword:
13739+
return trueType;
1368913740
case SyntaxKind.FalseKeyword:
13690-
return node.kind === SyntaxKind.TrueKeyword ? trueType : falseType;
13741+
return falseType;
1369113742
}
1369213743
}
1369313744

@@ -18472,7 +18523,7 @@ namespace ts {
1847218523
if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) {
1847318524
expr = <Expression>expr.parent;
1847418525
}
18475-
return checkExpression(expr);
18526+
return getRegularTypeOfLiteralType(checkExpression(expr));
1847618527
}
1847718528

1847818529
/**

src/compiler/types.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2372,7 +2372,7 @@ namespace ts {
23722372
/* @internal */
23732373
ObjectLiteral = 1 << 23, // Originates in an object literal
23742374
/* @internal */
2375-
FreshObjectLiteral = 1 << 24, // Fresh object literal type
2375+
FreshLiteral = 1 << 24, // Fresh literal type
23762376
/* @internal */
23772377
ContainsWideningType = 1 << 25, // Type is or contains undefined or null widening type
23782378
/* @internal */
@@ -2385,6 +2385,7 @@ namespace ts {
23852385
/* @internal */
23862386
Nullable = Undefined | Null,
23872387
Literal = StringLiteral | NumberLiteral | BooleanLiteral | EnumLiteral,
2388+
StringOrNumberLiteral = StringLiteral | NumberLiteral,
23882389
/* @internal */
23892390
DefinitelyFalsy = StringLiteral | NumberLiteral | BooleanLiteral | Void | Undefined | Null,
23902391
PossiblyFalsy = DefinitelyFalsy | String | Number | Boolean,
@@ -2426,12 +2427,15 @@ namespace ts {
24262427
/* @internal */
24272428
// Intrinsic types (TypeFlags.Intrinsic)
24282429
export interface IntrinsicType extends Type {
2429-
intrinsicName: string; // Name of intrinsic type
2430+
intrinsicName: string; // Name of intrinsic type
24302431
}
24312432

24322433
// String literal types (TypeFlags.StringLiteral)
2434+
// Numeric literal types (TypeFlags.NumberLiteral)
24332435
export interface LiteralType extends Type {
2434-
text: string; // Text of string literal
2436+
text: string; // Text of literal
2437+
freshType?: LiteralType; // Fresh version of type
2438+
regularType?: LiteralType; // Regular version of type
24352439
}
24362440

24372441
// Enum types (TypeFlags.Enum)
@@ -2441,7 +2445,7 @@ namespace ts {
24412445

24422446
// Enum types (TypeFlags.EnumLiteral)
24432447
export interface EnumLiteralType extends LiteralType {
2444-
baseType: EnumType & UnionType;
2448+
baseType: EnumType & UnionType; // Base enum type
24452449
}
24462450

24472451
// Object types (TypeFlags.ObjectType)

0 commit comments

Comments
 (0)