Skip to content

Commit f2c0288

Browse files
committed
Introduce boolean literal freshness
1 parent f2a1a42 commit f2c0288

15 files changed

+165
-58
lines changed

src/compiler/checker.ts

+49-31
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,8 @@ namespace ts {
296296
createPromiseType,
297297
createArrayType,
298298
getBooleanType: () => booleanType,
299-
getFalseType: () => falseType,
300-
getTrueType: () => trueType,
299+
getFalseType: (fresh?) => fresh ? falseType : regularFalseType,
300+
getTrueType: (fresh?) => fresh ? trueType : regularTrueType,
301301
getVoidType: () => voidType,
302302
getUndefinedType: () => undefinedType,
303303
getNullType: () => nullType,
@@ -405,9 +405,22 @@ namespace ts {
405405
const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsWideningType, "null");
406406
const stringType = createIntrinsicType(TypeFlags.String, "string");
407407
const numberType = createIntrinsicType(TypeFlags.Number, "number");
408-
const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false");
409-
const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true");
410-
const booleanType = createBooleanType([falseType, trueType]);
408+
const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType;
409+
const regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType;
410+
const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType;
411+
const regularTrueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType;
412+
falseType.flags |= TypeFlags.FreshLiteral;
413+
trueType.flags |= TypeFlags.FreshLiteral;
414+
trueType.regularType = regularTrueType;
415+
regularTrueType.freshType = trueType;
416+
falseType.regularType = regularFalseType;
417+
regularFalseType.freshType = falseType;
418+
const booleanType = createBooleanType([regularFalseType, regularTrueType]);
419+
// Also mark all combinations of fresh/regular booleans as "Boolean" so they print as `boolean` instead of `true | false`
420+
// (The union is cached, so simply doing the marking here is sufficient)
421+
createBooleanType([regularFalseType, trueType]);
422+
createBooleanType([falseType, regularTrueType]);
423+
createBooleanType([falseType, trueType]);
411424
const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol");
412425
const voidType = createIntrinsicType(TypeFlags.Void, "void");
413426
const neverType = createIntrinsicType(TypeFlags.Never, "never");
@@ -4170,7 +4183,7 @@ namespace ts {
41704183
const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLiteralType(<LiteralType>t);
41714184
if (baseType.flags & TypeFlags.Union) {
41724185
const count = (<UnionType>baseType).types.length;
4173-
if (i + count <= types.length && types[i + count - 1] === (<UnionType>baseType).types[count - 1]) {
4186+
if (i + count <= types.length && getRegularTypeOfLiteralType(types[i + count - 1]) === getRegularTypeOfLiteralType((<UnionType>baseType).types[count - 1])) {
41744187
result.push(baseType);
41754188
i += count - 1;
41764189
continue;
@@ -6157,7 +6170,7 @@ namespace ts {
61576170
if (type.flags & TypeFlags.UniqueESSymbol) {
61586171
return `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String;
61596172
}
6160-
if (type.flags & TypeFlags.StringOrNumberLiteral) {
6173+
if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
61616174
return escapeLeadingUnderscores("" + (<LiteralType>type).value);
61626175
}
61636176
return Debug.fail();
@@ -8810,7 +8823,7 @@ namespace ts {
88108823
t.flags & TypeFlags.StringLiteral && includes & TypeFlags.String ||
88118824
t.flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number ||
88128825
t.flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol ||
8813-
t.flags & TypeFlags.StringOrNumberLiteral && t.flags & TypeFlags.FreshLiteral && containsType(types, (<LiteralType>t).regularType);
8826+
t.flags & TypeFlags.Literal && t.flags & TypeFlags.FreshLiteral && containsType(types, (<LiteralType>t).regularType);
88148827
if (remove) {
88158828
orderedRemoveItemAt(types, i);
88168829
}
@@ -9810,8 +9823,8 @@ namespace ts {
98109823
}
98119824

98129825
function getFreshTypeOfLiteralType(type: Type): Type {
9813-
if (type.flags & TypeFlags.StringOrNumberLiteral && !(type.flags & TypeFlags.FreshLiteral)) {
9814-
if (!(<LiteralType>type).freshType) {
9826+
if (type.flags & TypeFlags.Literal && !(type.flags & TypeFlags.FreshLiteral)) {
9827+
if (!(<LiteralType>type).freshType) { // NOTE: Safe because all freshable intrinsics always have fresh types already
98159828
const freshType = createLiteralType(type.flags | TypeFlags.FreshLiteral, (<LiteralType>type).value, (<LiteralType>type).symbol);
98169829
freshType.regularType = <LiteralType>type;
98179830
(<LiteralType>type).freshType = freshType;
@@ -9822,7 +9835,7 @@ namespace ts {
98229835
}
98239836

98249837
function getRegularTypeOfLiteralType(type: Type): Type {
9825-
return type.flags & TypeFlags.StringOrNumberLiteral && type.flags & TypeFlags.FreshLiteral ? (<LiteralType>type).regularType :
9838+
return type.flags & TypeFlags.Literal && type.flags & TypeFlags.FreshLiteral ? (<LiteralType>type).regularType :
98269839
type.flags & TypeFlags.Union ? getUnionType(sameMap((<UnionType>type).types, getRegularTypeOfLiteralType)) :
98279840
type;
98289841
}
@@ -11035,11 +11048,11 @@ namespace ts {
1103511048
}
1103611049

1103711050
function isTypeRelatedTo(source: Type, target: Type, relation: Map<RelationComparisonResult>) {
11038-
if (source.flags & TypeFlags.StringOrNumberLiteral && source.flags & TypeFlags.FreshLiteral) {
11039-
source = (<LiteralType>source).regularType;
11051+
if (source.flags & TypeFlags.Literal && source.flags & TypeFlags.FreshLiteral) {
11052+
source = (<FreshableType>source).regularType;
1104011053
}
11041-
if (target.flags & TypeFlags.StringOrNumberLiteral && target.flags & TypeFlags.FreshLiteral) {
11042-
target = (<LiteralType>target).regularType;
11054+
if (target.flags & TypeFlags.Literal && target.flags & TypeFlags.FreshLiteral) {
11055+
target = (<FreshableType>target).regularType;
1104311056
}
1104411057
if (source === target ||
1104511058
relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) ||
@@ -11194,11 +11207,11 @@ namespace ts {
1119411207
* * Ternary.False if they are not related.
1119511208
*/
1119611209
function isRelatedTo(source: Type, target: Type, reportErrors = false, headMessage?: DiagnosticMessage): Ternary {
11197-
if (source.flags & TypeFlags.StringOrNumberLiteral && source.flags & TypeFlags.FreshLiteral) {
11198-
source = (<LiteralType>source).regularType;
11210+
if (source.flags & TypeFlags.Literal && source.flags & TypeFlags.FreshLiteral) {
11211+
source = (<FreshableType>source).regularType;
1119911212
}
11200-
if (target.flags & TypeFlags.StringOrNumberLiteral && target.flags & TypeFlags.FreshLiteral) {
11201-
target = (<LiteralType>target).regularType;
11213+
if (target.flags & TypeFlags.Literal && target.flags & TypeFlags.FreshLiteral) {
11214+
target = (<FreshableType>target).regularType;
1120211215
}
1120311216
if (source.flags & TypeFlags.Substitution) {
1120411217
source = relation === definitelyAssignableRelation ? (<SubstitutionType>source).typeVariable : (<SubstitutionType>source).substitute;
@@ -12766,7 +12779,7 @@ namespace ts {
1276612779
return type.flags & TypeFlags.EnumLiteral && type.flags & TypeFlags.FreshLiteral ? getBaseTypeOfEnumLiteralType(<LiteralType>type) :
1276712780
type.flags & TypeFlags.StringLiteral && type.flags & TypeFlags.FreshLiteral ? stringType :
1276812781
type.flags & TypeFlags.NumberLiteral && type.flags & TypeFlags.FreshLiteral ? numberType :
12769-
type.flags & TypeFlags.BooleanLiteral ? booleanType :
12782+
type.flags & TypeFlags.BooleanLiteral && type.flags & TypeFlags.FreshLiteral ? booleanType :
1277012783
type.flags & TypeFlags.Union ? getUnionType(sameMap((<UnionType>type).types, getWidenedLiteralType)) :
1277112784
type;
1277212785
}
@@ -12820,7 +12833,7 @@ namespace ts {
1282012833
return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((<UnionType>type).types) :
1282112834
type.flags & TypeFlags.StringLiteral ? (<LiteralType>type).value === "" ? TypeFlags.StringLiteral : 0 :
1282212835
type.flags & TypeFlags.NumberLiteral ? (<LiteralType>type).value === 0 ? TypeFlags.NumberLiteral : 0 :
12823-
type.flags & TypeFlags.BooleanLiteral ? type === falseType ? TypeFlags.BooleanLiteral : 0 :
12836+
type.flags & TypeFlags.BooleanLiteral ? (type === falseType || type === regularFalseType) ? TypeFlags.BooleanLiteral : 0 :
1282412837
type.flags & TypeFlags.PossiblyFalsy;
1282512838
}
1282612839

@@ -12837,7 +12850,8 @@ namespace ts {
1283712850
function getDefinitelyFalsyPartOfType(type: Type): Type {
1283812851
return type.flags & TypeFlags.String ? emptyStringType :
1283912852
type.flags & TypeFlags.Number ? zeroType :
12840-
type.flags & TypeFlags.Boolean || type === falseType ? falseType :
12853+
type.flags & TypeFlags.Boolean || type === regularFalseType ? regularFalseType :
12854+
type === falseType ? falseType :
1284112855
type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null) ||
1284212856
type.flags & TypeFlags.StringLiteral && (<LiteralType>type).value === "" ||
1284312857
type.flags & TypeFlags.NumberLiteral && (<LiteralType>type).value === 0 ? type :
@@ -14092,7 +14106,10 @@ namespace ts {
1409214106
if (assignedType.flags & TypeFlags.Never) {
1409314107
return assignedType;
1409414108
}
14095-
const reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
14109+
let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
14110+
if (assignedType.flags & (TypeFlags.FreshLiteral | TypeFlags.Literal)) {
14111+
reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types
14112+
}
1409614113
// Our crude heuristic produces an invalid result in some cases: see GH#26130.
1409714114
// For now, when that happens, we give up and don't narrow at all. (This also
1409814115
// means we'll never narrow for erroneous assignments where the assigned type
@@ -14145,8 +14162,8 @@ namespace ts {
1414514162
}
1414614163
if (flags & TypeFlags.BooleanLike) {
1414714164
return strictNullChecks ?
14148-
type === falseType ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts :
14149-
type === falseType ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
14165+
(type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts :
14166+
(type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
1415014167
}
1415114168
if (flags & TypeFlags.Object) {
1415214169
return isFunctionObjectType(<ObjectType>type) ?
@@ -28351,19 +28368,20 @@ namespace ts {
2835128368
function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean {
2835228369
if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) {
2835328370
const type = getTypeOfSymbol(getSymbolOfNode(node));
28354-
return !!(type.flags & TypeFlags.StringOrNumberLiteral && type.flags & TypeFlags.FreshLiteral);
28371+
return !!(type.flags & TypeFlags.Literal && type.flags & TypeFlags.FreshLiteral);
2835528372
}
2835628373
return false;
2835728374
}
2835828375

28359-
function literalTypeToNode(type: LiteralType, enclosing: Node): Expression {
28360-
const enumResult = type.flags & TypeFlags.EnumLiteral && nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing);
28361-
return enumResult || createLiteral(type.value);
28376+
function literalTypeToNode(type: FreshableType, enclosing: Node): Expression {
28377+
const enumResult = type.flags & TypeFlags.EnumLiteral ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing)
28378+
: type === trueType ? createTrue() : type === falseType && createFalse();
28379+
return enumResult || createLiteral((type as LiteralType).value);
2836228380
}
2836328381

2836428382
function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration) {
2836528383
const type = getTypeOfSymbol(getSymbolOfNode(node));
28366-
return literalTypeToNode(<LiteralType>type, node);
28384+
return literalTypeToNode(<FreshableType>type, node);
2836728385
}
2836828386

2836928387
function createResolver(): EmitResolver {
@@ -29735,7 +29753,7 @@ namespace ts {
2973529753

2973629754
function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) {
2973729755
if (node.initializer) {
29738-
const isInvalidInitializer = !(isStringOrNumberLiteralExpression(node.initializer) || isSimpleLiteralEnumReference(node.initializer));
29756+
const isInvalidInitializer = !(isStringOrNumberLiteralExpression(node.initializer) || isSimpleLiteralEnumReference(node.initializer) || node.initializer.kind === SyntaxKind.TrueKeyword || node.initializer.kind === SyntaxKind.FalseKeyword);
2973929757
const isConstOrReadonly = isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node);
2974029758
if (isConstOrReadonly && !node.type) {
2974129759
if (isInvalidInitializer) {

src/compiler/types.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -3034,8 +3034,8 @@ namespace ts {
30343034
/* @internal */ getStringType(): Type;
30353035
/* @internal */ getNumberType(): Type;
30363036
/* @internal */ getBooleanType(): Type;
3037-
/* @internal */ getFalseType(): Type;
3038-
/* @internal */ getTrueType(): Type;
3037+
/* @internal */ getFalseType(fresh?: boolean): Type;
3038+
/* @internal */ getTrueType(fresh?: boolean): Type;
30393039
/* @internal */ getVoidType(): Type;
30403040
/* @internal */ getUndefinedType(): Type;
30413041
/* @internal */ getNullType(): Type;
@@ -3731,7 +3731,7 @@ namespace ts {
37313731
Unit = Literal | UniqueESSymbol | Nullable,
37323732
StringOrNumberLiteral = StringLiteral | NumberLiteral,
37333733
/* @internal */
3734-
StringOrNumberLiteralOrUnique = StringOrNumberLiteral | UniqueESSymbol,
3734+
StringOrNumberLiteralOrUnique = StringLiteral | NumberLiteral | UniqueESSymbol,
37353735
/* @internal */
37363736
DefinitelyFalsy = StringLiteral | NumberLiteral | BooleanLiteral | Void | Undefined | Null,
37373737
PossiblyFalsy = DefinitelyFalsy | String | Number | Boolean,
@@ -3802,6 +3802,15 @@ namespace ts {
38023802
intrinsicName: string; // Name of intrinsic type
38033803
}
38043804

3805+
/* @internal */
3806+
export interface FreshableIntrinsicType extends IntrinsicType {
3807+
freshType: IntrinsicType; // Fresh version of type
3808+
regularType: IntrinsicType; // Regular version of type
3809+
}
3810+
3811+
/* @internal */
3812+
export type FreshableType = LiteralType | FreshableIntrinsicType;
3813+
38053814
// String literal types (TypeFlags.StringLiteral)
38063815
// Numeric literal types (TypeFlags.NumberLiteral)
38073816
export interface LiteralType extends Type {

src/services/codefixes/fixStrictClassInitialization.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ namespace ts.codefix {
110110

111111
function getDefaultValueFromType (checker: TypeChecker, type: Type): Expression | undefined {
112112
if (type.flags & TypeFlags.BooleanLiteral) {
113-
return type === checker.getFalseType() ? createFalse() : createTrue();
113+
return (type === checker.getFalseType() || type === checker.getFalseType(/*fresh*/ true)) ? createFalse() : createTrue();
114114
}
115115
else if (type.isLiteral()) {
116116
return createLiteral(type.value);

tests/baselines/reference/ambientConstLiterals.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ declare const c3 = "abc";
6363
declare const c4 = 123;
6464
declare const c5 = 123;
6565
declare const c6 = -123;
66-
declare const c7: boolean;
66+
declare const c7 = true;
6767
declare const c8 = E.A;
6868
declare const c8b = E["non identifier"];
6969
declare const c9: {

tests/baselines/reference/booleanLiteralTypes1.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,13 @@ function f4(t: true, f: false) {
8282
>false : false
8383

8484
var x1 = t && f;
85-
>x1 : boolean
85+
>x1 : false
8686
>t && f : false
8787
>t : true
8888
>f : false
8989

9090
var x2 = f && t;
91-
>x2 : boolean
91+
>x2 : false
9292
>f && t : false
9393
>f : false
9494
>t : true
@@ -100,7 +100,7 @@ function f4(t: true, f: false) {
100100
>f : false
101101

102102
var x4 = f || t;
103-
>x4 : boolean
103+
>x4 : true
104104
>f || t : true
105105
>f : false
106106
>t : true

tests/baselines/reference/booleanLiteralTypes2.types

+4-4
Original file line numberDiff line numberDiff line change
@@ -82,25 +82,25 @@ function f4(t: true, f: false) {
8282
>false : false
8383

8484
var x1 = t && f;
85-
>x1 : boolean
85+
>x1 : false
8686
>t && f : false
8787
>t : true
8888
>f : false
8989

9090
var x2 = f && t;
91-
>x2 : boolean
91+
>x2 : false
9292
>f && t : false
9393
>f : false
9494
>t : true
9595

9696
var x3 = t || f;
97-
>x3 : boolean
97+
>x3 : true
9898
>t || f : true
9999
>t : true
100100
>f : false
101101

102102
var x4 = f || t;
103-
>x4 : boolean
103+
>x4 : true
104104
>f || t : true
105105
>f : false
106106
>t : true

tests/baselines/reference/constDeclarations.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ for (const c5 = 0, c6 = 0; c5 < c6;) {
2424

2525

2626
//// [constDeclarations.d.ts]
27-
declare const c1: boolean;
27+
declare const c1 = false;
2828
declare const c2: number;
2929
declare const c3 = 0, c4: string, c5: any;

tests/baselines/reference/constDeclarations2.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ var M;
1919

2020
//// [constDeclarations2.d.ts]
2121
declare module M {
22-
const c1: boolean;
22+
const c1 = false;
2323
const c2: number;
2424
const c3 = 0, c4: string, c5: any;
2525
}

0 commit comments

Comments
 (0)