Skip to content

Commit c48cd4a

Browse files
committed
Unify 'boolean' and 'true | false'
1 parent cb27e54 commit c48cd4a

2 files changed

Lines changed: 101 additions & 55 deletions

File tree

src/compiler/checker.ts

Lines changed: 100 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,21 @@ namespace ts {
108108
isOptionalParameter
109109
};
110110

111+
const tupleTypes: Map<TupleType> = {};
112+
const unionTypes: Map<UnionType> = {};
113+
const intersectionTypes: Map<IntersectionType> = {};
114+
const stringLiteralTypes: Map<LiteralType> = {};
115+
const numericLiteralTypes: Map<LiteralType> = {};
116+
111117
const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown");
112118
const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__");
113119

114120
const anyType = createIntrinsicType(TypeFlags.Any, "any");
115121
const stringType = createIntrinsicType(TypeFlags.String, "string");
116122
const numberType = createIntrinsicType(TypeFlags.Number, "number");
117-
const booleanType = createIntrinsicType(TypeFlags.Boolean, "boolean");
118123
const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true");
119124
const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false");
125+
const booleanType = createBooleanType([trueType, falseType]);
120126
const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol");
121127
const voidType = createIntrinsicType(TypeFlags.Void, "void");
122128
const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined");
@@ -196,14 +202,8 @@ namespace ts {
196202
let flowLoopCount = 0;
197203
let visitedFlowCount = 0;
198204

199-
const tupleTypes: Map<TupleType> = {};
200-
const unionTypes: Map<UnionType> = {};
201-
const intersectionTypes: Map<IntersectionType> = {};
202-
const stringLiteralTypes: Map<LiteralType> = {};
203-
const numericLiteralTypes: Map<LiteralType> = {};
204205
const emptyStringType = getLiteralTypeForText(TypeFlags.StringLiteral, "");
205206
const zeroType = getLiteralTypeForText(TypeFlags.NumberLiteral, "0");
206-
const trueFalseType = getUnionType([trueType, falseType]);
207207

208208
const resolutionTargets: TypeSystemEntity[] = [];
209209
const resolutionResults: boolean[] = [];
@@ -1547,6 +1547,13 @@ namespace ts {
15471547
return type;
15481548
}
15491549

1550+
function createBooleanType(trueFalseTypes: Type[]): IntrinsicType {
1551+
const type = <IntrinsicType>getUnionType(trueFalseTypes, /*noSubtypeReduction*/ true);
1552+
type.flags |= TypeFlags.Boolean;
1553+
type.intrinsicName = "boolean";
1554+
return type;
1555+
}
1556+
15501557
function createObjectType(kind: TypeFlags, symbol?: Symbol): ObjectType {
15511558
const type = <ObjectType>createType(kind);
15521559
type.symbol = symbol;
@@ -1921,6 +1928,19 @@ namespace ts {
19211928
return result;
19221929
}
19231930

1931+
function replaceTrueFalseWithBoolean(types: Type[]): Type[] {
1932+
if (contains(types, trueType) && contains(types, falseType)) {
1933+
const result: Type[] = [];
1934+
for (const t of types) {
1935+
if (t !== falseType) {
1936+
result.push(t === trueType ? booleanType : t);
1937+
}
1938+
}
1939+
return result;
1940+
}
1941+
return types;
1942+
}
1943+
19241944
function visibilityToString(flags: NodeFlags) {
19251945
if (flags === NodeFlags.Private) {
19261946
return "private";
@@ -2213,7 +2233,12 @@ namespace ts {
22132233
if (flags & TypeFormatFlags.InElementType) {
22142234
writePunctuation(writer, SyntaxKind.OpenParenToken);
22152235
}
2216-
writeTypeList(type.types, type.flags & TypeFlags.Union ? SyntaxKind.BarToken : SyntaxKind.AmpersandToken);
2236+
if (type.flags & TypeFlags.Union) {
2237+
writeTypeList(replaceTrueFalseWithBoolean(type.types), SyntaxKind.BarToken);
2238+
}
2239+
else {
2240+
writeTypeList(type.types, SyntaxKind.AmpersandToken);
2241+
}
22172242
if (flags & TypeFormatFlags.InElementType) {
22182243
writePunctuation(writer, SyntaxKind.CloseParenToken);
22192244
}
@@ -5661,7 +5686,7 @@ namespace ts {
56615686
if (type.flags & TypeFlags.Tuple) {
56625687
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
56635688
}
5664-
if (type.flags & TypeFlags.Union) {
5689+
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) {
56655690
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noSubtypeReduction*/ true);
56665691
}
56675692
if (type.flags & TypeFlags.Intersection) {
@@ -6070,10 +6095,10 @@ namespace ts {
60706095
// Note that these checks are specifically ordered to produce correct results.
60716096
if (source.flags & TypeFlags.Union) {
60726097
if (relation === comparableRelation) {
6073-
result = someTypeRelatedToType(source as UnionType, target, reportErrors);
6098+
result = someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive));
60746099
}
60756100
else {
6076-
result = eachTypeRelatedToType(source as UnionType, target, reportErrors);
6101+
result = eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive));
60776102
}
60786103

60796104
if (result) {
@@ -6110,12 +6135,9 @@ namespace ts {
61106135
}
61116136
}
61126137
if (target.flags & TypeFlags.Union) {
6113-
if (result = typeRelatedToSomeType(source, <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive))) {
6138+
if (result = typeRelatedToSomeType(source, <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive))) {
61146139
return result;
61156140
}
6116-
if (source === booleanType && contains((<UnionType>target).types, trueType) && contains((<UnionType>target).types, falseType)) {
6117-
return Ternary.True;
6118-
}
61196141
}
61206142
}
61216143

@@ -6969,7 +6991,9 @@ namespace ts {
69696991
}
69706992

69716993
function isUnitUnionType(type: Type): boolean {
6972-
return type.flags & TypeFlags.Union ? !forEach((<UnionType>type).types, t => !isUnitType(t)) : isUnitType(type);
6994+
return type.flags & TypeFlags.Boolean ? true :
6995+
type.flags & TypeFlags.Union ? !forEach((<UnionType>type).types, t => !isUnitType(t)) :
6996+
isUnitType(type);
69736997
}
69746998

69756999
function getBaseTypeOfUnitType(type: Type): Type {
@@ -6981,10 +7005,6 @@ namespace ts {
69817005
type;
69827006
}
69837007

6984-
function isUnionWithTrueOrFalse(type: Type) {
6985-
return type.flags & TypeFlags.Union && (contains((<UnionType>type).types, trueType) || contains((<UnionType>type).types, falseType));
6986-
}
6987-
69887008
/**
69897009
* Check if a Type was written as a tuple type literal.
69907010
* Prefer using isTupleLikeType() unless the use of `elementTypes` is required.
@@ -7001,12 +7021,15 @@ namespace ts {
70017021
return result;
70027022
}
70037023

7024+
// Returns the String, Number, Boolean, StringLiteral, NumberLiteral, BooleanLiteral, Void, Undefined, or Null
7025+
// flags for the string, number, boolean, "", 0, false, void, undefined, or null types respectively. Returns
7026+
// no flags for all other types (including non-falsy literal types).
70047027
function getFalsyFlags(type: Type): TypeFlags {
7005-
return type === emptyStringType ? TypeFlags.StringLiteral :
7006-
type === zeroType ? TypeFlags.NumberLiteral :
7007-
type === falseType ? TypeFlags.BooleanLiteral :
7008-
type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((<UnionType>type).types) :
7009-
type.flags & TypeFlags.AlwaysPossiblyFalsy;
7028+
return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((<UnionType>type).types) :
7029+
type.flags & TypeFlags.StringLiteral ? type === emptyStringType ? TypeFlags.StringLiteral : 0 :
7030+
type.flags & TypeFlags.NumberLiteral ? type === zeroType ? TypeFlags.NumberLiteral : 0 :
7031+
type.flags & TypeFlags.BooleanLiteral ? type === falseType ? TypeFlags.BooleanLiteral : 0 :
7032+
type.flags & TypeFlags.PossiblyFalsy;
70107033
}
70117034

70127035
function includeFalsyTypes(type: Type, flags: TypeFlags) {
@@ -10403,8 +10426,11 @@ namespace ts {
1040310426
checkClassPropertyAccess(node, left, apparentType, prop);
1040410427
}
1040510428

10406-
const propType = prop.flags & SymbolFlags.EnumMember && getParentOfSymbol(prop).flags & SymbolFlags.ConstEnum &&
10407-
isLiteralTypeContext(<Expression>node) ? getDeclaredTypeOfSymbol(prop) : getTypeOfSymbol(prop);
10429+
let propType = getTypeOfSymbol(prop);
10430+
if (prop.flags & SymbolFlags.EnumMember && getParentOfSymbol(prop).flags & SymbolFlags.ConstEnum && isLiteralContextForType(<Expression>node, propType)) {
10431+
propType = getDeclaredTypeOfSymbol(prop);
10432+
}
10433+
1040810434
// Only compute control flow type if this is a property access expression that isn't an
1040910435
// assignment target, and the referenced property was declared as a variable, property,
1041010436
// accessor, or optional method.
@@ -12460,7 +12486,7 @@ namespace ts {
1246012486

1246112487
function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type {
1246212488
const operandType = checkExpression(node.operand);
12463-
if (node.operator === SyntaxKind.MinusToken && node.operand.kind === SyntaxKind.NumericLiteral && isLiteralTypeContext(node)) {
12489+
if (node.operator === SyntaxKind.MinusToken && node.operand.kind === SyntaxKind.NumericLiteral && isLiteralContextForType(node, numberType)) {
1246412490
return getLiteralTypeForText(TypeFlags.NumberLiteral, "" + -(<LiteralExpression>node.operand).text);
1246512491
}
1246612492
switch (node.operator) {
@@ -12475,7 +12501,6 @@ namespace ts {
1247512501
const facts = getTypeFacts(operandType) & (TypeFacts.Truthy | TypeFacts.Falsy);
1247612502
return facts === TypeFacts.Truthy ? falseType :
1247712503
facts === TypeFacts.Falsy ? trueType :
12478-
isUnionWithTrueOrFalse(operandType) ? trueFalseType :
1247912504
booleanType;
1248012505
case SyntaxKind.PlusPlusToken:
1248112506
case SyntaxKind.MinusMinusToken:
@@ -12866,7 +12891,7 @@ namespace ts {
1286612891
return checkInExpression(left, right, leftType, rightType);
1286712892
case SyntaxKind.AmpersandAmpersandToken:
1286812893
return getTypeFacts(leftType) & TypeFacts.Truthy ?
12869-
strictNullChecks ? includeFalsyTypes(rightType, getFalsyFlags(leftType)) : rightType :
12894+
includeFalsyTypes(rightType, getFalsyFlags(strictNullChecks ? leftType : getBaseTypeOfUnitType(rightType))) :
1287012895
leftType;
1287112896
case SyntaxKind.BarBarToken:
1287212897
return getTypeFacts(leftType) & TypeFacts.Falsy ?
@@ -13000,45 +13025,64 @@ namespace ts {
1300013025
return getUnionType([type1, type2]);
1300113026
}
1300213027

13003-
function isLiteralUnionType(type: Type): boolean {
13004-
return type.flags & TypeFlags.Literal ? true :
13005-
type.flags & TypeFlags.Enum ? (type.symbol.flags & SymbolFlags.EnumMember) !== 0 :
13006-
type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, isLiteralUnionType) :
13007-
false;
13028+
function typeContainsEnumLiteral(type: Type, enumType: Type) {
13029+
if (type.flags & TypeFlags.Union) {
13030+
for (const t of (<UnionType>type).types) {
13031+
if (t.flags & TypeFlags.Enum && t.symbol.flags & SymbolFlags.EnumMember && t.symbol.parent === enumType.symbol) {
13032+
return true;
13033+
}
13034+
}
13035+
}
13036+
if (type.flags & TypeFlags.Enum) {
13037+
return type.symbol.flags & SymbolFlags.EnumMember && type.symbol.parent === enumType.symbol;
13038+
}
13039+
return false;
1300813040
}
1300913041

13010-
function hasLiteralContextualType(node: Expression) {
13011-
const contextualType = getContextualType(node);
13012-
if (!contextualType) {
13013-
return false;
13042+
function isLiteralContextForType(node: Expression, type: Type) {
13043+
if (isLiteralTypeLocation(node)) {
13044+
return true;
1301413045
}
13015-
if (contextualType.flags & TypeFlags.TypeParameter) {
13016-
const apparentType = getApparentTypeOfTypeParameter(<TypeParameter>contextualType);
13017-
if (apparentType.flags & (TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.BooleanLike)) {
13018-
return true;
13046+
let contextualType = getContextualType(node);
13047+
if (contextualType) {
13048+
if (contextualType.flags & TypeFlags.TypeParameter) {
13049+
const apparentType = getApparentTypeOfTypeParameter(<TypeParameter>contextualType);
13050+
// If the type parameter is constrained to the base primitive type we're checking for,
13051+
// consider this a literal context. For example, given a type parameter 'T extends string',
13052+
// this causes us to infer string literal types for T.
13053+
if (type === apparentType) {
13054+
return true;
13055+
}
13056+
contextualType = apparentType;
13057+
}
13058+
if (type.flags & TypeFlags.String) {
13059+
return maybeTypeOfKind(contextualType, TypeFlags.StringLiteral);
13060+
}
13061+
if (type.flags & TypeFlags.Number) {
13062+
return maybeTypeOfKind(contextualType, TypeFlags.NumberLiteral);
13063+
}
13064+
if (type.flags & TypeFlags.Boolean) {
13065+
return maybeTypeOfKind(contextualType, TypeFlags.BooleanLiteral) && !isTypeAssignableTo(booleanType, contextualType);
13066+
}
13067+
if (type.flags & TypeFlags.Enum && type.symbol.flags & SymbolFlags.ConstEnum) {
13068+
return typeContainsEnumLiteral(contextualType, type);
1301913069
}
1302013070
}
13021-
return isLiteralUnionType(contextualType);
13022-
}
13023-
13024-
function isLiteralTypeContext(node: Expression) {
13025-
return isLiteralTypeLocation(node) || hasLiteralContextualType(node);
13071+
return false;
1302613072
}
1302713073

1302813074
function checkLiteralExpression(node: Expression): Type {
1302913075
if (node.kind === SyntaxKind.NumericLiteral) {
1303013076
checkGrammarNumericLiteral(<LiteralExpression>node);
1303113077
}
13032-
const hasLiteralType = isLiteralTypeContext(node);
1303313078
switch (node.kind) {
1303413079
case SyntaxKind.StringLiteral:
13035-
return hasLiteralType ? getLiteralTypeForText(TypeFlags.StringLiteral, (<LiteralExpression>node).text) : stringType;
13080+
return isLiteralContextForType(node, stringType) ? getLiteralTypeForText(TypeFlags.StringLiteral, (<LiteralExpression>node).text) : stringType;
1303613081
case SyntaxKind.NumericLiteral:
13037-
return hasLiteralType ? getLiteralTypeForText(TypeFlags.NumberLiteral, (<LiteralExpression>node).text) : numberType;
13082+
return isLiteralContextForType(node, numberType) ? getLiteralTypeForText(TypeFlags.NumberLiteral, (<LiteralExpression>node).text) : numberType;
1303813083
case SyntaxKind.TrueKeyword:
13039-
return hasLiteralType ? trueType : booleanType;
1304013084
case SyntaxKind.FalseKeyword:
13041-
return hasLiteralType ? falseType : booleanType;
13085+
return isLiteralContextForType(node, booleanType) ? node.kind === SyntaxKind.TrueKeyword ? trueType : falseType : booleanType;
1304213086
}
1304313087
}
1304413088

@@ -18324,6 +18368,9 @@ namespace ts {
1832418368
}
1832518369
}
1832618370

18371+
// The built-in boolean type is 'true | false', also mark 'false | true' as a boolean type
18372+
createBooleanType([falseType, trueType]);
18373+
1832718374
// Setup global builtins
1832818375
addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0);
1832918376

src/compiler/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2254,7 +2254,6 @@ namespace ts {
22542254
/* @internal */
22552255
DefinitelyFalsy = StringLiteral | NumberLiteral | BooleanLiteral | Void | Undefined | Null,
22562256
PossiblyFalsy = DefinitelyFalsy | String | Number | Boolean,
2257-
AlwaysPossiblyFalsy = String | Number | Boolean | Void | Undefined | Null,
22582257
/* @internal */
22592258
Intrinsic = Any | String | Number | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never,
22602259
/* @internal */
@@ -2269,7 +2268,7 @@ namespace ts {
22692268
// 'Narrowable' types are types where narrowing actually narrows.
22702269
// This *should* be every type other than null, undefined, void, and never
22712270
Narrowable = Any | StructuredType | TypeParameter | StringLike | NumberLike | BooleanLike | ESSymbol,
2272-
NotUnionOrUnit = Any | String | Number | Boolean | ESSymbol | ObjectType,
2271+
NotUnionOrUnit = Any | String | Number | ESSymbol | ObjectType,
22732272
/* @internal */
22742273
RequiresWidening = ContainsWideningType | ContainsObjectLiteral,
22752274
/* @internal */

0 commit comments

Comments
 (0)