Skip to content

Commit

Permalink
Introduce boolean literal freshness
Browse files Browse the repository at this point in the history
  • Loading branch information
weswigham committed Sep 12, 2018
1 parent f2a1a42 commit f2c0288
Show file tree
Hide file tree
Showing 15 changed files with 165 additions and 58 deletions.
80 changes: 49 additions & 31 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,8 @@ namespace ts {
createPromiseType,
createArrayType,
getBooleanType: () => booleanType,
getFalseType: () => falseType,
getTrueType: () => trueType,
getFalseType: (fresh?) => fresh ? falseType : regularFalseType,
getTrueType: (fresh?) => fresh ? trueType : regularTrueType,
getVoidType: () => voidType,
getUndefinedType: () => undefinedType,
getNullType: () => nullType,
Expand Down Expand Up @@ -405,9 +405,22 @@ namespace ts {
const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsWideningType, "null");
const stringType = createIntrinsicType(TypeFlags.String, "string");
const numberType = createIntrinsicType(TypeFlags.Number, "number");
const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false");
const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true");
const booleanType = createBooleanType([falseType, trueType]);
const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType;
const regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType;
const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType;
const regularTrueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType;
falseType.flags |= TypeFlags.FreshLiteral;
trueType.flags |= TypeFlags.FreshLiteral;
trueType.regularType = regularTrueType;
regularTrueType.freshType = trueType;
falseType.regularType = regularFalseType;
regularFalseType.freshType = falseType;
const booleanType = createBooleanType([regularFalseType, regularTrueType]);
// Also mark all combinations of fresh/regular booleans as "Boolean" so they print as `boolean` instead of `true | false`
// (The union is cached, so simply doing the marking here is sufficient)
createBooleanType([regularFalseType, trueType]);
createBooleanType([falseType, regularTrueType]);
createBooleanType([falseType, trueType]);
const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol");
const voidType = createIntrinsicType(TypeFlags.Void, "void");
const neverType = createIntrinsicType(TypeFlags.Never, "never");
Expand Down Expand Up @@ -4170,7 +4183,7 @@ namespace ts {
const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLiteralType(<LiteralType>t);
if (baseType.flags & TypeFlags.Union) {
const count = (<UnionType>baseType).types.length;
if (i + count <= types.length && types[i + count - 1] === (<UnionType>baseType).types[count - 1]) {
if (i + count <= types.length && getRegularTypeOfLiteralType(types[i + count - 1]) === getRegularTypeOfLiteralType((<UnionType>baseType).types[count - 1])) {
result.push(baseType);
i += count - 1;
continue;
Expand Down Expand Up @@ -6157,7 +6170,7 @@ namespace ts {
if (type.flags & TypeFlags.UniqueESSymbol) {
return `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String;
}
if (type.flags & TypeFlags.StringOrNumberLiteral) {
if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
return escapeLeadingUnderscores("" + (<LiteralType>type).value);
}
return Debug.fail();
Expand Down Expand Up @@ -8810,7 +8823,7 @@ namespace ts {
t.flags & TypeFlags.StringLiteral && includes & TypeFlags.String ||
t.flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number ||
t.flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol ||
t.flags & TypeFlags.StringOrNumberLiteral && t.flags & TypeFlags.FreshLiteral && containsType(types, (<LiteralType>t).regularType);
t.flags & TypeFlags.Literal && t.flags & TypeFlags.FreshLiteral && containsType(types, (<LiteralType>t).regularType);
if (remove) {
orderedRemoveItemAt(types, i);
}
Expand Down Expand Up @@ -9810,8 +9823,8 @@ namespace ts {
}

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

function getRegularTypeOfLiteralType(type: Type): Type {
return type.flags & TypeFlags.StringOrNumberLiteral && type.flags & TypeFlags.FreshLiteral ? (<LiteralType>type).regularType :
return type.flags & TypeFlags.Literal && type.flags & TypeFlags.FreshLiteral ? (<LiteralType>type).regularType :
type.flags & TypeFlags.Union ? getUnionType(sameMap((<UnionType>type).types, getRegularTypeOfLiteralType)) :
type;
}
Expand Down Expand Up @@ -11035,11 +11048,11 @@ namespace ts {
}

function isTypeRelatedTo(source: Type, target: Type, relation: Map<RelationComparisonResult>) {
if (source.flags & TypeFlags.StringOrNumberLiteral && source.flags & TypeFlags.FreshLiteral) {
source = (<LiteralType>source).regularType;
if (source.flags & TypeFlags.Literal && source.flags & TypeFlags.FreshLiteral) {
source = (<FreshableType>source).regularType;
}
if (target.flags & TypeFlags.StringOrNumberLiteral && target.flags & TypeFlags.FreshLiteral) {
target = (<LiteralType>target).regularType;
if (target.flags & TypeFlags.Literal && target.flags & TypeFlags.FreshLiteral) {
target = (<FreshableType>target).regularType;
}
if (source === target ||
relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) ||
Expand Down Expand Up @@ -11194,11 +11207,11 @@ namespace ts {
* * Ternary.False if they are not related.
*/
function isRelatedTo(source: Type, target: Type, reportErrors = false, headMessage?: DiagnosticMessage): Ternary {
if (source.flags & TypeFlags.StringOrNumberLiteral && source.flags & TypeFlags.FreshLiteral) {
source = (<LiteralType>source).regularType;
if (source.flags & TypeFlags.Literal && source.flags & TypeFlags.FreshLiteral) {
source = (<FreshableType>source).regularType;
}
if (target.flags & TypeFlags.StringOrNumberLiteral && target.flags & TypeFlags.FreshLiteral) {
target = (<LiteralType>target).regularType;
if (target.flags & TypeFlags.Literal && target.flags & TypeFlags.FreshLiteral) {
target = (<FreshableType>target).regularType;
}
if (source.flags & TypeFlags.Substitution) {
source = relation === definitelyAssignableRelation ? (<SubstitutionType>source).typeVariable : (<SubstitutionType>source).substitute;
Expand Down Expand Up @@ -12766,7 +12779,7 @@ namespace ts {
return type.flags & TypeFlags.EnumLiteral && type.flags & TypeFlags.FreshLiteral ? getBaseTypeOfEnumLiteralType(<LiteralType>type) :
type.flags & TypeFlags.StringLiteral && type.flags & TypeFlags.FreshLiteral ? stringType :
type.flags & TypeFlags.NumberLiteral && type.flags & TypeFlags.FreshLiteral ? numberType :
type.flags & TypeFlags.BooleanLiteral ? booleanType :
type.flags & TypeFlags.BooleanLiteral && type.flags & TypeFlags.FreshLiteral ? booleanType :
type.flags & TypeFlags.Union ? getUnionType(sameMap((<UnionType>type).types, getWidenedLiteralType)) :
type;
}
Expand Down Expand Up @@ -12820,7 +12833,7 @@ namespace ts {
return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((<UnionType>type).types) :
type.flags & TypeFlags.StringLiteral ? (<LiteralType>type).value === "" ? TypeFlags.StringLiteral : 0 :
type.flags & TypeFlags.NumberLiteral ? (<LiteralType>type).value === 0 ? TypeFlags.NumberLiteral : 0 :
type.flags & TypeFlags.BooleanLiteral ? type === falseType ? TypeFlags.BooleanLiteral : 0 :
type.flags & TypeFlags.BooleanLiteral ? (type === falseType || type === regularFalseType) ? TypeFlags.BooleanLiteral : 0 :
type.flags & TypeFlags.PossiblyFalsy;
}

Expand All @@ -12837,7 +12850,8 @@ namespace ts {
function getDefinitelyFalsyPartOfType(type: Type): Type {
return type.flags & TypeFlags.String ? emptyStringType :
type.flags & TypeFlags.Number ? zeroType :
type.flags & TypeFlags.Boolean || type === falseType ? falseType :
type.flags & TypeFlags.Boolean || type === regularFalseType ? regularFalseType :
type === falseType ? falseType :
type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null) ||
type.flags & TypeFlags.StringLiteral && (<LiteralType>type).value === "" ||
type.flags & TypeFlags.NumberLiteral && (<LiteralType>type).value === 0 ? type :
Expand Down Expand Up @@ -14092,7 +14106,10 @@ namespace ts {
if (assignedType.flags & TypeFlags.Never) {
return assignedType;
}
const reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
if (assignedType.flags & (TypeFlags.FreshLiteral | TypeFlags.Literal)) {
reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types
}
// Our crude heuristic produces an invalid result in some cases: see GH#26130.
// For now, when that happens, we give up and don't narrow at all. (This also
// means we'll never narrow for erroneous assignments where the assigned type
Expand Down Expand Up @@ -14145,8 +14162,8 @@ namespace ts {
}
if (flags & TypeFlags.BooleanLike) {
return strictNullChecks ?
type === falseType ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts :
type === falseType ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
(type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts :
(type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
}
if (flags & TypeFlags.Object) {
return isFunctionObjectType(<ObjectType>type) ?
Expand Down Expand Up @@ -28351,19 +28368,20 @@ namespace ts {
function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean {
if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) {
const type = getTypeOfSymbol(getSymbolOfNode(node));
return !!(type.flags & TypeFlags.StringOrNumberLiteral && type.flags & TypeFlags.FreshLiteral);
return !!(type.flags & TypeFlags.Literal && type.flags & TypeFlags.FreshLiteral);
}
return false;
}

function literalTypeToNode(type: LiteralType, enclosing: Node): Expression {
const enumResult = type.flags & TypeFlags.EnumLiteral && nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing);
return enumResult || createLiteral(type.value);
function literalTypeToNode(type: FreshableType, enclosing: Node): Expression {
const enumResult = type.flags & TypeFlags.EnumLiteral ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing)
: type === trueType ? createTrue() : type === falseType && createFalse();
return enumResult || createLiteral((type as LiteralType).value);
}

function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration) {
const type = getTypeOfSymbol(getSymbolOfNode(node));
return literalTypeToNode(<LiteralType>type, node);
return literalTypeToNode(<FreshableType>type, node);
}

function createResolver(): EmitResolver {
Expand Down Expand Up @@ -29735,7 +29753,7 @@ namespace ts {

function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) {
if (node.initializer) {
const isInvalidInitializer = !(isStringOrNumberLiteralExpression(node.initializer) || isSimpleLiteralEnumReference(node.initializer));
const isInvalidInitializer = !(isStringOrNumberLiteralExpression(node.initializer) || isSimpleLiteralEnumReference(node.initializer) || node.initializer.kind === SyntaxKind.TrueKeyword || node.initializer.kind === SyntaxKind.FalseKeyword);
const isConstOrReadonly = isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node);
if (isConstOrReadonly && !node.type) {
if (isInvalidInitializer) {
Expand Down
15 changes: 12 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3034,8 +3034,8 @@ namespace ts {
/* @internal */ getStringType(): Type;
/* @internal */ getNumberType(): Type;
/* @internal */ getBooleanType(): Type;
/* @internal */ getFalseType(): Type;
/* @internal */ getTrueType(): Type;
/* @internal */ getFalseType(fresh?: boolean): Type;
/* @internal */ getTrueType(fresh?: boolean): Type;
/* @internal */ getVoidType(): Type;
/* @internal */ getUndefinedType(): Type;
/* @internal */ getNullType(): Type;
Expand Down Expand Up @@ -3731,7 +3731,7 @@ namespace ts {
Unit = Literal | UniqueESSymbol | Nullable,
StringOrNumberLiteral = StringLiteral | NumberLiteral,
/* @internal */
StringOrNumberLiteralOrUnique = StringOrNumberLiteral | UniqueESSymbol,
StringOrNumberLiteralOrUnique = StringLiteral | NumberLiteral | UniqueESSymbol,
/* @internal */
DefinitelyFalsy = StringLiteral | NumberLiteral | BooleanLiteral | Void | Undefined | Null,
PossiblyFalsy = DefinitelyFalsy | String | Number | Boolean,
Expand Down Expand Up @@ -3802,6 +3802,15 @@ namespace ts {
intrinsicName: string; // Name of intrinsic type
}

/* @internal */
export interface FreshableIntrinsicType extends IntrinsicType {
freshType: IntrinsicType; // Fresh version of type
regularType: IntrinsicType; // Regular version of type
}

/* @internal */
export type FreshableType = LiteralType | FreshableIntrinsicType;

// String literal types (TypeFlags.StringLiteral)
// Numeric literal types (TypeFlags.NumberLiteral)
export interface LiteralType extends Type {
Expand Down
2 changes: 1 addition & 1 deletion src/services/codefixes/fixStrictClassInitialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ namespace ts.codefix {

function getDefaultValueFromType (checker: TypeChecker, type: Type): Expression | undefined {
if (type.flags & TypeFlags.BooleanLiteral) {
return type === checker.getFalseType() ? createFalse() : createTrue();
return (type === checker.getFalseType() || type === checker.getFalseType(/*fresh*/ true)) ? createFalse() : createTrue();
}
else if (type.isLiteral()) {
return createLiteral(type.value);
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ambientConstLiterals.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ declare const c3 = "abc";
declare const c4 = 123;
declare const c5 = 123;
declare const c6 = -123;
declare const c7: boolean;
declare const c7 = true;
declare const c8 = E.A;
declare const c8b = E["non identifier"];
declare const c9: {
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/booleanLiteralTypes1.types
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ function f4(t: true, f: false) {
>false : false

var x1 = t && f;
>x1 : boolean
>x1 : false
>t && f : false
>t : true
>f : false

var x2 = f && t;
>x2 : boolean
>x2 : false
>f && t : false
>f : false
>t : true
Expand All @@ -100,7 +100,7 @@ function f4(t: true, f: false) {
>f : false

var x4 = f || t;
>x4 : boolean
>x4 : true
>f || t : true
>f : false
>t : true
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/booleanLiteralTypes2.types
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,25 @@ function f4(t: true, f: false) {
>false : false

var x1 = t && f;
>x1 : boolean
>x1 : false
>t && f : false
>t : true
>f : false

var x2 = f && t;
>x2 : boolean
>x2 : false
>f && t : false
>f : false
>t : true

var x3 = t || f;
>x3 : boolean
>x3 : true
>t || f : true
>t : true
>f : false

var x4 = f || t;
>x4 : boolean
>x4 : true
>f || t : true
>f : false
>t : true
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/constDeclarations.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ for (const c5 = 0, c6 = 0; c5 < c6;) {


//// [constDeclarations.d.ts]
declare const c1: boolean;
declare const c1 = false;
declare const c2: number;
declare const c3 = 0, c4: string, c5: any;
2 changes: 1 addition & 1 deletion tests/baselines/reference/constDeclarations2.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var M;

//// [constDeclarations2.d.ts]
declare module M {
const c1: boolean;
const c1 = false;
const c2: number;
const c3 = 0, c4: string, c5: any;
}
Loading

0 comments on commit f2c0288

Please sign in to comment.