From 87ae0489eb8b0b9f4fac16f8dd503a56978bb58b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 3 Mar 2016 17:44:46 -0800 Subject: [PATCH] Reinstate separate type kinds for 'null' and 'undefined' --- src/compiler/checker.ts | 100 +++++++++++++++++++++++++++------------- src/compiler/types.ts | 19 ++++---- 2 files changed, 77 insertions(+), 42 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 71ef823d137..70f1a31e186 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -113,9 +113,9 @@ namespace ts { const booleanType = createIntrinsicType(TypeFlags.Boolean, "boolean"); const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); const voidType = createIntrinsicType(TypeFlags.Void, "void"); - const undefinedType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefined, "undefined"); - const nullType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefined, "null"); - const emptyArrayElementType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefined, "undefined"); + const undefinedType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined"); + const nullType = createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsUndefinedOrNull, "null"); + const emptyArrayElementType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined"); const unknownType = createIntrinsicType(TypeFlags.Any, "unknown"); const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); @@ -209,7 +209,7 @@ namespace ts { }, "undefined": { type: undefinedType, - flags: TypeFlags.ContainsUndefined + flags: TypeFlags.ContainsUndefinedOrNull } }; @@ -1883,7 +1883,7 @@ namespace ts { else if (type.flags & TypeFlags.Tuple) { writeTupleType(type); } - else if (isNullableType(type)) { + else if (isNullableType(type) && (type).types.length > 2) { writeType(getNonNullableType(type), TypeFormatFlags.InElementType); writePunctuation(writer, SyntaxKind.QuestionToken); } @@ -4805,7 +4805,7 @@ namespace ts { const id = getTypeListId(typeSet); let type = unionTypes[id]; if (!type) { - const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Undefined); + const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Nullable); type = unionTypes[id] = createObjectType(TypeFlags.Union | propagatedFlags); type.types = typeSet; } @@ -4840,7 +4840,7 @@ namespace ts { const id = getTypeListId(typeSet); let type = intersectionTypes[id]; if (!type) { - const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Undefined); + const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Nullable); type = intersectionTypes[id] = createObjectType(TypeFlags.Intersection | propagatedFlags); type.types = typeSet; } @@ -5502,6 +5502,9 @@ namespace ts { if (source.flags & TypeFlags.Undefined) { if (!strictNullChecks || target.flags & TypeFlags.Undefined || source === emptyArrayElementType) return Ternary.True; } + if (source.flags & TypeFlags.Null) { + if (!strictNullChecks || target.flags & TypeFlags.Null) return Ternary.True; + } if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True; if (source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum) { if (result = enumRelatedTo(source, target)) { @@ -6325,7 +6328,7 @@ namespace ts { // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, // or if it is not the undefined or null type and if it is assignable to ReadonlyArray return type.flags & TypeFlags.Reference && ((type).target === globalArrayType || (type).target === globalReadonlyArrayType) || - !(type.flags & TypeFlags.Undefined) && isTypeAssignableTo(type, anyReadonlyArrayType); + !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType); } function isTupleLikeType(type: Type): boolean { @@ -6344,18 +6347,18 @@ namespace ts { return !!(type.flags & TypeFlags.Tuple); } - function isNullableType(type: Type): boolean { - if (type.flags & TypeFlags.Undefined) { - return true; - } - if (type.flags & TypeFlags.Union) { + function getNullableKind(type: Type): TypeFlags { + let flags = type.flags; + if (flags & TypeFlags.Union) { for (const t of (type as UnionType).types) { - if (t.flags & TypeFlags.Undefined) { - return true; - } + flags |= t.flags; } } - return false; + return flags & TypeFlags.Nullable; + } + + function isNullableType(type: Type) { + return getNullableKind(type) === TypeFlags.Nullable; } function getNullableType(type: Type): Type { @@ -6363,20 +6366,51 @@ namespace ts { return type; } if (!type.nullableType) { - type.nullableType = isNullableType(type) ? type : getUnionType([type, undefinedType]); + type.nullableType = isNullableType(type) ? type : getUnionType([type, undefinedType, nullType]); } return type.nullableType; } - function getNonNullableTypeFromUnionType(type: UnionType): Type { - if (!type.nonNullableType) { - type.nonNullableType = removeTypesFromUnionOrIntersection(type, [undefinedType, nullType]); + function addNullableKind(type: Type, kind: TypeFlags): Type { + if ((getNullableKind(type) & kind) !== kind) { + const types = [type]; + if (kind & TypeFlags.Undefined) { + types.push(undefinedType); + } + if (kind & TypeFlags.Null) { + types.push(nullType); + } + type = getUnionType(types); } - return type.nonNullableType; + return type; + } + + function removeNullableKind(type: Type, kind: TypeFlags) { + if (type.flags & TypeFlags.Union && getNullableKind(type) & kind) { + let firstType: Type; + let types: Type[]; + for (const t of (type as UnionType).types) { + if (!(t.flags & kind)) { + if (!firstType) { + firstType = t; + } + else { + if (!types) { + types = [firstType]; + } + types.push(t); + } + } + } + if (firstType) { + type = types ? getUnionType(types) : firstType; + } + } + return type; } function getNonNullableType(type: Type): Type { - return strictNullChecks && type.flags & TypeFlags.Union ? getNonNullableTypeFromUnionType(type as UnionType) : type; + return strictNullChecks ? removeNullableKind(type, TypeFlags.Nullable) : type; } /** @@ -6433,12 +6467,12 @@ namespace ts { } function getWidenedConstituentType(type: Type): Type { - return type.flags & TypeFlags.Undefined ? type : getWidenedType(type); + return type.flags & TypeFlags.Nullable ? type : getWidenedType(type); } function getWidenedType(type: Type): Type { if (type.flags & TypeFlags.RequiresWidening) { - if (type.flags & TypeFlags.Undefined) { + if (type.flags & TypeFlags.Nullable) { return anyType; } if (type.flags & TypeFlags.ObjectLiteral) { @@ -6490,7 +6524,7 @@ namespace ts { if (type.flags & TypeFlags.ObjectLiteral) { for (const p of getPropertiesOfObjectType(type)) { const t = getTypeOfSymbol(p); - if (t.flags & TypeFlags.ContainsUndefined) { + if (t.flags & TypeFlags.ContainsUndefinedOrNull) { if (!reportWideningErrorsInType(t)) { error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, p.name, typeToString(getWidenedType(t))); } @@ -6534,7 +6568,7 @@ namespace ts { } function reportErrorsFromWidening(declaration: Declaration, type: Type) { - if (produceDiagnostics && compilerOptions.noImplicitAny && type.flags & TypeFlags.ContainsUndefined) { + if (produceDiagnostics && compilerOptions.noImplicitAny && type.flags & TypeFlags.ContainsUndefinedOrNull) { // Report implicit any error within type if possible, otherwise report error on declaration if (!reportWideningErrorsInType(type)) { reportImplicitAnyError(declaration, type); @@ -7542,7 +7576,7 @@ namespace ts { checkNestedBlockScopedBinding(node, symbol); const type = getTypeOfSymbol(localOrExportSymbol); - if (strictNullChecks && !isAssignmentTarget(node) && !(type.flags & TypeFlags.Any) && !isNullableType(type)) { + if (strictNullChecks && !isAssignmentTarget(node) && !(type.flags & TypeFlags.Any) && !(getNullableKind(type) & TypeFlags.Undefined)) { checkVariableAssignedBefore(symbol, node); } return getNarrowedTypeOfReference(type, node); @@ -11524,8 +11558,8 @@ namespace ts { // as having the primitive type Number. If one operand is the null or undefined value, // it is treated as having the type of the other operand. // The result is always of the Number primitive type. - if (leftType.flags & TypeFlags.Undefined) leftType = rightType; - if (rightType.flags & TypeFlags.Undefined) rightType = leftType; + if (leftType.flags & TypeFlags.Nullable) leftType = rightType; + if (rightType.flags & TypeFlags.Nullable) rightType = leftType; leftType = getNonNullableType(leftType); rightType = getNonNullableType(rightType); @@ -11555,8 +11589,8 @@ namespace ts { // or at least one of the operands to be of type Any or the String primitive type. // If one operand is the null or undefined value, it is treated as having the type of the other operand. - if (leftType.flags & TypeFlags.Undefined) leftType = rightType; - if (rightType.flags & TypeFlags.Undefined) rightType = leftType; + if (leftType.flags & TypeFlags.Nullable) leftType = rightType; + if (rightType.flags & TypeFlags.Nullable) rightType = leftType; leftType = getNonNullableType(leftType); rightType = getNonNullableType(rightType); @@ -11618,7 +11652,7 @@ namespace ts { case SyntaxKind.InKeyword: return checkInExpression(left, right, leftType, rightType); case SyntaxKind.AmpersandAmpersandToken: - return isNullableType(leftType) ? getNullableType(rightType) : rightType; + return addNullableKind(rightType, getNullableKind(leftType)); case SyntaxKind.BarBarToken: return getUnionType([getNonNullableType(leftType), rightType]); case SyntaxKind.EqualsToken: diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9f709c8fa68..56611961998 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2096,7 +2096,8 @@ namespace ts { Number = 0x00000004, Boolean = 0x00000008, Void = 0x00000010, - Undefined = 0x00000020, // Undefined or null + Undefined = 0x00000020, + Null = 0x00000040, Enum = 0x00000080, // Enum type StringLiteral = 0x00000100, // String literal type TypeParameter = 0x00000200, // Type parameter @@ -2114,7 +2115,7 @@ namespace ts { /* @internal */ FreshObjectLiteral = 0x00100000, // Fresh object literal type /* @internal */ - ContainsUndefined = 0x00200000, // Type is or contains undefined type + ContainsUndefinedOrNull = 0x00200000, // Type is or contains undefined or null type /* @internal */ ContainsObjectLiteral = 0x00400000, // Type is or contains object literal type /* @internal */ @@ -2124,18 +2125,20 @@ namespace ts { ObjectLiteralPatternWithComputedProperties = 0x04000000, // Object literal type implied by binding pattern has computed properties /* @internal */ - Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined, + Nullable = Undefined | Null, /* @internal */ - Primitive = String | Number | Boolean | ESSymbol | Void | Undefined | StringLiteral | Enum, + Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null, + /* @internal */ + Primitive = String | Number | Boolean | ESSymbol | Void | Undefined | Null | StringLiteral | Enum, StringLike = String | StringLiteral, NumberLike = Number | Enum, ObjectType = Class | Interface | Reference | Tuple | Anonymous, UnionOrIntersection = Union | Intersection, StructuredType = ObjectType | Union | Intersection, /* @internal */ - RequiresWidening = ContainsUndefined | ContainsObjectLiteral, + RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral, /* @internal */ - PropagatingFlags = ContainsUndefined | ContainsObjectLiteral | ContainsAnyFunctionType + PropagatingFlags = ContainsUndefinedOrNull | ContainsObjectLiteral | ContainsAnyFunctionType } export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; @@ -2214,9 +2217,7 @@ namespace ts { resolvedProperties: SymbolTable; // Cache of resolved properties } - export interface UnionType extends UnionOrIntersectionType { - nonNullableType?: Type; // Cached non-nullable form of type - } + export interface UnionType extends UnionOrIntersectionType { } export interface IntersectionType extends UnionOrIntersectionType { }