From 26cc99b92d63540bbd7792415a9f82a1e9936106 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 14 Feb 2016 18:41:38 -0800 Subject: [PATCH 01/59] Introduce -strictNullChecks compiler option --- src/compiler/commandLineParser.ts | 5 +++++ src/compiler/diagnosticMessages.json | 4 ++++ src/compiler/types.ts | 1 + 3 files changed, 10 insertions(+) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 86d073f7d49e0..bdc586b603788 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -326,6 +326,11 @@ namespace ts { name: "noImplicitUseStrict", type: "boolean", description: Diagnostics.Do_not_emit_use_strict_directives_in_module_output + }, + { + name: "strictNullChecks", + type: "boolean", + description: Diagnostics.Enable_strict_null_checks } ]; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 25b86e67d34fe..6978a9ad61186 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2576,6 +2576,10 @@ "category": "Message", "code": 6112 }, + "Enable strict null checks.": { + "category": "Message", + "code": 6113 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6b9f49cac014f..3e59a623c2f36 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2420,6 +2420,7 @@ namespace ts { allowSyntheticDefaultImports?: boolean; allowJs?: boolean; noImplicitUseStrict?: boolean; + strictNullChecks?: boolean; /* @internal */ stripInternal?: boolean; // Skip checking lib.d.ts to help speed up tests. From 8e926035b7e17533b205d06d4c60a89c21238e00 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 14 Feb 2016 18:59:58 -0800 Subject: [PATCH 02/59] Parsing of nullable types --- src/compiler/parser.ts | 23 +++++++++++++++++------ src/compiler/types.ts | 8 +++++++- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 66d33f9d7d9e4..16ee3eb1627a4 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -127,7 +127,8 @@ namespace ts { case SyntaxKind.IntersectionType: return visitNodes(cbNodes, (node).types); case SyntaxKind.ParenthesizedType: - return visitNode(cbNode, (node).type); + case SyntaxKind.NullableType: + return visitNode(cbNode, (node).type); case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: return visitNodes(cbNodes, (node).elements); @@ -2413,11 +2414,21 @@ namespace ts { function parseArrayTypeOrHigher(): TypeNode { let type = parseNonArrayType(); - while (!scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) { - parseExpected(SyntaxKind.CloseBracketToken); - const node = createNode(SyntaxKind.ArrayType, type.pos); - node.elementType = type; - type = finishNode(node); + while (!scanner.hasPrecedingLineBreak()) { + if (parseOptional(SyntaxKind.OpenBracketToken)) { + parseExpected(SyntaxKind.CloseBracketToken); + const node = createNode(SyntaxKind.ArrayType, type.pos); + node.elementType = type; + type = finishNode(node); + } + else if (parseOptional(SyntaxKind.QuestionToken)) { + const node = createNode(SyntaxKind.NullableType, type.pos); + node.type = type; + type = finishNode(node); + } + else { + break; + } } return type; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3e59a623c2f36..c2e5b1668154d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -208,6 +208,7 @@ namespace ts { ParenthesizedType, ThisType, StringLiteralType, + NullableType, // Binding patterns ObjectBindingPattern, ArrayBindingPattern, @@ -353,7 +354,7 @@ namespace ts { FirstFutureReservedWord = ImplementsKeyword, LastFutureReservedWord = YieldKeyword, FirstTypeNode = TypePredicate, - LastTypeNode = StringLiteralType, + LastTypeNode = NullableType, FirstPunctuation = OpenBraceToken, LastPunctuation = CaretEqualsToken, FirstToken = Unknown, @@ -777,6 +778,11 @@ namespace ts { _stringLiteralTypeBrand: any; } + // @kind(SyntaxKind.NullableType) + export interface NullableTypeNode extends TypeNode { + type: TypeNode; + } + // @kind(SyntaxKind.StringLiteral) export interface StringLiteral extends LiteralExpression { _stringLiteralBrand: any; From 26e371d7bd985361c2a251dce5707129079cf362 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 14 Feb 2016 19:15:04 -0800 Subject: [PATCH 03/59] Use TypeFlags.Undefined for both undefined and null types --- src/compiler/checker.ts | 22 +++++++++++----------- src/compiler/types.ts | 13 ++++++------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6b43b3802c2a4..4667ff64d95a4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -115,8 +115,8 @@ 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.ContainsUndefinedOrNull, "undefined"); - const nullType = createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsUndefinedOrNull, "null"); + const undefinedType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefined, "undefined"); + const nullType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefined, "null"); const unknownType = createIntrinsicType(TypeFlags.Any, "unknown"); const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); @@ -210,7 +210,7 @@ namespace ts { }, "undefined": { type: undefinedType, - flags: TypeFlags.ContainsUndefinedOrNull + flags: TypeFlags.ContainsUndefined } }; @@ -6244,7 +6244,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 | TypeFlags.Null)) && isTypeAssignableTo(type, anyReadonlyArrayType); + !(type.flags & TypeFlags.Undefined) && isTypeAssignableTo(type, anyReadonlyArrayType); } function isTupleLikeType(type: Type): boolean { @@ -6308,7 +6308,7 @@ namespace ts { function getWidenedType(type: Type): Type { if (type.flags & TypeFlags.RequiresWidening) { - if (type.flags & (TypeFlags.Undefined | TypeFlags.Null)) { + if (type.flags & TypeFlags.Undefined) { return anyType; } if (type.flags & TypeFlags.PredicateType) { @@ -6363,7 +6363,7 @@ namespace ts { if (type.flags & TypeFlags.ObjectLiteral) { for (const p of getPropertiesOfObjectType(type)) { const t = getTypeOfSymbol(p); - if (t.flags & TypeFlags.ContainsUndefinedOrNull) { + if (t.flags & TypeFlags.ContainsUndefined) { if (!reportWideningErrorsInType(t)) { error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, p.name, typeToString(getWidenedType(t))); } @@ -6407,7 +6407,7 @@ namespace ts { } function reportErrorsFromWidening(declaration: Declaration, type: Type) { - if (produceDiagnostics && compilerOptions.noImplicitAny && type.flags & TypeFlags.ContainsUndefinedOrNull) { + if (produceDiagnostics && compilerOptions.noImplicitAny && type.flags & TypeFlags.ContainsUndefined) { // Report implicit any error within type if possible, otherwise report error on declaration if (!reportWideningErrorsInType(type)) { reportImplicitAnyError(declaration, type); @@ -11087,8 +11087,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 | TypeFlags.Null)) leftType = rightType; - if (rightType.flags & (TypeFlags.Undefined | TypeFlags.Null)) rightType = leftType; + if (leftType.flags & TypeFlags.Undefined) leftType = rightType; + if (rightType.flags & TypeFlags.Undefined) rightType = leftType; let suggestedOperator: SyntaxKind; // if a user tries to apply a bitwise operator to 2 boolean operands @@ -11115,8 +11115,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 | TypeFlags.Null)) leftType = rightType; - if (rightType.flags & (TypeFlags.Undefined | TypeFlags.Null)) rightType = leftType; + if (leftType.flags & TypeFlags.Undefined) leftType = rightType; + if (rightType.flags & TypeFlags.Undefined) rightType = leftType; let resultType: Type; if (isTypeOfKind(leftType, TypeFlags.NumberLike) && isTypeOfKind(rightType, TypeFlags.NumberLike)) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c2e5b1668154d..17a50a1c5716b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2074,8 +2074,7 @@ namespace ts { Number = 0x00000004, Boolean = 0x00000008, Void = 0x00000010, - Undefined = 0x00000020, - Null = 0x00000040, + Undefined = 0x00000020, // Undefined or null Enum = 0x00000080, // Enum type StringLiteral = 0x00000100, // String literal type TypeParameter = 0x00000200, // Type parameter @@ -2093,7 +2092,7 @@ namespace ts { /* @internal */ FreshObjectLiteral = 0x00100000, // Fresh object literal type /* @internal */ - ContainsUndefinedOrNull = 0x00200000, // Type is or contains Undefined or Null type + ContainsUndefined = 0x00200000, // Type is or contains undefined type /* @internal */ ContainsObjectLiteral = 0x00400000, // Type is or contains object literal type /* @internal */ @@ -2104,18 +2103,18 @@ namespace ts { PredicateType = 0x08000000, // Predicate types are also Boolean types, but should not be considered Intrinsics - there's no way to capture this with flags /* @internal */ - Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null, + Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined, /* @internal */ - Primitive = String | Number | Boolean | ESSymbol | Void | Undefined | Null | StringLiteral | Enum, + Primitive = String | Number | Boolean | ESSymbol | Void | Undefined | StringLiteral | Enum, StringLike = String | StringLiteral, NumberLike = Number | Enum, ObjectType = Class | Interface | Reference | Tuple | Anonymous, UnionOrIntersection = Union | Intersection, StructuredType = ObjectType | Union | Intersection, /* @internal */ - RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral | PredicateType, + RequiresWidening = ContainsUndefined | ContainsObjectLiteral | PredicateType, /* @internal */ - PropagatingFlags = ContainsUndefinedOrNull | ContainsObjectLiteral | ContainsAnyFunctionType + PropagatingFlags = ContainsUndefined | ContainsObjectLiteral | ContainsAnyFunctionType } export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; From 98b6a5ad11f3ac8d0cec8c6358468b08079dbf90 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Feb 2016 09:23:40 -0800 Subject: [PATCH 04/59] Make undefined and null assignable to each other --- src/compiler/checker.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4667ff64d95a4..2de8c86e5fd14 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5415,8 +5415,7 @@ namespace ts { } if (isTypeAny(target)) return Ternary.True; - if (source === undefinedType) return Ternary.True; - if (source === nullType && target !== undefinedType) return Ternary.True; + if (source.flags & TypeFlags.Undefined) 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)) { From e79df80e224957449f18608db6d1fdadc1646629 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Feb 2016 09:24:20 -0800 Subject: [PATCH 05/59] Accepting new baselines --- tests/baselines/reference/arrayLiteralWidened.types | 2 +- tests/baselines/reference/arrayLiterals2ES5.types | 2 +- .../reference/destructuringVariableDeclaration1ES5.types | 4 ++-- .../reference/destructuringVariableDeclaration1ES6.types | 4 ++-- .../baselines/reference/logicalOrOperatorWithEveryType.types | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/baselines/reference/arrayLiteralWidened.types b/tests/baselines/reference/arrayLiteralWidened.types index 9599db2dff53b..83db7046eed4a 100644 --- a/tests/baselines/reference/arrayLiteralWidened.types +++ b/tests/baselines/reference/arrayLiteralWidened.types @@ -19,7 +19,7 @@ var a = [undefined, undefined]; var b = [[], [null, null]]; // any[][] >b : any[][] ->[[], [null, null]] : null[][] +>[[], [null, null]] : undefined[][] >[] : undefined[] >[null, null] : null[] >null : null diff --git a/tests/baselines/reference/arrayLiterals2ES5.types b/tests/baselines/reference/arrayLiterals2ES5.types index a9cf31611c26f..cbfb620fb00d6 100644 --- a/tests/baselines/reference/arrayLiterals2ES5.types +++ b/tests/baselines/reference/arrayLiterals2ES5.types @@ -130,7 +130,7 @@ var temp2: [number[], string[]] = [[1, 2, 3], ["hello", "string"]]; var temp3 = [undefined, null, undefined]; >temp3 : any[] ->[undefined, null, undefined] : null[] +>[undefined, null, undefined] : undefined[] >undefined : undefined >null : null >undefined : undefined diff --git a/tests/baselines/reference/destructuringVariableDeclaration1ES5.types b/tests/baselines/reference/destructuringVariableDeclaration1ES5.types index f8188147a7908..aab01926a5a9e 100644 --- a/tests/baselines/reference/destructuringVariableDeclaration1ES5.types +++ b/tests/baselines/reference/destructuringVariableDeclaration1ES5.types @@ -168,7 +168,7 @@ var {f: [f1, f2, { f3: f4, f5 }, , ]} = { f: [1, 2, { f3: 4, f5: 0 }] }; var {g: {g1 = [undefined, null]}}: { g: { g1: any[] } } = { g: { g1: [1, 2] } }; >g : any >g1 : any[] ->[undefined, null] : null[] +>[undefined, null] : undefined[] >undefined : undefined >null : null >g : { g1: any[]; } @@ -184,7 +184,7 @@ var {g: {g1 = [undefined, null]}}: { g: { g1: any[] } } = { g: { g1: [1, 2] } }; var {h: {h1 = [undefined, null]}}: { h: { h1: number[] } } = { h: { h1: [1, 2] } }; >h : any >h1 : number[] ->[undefined, null] : null[] +>[undefined, null] : undefined[] >undefined : undefined >null : null >h : { h1: number[]; } diff --git a/tests/baselines/reference/destructuringVariableDeclaration1ES6.types b/tests/baselines/reference/destructuringVariableDeclaration1ES6.types index 7b4fe5409dbaa..7e98817c052db 100644 --- a/tests/baselines/reference/destructuringVariableDeclaration1ES6.types +++ b/tests/baselines/reference/destructuringVariableDeclaration1ES6.types @@ -168,7 +168,7 @@ var {f: [f1, f2, { f3: f4, f5 }, , ]} = { f: [1, 2, { f3: 4, f5: 0 }] }; var {g: {g1 = [undefined, null]}}: { g: { g1: any[] } } = { g: { g1: [1, 2] } }; >g : any >g1 : any[] ->[undefined, null] : null[] +>[undefined, null] : undefined[] >undefined : undefined >null : null >g : { g1: any[]; } @@ -184,7 +184,7 @@ var {g: {g1 = [undefined, null]}}: { g: { g1: any[] } } = { g: { g1: [1, 2] } }; var {h: {h1 = [undefined, null]}}: { h: { h1: number[] } } = { h: { h1: [1, 2] } }; >h : any >h1 : number[] ->[undefined, null] : null[] +>[undefined, null] : undefined[] >undefined : undefined >null : null >h : { h1: number[]; } diff --git a/tests/baselines/reference/logicalOrOperatorWithEveryType.types b/tests/baselines/reference/logicalOrOperatorWithEveryType.types index 4540e35aaf459..609af4ecc94fa 100644 --- a/tests/baselines/reference/logicalOrOperatorWithEveryType.types +++ b/tests/baselines/reference/logicalOrOperatorWithEveryType.types @@ -572,7 +572,7 @@ var rj9 = null || null; // null || null is any var rj10 = undefined || null; // undefined || null is any >rj10 : any ->undefined || null : null +>undefined || null : undefined >undefined : undefined >null : null From 6d6d2a11bc07c5ab15462362e27164446a3d05bd Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Feb 2016 09:34:14 -0800 Subject: [PATCH 06/59] Introduce nullable types in checker --- src/compiler/checker.ts | 61 ++++++++++++++++++++++++++++++++++++++--- src/compiler/types.ts | 5 +++- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2de8c86e5fd14..4d930b3566739 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -51,6 +51,8 @@ namespace ts { const languageVersion = compilerOptions.target || ScriptTarget.ES3; const modulekind = getEmitModuleKind(compilerOptions); const allowSyntheticDefaultImports = typeof compilerOptions.allowSyntheticDefaultImports !== "undefined" ? compilerOptions.allowSyntheticDefaultImports : modulekind === ModuleKind.System; + const strictNullChecks = compilerOptions.strictNullChecks; + const emitResolver = createResolver(); @@ -2340,6 +2342,7 @@ namespace ts { case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: case SyntaxKind.ParenthesizedType: + case SyntaxKind.NullableType: return isDeclarationVisible(node.parent); // Default binding, import specifier and namespace import is visible @@ -4664,6 +4667,14 @@ namespace ts { return links.resolvedType; } + function getTypeFromNullableTypeNode(node: NullableTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getNullableType(getTypeFromTypeNode(node.type)); + } + return links.resolvedType; + } + function addTypeToSet(typeSet: Type[], type: Type, typeSetKind: TypeFlags) { if (type.flags & typeSetKind) { addTypesToSet(typeSet, (type).types, typeSetKind); @@ -4736,8 +4747,10 @@ namespace ts { return anyType; } if (noSubtypeReduction) { - removeAllButLast(typeSet, undefinedType); - removeAllButLast(typeSet, nullType); + if (!strictNullChecks) { + removeAllButLast(typeSet, undefinedType); + removeAllButLast(typeSet, nullType); + } } else { removeSubtypes(typeSet); @@ -4920,6 +4933,8 @@ namespace ts { return getTypeFromUnionTypeNode(node); case SyntaxKind.IntersectionType: return getTypeFromIntersectionTypeNode(node); + case SyntaxKind.NullableType: + return getTypeFromNullableTypeNode(node); case SyntaxKind.ParenthesizedType: case SyntaxKind.JSDocNullableType: case SyntaxKind.JSDocNonNullableType: @@ -5415,7 +5430,9 @@ namespace ts { } if (isTypeAny(target)) return Ternary.True; - if (source.flags & TypeFlags.Undefined) return Ternary.True; + if (source.flags & TypeFlags.Undefined) { + if (!strictNullChecks || target.flags & TypeFlags.Undefined) 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)) { @@ -6262,6 +6279,41 @@ namespace ts { return !!(type.flags & TypeFlags.Tuple); } + function isNullableType(type: Type): boolean { + if (type.flags & TypeFlags.Undefined) { + return true; + } + if (type.flags & TypeFlags.Union) { + for (const t of (type as UnionType).types) { + if (t.flags & TypeFlags.Undefined) { + return true; + } + } + } + return false; + } + + function getNullableType(type: Type): Type { + if (!strictNullChecks) { + return type; + } + if (!type.nullableType) { + type.nullableType = isNullableType(type) ? type : getUnionType([type, undefinedType]); + } + return type.nullableType; + } + + function getNonNullableTypeFromUnionType(type: UnionType): Type { + if (!type.nonNullableType) { + type.nonNullableType = removeTypesFromUnionOrIntersection(type, [undefinedType, nullType]); + } + return type.nonNullableType; + } + + function getNonNullableType(type: Type): Type { + return strictNullChecks && type.flags & TypeFlags.Union ? getNonNullableTypeFromUnionType(type as UnionType) : type; + } + function getRegularTypeOfObjectLiteral(type: Type): Type { if (type.flags & TypeFlags.FreshObjectLiteral) { let regularType = (type).regularType; @@ -14988,7 +15040,8 @@ namespace ts { case SyntaxKind.IntersectionType: return checkUnionOrIntersectionType(node); case SyntaxKind.ParenthesizedType: - return checkSourceElement((node).type); + case SyntaxKind.NullableType: + return checkSourceElement((node).type); case SyntaxKind.FunctionDeclaration: return checkFunctionDeclaration(node); case SyntaxKind.Block: diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 17a50a1c5716b..d7abcc0296383 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2125,6 +2125,7 @@ namespace ts { /* @internal */ id: number; // Unique ID symbol?: Symbol; // Symbol associated with type (if any) pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any) + nullableType?: Type; // Cached nullable form of this type } /* @internal */ @@ -2197,7 +2198,9 @@ namespace ts { resolvedProperties: SymbolTable; // Cache of resolved properties } - export interface UnionType extends UnionOrIntersectionType { } + export interface UnionType extends UnionOrIntersectionType { + nonNullableType?: Type; // Cached non-nullable form of type + } export interface IntersectionType extends UnionOrIntersectionType { } From f08f6067e8c86059ee868cb4eb3e0db72b6e0e77 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Feb 2016 09:38:49 -0800 Subject: [PATCH 07/59] Display support for nullable types --- src/compiler/checker.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4d930b3566739..96c6642fb2646 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -53,7 +53,6 @@ namespace ts { const allowSyntheticDefaultImports = typeof compilerOptions.allowSyntheticDefaultImports !== "undefined" ? compilerOptions.allowSyntheticDefaultImports : modulekind === ModuleKind.System; const strictNullChecks = compilerOptions.strictNullChecks; - const emitResolver = createResolver(); const undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined"); @@ -1873,6 +1872,10 @@ namespace ts { else if (type.flags & TypeFlags.Tuple) { writeTupleType(type); } + else if (isNullableType(type)) { + writeType(getNonNullableType(type), TypeFormatFlags.InElementType); + writePunctuation(writer, SyntaxKind.QuestionToken); + } else if (type.flags & TypeFlags.UnionOrIntersection) { writeUnionOrIntersectionType(type, flags); } From fa36ff85ca78861fd24586c0403eeb4a1bf50305 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Feb 2016 09:42:16 -0800 Subject: [PATCH 08/59] Don't widen undefined types in unions --- src/compiler/checker.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 96c6642fb2646..b17de2a2b74ee 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6360,6 +6360,10 @@ namespace ts { numberIndexInfo && createIndexInfo(getWidenedType(numberIndexInfo.type), numberIndexInfo.isReadonly)); } + function getWidenedConstituentType(type: Type): Type { + return type.flags & TypeFlags.Undefined ? type : getWidenedType(type); + } + function getWidenedType(type: Type): Type { if (type.flags & TypeFlags.RequiresWidening) { if (type.flags & TypeFlags.Undefined) { @@ -6372,7 +6376,7 @@ namespace ts { return getWidenedTypeOfObjectLiteral(type); } if (type.flags & TypeFlags.Union) { - return getUnionType(map((type).types, getWidenedType), /*noSubtypeReduction*/ true); + return getUnionType(map((type).types, getWidenedConstituentType), /*noSubtypeReduction*/ true); } if (isArrayType(type)) { return createArrayType(getWidenedType((type).typeArguments[0])); From 0d3005b85de012da071b2a11b827ba6126800dfe Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Feb 2016 09:58:49 -0800 Subject: [PATCH 09/59] Support nullable types with expression operators --- src/compiler/checker.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b17de2a2b74ee..eefc636f5bdc7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10880,7 +10880,8 @@ namespace ts { return booleanType; case SyntaxKind.PlusPlusToken: case SyntaxKind.MinusMinusToken: - const ok = checkArithmeticOperandType(node.operand, operandType, Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type); + const ok = checkArithmeticOperandType(node.operand, getNonNullableType(operandType), + Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type); if (ok) { // run check only if former checks succeeded to avoid reporting cascading errors checkReferenceExpression(node.operand, @@ -10894,7 +10895,8 @@ namespace ts { function checkPostfixUnaryExpression(node: PostfixUnaryExpression): Type { const operandType = checkExpression(node.operand); - const ok = checkArithmeticOperandType(node.operand, operandType, Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type); + const ok = checkArithmeticOperandType(node.operand, getNonNullableType(operandType), + Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type); if (ok) { // run check only if former checks succeeded to avoid reporting cascading errors checkReferenceExpression(node.operand, @@ -11148,6 +11150,9 @@ namespace ts { if (leftType.flags & TypeFlags.Undefined) leftType = rightType; if (rightType.flags & TypeFlags.Undefined) rightType = leftType; + leftType = getNonNullableType(leftType); + rightType = getNonNullableType(rightType); + let suggestedOperator: SyntaxKind; // if a user tries to apply a bitwise operator to 2 boolean operands // try and return them a helpful suggestion @@ -11176,6 +11181,9 @@ namespace ts { if (leftType.flags & TypeFlags.Undefined) leftType = rightType; if (rightType.flags & TypeFlags.Undefined) rightType = leftType; + leftType = getNonNullableType(leftType); + rightType = getNonNullableType(rightType); + let resultType: Type; if (isTypeOfKind(leftType, TypeFlags.NumberLike) && isTypeOfKind(rightType, TypeFlags.NumberLike)) { // Operands of an enum type are treated as having the primitive type Number. @@ -11235,7 +11243,7 @@ namespace ts { case SyntaxKind.AmpersandAmpersandToken: return rightType; case SyntaxKind.BarBarToken: - return getUnionType([leftType, rightType]); + return getUnionType([getNonNullableType(leftType), rightType]); case SyntaxKind.EqualsToken: checkAssignmentOperator(rightType); return getRegularTypeOfObjectLiteral(rightType); From 09fa3e5e158b488e42a86ca560394a1a74167af4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Feb 2016 11:37:36 -0800 Subject: [PATCH 10/59] Ensure empty array literal is assignable to array of non-null type in strict null mode --- src/compiler/checker.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index eefc636f5bdc7..1464d719478ed 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -118,6 +118,7 @@ namespace ts { 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 unknownType = createIntrinsicType(TypeFlags.Any, "unknown"); const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); @@ -5434,7 +5435,7 @@ namespace ts { if (isTypeAny(target)) return Ternary.True; if (source.flags & TypeFlags.Undefined) { - if (!strictNullChecks || target.flags & TypeFlags.Undefined) return Ternary.True; + if (!strictNullChecks || target.flags & TypeFlags.Undefined || source === emptyArrayElementType) return Ternary.True; } if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True; if (source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum) { @@ -8222,7 +8223,7 @@ namespace ts { } } } - return createArrayType(elementTypes.length ? getUnionType(elementTypes) : undefinedType); + return createArrayType(elementTypes.length ? getUnionType(elementTypes) : emptyArrayElementType); } function isNumericName(name: DeclarationName): boolean { From 41401c7caeb73d45c08ea8fa2f18d90e6a401458 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Feb 2016 17:02:43 -0800 Subject: [PATCH 11/59] Make types of optional parameters and properties nullable --- src/compiler/checker.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1464d719478ed..c7e74d86f9083 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2677,7 +2677,8 @@ namespace ts { // Use type from type annotation if one is present if (declaration.type) { - return getTypeFromTypeNode(declaration.type); + const type = getTypeFromTypeNode(declaration.type); + return declaration.questionToken ? getNullableType(type) : type; } if (declaration.kind === SyntaxKind.Parameter) { @@ -2692,7 +2693,7 @@ namespace ts { // Use contextual parameter type if one is available const type = getContextuallyTypedParameterType(declaration); if (type) { - return type; + return declaration.questionToken ? getNullableType(type) : type; } } From 586c3ac86fb27282723bcb88907e80bccbf9860a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 15 Feb 2016 19:26:20 -0800 Subject: [PATCH 12/59] Exclude undefined/null from flags propagation within union types --- src/compiler/checker.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c7e74d86f9083..83a149efdc13b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4387,10 +4387,12 @@ namespace ts { // It is only necessary to do so if a constituent type might be the undefined type, the null type, the type // of an object literal or the anyFunctionType. This is because there are operations in the type checker // that care about the presence of such types at arbitrary depth in a containing type. - function getPropagatingFlagsOfTypes(types: Type[]): TypeFlags { + function getPropagatingFlagsOfTypes(types: Type[], excludeKinds: TypeFlags): TypeFlags { let result: TypeFlags = 0; for (const type of types) { - result |= type.flags; + if (!(type.flags & excludeKinds)) { + result |= type.flags; + } } return result & TypeFlags.PropagatingFlags; } @@ -4399,7 +4401,8 @@ namespace ts { const id = getTypeListId(typeArguments); let type = target.instantiations[id]; if (!type) { - const flags = TypeFlags.Reference | (typeArguments ? getPropagatingFlagsOfTypes(typeArguments) : 0); + const propagatedFlags = typeArguments ? getPropagatingFlagsOfTypes(typeArguments, /*excludeKinds*/ 0) : 0; + const flags = TypeFlags.Reference | propagatedFlags; type = target.instantiations[id] = createObjectType(flags, target.symbol); type.target = target; type.typeArguments = typeArguments; @@ -4659,7 +4662,8 @@ namespace ts { } function createNewTupleType(elementTypes: Type[]) { - const type = createObjectType(TypeFlags.Tuple | getPropagatingFlagsOfTypes(elementTypes)); + const propagatedFlags = getPropagatingFlagsOfTypes(elementTypes, /*excludeKinds*/ 0); + const type = createObjectType(TypeFlags.Tuple | propagatedFlags); type.elementTypes = elementTypes; return type; } @@ -4766,7 +4770,8 @@ namespace ts { const id = getTypeListId(typeSet); let type = unionTypes[id]; if (!type) { - type = unionTypes[id] = createObjectType(TypeFlags.Union | getPropagatingFlagsOfTypes(typeSet)); + const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Undefined); + type = unionTypes[id] = createObjectType(TypeFlags.Union | propagatedFlags); type.types = typeSet; } return type; @@ -4800,7 +4805,8 @@ namespace ts { const id = getTypeListId(typeSet); let type = intersectionTypes[id]; if (!type) { - type = intersectionTypes[id] = createObjectType(TypeFlags.Intersection | getPropagatingFlagsOfTypes(typeSet)); + const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Undefined); + type = intersectionTypes[id] = createObjectType(TypeFlags.Intersection | propagatedFlags); type.types = typeSet; } return type; From bf89530e3621ded10c57716b2c76a18c9845c5ae Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 16 Feb 2016 09:51:49 -0800 Subject: [PATCH 13/59] Add truthy/falsey guards for nullable types --- src/compiler/checker.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 83a149efdc13b..2e7595f65be78 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6993,6 +6993,10 @@ namespace ts { return type; + function narrowTypeByTruthiness(type: Type, expr: Identifier, assumeTrue: boolean): Type { + return strictNullChecks && assumeTrue && getResolvedSymbol(expr) === symbol ? getNonNullableType(type) : type; + } + function narrowTypeByEquality(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { // Check that we have 'typeof ' on the left and string literal on the right if (expr.left.kind !== SyntaxKind.TypeOfExpression || expr.right.kind !== SyntaxKind.StringLiteral) { @@ -7195,6 +7199,8 @@ namespace ts { // will be a subtype or the same type as the argument. function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { switch (expr.kind) { + case SyntaxKind.Identifier: + return narrowTypeByTruthiness(type, expr, assumeTrue) case SyntaxKind.CallExpression: return narrowTypeByTypePredicate(type, expr, assumeTrue); case SyntaxKind.ParenthesizedExpression: From bd12f1b9138f826847667f2c42aa0414c562dcf0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 16 Feb 2016 11:03:28 -0800 Subject: [PATCH 14/59] Add missing semicolon --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2e7595f65be78..f3aece5dcdb40 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7200,7 +7200,7 @@ namespace ts { function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { switch (expr.kind) { case SyntaxKind.Identifier: - return narrowTypeByTruthiness(type, expr, assumeTrue) + return narrowTypeByTruthiness(type, expr, assumeTrue); case SyntaxKind.CallExpression: return narrowTypeByTypePredicate(type, expr, assumeTrue); case SyntaxKind.ParenthesizedExpression: From 1f096bd0806f6a0c7807bf9d19601d6105a4495a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Feb 2016 18:44:15 -0800 Subject: [PATCH 15/59] Add '!' non-null assertion postfix operator --- src/compiler/checker.ts | 7 +++++++ src/compiler/emitter.ts | 30 ++++++++++++++++++++---------- src/compiler/parser.ts | 9 +++++++++ src/compiler/types.ts | 6 ++++++ src/compiler/utilities.ts | 2 ++ 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f3aece5dcdb40..7d7290b90310f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6878,6 +6878,7 @@ namespace ts { case SyntaxKind.NewExpression: case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: + case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.DeleteExpression: @@ -10383,6 +10384,10 @@ namespace ts { return targetType; } + function checkNonNullExpression(node: NonNullExpression) { + return getNonNullableType(checkExpression(node.expression)); + } + function getTypeAtPosition(signature: Signature, pos: number): Type { return signature.hasRestParameter ? pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) : @@ -11555,6 +11560,8 @@ namespace ts { case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: return checkAssertion(node); + case SyntaxKind.NonNullExpression: + return checkNonNullExpression(node); case SyntaxKind.DeleteExpression: return checkDeleteExpression(node); case SyntaxKind.VoidExpression: diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index f08082d72c838..d0d908d84b923 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1533,6 +1533,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge case SyntaxKind.JsxSpreadAttribute: case SyntaxKind.JsxExpression: case SyntaxKind.NewExpression: + case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.PostfixUnaryExpression: case SyntaxKind.PrefixUnaryExpression: @@ -2077,8 +2078,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function parenthesizeForAccess(expr: Expression): LeftHandSideExpression { // When diagnosing whether the expression needs parentheses, the decision should be based // on the innermost expression in a chain of nested type assertions. - while (expr.kind === SyntaxKind.TypeAssertionExpression || expr.kind === SyntaxKind.AsExpression) { - expr = (expr).expression; + while (expr.kind === SyntaxKind.TypeAssertionExpression || + expr.kind === SyntaxKind.AsExpression || + expr.kind === SyntaxKind.NonNullExpression) { + expr = (expr).expression; } // isLeftHandSideExpression is almost the correct criterion for when it is not necessary @@ -2326,8 +2329,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge } function skipParentheses(node: Expression): Expression { - while (node.kind === SyntaxKind.ParenthesizedExpression || node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression) { - node = (node).expression; + while (node.kind === SyntaxKind.ParenthesizedExpression || + node.kind === SyntaxKind.TypeAssertionExpression || + node.kind === SyntaxKind.AsExpression || + node.kind === SyntaxKind.NonNullExpression) { + node = (node).expression; } return node; } @@ -2501,13 +2507,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge // not the user. If we didn't want them, the emitter would not have put them // there. if (!nodeIsSynthesized(node) && node.parent.kind !== SyntaxKind.ArrowFunction) { - if (node.expression.kind === SyntaxKind.TypeAssertionExpression || node.expression.kind === SyntaxKind.AsExpression) { - let operand = (node.expression).expression; + if (node.expression.kind === SyntaxKind.TypeAssertionExpression || + node.expression.kind === SyntaxKind.AsExpression || + node.expression.kind === SyntaxKind.NonNullExpression) { + let operand = (node.expression).expression; // Make sure we consider all nested cast expressions, e.g.: // (-A).x; - while (operand.kind === SyntaxKind.TypeAssertionExpression || operand.kind === SyntaxKind.AsExpression) { - operand = (operand).expression; + while (operand.kind === SyntaxKind.TypeAssertionExpression || + operand.kind === SyntaxKind.AsExpression || + operand.kind === SyntaxKind.NonNullExpression) { + operand = (operand).expression; } // We have an expression of the form: (SubExpr) @@ -7887,9 +7897,9 @@ const _super = (function (geti, seti) { case SyntaxKind.TaggedTemplateExpression: return emitTaggedTemplateExpression(node); case SyntaxKind.TypeAssertionExpression: - return emit((node).expression); case SyntaxKind.AsExpression: - return emit((node).expression); + case SyntaxKind.NonNullExpression: + return emit((node).expression); case SyntaxKind.ParenthesizedExpression: return emitParenExpression(node); case SyntaxKind.FunctionDeclaration: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 16ee3eb1627a4..25b9553694780 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -178,6 +178,8 @@ namespace ts { case SyntaxKind.AsExpression: return visitNode(cbNode, (node).expression) || visitNode(cbNode, (node).type); + case SyntaxKind.NonNullExpression: + return visitNode(cbNode, (node).expression); case SyntaxKind.ConditionalExpression: return visitNode(cbNode, (node).condition) || visitNode(cbNode, (node).questionToken) || @@ -3726,6 +3728,13 @@ namespace ts { continue; } + if (parseOptional(SyntaxKind.ExclamationToken)) { + const nonNullExpression = createNode(SyntaxKind.NonNullExpression, expression.pos); + nonNullExpression.expression = expression; + expression = finishNode(nonNullExpression); + continue; + } + // when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName if (!inDecoratorContext() && parseOptional(SyntaxKind.OpenBracketToken)) { const indexedAccess = createNode(SyntaxKind.ElementAccessExpression, expression.pos); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d7abcc0296383..af4045cf643db 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -240,6 +240,7 @@ namespace ts { OmittedExpression, ExpressionWithTypeArguments, AsExpression, + NonNullExpression, // Misc TemplateSpan, @@ -1017,6 +1018,11 @@ namespace ts { export type AssertionExpression = TypeAssertion | AsExpression; + // @kind(SyntaxKind.NonNullExpression) + export interface NonNullExpression extends LeftHandSideExpression { + expression: Expression; + } + /// A JSX expression of the form ... // @kind(SyntaxKind.JsxElement) export interface JsxElement extends PrimaryExpression { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5494db5fbd6e8..50005d5c070cb 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -968,6 +968,7 @@ namespace ts { case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.AsExpression: case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.ClassExpression: @@ -2394,6 +2395,7 @@ namespace ts { case SyntaxKind.ElementAccessExpression: case SyntaxKind.NewExpression: case SyntaxKind.CallExpression: + case SyntaxKind.NonNullExpression: case SyntaxKind.JsxElement: case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.TaggedTemplateExpression: From 46837fd77d23569cd425f3b10f59e7c4237bf5d6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Feb 2016 19:03:56 -0800 Subject: [PATCH 16/59] Disallow line breaks between operand and '!' non-null assertion operator --- src/compiler/parser.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 25b9553694780..f3a9e5cbf8591 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3728,7 +3728,8 @@ namespace ts { continue; } - if (parseOptional(SyntaxKind.ExclamationToken)) { + if (token === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + nextToken(); const nonNullExpression = createNode(SyntaxKind.NonNullExpression, expression.pos); nonNullExpression.expression = expression; expression = finishNode(nonNullExpression); From 54ee0b13b36610710880d5afa1c98c4b5b001d43 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 17 Feb 2016 19:04:25 -0800 Subject: [PATCH 17/59] Accepting new baselines --- .../logicalNotOperatorInvalidOperations.errors.txt | 8 +------- .../reference/logicalNotOperatorInvalidOperations.js | 3 +-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/baselines/reference/logicalNotOperatorInvalidOperations.errors.txt b/tests/baselines/reference/logicalNotOperatorInvalidOperations.errors.txt index 07262962b081a..c7d66e17f9d5e 100644 --- a/tests/baselines/reference/logicalNotOperatorInvalidOperations.errors.txt +++ b/tests/baselines/reference/logicalNotOperatorInvalidOperations.errors.txt @@ -1,19 +1,13 @@ -tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts(5,17): error TS1005: ',' expected. -tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts(5,18): error TS1109: Expression expected. tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts(8,16): error TS2365: Operator '+' cannot be applied to types 'boolean' and 'number'. tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts(11,16): error TS1109: Expression expected. -==== tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts (4 errors) ==== +==== tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts (2 errors) ==== // Unary operator ! var b: number; // operand before ! var BOOLEAN1 = b!; //expect error - ~ -!!! error TS1005: ',' expected. - ~ -!!! error TS1109: Expression expected. // miss parentheses var BOOLEAN2 = !b + b; diff --git a/tests/baselines/reference/logicalNotOperatorInvalidOperations.js b/tests/baselines/reference/logicalNotOperatorInvalidOperations.js index 7b16f2ef36f50..0cb941b0e41d5 100644 --- a/tests/baselines/reference/logicalNotOperatorInvalidOperations.js +++ b/tests/baselines/reference/logicalNotOperatorInvalidOperations.js @@ -15,8 +15,7 @@ var BOOLEAN3 =!; // Unary operator ! var b; // operand before ! -var BOOLEAN1 = b; -!; //expect error +var BOOLEAN1 = b; //expect error // miss parentheses var BOOLEAN2 = !b + b; // miss an operand From 1e8a7e28d00734578098255705b646269aa52f2e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 18 Feb 2016 09:13:18 -0800 Subject: [PATCH 18/59] Correct && operator to produce nullable values --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7d7290b90310f..6a95162804a79 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11260,7 +11260,7 @@ namespace ts { case SyntaxKind.InKeyword: return checkInExpression(left, right, leftType, rightType); case SyntaxKind.AmpersandAmpersandToken: - return rightType; + return isNullableType(leftType) ? getNullableType(rightType) : rightType; case SyntaxKind.BarBarToken: return getUnionType([getNonNullableType(leftType), rightType]); case SyntaxKind.EqualsToken: From 50ea0bfc711ff68bee03f954eb86e439ff138d5c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 19 Feb 2016 09:32:56 -0800 Subject: [PATCH 19/59] Support x == null and x != null in non-null guards. Also, allow == and != in type guards. --- src/compiler/checker.ts | 60 ++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7000ace8aff70..874dcb40d5a14 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7065,17 +7065,54 @@ namespace ts { return strictNullChecks && assumeTrue && getResolvedSymbol(expr) === symbol ? getNonNullableType(type) : type; } - function narrowTypeByEquality(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - // Check that we have 'typeof ' on the left and string literal on the right - if (expr.left.kind !== SyntaxKind.TypeOfExpression || expr.right.kind !== SyntaxKind.StringLiteral) { + function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + switch (expr.operatorToken.kind) { + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + if (expr.right.kind === SyntaxKind.NullKeyword) { + return narrowTypeByNullCheck(type, expr, assumeTrue); + } + // Fall through + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral) { + return narrowTypeByTypeof(type, expr, assumeTrue); + } + break; + case SyntaxKind.AmpersandAmpersandToken: + return narrowTypeByAnd(type, expr, assumeTrue); + case SyntaxKind.BarBarToken: + return narrowTypeByOr(type, expr, assumeTrue); + case SyntaxKind.InstanceOfKeyword: + return narrowTypeByInstanceof(type, expr, assumeTrue); + } + return type; + } + + function narrowTypeByNullCheck(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + // We have '==' or '!=' operator with 'null' on the right + if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsToken) { + assumeTrue = !assumeTrue; + } + if (!strictNullChecks || assumeTrue) { return type; } + if (expr.left.kind !== SyntaxKind.Identifier || getResolvedSymbol(expr.left) !== symbol) { + return type; + } + return getNonNullableType(type); + } + + function narrowTypeByTypeof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + // We have '==', '!=', '====', or !==' operator with 'typeof xxx' on the left + // and string literal on the right const left = expr.left; const right = expr.right; if (left.expression.kind !== SyntaxKind.Identifier || getResolvedSymbol(left.expression) !== symbol) { return type; } - if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsEqualsToken) { + if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsToken || + expr.operatorToken.kind === SyntaxKind.ExclamationEqualsEqualsToken) { assumeTrue = !assumeTrue; } const typeInfo = primitiveTypeInfo[right.text]; @@ -7260,20 +7297,7 @@ namespace ts { case SyntaxKind.ParenthesizedExpression: return narrowType(type, (expr).expression, assumeTrue); case SyntaxKind.BinaryExpression: - const operator = (expr).operatorToken.kind; - if (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { - return narrowTypeByEquality(type, expr, assumeTrue); - } - else if (operator === SyntaxKind.AmpersandAmpersandToken) { - return narrowTypeByAnd(type, expr, assumeTrue); - } - else if (operator === SyntaxKind.BarBarToken) { - return narrowTypeByOr(type, expr, assumeTrue); - } - else if (operator === SyntaxKind.InstanceOfKeyword) { - return narrowTypeByInstanceof(type, expr, assumeTrue); - } - break; + return narrowTypeByBinaryExpression(type, expr, assumeTrue); case SyntaxKind.PrefixUnaryExpression: if ((expr).operator === SyntaxKind.ExclamationToken) { return narrowType(type, (expr).operand, !assumeTrue); From d10017f165ca08417c516e8723a3c3faa9cb3da4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 19 Feb 2016 09:33:11 -0800 Subject: [PATCH 20/59] Accepting new baselines --- ...FormTypeOfEqualEqualHasNoEffect.errors.txt | 50 +++++++++++ ...dOfFormTypeOfEqualEqualHasNoEffect.symbols | 70 ---------------- ...ardOfFormTypeOfEqualEqualHasNoEffect.types | 82 ------------------- ...OfFormTypeOfNotEqualHasNoEffect.errors.txt | 50 +++++++++++ ...ardOfFormTypeOfNotEqualHasNoEffect.symbols | 70 ---------------- ...GuardOfFormTypeOfNotEqualHasNoEffect.types | 82 ------------------- 6 files changed, 100 insertions(+), 304 deletions(-) create mode 100644 tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.errors.txt delete mode 100644 tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.symbols delete mode 100644 tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.types create mode 100644 tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.errors.txt delete mode 100644 tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols delete mode 100644 tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.types diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.errors.txt b/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.errors.txt new file mode 100644 index 0000000000000..c76c6e819df11 --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.errors.txt @@ -0,0 +1,50 @@ +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts(13,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r1' must be of type 'string', but here has type 'number'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts(20,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r2' must be of type 'boolean', but here has type 'string'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts(27,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r3' must be of type 'number', but here has type 'boolean'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts(34,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r4' must be of type 'C', but here has type 'string'. + + +==== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts (4 errors) ==== + class C { private p: string }; + + var strOrNum: string | number; + var strOrBool: string | boolean; + var numOrBool: number | boolean + var strOrC: string | C; + + // typeof x == s has not effect on typeguard + if (typeof strOrNum == "string") { + var r1 = strOrNum; // string | number + } + else { + var r1 = strOrNum; // string | number + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r1' must be of type 'string', but here has type 'number'. + } + + if (typeof strOrBool == "boolean") { + var r2 = strOrBool; // string | boolean + } + else { + var r2 = strOrBool; // string | boolean + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r2' must be of type 'boolean', but here has type 'string'. + } + + if (typeof numOrBool == "number") { + var r3 = numOrBool; // number | boolean + } + else { + var r3 = numOrBool; // number | boolean + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r3' must be of type 'number', but here has type 'boolean'. + } + + if (typeof strOrC == "Object") { + var r4 = strOrC; // string | C + } + else { + var r4 = strOrC; // string | C + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r4' must be of type 'C', but here has type 'string'. + } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.symbols b/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.symbols deleted file mode 100644 index 69e0dc78738da..0000000000000 --- a/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.symbols +++ /dev/null @@ -1,70 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts === -class C { private p: string }; ->C : Symbol(C, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 0, 0)) ->p : Symbol(p, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 0, 9)) - -var strOrNum: string | number; ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 2, 3)) - -var strOrBool: string | boolean; ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 3, 3)) - -var numOrBool: number | boolean ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 4, 3)) - -var strOrC: string | C; ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 5, 3)) ->C : Symbol(C, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 0, 0)) - -// typeof x == s has not effect on typeguard -if (typeof strOrNum == "string") { ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 2, 3)) - - var r1 = strOrNum; // string | number ->r1 : Symbol(r1, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 9, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 12, 7)) ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 2, 3)) -} -else { - var r1 = strOrNum; // string | number ->r1 : Symbol(r1, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 9, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 12, 7)) ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 2, 3)) -} - -if (typeof strOrBool == "boolean") { ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 3, 3)) - - var r2 = strOrBool; // string | boolean ->r2 : Symbol(r2, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 16, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 19, 7)) ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 3, 3)) -} -else { - var r2 = strOrBool; // string | boolean ->r2 : Symbol(r2, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 16, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 19, 7)) ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 3, 3)) -} - -if (typeof numOrBool == "number") { ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 4, 3)) - - var r3 = numOrBool; // number | boolean ->r3 : Symbol(r3, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 23, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 26, 7)) ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 4, 3)) -} -else { - var r3 = numOrBool; // number | boolean ->r3 : Symbol(r3, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 23, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 26, 7)) ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 4, 3)) -} - -if (typeof strOrC == "Object") { ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 5, 3)) - - var r4 = strOrC; // string | C ->r4 : Symbol(r4, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 30, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 33, 7)) ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 5, 3)) -} -else { - var r4 = strOrC; // string | C ->r4 : Symbol(r4, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 30, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 33, 7)) ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 5, 3)) -} diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.types b/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.types deleted file mode 100644 index 4bfc8fe6bf1c4..0000000000000 --- a/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.types +++ /dev/null @@ -1,82 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts === -class C { private p: string }; ->C : C ->p : string - -var strOrNum: string | number; ->strOrNum : string | number - -var strOrBool: string | boolean; ->strOrBool : string | boolean - -var numOrBool: number | boolean ->numOrBool : number | boolean - -var strOrC: string | C; ->strOrC : string | C ->C : C - -// typeof x == s has not effect on typeguard -if (typeof strOrNum == "string") { ->typeof strOrNum == "string" : boolean ->typeof strOrNum : string ->strOrNum : string | number ->"string" : string - - var r1 = strOrNum; // string | number ->r1 : string | number ->strOrNum : string | number -} -else { - var r1 = strOrNum; // string | number ->r1 : string | number ->strOrNum : string | number -} - -if (typeof strOrBool == "boolean") { ->typeof strOrBool == "boolean" : boolean ->typeof strOrBool : string ->strOrBool : string | boolean ->"boolean" : string - - var r2 = strOrBool; // string | boolean ->r2 : string | boolean ->strOrBool : string | boolean -} -else { - var r2 = strOrBool; // string | boolean ->r2 : string | boolean ->strOrBool : string | boolean -} - -if (typeof numOrBool == "number") { ->typeof numOrBool == "number" : boolean ->typeof numOrBool : string ->numOrBool : number | boolean ->"number" : string - - var r3 = numOrBool; // number | boolean ->r3 : number | boolean ->numOrBool : number | boolean -} -else { - var r3 = numOrBool; // number | boolean ->r3 : number | boolean ->numOrBool : number | boolean -} - -if (typeof strOrC == "Object") { ->typeof strOrC == "Object" : boolean ->typeof strOrC : string ->strOrC : string | C ->"Object" : string - - var r4 = strOrC; // string | C ->r4 : string | C ->strOrC : string | C -} -else { - var r4 = strOrC; // string | C ->r4 : string | C ->strOrC : string | C -} diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.errors.txt b/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.errors.txt new file mode 100644 index 0000000000000..3b29f3ecba841 --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.errors.txt @@ -0,0 +1,50 @@ +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts(13,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r1' must be of type 'number', but here has type 'string'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts(20,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r2' must be of type 'string', but here has type 'boolean'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts(27,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r3' must be of type 'boolean', but here has type 'number'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts(34,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r4' must be of type 'string', but here has type 'C'. + + +==== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts (4 errors) ==== + class C { private p: string }; + + var strOrNum: string | number; + var strOrBool: string | boolean; + var numOrBool: number | boolean + var strOrC: string | C; + + // typeof x != s has not effect on typeguard + if (typeof strOrNum != "string") { + var r1 = strOrNum; // string | number + } + else { + var r1 = strOrNum; // string | number + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r1' must be of type 'number', but here has type 'string'. + } + + if (typeof strOrBool != "boolean") { + var r2 = strOrBool; // string | boolean + } + else { + var r2 = strOrBool; // string | boolean + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r2' must be of type 'string', but here has type 'boolean'. + } + + if (typeof numOrBool != "number") { + var r3 = numOrBool; // number | boolean + } + else { + var r3 = numOrBool; // number | boolean + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r3' must be of type 'boolean', but here has type 'number'. + } + + if (typeof strOrC != "Object") { + var r4 = strOrC; // string | C + } + else { + var r4 = strOrC; // string | C + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r4' must be of type 'string', but here has type 'C'. + } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols b/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols deleted file mode 100644 index b22f1e313a420..0000000000000 --- a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols +++ /dev/null @@ -1,70 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts === -class C { private p: string }; ->C : Symbol(C, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 0, 0)) ->p : Symbol(p, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 0, 9)) - -var strOrNum: string | number; ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) - -var strOrBool: string | boolean; ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) - -var numOrBool: number | boolean ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) - -var strOrC: string | C; ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) ->C : Symbol(C, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 0, 0)) - -// typeof x != s has not effect on typeguard -if (typeof strOrNum != "string") { ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) - - var r1 = strOrNum; // string | number ->r1 : Symbol(r1, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 9, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 12, 7)) ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) -} -else { - var r1 = strOrNum; // string | number ->r1 : Symbol(r1, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 9, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 12, 7)) ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) -} - -if (typeof strOrBool != "boolean") { ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) - - var r2 = strOrBool; // string | boolean ->r2 : Symbol(r2, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 16, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 19, 7)) ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) -} -else { - var r2 = strOrBool; // string | boolean ->r2 : Symbol(r2, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 16, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 19, 7)) ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) -} - -if (typeof numOrBool != "number") { ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) - - var r3 = numOrBool; // number | boolean ->r3 : Symbol(r3, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 23, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 26, 7)) ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) -} -else { - var r3 = numOrBool; // number | boolean ->r3 : Symbol(r3, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 23, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 26, 7)) ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) -} - -if (typeof strOrC != "Object") { ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) - - var r4 = strOrC; // string | C ->r4 : Symbol(r4, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 30, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 33, 7)) ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) -} -else { - var r4 = strOrC; // string | C ->r4 : Symbol(r4, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 30, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 33, 7)) ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) -} diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.types b/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.types deleted file mode 100644 index 6eabeb25ede19..0000000000000 --- a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.types +++ /dev/null @@ -1,82 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts === -class C { private p: string }; ->C : C ->p : string - -var strOrNum: string | number; ->strOrNum : string | number - -var strOrBool: string | boolean; ->strOrBool : string | boolean - -var numOrBool: number | boolean ->numOrBool : number | boolean - -var strOrC: string | C; ->strOrC : string | C ->C : C - -// typeof x != s has not effect on typeguard -if (typeof strOrNum != "string") { ->typeof strOrNum != "string" : boolean ->typeof strOrNum : string ->strOrNum : string | number ->"string" : string - - var r1 = strOrNum; // string | number ->r1 : string | number ->strOrNum : string | number -} -else { - var r1 = strOrNum; // string | number ->r1 : string | number ->strOrNum : string | number -} - -if (typeof strOrBool != "boolean") { ->typeof strOrBool != "boolean" : boolean ->typeof strOrBool : string ->strOrBool : string | boolean ->"boolean" : string - - var r2 = strOrBool; // string | boolean ->r2 : string | boolean ->strOrBool : string | boolean -} -else { - var r2 = strOrBool; // string | boolean ->r2 : string | boolean ->strOrBool : string | boolean -} - -if (typeof numOrBool != "number") { ->typeof numOrBool != "number" : boolean ->typeof numOrBool : string ->numOrBool : number | boolean ->"number" : string - - var r3 = numOrBool; // number | boolean ->r3 : number | boolean ->numOrBool : number | boolean -} -else { - var r3 = numOrBool; // number | boolean ->r3 : number | boolean ->numOrBool : number | boolean -} - -if (typeof strOrC != "Object") { ->typeof strOrC != "Object" : boolean ->typeof strOrC : string ->strOrC : string | C ->"Object" : string - - var r4 = strOrC; // string | C ->r4 : string | C ->strOrC : string | C -} -else { - var r4 = strOrC; // string | C ->r4 : string | C ->strOrC : string | C -} From ed40fbf2d8fbdca5a6407d5ce07df832094643ca Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 19 Feb 2016 16:48:58 -0800 Subject: [PATCH 21/59] Suport both x != null and x != undefined in non-null type guards --- src/compiler/checker.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 874dcb40d5a14..eafbff70822da 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6986,6 +6986,11 @@ namespace ts { } } + function isNullOrUndefinedLiteral(node: Expression) { + return node.kind === SyntaxKind.NullKeyword || + node.kind === SyntaxKind.Identifier && getResolvedSymbol(node) === undefinedSymbol; + } + // Get the narrowed type of a given symbol at a given location function getNarrowedTypeOfSymbol(symbol: Symbol, node: Node) { let type = getTypeOfSymbol(symbol); @@ -7069,7 +7074,7 @@ namespace ts { switch (expr.operatorToken.kind) { case SyntaxKind.EqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: - if (expr.right.kind === SyntaxKind.NullKeyword) { + if (isNullOrUndefinedLiteral(expr.right)) { return narrowTypeByNullCheck(type, expr, assumeTrue); } // Fall through From 3d7631dbe86b5b500c6218588df0833932ca809a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 27 Feb 2016 11:39:16 -0800 Subject: [PATCH 22/59] Support dotted names ("x.y.z") in type guards --- src/compiler/checker.ts | 315 +++++++++++++++++++++++----------------- src/compiler/types.ts | 4 +- 2 files changed, 187 insertions(+), 132 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a2b8b5a2a7aae..cd4a21204a236 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -69,10 +69,7 @@ namespace ts { isUnknownSymbol: symbol => symbol === unknownSymbol, getDiagnostics, getGlobalDiagnostics, - - // The language service will always care about the narrowed type of a symbol, because that is - // the type the language says the symbol should have. - getTypeOfSymbolAtLocation: getNarrowedTypeOfSymbol, + getTypeOfSymbolAtLocation, getSymbolsOfParameterPropertyDeclaration, getDeclaredTypeOfSymbol, getPropertiesOfType, @@ -6869,7 +6866,7 @@ namespace ts { function getResolvedSymbol(node: Identifier): Symbol { const links = getNodeLinks(node); if (!links.resolvedSymbol) { - links.resolvedSymbol = (!nodeIsMissing(node) && resolveName(node, node.text, SymbolFlags.Value | SymbolFlags.ExportValue, Diagnostics.Cannot_find_name_0, node)) || unknownSymbol; + links.resolvedSymbol = !nodeIsMissing(node) && resolveName(node, node.text, SymbolFlags.Value | SymbolFlags.ExportValue, Diagnostics.Cannot_find_name_0, node) || unknownSymbol; } return links.resolvedSymbol; } @@ -6893,48 +6890,59 @@ namespace ts { Debug.fail("should not get here"); } + // Return the assignment key for a "dotted name" (i.e. a sequence of identifiers + // separated by dots). The key consists of the id of the symbol referenced by the + // leftmost identifier followed by zero or more property names separated by dots. + // The result is undefined if the reference isn't a dotted name. + function getAssignmentKey(node: Node): string { + if (node.kind === SyntaxKind.Identifier) { + const symbol = getResolvedSymbol(node); + return symbol !== unknownSymbol ? "" + getSymbolId(symbol) : undefined; + } + if (node.kind === SyntaxKind.PropertyAccessExpression) { + const key = getAssignmentKey((node).expression); + return key && key + "." + (node).name.text; + } + return undefined; + } + function hasInitializer(node: VariableLikeDeclaration): boolean { return !!(node.initializer || isBindingPattern(node.parent) && hasInitializer(node.parent.parent)); } - // Check if a given variable is assigned within a given syntax node - function isVariableAssignedWithin(symbol: Symbol, node: Node): boolean { - const links = getNodeLinks(node); - if (links.assignmentChecks) { - const cachedResult = links.assignmentChecks[symbol.id]; - if (cachedResult !== undefined) { - return cachedResult; - } - } - else { - links.assignmentChecks = {}; - } - return links.assignmentChecks[symbol.id] = isAssignedIn(node); + // For a given node compute a map of which dotted names are assigned within + // the node. + function getAssignmentMap(node: Node): Map { + const assignmentMap: Map = {}; + visit(node); + return assignmentMap; - function isAssignedInBinaryExpression(node: BinaryExpression) { + function visitBinaryExpression(node: BinaryExpression) { if (node.operatorToken.kind >= SyntaxKind.FirstAssignment && node.operatorToken.kind <= SyntaxKind.LastAssignment) { - const n = skipParenthesizedNodes(node.left); - if (n.kind === SyntaxKind.Identifier && getResolvedSymbol(n) === symbol) { - return true; + const key = getAssignmentKey(skipParenthesizedNodes(node.left)); + if (key) { + assignmentMap[key] = true; } } - return forEachChild(node, isAssignedIn); + forEachChild(node, visit); } - function isAssignedInVariableDeclaration(node: VariableLikeDeclaration) { - if (!isBindingPattern(node.name) && getSymbolOfNode(node) === symbol && hasInitializer(node)) { - return true; + function visitVariableDeclaration(node: VariableLikeDeclaration) { + if (!isBindingPattern(node.name) && hasInitializer(node)) { + assignmentMap[getSymbolId(getSymbolOfNode(node))] = true; } - return forEachChild(node, isAssignedIn); + forEachChild(node, visit); } - function isAssignedIn(node: Node): boolean { + function visit(node: Node) { switch (node.kind) { case SyntaxKind.BinaryExpression: - return isAssignedInBinaryExpression(node); + visitBinaryExpression(node); + break; case SyntaxKind.VariableDeclaration: case SyntaxKind.BindingElement: - return isAssignedInVariableDeclaration(node); + visitVariableDeclaration(node); + break; case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: case SyntaxKind.ArrayLiteralExpression: @@ -6980,9 +6988,30 @@ namespace ts { case SyntaxKind.JsxSpreadAttribute: case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxExpression: - return forEachChild(node, isAssignedIn); + forEachChild(node, visit); + break; } - return false; + } + } + + function isReferenceAssignedWithin(reference: Node, node: Node): boolean { + const key = getAssignmentKey(reference); + if (key) { + const links = getNodeLinks(node); + return (links.assignmentMap || (links.assignmentMap = getAssignmentMap(node)))[key]; + } + return false; + } + + function isAnyPartOfReferenceAssignedWithin(reference: Node, node: Node) { + while (true) { + if (isReferenceAssignedWithin(reference, node)) { + return true; + } + if (reference.kind !== SyntaxKind.PropertyAccessExpression) { + return false; + } + reference = (reference).expression; } } @@ -6991,83 +7020,112 @@ namespace ts { node.kind === SyntaxKind.Identifier && getResolvedSymbol(node) === undefinedSymbol; } + function getLeftmostIdentifier(node: Node): Identifier { + switch (node.kind) { + case SyntaxKind.Identifier: + return node; + case SyntaxKind.PropertyAccessExpression: + return getLeftmostIdentifier((node).expression); + } + return undefined; + } + + function isMatchingReference(source: Node, target: Node): boolean { + if (source.kind === target.kind) { + if (source.kind === SyntaxKind.Identifier) { + return getResolvedSymbol(source) === getResolvedSymbol(target); + } + if (source.kind === SyntaxKind.PropertyAccessExpression) { + return (source).name.text === (target).name.text && + isMatchingReference((source).expression, (target).expression); + } + } + return false; + } + // Get the narrowed type of a given symbol at a given location - function getNarrowedTypeOfSymbol(symbol: Symbol, node: Node) { - let type = getTypeOfSymbol(symbol); - // Only narrow when symbol is variable of type any or an object, union, or type parameter type - if (node && symbol.flags & SymbolFlags.Variable) { - if (isTypeAny(type) || type.flags & (TypeFlags.ObjectType | TypeFlags.Union | TypeFlags.TypeParameter)) { - const declaration = getDeclarationOfKind(symbol, SyntaxKind.VariableDeclaration); - const top = declaration && getDeclarationContainer(declaration); - const originalType = type; - const nodeStack: {node: Node, child: Node}[] = []; - loop: while (node.parent) { - const child = node; - node = node.parent; - switch (node.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.ConditionalExpression: - case SyntaxKind.BinaryExpression: - nodeStack.push({node, child}); - break; - case SyntaxKind.SourceFile: - case SyntaxKind.ModuleDeclaration: - // Stop at the first containing file or module declaration - break loop; - } - if (node === top) { - break; - } - } + function getNarrowedTypeOfReference(type: Type, reference: IdentifierOrPropertyAccess) { + if (!(type.flags & (TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.Union | TypeFlags.TypeParameter))) { + return type; + } + const leftmostIdentifier = getLeftmostIdentifier(reference); + if (!leftmostIdentifier) { + return type; + } + const leftmostSymbol = getResolvedSymbol(leftmostIdentifier); + if (!(leftmostSymbol.flags & SymbolFlags.Variable)) { + return type; + } + const declaration = getDeclarationOfKind(leftmostSymbol, SyntaxKind.VariableDeclaration); + const top = declaration && getDeclarationContainer(declaration); + const originalType = type; + const nodeStack: { node: Node, child: Node }[] = []; + let node: Node = reference; + loop: while (node.parent) { + const child = node; + node = node.parent; + switch (node.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.ConditionalExpression: + case SyntaxKind.BinaryExpression: + nodeStack.push({node, child}); + break; + case SyntaxKind.SourceFile: + case SyntaxKind.ModuleDeclaration: + // Stop at the first containing file or module declaration + break loop; + } + if (node === top) { + break; + } + } - let nodes: {node: Node, child: Node}; - while (nodes = nodeStack.pop()) { - const {node, child} = nodes; - switch (node.kind) { - case SyntaxKind.IfStatement: - // In a branch of an if statement, narrow based on controlling expression - if (child !== (node).expression) { - type = narrowType(type, (node).expression, /*assumeTrue*/ child === (node).thenStatement); - } - break; - case SyntaxKind.ConditionalExpression: - // In a branch of a conditional expression, narrow based on controlling condition - if (child !== (node).condition) { - type = narrowType(type, (node).condition, /*assumeTrue*/ child === (node).whenTrue); - } - break; - case SyntaxKind.BinaryExpression: - // In the right operand of an && or ||, narrow based on left operand - if (child === (node).right) { - if ((node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { - type = narrowType(type, (node).left, /*assumeTrue*/ true); - } - else if ((node).operatorToken.kind === SyntaxKind.BarBarToken) { - type = narrowType(type, (node).left, /*assumeTrue*/ false); - } - } - break; - default: - Debug.fail("Unreachable!"); + let nodes: { node: Node, child: Node }; + while (nodes = nodeStack.pop()) { + const {node, child} = nodes; + switch (node.kind) { + case SyntaxKind.IfStatement: + // In a branch of an if statement, narrow based on controlling expression + if (child !== (node).expression) { + type = narrowType(type, (node).expression, /*assumeTrue*/ child === (node).thenStatement); } - - // Use original type if construct contains assignments to variable - if (type !== originalType && isVariableAssignedWithin(symbol, node)) { - type = originalType; + break; + case SyntaxKind.ConditionalExpression: + // In a branch of a conditional expression, narrow based on controlling condition + if (child !== (node).condition) { + type = narrowType(type, (node).condition, /*assumeTrue*/ child === (node).whenTrue); } - } + break; + case SyntaxKind.BinaryExpression: + // In the right operand of an && or ||, narrow based on left operand + if (child === (node).right) { + if ((node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + type = narrowType(type, (node).left, /*assumeTrue*/ true); + } + else if ((node).operatorToken.kind === SyntaxKind.BarBarToken) { + type = narrowType(type, (node).left, /*assumeTrue*/ false); + } + } + break; + default: + Debug.fail("Unreachable!"); + } - // Preserve old top-level behavior - if the branch is really an empty set, revert to prior type - if (type === emptyUnionType) { - type = originalType; - } + // Use original type if construct contains assignments to variable + if (type !== originalType && isAnyPartOfReferenceAssignedWithin(reference, node)) { + type = originalType; } } + // Preserve old top-level behavior - if the branch is really an empty set, revert to prior type + if (type === emptyUnionType) { + type = originalType; + } + return type; function narrowTypeByTruthiness(type: Type, expr: Identifier, assumeTrue: boolean): Type { - return strictNullChecks && assumeTrue && getResolvedSymbol(expr) === symbol ? getNonNullableType(type) : type; + return strictNullChecks && assumeTrue && isMatchingReference(expr, reference) ? getNonNullableType(type) : type; } function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { @@ -7095,14 +7153,11 @@ namespace ts { } function narrowTypeByNullCheck(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - // We have '==' or '!=' operator with 'null' on the right + // We have '==' or '!=' operator with 'null' or 'undefined' on the right if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsToken) { assumeTrue = !assumeTrue; } - if (!strictNullChecks || assumeTrue) { - return type; - } - if (expr.left.kind !== SyntaxKind.Identifier || getResolvedSymbol(expr.left) !== symbol) { + if (!strictNullChecks || assumeTrue || !isMatchingReference(expr.left, reference)) { return type; } return getNonNullableType(type); @@ -7113,7 +7168,7 @@ namespace ts { // and string literal on the right const left = expr.left; const right = expr.right; - if (left.expression.kind !== SyntaxKind.Identifier || getResolvedSymbol(left.expression) !== symbol) { + if (!isMatchingReference(left.expression, reference)) { return type; } if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsToken || @@ -7181,7 +7236,7 @@ namespace ts { function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { // Check that type is not any, assumed result is true, and we have variable symbol on the left - if (isTypeAny(type) || expr.left.kind !== SyntaxKind.Identifier || getResolvedSymbol(expr.left) !== symbol) { + if (isTypeAny(type) || !isMatchingReference(expr.left, reference)) { return type; } @@ -7252,50 +7307,35 @@ namespace ts { return type; } const signature = getResolvedSignature(callExpression); - const predicate = signature.typePredicate; if (!predicate) { return type; } - if (isIdentifierTypePredicate(predicate)) { - if (callExpression.arguments[predicate.parameterIndex] && - getSymbolAtTypePredicatePosition(callExpression.arguments[predicate.parameterIndex]) === symbol) { + const predicateArgument = callExpression.arguments[predicate.parameterIndex]; + if (predicateArgument && isMatchingReference(predicateArgument, reference)) { return getNarrowedType(type, predicate.type, assumeTrue); } } else { const invokedExpression = skipParenthesizedNodes(callExpression.expression); - return narrowTypeByThisTypePredicate(type, predicate, invokedExpression, assumeTrue); - } - return type; - } - - function narrowTypeByThisTypePredicate(type: Type, predicate: ThisTypePredicate, invokedExpression: Expression, assumeTrue: boolean): Type { - if (invokedExpression.kind === SyntaxKind.ElementAccessExpression || invokedExpression.kind === SyntaxKind.PropertyAccessExpression) { - const accessExpression = invokedExpression as ElementAccessExpression | PropertyAccessExpression; - const possibleIdentifier = skipParenthesizedNodes(accessExpression.expression); - if (possibleIdentifier.kind === SyntaxKind.Identifier && getSymbolAtTypePredicatePosition(possibleIdentifier) === symbol) { - return getNarrowedType(type, predicate.type, assumeTrue); + if (invokedExpression.kind === SyntaxKind.ElementAccessExpression || invokedExpression.kind === SyntaxKind.PropertyAccessExpression) { + const accessExpression = invokedExpression as ElementAccessExpression | PropertyAccessExpression; + const possibleReference= skipParenthesizedNodes(accessExpression.expression); + if (isMatchingReference(possibleReference, reference)) { + return getNarrowedType(type, predicate.type, assumeTrue); + } } } return type; } - function getSymbolAtTypePredicatePosition(expr: Expression): Symbol { - expr = skipParenthesizedNodes(expr); - switch (expr.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: - return getSymbolOfEntityNameOrPropertyAccessExpression(expr as (Identifier | PropertyAccessExpression)); - } - } - // Narrow the given type based on the given expression having the assumed boolean value. The returned type // will be a subtype or the same type as the argument. function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { switch (expr.kind) { case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: return narrowTypeByTruthiness(type, expr, assumeTrue); case SyntaxKind.CallExpression: return narrowTypeByTypePredicate(type, expr, assumeTrue); @@ -7313,6 +7353,16 @@ namespace ts { } } + function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { + // The language service will always care about the narrowed type of a symbol, because that is + // the type the language says the symbol should have. + let type = getTypeOfSymbol(symbol); + if (location.kind === SyntaxKind.Identifier && isExpression(location) && getResolvedSymbol(location) === symbol) { + type = getNarrowedTypeOfReference(type, location); + } + return type; + } + function skipParenthesizedNodes(expression: Expression): Expression { while (expression.kind === SyntaxKind.ParenthesizedExpression) { expression = (expression as ParenthesizedExpression).expression; @@ -7371,7 +7421,7 @@ namespace ts { checkCollisionWithCapturedThisVariable(node, node); checkNestedBlockScopedBinding(node, symbol); - return getNarrowedTypeOfSymbol(localOrExportSymbol, node); + return getNarrowedTypeOfReference(getTypeOfSymbol(localOrExportSymbol), node); } function isInsideFunction(node: Node, threshold: Node): boolean { @@ -9122,7 +9172,10 @@ namespace ts { if (prop.parent && prop.parent.flags & SymbolFlags.Class) { checkClassPropertyAccess(node, left, apparentType, prop); } - return getTypeOfSymbol(prop); + + const propType = getTypeOfSymbol(prop); + return node.kind === SyntaxKind.PropertyAccessExpression && prop.flags & SymbolFlags.Property ? + getNarrowedTypeOfReference(propType, node) : propType; } function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 380bbdb3b90ed..b4b81c2732f4a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -974,6 +974,8 @@ namespace ts { name: Identifier; } + export type IdentifierOrPropertyAccess = Identifier | PropertyAccessExpression; + // @kind(SyntaxKind.ElementAccessExpression) export interface ElementAccessExpression extends MemberExpression { expression: LeftHandSideExpression; @@ -2071,7 +2073,7 @@ namespace ts { isVisible?: boolean; // Is this node visible generatedName?: string; // Generated name for module, enum, or import declaration generatedNames?: Map; // Generated names table for source file - assignmentChecks?: Map; // Cache of assignment checks + assignmentMap?: Map; // Cached map of references assigned within this node hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context importOnRightSide?: Symbol; // for import declarations - import that appear on the right side jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with From 82169ce7eb2e31c027fd2c9c37883faedb207c3c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 27 Feb 2016 18:12:40 -0800 Subject: [PATCH 23/59] Fix getTypeOfSymbolAtLocation to handle hypothetical lookups --- src/compiler/checker.ts | 34 ++++++++++++++++++++++++++++++---- src/compiler/types.ts | 5 +++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cd4a21204a236..b3be9545848af 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6863,7 +6863,19 @@ namespace ts { // EXPRESSION TYPE CHECKING + function createTransientIdentifier(symbol: Symbol, location: Node): Identifier { + let result = createNode(SyntaxKind.Identifier); + result.text = symbol.name; + result.resolvedSymbol = symbol; + result.parent = location; + result.id = -1; + return result; + } + function getResolvedSymbol(node: Identifier): Symbol { + if (node.id === -1) { + return (node).resolvedSymbol; + } const links = getNodeLinks(node); if (!links.resolvedSymbol) { links.resolvedSymbol = !nodeIsMissing(node) && resolveName(node, node.text, SymbolFlags.Value | SymbolFlags.ExportValue, Diagnostics.Cannot_find_name_0, node) || unknownSymbol; @@ -7356,11 +7368,25 @@ namespace ts { function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { // The language service will always care about the narrowed type of a symbol, because that is // the type the language says the symbol should have. - let type = getTypeOfSymbol(symbol); - if (location.kind === SyntaxKind.Identifier && isExpression(location) && getResolvedSymbol(location) === symbol) { - type = getNarrowedTypeOfReference(type, location); + const type = getTypeOfSymbol(symbol); + if (location.kind === SyntaxKind.Identifier) { + if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { + location = location.parent; + } + // If location is an identifier or property access that references the given + // symbol, use the location as the reference with respect to which we narrow. + if (isExpression(location)) { + checkExpression(location); + if (getNodeLinks(location).resolvedSymbol === symbol) { + return getNarrowedTypeOfReference(type, location); + } + } } - return type; + // The location isn't a reference to the given symbol, meaning we're being asked + // a hypothetical question of what type the symbol would have if there was a reference + // to it at the given location. To answer that question we manufacture a transient + // identifier at the location and narrow with respect to that identifier. + return getNarrowedTypeOfReference(type, createTransientIdentifier(symbol, location)); } function skipParenthesizedNodes(expression: Expression): Expression { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b4b81c2732f4a..840d79884055d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -476,6 +476,11 @@ namespace ts { originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later } + // Transient identifier node (marked by id === -1) + export interface TransientIdentifier extends Identifier { + resolvedSymbol: Symbol; + } + // @kind(SyntaxKind.QualifiedName) export interface QualifiedName extends Node { // Must have same layout as PropertyAccess From 7dd59ceff6a20505b960ec319b4326689f816ff1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 27 Feb 2016 18:13:26 -0800 Subject: [PATCH 24/59] Accepting new baselines --- .../reference/typeGuardFunctionOfFormThis.types | 4 ++-- .../reference/typeGuardsInProperties.types | 16 ++++++++-------- .../typeGuardsOnClassProperty.errors.txt | 5 +---- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/tests/baselines/reference/typeGuardFunctionOfFormThis.types b/tests/baselines/reference/typeGuardFunctionOfFormThis.types index 6b21fea34576a..cd0381a94e9da 100644 --- a/tests/baselines/reference/typeGuardFunctionOfFormThis.types +++ b/tests/baselines/reference/typeGuardFunctionOfFormThis.types @@ -174,9 +174,9 @@ if (holder2.a.isLeader()) { >isLeader : () => this is LeadGuard holder2.a; ->holder2.a : RoyalGuard +>holder2.a : LeadGuard >holder2 : { a: RoyalGuard; } ->a : RoyalGuard +>a : LeadGuard } else { holder2.a; diff --git a/tests/baselines/reference/typeGuardsInProperties.types b/tests/baselines/reference/typeGuardsInProperties.types index ca4d8b9452786..d6c9602a450d4 100644 --- a/tests/baselines/reference/typeGuardsInProperties.types +++ b/tests/baselines/reference/typeGuardsInProperties.types @@ -76,18 +76,18 @@ var c1: C1; >C1 : C1 strOrNum = typeof c1.pp2 === "string" && c1.pp2; // string | number ->strOrNum = typeof c1.pp2 === "string" && c1.pp2 : string | number +>strOrNum = typeof c1.pp2 === "string" && c1.pp2 : string >strOrNum : string | number ->typeof c1.pp2 === "string" && c1.pp2 : string | number +>typeof c1.pp2 === "string" && c1.pp2 : string >typeof c1.pp2 === "string" : boolean >typeof c1.pp2 : string >c1.pp2 : string | number >c1 : C1 >pp2 : string | number >"string" : string ->c1.pp2 : string | number +>c1.pp2 : string >c1 : C1 ->pp2 : string | number +>pp2 : string strOrNum = typeof c1.pp3 === "string" && c1.pp3; // string | number >strOrNum = typeof c1.pp3 === "string" && c1.pp3 : string | number @@ -111,16 +111,16 @@ var obj1: { }; strOrNum = typeof obj1.x === "string" && obj1.x; // string | number ->strOrNum = typeof obj1.x === "string" && obj1.x : string | number +>strOrNum = typeof obj1.x === "string" && obj1.x : string >strOrNum : string | number ->typeof obj1.x === "string" && obj1.x : string | number +>typeof obj1.x === "string" && obj1.x : string >typeof obj1.x === "string" : boolean >typeof obj1.x : string >obj1.x : string | number >obj1 : { x: string | number; } >x : string | number >"string" : string ->obj1.x : string | number +>obj1.x : string >obj1 : { x: string | number; } ->x : string | number +>x : string diff --git a/tests/baselines/reference/typeGuardsOnClassProperty.errors.txt b/tests/baselines/reference/typeGuardsOnClassProperty.errors.txt index 91d6d6998bc49..660962ec1a760 100644 --- a/tests/baselines/reference/typeGuardsOnClassProperty.errors.txt +++ b/tests/baselines/reference/typeGuardsOnClassProperty.errors.txt @@ -1,8 +1,7 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts(14,70): error TS2339: Property 'join' does not exist on type 'string | string[]'. -tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts(26,44): error TS2339: Property 'toLowerCase' does not exist on type 'number | string'. -==== tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts (2 errors) ==== +==== tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts (1 errors) ==== // Note that type guards affect types of variables and parameters only and // have no effect on members of objects such as properties. @@ -31,7 +30,5 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts(26,4 } if (typeof o.prop1 === "string" && o.prop1.toLowerCase()) {} - ~~~~~~~~~~~ -!!! error TS2339: Property 'toLowerCase' does not exist on type 'number | string'. var prop1 = o.prop1; if (typeof prop1 === "string" && prop1.toLocaleLowerCase()) { } \ No newline at end of file From ea3593239cde31d2cd35788d97bce5204b985b19 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 28 Feb 2016 10:30:19 -0800 Subject: [PATCH 25/59] Fix linting error --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b3be9545848af..80aadc977a979 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6864,7 +6864,7 @@ namespace ts { // EXPRESSION TYPE CHECKING function createTransientIdentifier(symbol: Symbol, location: Node): Identifier { - let result = createNode(SyntaxKind.Identifier); + const result = createNode(SyntaxKind.Identifier); result.text = symbol.name; result.resolvedSymbol = symbol; result.parent = location; From 33e3825beb4aa7b8114923a7126eb3f27779d410 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 2 Mar 2016 16:40:16 -0800 Subject: [PATCH 26/59] Assigned-before-use checking for non-nullable variables --- src/compiler/checker.ts | 143 ++++++++++++++++++++++++--- src/compiler/diagnosticMessages.json | 4 + src/compiler/types.ts | 4 +- 3 files changed, 135 insertions(+), 16 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 80aadc977a979..89517b7539e9c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6929,9 +6929,9 @@ namespace ts { visit(node); return assignmentMap; - function visitBinaryExpression(node: BinaryExpression) { - if (node.operatorToken.kind >= SyntaxKind.FirstAssignment && node.operatorToken.kind <= SyntaxKind.LastAssignment) { - const key = getAssignmentKey(skipParenthesizedNodes(node.left)); + function visitReference(node: Identifier | PropertyAccessExpression) { + if (isAssignmentTarget(node) || isCompoundAssignmentTarget(node)) { + const key = getAssignmentKey(node); if (key) { assignmentMap[key] = true; } @@ -6948,18 +6948,19 @@ namespace ts { function visit(node: Node) { switch (node.kind) { - case SyntaxKind.BinaryExpression: - visitBinaryExpression(node); + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: + visitReference(node); break; case SyntaxKind.VariableDeclaration: case SyntaxKind.BindingElement: visitVariableDeclaration(node); break; + case SyntaxKind.BinaryExpression: case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: @@ -7396,6 +7397,93 @@ namespace ts { return expression; } + function findFirstAssignment(symbol: Symbol, container: Node): Node { + return visit(isFunctionLike(container) ? (container).body : container); + + function visit(node: Node): Node { + switch (node.kind) { + case SyntaxKind.Identifier: + const assignment = getAssignmentRoot(node); + return assignment && getResolvedSymbol(node) === symbol ? assignment : undefined; + case SyntaxKind.BinaryExpression: + case SyntaxKind.VariableDeclaration: + case SyntaxKind.BindingElement: + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.NonNullExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.DeleteExpression: + case SyntaxKind.AwaitExpression: + case SyntaxKind.TypeOfExpression: + case SyntaxKind.VoidExpression: + case SyntaxKind.PostfixUnaryExpression: + case SyntaxKind.YieldExpression: + case SyntaxKind.ConditionalExpression: + case SyntaxKind.SpreadElementExpression: + case SyntaxKind.VariableStatement: + case SyntaxKind.ExpressionStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ReturnStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + case SyntaxKind.LabeledStatement: + case SyntaxKind.ThrowStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.CatchClause: + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxExpression: + case SyntaxKind.Block: + case SyntaxKind.SourceFile: + return forEachChild(node, visit); + } + return undefined; + } + } + + function checkVariableAssignedBefore(symbol: Symbol, reference: Node) { + if (!(symbol.flags & SymbolFlags.Variable)) { + return; + } + const declaration = symbol.valueDeclaration; + if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration || (declaration).initializer) { + return; + } + const declarationContainer = getContainingFunction(declaration) || getSourceFileOfNode(declaration); + const referenceContainer = getContainingFunction(reference) || getSourceFileOfNode(reference); + if (declarationContainer !== referenceContainer) { + return; + } + const links = getSymbolLinks(symbol); + if (!links.firstAssignmentChecked) { + links.firstAssignmentChecked = true; + links.firstAssignment = findFirstAssignment(symbol, declarationContainer); + } + if (links.firstAssignment && links.firstAssignment.end <= reference.pos) { + return; + } + error(reference, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); + } + function checkIdentifier(node: Identifier): Type { const symbol = getResolvedSymbol(node); @@ -7447,7 +7535,11 @@ namespace ts { checkCollisionWithCapturedThisVariable(node, node); checkNestedBlockScopedBinding(node, symbol); - return getNarrowedTypeOfReference(getTypeOfSymbol(localOrExportSymbol), node); + const type = getTypeOfSymbol(localOrExportSymbol); + if (strictNullChecks && !isAssignmentTarget(node) && !(type.flags & TypeFlags.Any) && !isNullableType(type)) { + checkVariableAssignedBefore(symbol, node); + } + return getNarrowedTypeOfReference(type, node); } function isInsideFunction(node: Node, threshold: Node): boolean { @@ -8344,19 +8436,40 @@ namespace ts { return mapper && mapper.context; } + // Return the root assignment node of an assignment target + function getAssignmentRoot(node: Node): Node { + while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { + node = node.parent; + } + while (true) { + if (node.parent.kind === SyntaxKind.PropertyAssignment) { + node = node.parent.parent; + } + else if (node.parent.kind === SyntaxKind.ArrayLiteralExpression) { + node = node.parent; + } + else { + break; + } + } + const parent = node.parent; + return parent.kind === SyntaxKind.BinaryExpression && + (parent).operatorToken.kind === SyntaxKind.EqualsToken && + (parent).left === node ? parent : undefined; + } + // A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property // assignment in an object literal that is an assignment target, or if it is parented by an array literal that is // an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ p: a}] = xxx'. function isAssignmentTarget(node: Node): boolean { + return !!getAssignmentRoot(node); + } + + function isCompoundAssignmentTarget(node: Node) { const parent = node.parent; - if (parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.EqualsToken && (parent).left === node) { - return true; - } - if (parent.kind === SyntaxKind.PropertyAssignment) { - return isAssignmentTarget(parent.parent); - } - if (parent.kind === SyntaxKind.ArrayLiteralExpression) { - return isAssignmentTarget(parent); + if (parent.kind === SyntaxKind.BinaryExpression && (parent).left === node) { + const operator = (parent).operatorToken.kind; + return operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment; } return false; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 09a9795eccdde..0b70ab98ce7ff 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1411,6 +1411,10 @@ "category": "Error", "code": 2453 }, + "Variable '{0}' is used before being assigned.": { + "category": "Error", + "code": 2454 + }, "Type argument candidate '{1}' is not a valid type argument because it is not a supertype of candidate '{0}'.": { "category": "Error", "code": 2455 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 840d79884055d..ea3d95c49eb06 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2035,7 +2035,9 @@ namespace ts { exportsChecked?: boolean; // True if exports of external module have been checked isDeclarationWithCollidingName?: boolean; // True if symbol is block scoped redeclaration bindingElement?: BindingElement; // Binding element associated with property symbol - exportsSomeValue?: boolean; // true if module exports some value (not just types) + exportsSomeValue?: boolean; // True if module exports some value (not just types) + firstAssignmentChecked?: boolean; // True if first assignment node has been computed + firstAssignment?: Node; // First assignment node (undefined if no assignments) } /* @internal */ From ea4b13bdf94fb2241f15874d9d0b0cead15d6784 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 3 Mar 2016 11:18:12 -0800 Subject: [PATCH 27/59] Allow 'null' and 'undefined' as type names --- src/compiler/checker.ts | 6 ++++++ src/compiler/declarationEmitter.ts | 2 ++ src/compiler/parser.ts | 4 ++++ src/compiler/scanner.ts | 1 + src/compiler/types.ts | 1 + src/compiler/utilities.ts | 1 + 6 files changed, 15 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 89517b7539e9c..71ef823d13710 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3411,6 +3411,8 @@ namespace ts { case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NullKeyword: case SyntaxKind.StringLiteralType: return true; case SyntaxKind.ArrayType: @@ -4935,6 +4937,10 @@ namespace ts { return esSymbolType; case SyntaxKind.VoidKeyword: return voidType; + case SyntaxKind.UndefinedKeyword: + return undefinedType; + case SyntaxKind.NullKeyword: + return nullType; case SyntaxKind.ThisType: return getTypeFromThisTypeNode(node); case SyntaxKind.StringLiteralType: diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 1328a2e84844c..d2031a7bad649 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -367,6 +367,8 @@ namespace ts { case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NullKeyword: case SyntaxKind.ThisType: case SyntaxKind.StringLiteralType: return writeTextOfNode(currentText, type); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index a7e76ac3ae4da..c2d3173a86011 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2379,12 +2379,14 @@ namespace ts { case SyntaxKind.NumberKeyword: case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: + case SyntaxKind.UndefinedKeyword: // If these are followed by a dot, then parse these out as a dotted type reference instead. const node = tryParse(parseKeywordAndNoDot); return node || parseTypeReference(); case SyntaxKind.StringLiteral: return parseStringLiteralTypeNode(); case SyntaxKind.VoidKeyword: + case SyntaxKind.NullKeyword: return parseTokenNode(); case SyntaxKind.ThisKeyword: { const thisKeyword = parseThisTypeNode(); @@ -2416,6 +2418,8 @@ namespace ts { case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NullKeyword: case SyntaxKind.ThisKeyword: case SyntaxKind.TypeOfKeyword: case SyntaxKind.OpenBraceToken: diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 64747fc638c16..8979814a7a2a6 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -114,6 +114,7 @@ namespace ts { "try": SyntaxKind.TryKeyword, "type": SyntaxKind.TypeKeyword, "typeof": SyntaxKind.TypeOfKeyword, + "undefined": SyntaxKind.UndefinedKeyword, "var": SyntaxKind.VarKeyword, "void": SyntaxKind.VoidKeyword, "while": SyntaxKind.WhileKeyword, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ea3d95c49eb06..9f709c8fa6817 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -171,6 +171,7 @@ namespace ts { StringKeyword, SymbolKeyword, TypeKeyword, + UndefinedKeyword, FromKeyword, GlobalKeyword, OfKeyword, // LastKeyword and LastToken diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7012326fa9843..953e2a7c19471 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -515,6 +515,7 @@ namespace ts { case SyntaxKind.StringKeyword: case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: + case SyntaxKind.UndefinedKeyword: return true; case SyntaxKind.VoidKeyword: return node.parent.kind !== SyntaxKind.VoidExpression; From ed958119a179ffa4ca1280abfefda0a5dc7e9e9d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 3 Mar 2016 11:18:33 -0800 Subject: [PATCH 28/59] Fix unit test --- tests/cases/unittests/jsDocParsing.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/unittests/jsDocParsing.ts b/tests/cases/unittests/jsDocParsing.ts index 9988383b467da..5553ea77b8f05 100644 --- a/tests/cases/unittests/jsDocParsing.ts +++ b/tests/cases/unittests/jsDocParsing.ts @@ -792,6 +792,7 @@ module ts { "kind": "Identifier", "pos": 1, "end": 10, + "originalKeywordKind": "UndefinedKeyword", "text": "undefined" } }`); From 04c28b09a9c20fe71fb1555cdce0f25647eca8e9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 3 Mar 2016 11:18:47 -0800 Subject: [PATCH 29/59] Accepting new baselines --- .../reference/typeParameterConstraints1.errors.txt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/baselines/reference/typeParameterConstraints1.errors.txt b/tests/baselines/reference/typeParameterConstraints1.errors.txt index e837e19317deb..97bc816575138 100644 --- a/tests/baselines/reference/typeParameterConstraints1.errors.txt +++ b/tests/baselines/reference/typeParameterConstraints1.errors.txt @@ -1,11 +1,9 @@ tests/cases/compiler/typeParameterConstraints1.ts(6,25): error TS2304: Cannot find name 'hm'. tests/cases/compiler/typeParameterConstraints1.ts(9,25): error TS1110: Type expected. tests/cases/compiler/typeParameterConstraints1.ts(10,26): error TS1110: Type expected. -tests/cases/compiler/typeParameterConstraints1.ts(11,26): error TS1110: Type expected. -tests/cases/compiler/typeParameterConstraints1.ts(12,26): error TS2304: Cannot find name 'undefined'. -==== tests/cases/compiler/typeParameterConstraints1.ts (5 errors) ==== +==== tests/cases/compiler/typeParameterConstraints1.ts (3 errors) ==== function foo1(test: T) { } function foo2(test: T) { } function foo3(test: T) { } @@ -23,9 +21,5 @@ tests/cases/compiler/typeParameterConstraints1.ts(12,26): error TS2304: Cannot f ~ !!! error TS1110: Type expected. function foo11 (test: T) { } - ~~~~ -!!! error TS1110: Type expected. function foo12(test: T) { } - ~~~~~~~~~ -!!! error TS2304: Cannot find name 'undefined'. function foo13(test: T) { } \ No newline at end of file From 87ae0489eb8b0b9f4fac16f8dd503a56978bb58b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 3 Mar 2016 17:44:46 -0800 Subject: [PATCH 30/59] 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 71ef823d13710..70f1a31e18671 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 9f709c8fa6817..5661196199831 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 { } From c623e1f8c91950a0996273cec8d59781279023b2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Mar 2016 10:42:23 -0800 Subject: [PATCH 31/59] No widening of 'null' and 'undefined' types in --strictNullChecks mode --- src/compiler/checker.ts | 47 ++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 70f1a31e18671..1e693867baf6d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -107,14 +107,15 @@ namespace ts { const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__"); + const nullableWideningFlags = strictNullChecks ? 0 : TypeFlags.ContainsUndefinedOrNull; const anyType = createIntrinsicType(TypeFlags.Any, "any"); const stringType = createIntrinsicType(TypeFlags.String, "string"); const numberType = createIntrinsicType(TypeFlags.Number, "number"); const booleanType = createIntrinsicType(TypeFlags.Boolean, "boolean"); const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); const voidType = createIntrinsicType(TypeFlags.Void, "void"); - const undefinedType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined"); - const nullType = createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsUndefinedOrNull, "null"); + const undefinedType = createIntrinsicType(TypeFlags.Undefined | nullableWideningFlags, "undefined"); + const nullType = createIntrinsicType(TypeFlags.Null | nullableWideningFlags, "null"); const emptyArrayElementType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined"); const unknownType = createIntrinsicType(TypeFlags.Any, "unknown"); @@ -4719,10 +4720,21 @@ namespace ts { return links.resolvedType; } - function addTypeToSet(typeSet: Type[], type: Type, typeSetKind: TypeFlags) { + interface TypeSet extends Array { + containsAny?: boolean; + containsUndefined?: boolean; + containsNull?: boolean; + } + + function addTypeToSet(typeSet: TypeSet, type: Type, typeSetKind: TypeFlags) { if (type.flags & typeSetKind) { addTypesToSet(typeSet, (type).types, typeSetKind); } + else if (type.flags & (TypeFlags.Any | TypeFlags.Undefined | TypeFlags.Null)) { + if (type.flags & TypeFlags.Any) typeSet.containsAny = true; + if (type.flags & TypeFlags.Undefined) typeSet.containsUndefined = true; + if (type.flags & TypeFlags.Null) typeSet.containsNull = true; + } else if (!contains(typeSet, type)) { typeSet.push(type); } @@ -4730,7 +4742,7 @@ namespace ts { // Add the given types to the given type set. Order is preserved, duplicates are removed, // and nested types of the given kind are flattened into the set. - function addTypesToSet(typeSet: Type[], types: Type[], typeSetKind: TypeFlags) { + function addTypesToSet(typeSet: TypeSet, types: Type[], typeSetKind: TypeFlags) { for (const type of types) { addTypeToSet(typeSet, type, typeSetKind); } @@ -4785,21 +4797,22 @@ namespace ts { if (types.length === 0) { return emptyUnionType; } - const typeSet: Type[] = []; + const typeSet = [] as TypeSet; addTypesToSet(typeSet, types, TypeFlags.Union); - if (containsTypeAny(typeSet)) { + if (typeSet.containsAny) { return anyType; } - if (noSubtypeReduction) { - if (!strictNullChecks) { - removeAllButLast(typeSet, undefinedType); - removeAllButLast(typeSet, nullType); - } + if (strictNullChecks) { + if (typeSet.containsNull) typeSet.push(nullType); + if (typeSet.containsUndefined) typeSet.push(undefinedType); } - else { + if (!noSubtypeReduction) { removeSubtypes(typeSet); } - if (typeSet.length === 1) { + if (typeSet.length === 0) { + return typeSet.containsNull ? nullType : undefinedType; + } + else if (typeSet.length === 1) { return typeSet[0]; } const id = getTypeListId(typeSet); @@ -4829,11 +4842,15 @@ namespace ts { if (types.length === 0) { return emptyObjectType; } - const typeSet: Type[] = []; + const typeSet = [] as TypeSet; addTypesToSet(typeSet, types, TypeFlags.Intersection); - if (containsTypeAny(typeSet)) { + if (typeSet.containsAny) { return anyType; } + if (strictNullChecks) { + if (typeSet.containsNull) typeSet.push(nullType); + if (typeSet.containsUndefined) typeSet.push(undefinedType); + } if (typeSet.length === 1) { return typeSet[0]; } From 1302418776efba8d57bfe771e95597c9b2a3f123 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Mar 2016 10:43:10 -0800 Subject: [PATCH 32/59] Accepting new baselines --- tests/baselines/reference/arrayLiterals2ES5.types | 2 +- .../reference/destructuringVariableDeclaration1ES5.types | 4 ++-- .../reference/destructuringVariableDeclaration1ES6.types | 4 ++-- .../baselines/reference/logicalAndOperatorWithEveryType.types | 2 +- .../baselines/reference/logicalOrOperatorWithEveryType.types | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/baselines/reference/arrayLiterals2ES5.types b/tests/baselines/reference/arrayLiterals2ES5.types index cbfb620fb00d6..a9cf31611c26f 100644 --- a/tests/baselines/reference/arrayLiterals2ES5.types +++ b/tests/baselines/reference/arrayLiterals2ES5.types @@ -130,7 +130,7 @@ var temp2: [number[], string[]] = [[1, 2, 3], ["hello", "string"]]; var temp3 = [undefined, null, undefined]; >temp3 : any[] ->[undefined, null, undefined] : undefined[] +>[undefined, null, undefined] : null[] >undefined : undefined >null : null >undefined : undefined diff --git a/tests/baselines/reference/destructuringVariableDeclaration1ES5.types b/tests/baselines/reference/destructuringVariableDeclaration1ES5.types index aab01926a5a9e..f8188147a7908 100644 --- a/tests/baselines/reference/destructuringVariableDeclaration1ES5.types +++ b/tests/baselines/reference/destructuringVariableDeclaration1ES5.types @@ -168,7 +168,7 @@ var {f: [f1, f2, { f3: f4, f5 }, , ]} = { f: [1, 2, { f3: 4, f5: 0 }] }; var {g: {g1 = [undefined, null]}}: { g: { g1: any[] } } = { g: { g1: [1, 2] } }; >g : any >g1 : any[] ->[undefined, null] : undefined[] +>[undefined, null] : null[] >undefined : undefined >null : null >g : { g1: any[]; } @@ -184,7 +184,7 @@ var {g: {g1 = [undefined, null]}}: { g: { g1: any[] } } = { g: { g1: [1, 2] } }; var {h: {h1 = [undefined, null]}}: { h: { h1: number[] } } = { h: { h1: [1, 2] } }; >h : any >h1 : number[] ->[undefined, null] : undefined[] +>[undefined, null] : null[] >undefined : undefined >null : null >h : { h1: number[]; } diff --git a/tests/baselines/reference/destructuringVariableDeclaration1ES6.types b/tests/baselines/reference/destructuringVariableDeclaration1ES6.types index 7e98817c052db..7b4fe5409dbaa 100644 --- a/tests/baselines/reference/destructuringVariableDeclaration1ES6.types +++ b/tests/baselines/reference/destructuringVariableDeclaration1ES6.types @@ -168,7 +168,7 @@ var {f: [f1, f2, { f3: f4, f5 }, , ]} = { f: [1, 2, { f3: 4, f5: 0 }] }; var {g: {g1 = [undefined, null]}}: { g: { g1: any[] } } = { g: { g1: [1, 2] } }; >g : any >g1 : any[] ->[undefined, null] : undefined[] +>[undefined, null] : null[] >undefined : undefined >null : null >g : { g1: any[]; } @@ -184,7 +184,7 @@ var {g: {g1 = [undefined, null]}}: { g: { g1: any[] } } = { g: { g1: [1, 2] } }; var {h: {h1 = [undefined, null]}}: { h: { h1: number[] } } = { h: { h1: [1, 2] } }; >h : any >h1 : number[] ->[undefined, null] : undefined[] +>[undefined, null] : null[] >undefined : undefined >null : null >h : { h1: number[]; } diff --git a/tests/baselines/reference/logicalAndOperatorWithEveryType.types b/tests/baselines/reference/logicalAndOperatorWithEveryType.types index bd913e94da58c..b2628eed9219f 100644 --- a/tests/baselines/reference/logicalAndOperatorWithEveryType.types +++ b/tests/baselines/reference/logicalAndOperatorWithEveryType.types @@ -623,7 +623,7 @@ var rj8 = a8 && undefined; var rj9 = null && undefined; >rj9 : any ->null && undefined : undefined +>null && undefined : null >null : null >undefined : undefined diff --git a/tests/baselines/reference/logicalOrOperatorWithEveryType.types b/tests/baselines/reference/logicalOrOperatorWithEveryType.types index 609af4ecc94fa..4540e35aaf459 100644 --- a/tests/baselines/reference/logicalOrOperatorWithEveryType.types +++ b/tests/baselines/reference/logicalOrOperatorWithEveryType.types @@ -572,7 +572,7 @@ var rj9 = null || null; // null || null is any var rj10 = undefined || null; // undefined || null is any >rj10 : any ->undefined || null : undefined +>undefined || null : null >undefined : undefined >null : null From d6fcd1af1ba424eb163a0da153b4c296de825f7c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Mar 2016 13:19:58 -0800 Subject: [PATCH 33/59] Consider for-in and for-of variables to be definitely assigned --- src/compiler/checker.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1e693867baf6d..491e1fe0ee18f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7525,6 +7525,10 @@ namespace ts { if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration || (declaration).initializer) { return; } + const parentParentKind = declaration.parent.parent.kind; + if (parentParentKind === SyntaxKind.ForOfStatement || parentParentKind === SyntaxKind.ForInStatement) { + return; + } const declarationContainer = getContainingFunction(declaration) || getSourceFileOfNode(declaration); const referenceContainer = getContainingFunction(reference) || getSourceFileOfNode(reference); if (declarationContainer !== referenceContainer) { From 15b240548f11eae650e736a34750373d301009c9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Mar 2016 15:01:11 -0800 Subject: [PATCH 34/59] Extract and lift nullability over best common supertype --- src/compiler/checker.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 491e1fe0ee18f..ee9240f58e318 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6286,14 +6286,22 @@ namespace ts { } function isSupertypeOfEach(candidate: Type, types: Type[]): boolean { - for (const type of types) { - if (candidate !== type && !isTypeSubtypeOf(type, candidate)) return false; + for (const t of types) { + if (candidate !== t && !isTypeSubtypeOf(t, candidate)) return false; } return true; } function getCommonSupertype(types: Type[]): Type { - return forEach(types, t => isSupertypeOfEach(t, types) ? t : undefined); + if (!strictNullChecks) { + return forEach(types, t => isSupertypeOfEach(t, types) ? t : undefined); + } + const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable)); + if (!primaryTypes.length) { + return getUnionType(types); + } + const supertype = forEach(primaryTypes, t => isSupertypeOfEach(t, primaryTypes) ? t : undefined); + return supertype && addNullableKind(supertype, reduceLeft(types, (flags, t) => flags | t.flags, 0) & TypeFlags.Nullable); } function reportNoCommonSupertypeError(types: Type[], errorLocation: Node, errorMessageChainHead: DiagnosticMessageChain): void { From 25a72d60852ea01f41e6869a848d97836dd22be1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Mar 2016 15:51:22 -0800 Subject: [PATCH 35/59] Removing unused functions --- src/compiler/checker.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ee9240f58e318..666ed64b17691 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4767,25 +4767,6 @@ namespace ts { } } - function containsTypeAny(types: Type[]): boolean { - for (const type of types) { - if (isTypeAny(type)) { - return true; - } - } - return false; - } - - function removeAllButLast(types: Type[], typeToRemove: Type) { - let i = types.length; - while (i > 0 && types.length > 1) { - i--; - if (types[i] === typeToRemove) { - types.splice(i, 1); - } - } - } - // We reduce the constituent type set to only include types that aren't subtypes of other types, unless // the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on // object identity. Subtype reduction is possible only when union types are known not to circularly From 64f572747cef63f96382d99090e2ed822f4d9267 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Mar 2016 17:26:56 -0800 Subject: [PATCH 36/59] Introduce comparable (a.k.a. possibly assignable) relation --- src/compiler/checker.ts | 56 ++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 666ed64b17691..15a57ed893137 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -229,6 +229,7 @@ namespace ts { const subtypeRelation: Map = {}; const assignableRelation: Map = {}; + const comparableRelation: Map = {}; const identityRelation: Map = {}; // This is for caching the result of getSymbolDisplayBuilder. Do not access directly. @@ -5267,6 +5268,10 @@ namespace ts { return checkTypeAssignableTo(source, target, /*errorNode*/ undefined); } + function isTypeComparableTo(source: Type, target: Type): boolean { + return checkTypeComparableTo(source, target, /*errorNode*/ undefined); + } + function checkTypeSubtypeOf(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { return checkTypeRelatedTo(source, target, subtypeRelation, errorNode, headMessage, containingMessageChain); } @@ -5275,6 +5280,10 @@ namespace ts { return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain); } + function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { + return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); + } + function isSignatureAssignableTo(source: Signature, target: Signature, ignoreReturnTypes: boolean): boolean { @@ -5432,7 +5441,7 @@ namespace ts { * Checks if 'source' is related to 'target' (e.g.: is a assignable to). * @param source The left-hand-side of the relation. * @param target The right-hand-side of the relation. - * @param relation The relation considered. One of 'identityRelation', 'assignableRelation', or 'subTypeRelation'. + * @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'. * Used as both to determine which checks are performed and as a cache of previously computed results. * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used. * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. @@ -5510,7 +5519,7 @@ namespace ts { } } if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True; - if (relation === assignableRelation) { + if (relation === assignableRelation || relation === comparableRelation) { if (isTypeAny(source)) return Ternary.True; if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True; } @@ -5538,8 +5547,15 @@ namespace ts { // Note that the "each" checks must precede the "some" checks to produce the correct results if (source.flags & TypeFlags.Union) { - if (result = eachTypeRelatedToType(source, target, reportErrors)) { - return result; + if (relation === comparableRelation) { + if (result = someTypeRelatedToType(source, target, reportErrors)) { + return result; + } + } + else { + if (result = eachTypeRelatedToType(source, target, reportErrors)) { + return result; + } } } else if (target.flags & TypeFlags.Intersection) { @@ -5634,7 +5650,8 @@ namespace ts { function isKnownProperty(type: Type, name: string): boolean { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) || + if ((relation === assignableRelation || relation === comparableRelation) && + (type === globalObjectType || resolved.properties.length === 0) || resolved.stringIndexInfo || resolved.numberIndexInfo || getPropertyOfType(type, name)) { return true; } @@ -10774,12 +10791,8 @@ namespace ts { const targetType = getTypeFromTypeNode(node.type); if (produceDiagnostics && targetType !== unknownType) { const widenedType = getWidenedType(exprType); - - // Permit 'number[] | "foo"' to be asserted to 'string'. - const bothAreStringLike = maybeTypeOfKind(targetType, TypeFlags.StringLike) && - maybeTypeOfKind(widenedType, TypeFlags.StringLike); - if (!bothAreStringLike && !(isTypeAssignableTo(targetType, widenedType))) { - checkTypeAssignableTo(exprType, targetType, node, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other); + if (!isTypeComparableTo(targetType, widenedType)) { + checkTypeComparableTo(exprType, targetType, node, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other); } } return targetType; @@ -11649,11 +11662,7 @@ namespace ts { case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: - // Permit 'number[] | "foo"' to be asserted to 'string'. - if (maybeTypeOfKind(leftType, TypeFlags.StringLike) && maybeTypeOfKind(rightType, TypeFlags.StringLike)) { - return booleanType; - } - if (!isTypeAssignableTo(leftType, rightType) && !isTypeAssignableTo(rightType, leftType)) { + if (!isTypeComparableTo(leftType, rightType) && !isTypeComparableTo(rightType, leftType)) { reportOperatorError(); } return booleanType; @@ -14180,17 +14189,12 @@ namespace ts { if (produceDiagnostics && clause.kind === SyntaxKind.CaseClause) { const caseClause = clause; // TypeScript 1.0 spec (April 2014):5.9 - // In a 'switch' statement, each 'case' expression must be of a type that is assignable to or from the type of the 'switch' expression. + // In a 'switch' statement, each 'case' expression must be of a type that is comparable + // to or from the type of the 'switch' expression. const caseType = checkExpression(caseClause.expression); - - const expressionTypeIsAssignableToCaseType = - // Permit 'number[] | "foo"' to be asserted to 'string'. - (expressionTypeIsStringLike && maybeTypeOfKind(caseType, TypeFlags.StringLike)) || - isTypeAssignableTo(expressionType, caseType); - - if (!expressionTypeIsAssignableToCaseType) { - // 'expressionType is not assignable to caseType', try the reversed check and report errors if it fails - checkTypeAssignableTo(caseType, expressionType, caseClause.expression, /*headMessage*/ undefined); + if (!isTypeComparableTo(expressionType, caseType)) { + // expressionType is not comparable to caseType, try the reversed check and report errors if it fails + checkTypeComparableTo(caseType, expressionType, caseClause.expression, /*headMessage*/ undefined); } } forEach(clause.statements, checkSourceElement); From 436e70ea8f27993ece5ea625168a9304218dad9a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Mar 2016 17:27:42 -0800 Subject: [PATCH 37/59] Accepting new baselines --- .../reference/castingTuple.errors.txt | 23 +------------------ ...eralTypesWithVariousOperators02.errors.txt | 13 +++++++++-- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/tests/baselines/reference/castingTuple.errors.txt b/tests/baselines/reference/castingTuple.errors.txt index 62a36c9c15615..128ee5b5bb497 100644 --- a/tests/baselines/reference/castingTuple.errors.txt +++ b/tests/baselines/reference/castingTuple.errors.txt @@ -1,7 +1,3 @@ -tests/cases/conformance/types/tuple/castingTuple.ts(13,23): error TS2352: Neither type '[number, string]' nor type '[number, string, boolean]' is assignable to the other. - Property '2' is missing in type '[number, string]'. -tests/cases/conformance/types/tuple/castingTuple.ts(16,21): error TS2352: Neither type '[C, D]' nor type '[C, D, A]' is assignable to the other. - Property '2' is missing in type '[C, D]'. tests/cases/conformance/types/tuple/castingTuple.ts(28,10): error TS2352: Neither type '[number, string]' nor type '[number, number]' is assignable to the other. Types of property '1' are incompatible. Type 'string' is not assignable to type 'number'. @@ -10,15 +6,10 @@ tests/cases/conformance/types/tuple/castingTuple.ts(29,10): error TS2352: Neithe Type 'C' is not assignable to type 'A'. Property 'a' is missing in type 'C'. tests/cases/conformance/types/tuple/castingTuple.ts(30,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'array1' must be of type '{}[]', but here has type 'number[]'. -tests/cases/conformance/types/tuple/castingTuple.ts(30,14): error TS2352: Neither type '[number, string]' nor type 'number[]' is assignable to the other. - Types of property 'pop' are incompatible. - Type '() => number | string' is not assignable to type '() => number'. - Type 'number | string' is not assignable to type 'number'. - Type 'string' is not assignable to type 'number'. tests/cases/conformance/types/tuple/castingTuple.ts(31,1): error TS2304: Cannot find name 't4'. -==== tests/cases/conformance/types/tuple/castingTuple.ts (7 errors) ==== +==== tests/cases/conformance/types/tuple/castingTuple.ts (4 errors) ==== interface I { } class A { a = 10; } class C implements I { c }; @@ -32,15 +23,9 @@ tests/cases/conformance/types/tuple/castingTuple.ts(31,1): error TS2304: Cannot var numStrTuple: [number, string] = [5, "foo"]; var emptyObjTuple = <[{}, {}]>numStrTuple; var numStrBoolTuple = <[number, string, boolean]>numStrTuple; - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2352: Neither type '[number, string]' nor type '[number, string, boolean]' is assignable to the other. -!!! error TS2352: Property '2' is missing in type '[number, string]'. var classCDTuple: [C, D] = [new C(), new D()]; var interfaceIITuple = <[I, I]>classCDTuple; var classCDATuple = <[C, D, A]>classCDTuple; - ~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2352: Neither type '[C, D]' nor type '[C, D, A]' is assignable to the other. -!!! error TS2352: Property '2' is missing in type '[C, D]'. var eleFromCDA1 = classCDATuple[2]; // A var eleFromCDA2 = classCDATuple[5]; // C | D | A var t10: [E1, E2] = [E1.one, E2.one]; @@ -66,12 +51,6 @@ tests/cases/conformance/types/tuple/castingTuple.ts(31,1): error TS2304: Cannot var array1 = numStrTuple; ~~~~~~ !!! error TS2403: Subsequent variable declarations must have the same type. Variable 'array1' must be of type '{}[]', but here has type 'number[]'. - ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2352: Neither type '[number, string]' nor type 'number[]' is assignable to the other. -!!! error TS2352: Types of property 'pop' are incompatible. -!!! error TS2352: Type '() => number | string' is not assignable to type '() => number'. -!!! error TS2352: Type 'number | string' is not assignable to type 'number'. -!!! error TS2352: Type 'string' is not assignable to type 'number'. t4[2] = 10; ~~ !!! error TS2304: Cannot find name 't4'. diff --git a/tests/baselines/reference/stringLiteralTypesWithVariousOperators02.errors.txt b/tests/baselines/reference/stringLiteralTypesWithVariousOperators02.errors.txt index 69dd6c2f6a955..92afe67df0c62 100644 --- a/tests/baselines/reference/stringLiteralTypesWithVariousOperators02.errors.txt +++ b/tests/baselines/reference/stringLiteralTypesWithVariousOperators02.errors.txt @@ -7,9 +7,12 @@ tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperato tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(13,11): error TS2356: An arithmetic operand must be of type 'any', 'number' or an enum type. tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(14,9): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(15,9): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. +tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(16,9): error TS2365: Operator '<' cannot be applied to types '"ABC"' and '"XYZ"'. +tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(17,9): error TS2365: Operator '===' cannot be applied to types '"ABC"' and '"XYZ"'. +tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(18,9): error TS2365: Operator '!=' cannot be applied to types '"ABC"' and '"XYZ"'. -==== tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts (9 errors) ==== +==== tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts (12 errors) ==== let abc: "ABC" = "ABC"; let xyz: "XYZ" = "XYZ"; @@ -44,5 +47,11 @@ tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperato ~~~~~~~~~~~~~~~~ !!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. let j = abc < xyz; + ~~~~~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types '"ABC"' and '"XYZ"'. let k = abc === xyz; - let l = abc != xyz; \ No newline at end of file + ~~~~~~~~~~~ +!!! error TS2365: Operator '===' cannot be applied to types '"ABC"' and '"XYZ"'. + let l = abc != xyz; + ~~~~~~~~~~ +!!! error TS2365: Operator '!=' cannot be applied to types '"ABC"' and '"XYZ"'. \ No newline at end of file From a0790fba7df3ee2109282a56d2635bfd3a09c35c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Mar 2016 17:39:56 -0800 Subject: [PATCH 38/59] Add only 'undefined' to optional parameter types --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 15a57ed893137..9384145164ece 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2698,7 +2698,7 @@ namespace ts { // Use type from type annotation if one is present if (declaration.type) { const type = getTypeFromTypeNode(declaration.type); - return declaration.questionToken ? getNullableType(type) : type; + return strictNullChecks && declaration.questionToken ? addNullableKind(type, TypeFlags.Undefined) : type; } if (declaration.kind === SyntaxKind.Parameter) { @@ -2713,7 +2713,7 @@ namespace ts { // Use contextual parameter type if one is available const type = getContextuallyTypedParameterType(declaration); if (type) { - return declaration.questionToken ? getNullableType(type) : type; + return strictNullChecks && declaration.questionToken ? addNullableKind(type, TypeFlags.Undefined) : type; } } From eed4093be500b50d659bcd8efd97f588c3df13b9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Mar 2016 09:56:01 -0800 Subject: [PATCH 39/59] Fix bugs in reduceLeft and reduceRight --- src/compiler/core.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 702ded96a3fdb..5e4aaf89aa16d 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -242,11 +242,9 @@ namespace ts { const count = array.length; if (count > 0) { let pos = 0; - let result = arguments.length <= 2 ? array[pos] : initial; - pos++; + let result = arguments.length <= 2 ? array[pos++] : initial; while (pos < count) { - result = f(result, array[pos]); - pos++; + result = f(result, array[pos++]); } return result; } @@ -260,11 +258,9 @@ namespace ts { if (array) { let pos = array.length - 1; if (pos >= 0) { - let result = arguments.length <= 2 ? array[pos] : initial; - pos--; + let result = arguments.length <= 2 ? array[pos--] : initial; while (pos >= 0) { - result = f(result, array[pos]); - pos--; + result = f(result, array[pos--]); } return result; } From 2762772afd6ad6d5bc8981e833bb15db815da069 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Mar 2016 09:58:47 -0800 Subject: [PATCH 40/59] Include 'undefined' in return type for implicit or expressionless returns --- src/compiler/checker.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9384145164ece..e269dc380caba 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6290,6 +6290,14 @@ namespace ts { return true; } + function getCombinedFlagsOfTypes(types: Type[]) { + let flags: TypeFlags = 0; + for (const t of types) { + flags |= t.flags; + } + return flags; + } + function getCommonSupertype(types: Type[]): Type { if (!strictNullChecks) { return forEach(types, t => isSupertypeOfEach(t, types) ? t : undefined); @@ -6299,7 +6307,7 @@ namespace ts { return getUnionType(types); } const supertype = forEach(primaryTypes, t => isSupertypeOfEach(t, primaryTypes) ? t : undefined); - return supertype && addNullableKind(supertype, reduceLeft(types, (flags, t) => flags | t.flags, 0) & TypeFlags.Nullable); + return supertype && addNullableKind(supertype, getCombinedFlagsOfTypes(types) & TypeFlags.Nullable); } function reportNoCommonSupertypeError(types: Type[], errorLocation: Node, errorMessageChainHead: DiagnosticMessageChain): void { @@ -10931,7 +10939,8 @@ namespace ts { } } else { - types = checkAndAggregateReturnExpressionTypes(func.body, contextualMapper, isAsync); + const hasImplicitReturn = !!(func.flags & NodeFlags.HasImplicitReturn); + types = checkAndAggregateReturnExpressionTypes(func.body, contextualMapper, isAsync, hasImplicitReturn); if (types.length === 0) { if (isAsync) { // For an async function, the return type will not be void, but rather a Promise for void. @@ -11011,9 +11020,9 @@ namespace ts { return aggregatedTypes; } - function checkAndAggregateReturnExpressionTypes(body: Block, contextualMapper?: TypeMapper, isAsync?: boolean): Type[] { + function checkAndAggregateReturnExpressionTypes(body: Block, contextualMapper: TypeMapper, isAsync: boolean, hasImplicitReturn: boolean): Type[] { const aggregatedTypes: Type[] = []; - + let hasOmittedExpressions = false; forEachReturnStatement(body, returnStatement => { const expr = returnStatement.expression; if (expr) { @@ -11025,13 +11034,19 @@ namespace ts { // the native Promise type by the caller. type = checkAwaitedType(type, body.parent, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member); } - if (!contains(aggregatedTypes, type)) { aggregatedTypes.push(type); } } + else { + hasOmittedExpressions = true; + } }); - + if (strictNullChecks && aggregatedTypes.length && (hasOmittedExpressions || hasImplicitReturn)) { + if (!contains(aggregatedTypes, undefinedType)) { + aggregatedTypes.push(undefinedType); + } + } return aggregatedTypes; } From 097f4564bb06a7a73b71b95cba18f1e6dce54d44 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Mar 2016 10:07:28 -0800 Subject: [PATCH 41/59] Remove unused variable --- src/compiler/checker.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e269dc380caba..5cfdc6bc104d6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14185,7 +14185,6 @@ namespace ts { let hasDuplicateDefaultClause = false; const expressionType = checkExpression(node.expression); - const expressionTypeIsStringLike = maybeTypeOfKind(expressionType, TypeFlags.StringLike); forEach(node.caseBlock.clauses, clause => { // Grammar check for duplicate default clauses, skip if we already report duplicate default clause if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) { From 689e28d3aca1b4ac91cd419a23d03b5650ab6a10 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Mar 2016 11:14:57 -0800 Subject: [PATCH 42/59] Keep linter happy with fix in reduceLeft/reduceRight --- src/compiler/core.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 5e4aaf89aa16d..362d0834cd837 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -242,9 +242,17 @@ namespace ts { const count = array.length; if (count > 0) { let pos = 0; - let result = arguments.length <= 2 ? array[pos++] : initial; + let result: T | U; + if (arguments.length <= 2) { + result = array[pos]; + pos++; + } + else { + result = initial; + } while (pos < count) { - result = f(result, array[pos++]); + result = f(result, array[pos]); + pos++; } return result; } @@ -258,9 +266,17 @@ namespace ts { if (array) { let pos = array.length - 1; if (pos >= 0) { - let result = arguments.length <= 2 ? array[pos--] : initial; + let result: T | U; + if (arguments.length <= 2) { + result = array[pos]; + pos--; + } + else { + result = initial; + } while (pos >= 0) { - result = f(result, array[pos--]); + result = f(result, array[pos]); + pos--; } return result; } From 8db7af035d2406bed79cd29d3d7257fa8479f374 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Mar 2016 11:16:31 -0800 Subject: [PATCH 43/59] Proper handling of 'null' and 'undefined' in equals and not equals guards --- src/compiler/checker.ts | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5cfdc6bc104d6..d90c8a740f4a8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6388,6 +6388,11 @@ namespace ts { return flags & TypeFlags.Nullable; } + function getNullableTypeOfKind(kind: TypeFlags) { + return kind & TypeFlags.Null ? kind & TypeFlags.Undefined ? + getUnionType([nullType, undefinedType]) : nullType : undefinedType; + } + function isNullableType(type: Type) { return getNullableKind(type) === TypeFlags.Nullable; } @@ -7216,12 +7221,11 @@ namespace ts { switch (expr.operatorToken.kind) { case SyntaxKind.EqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: if (isNullOrUndefinedLiteral(expr.right)) { return narrowTypeByNullCheck(type, expr, assumeTrue); } - // Fall through - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral) { return narrowTypeByTypeof(type, expr, assumeTrue); } @@ -7237,14 +7241,22 @@ namespace ts { } function narrowTypeByNullCheck(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - // We have '==' or '!=' operator with 'null' or 'undefined' on the right - if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsToken) { + // We have '==', '!=', '===', or '!==' operator with 'null' or 'undefined' on the right + const operator = expr.operatorToken.kind; + if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { assumeTrue = !assumeTrue; } - if (!strictNullChecks || assumeTrue || !isMatchingReference(expr.left, reference)) { + if (!strictNullChecks || !isMatchingReference(expr.left, reference)) { return type; } - return getNonNullableType(type); + const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; + const exprNullableKind = doubleEquals ? TypeFlags.Nullable : + expr.right.kind === SyntaxKind.NullKeyword ? TypeFlags.Null : TypeFlags.Undefined; + if (assumeTrue) { + const nullableKind = getNullableKind(type) & exprNullableKind; + return nullableKind ? getNullableTypeOfKind(nullableKind) : type; + } + return removeNullableKind(type, exprNullableKind); } function narrowTypeByTypeof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { From d0e4b4ae35215b2e288c96cc17d0d0e1b902898a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Mar 2016 15:23:00 -0800 Subject: [PATCH 44/59] Treat 'return' as 'return undefined' for type checking purposes --- src/compiler/checker.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d90c8a740f4a8..6d09cbffce8bf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14135,12 +14135,12 @@ namespace ts { } } - if (node.expression) { + if (strictNullChecks || node.expression) { const func = getContainingFunction(node); if (func) { const signature = getSignatureFromDeclaration(func); const returnType = getReturnTypeOfSignature(signature); - const exprType = checkExpressionCached(node.expression); + const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; if (func.asteriskToken) { // A generator does not need its return expressions checked against its return type. @@ -14151,26 +14151,28 @@ namespace ts { } if (func.kind === SyntaxKind.SetAccessor) { - error(node.expression, Diagnostics.Setters_cannot_return_a_value); + if (node.expression) { + error(node.expression, Diagnostics.Setters_cannot_return_a_value); + } } else if (func.kind === SyntaxKind.Constructor) { - if (!checkTypeAssignableTo(exprType, returnType, node.expression)) { + if (node.expression && !checkTypeAssignableTo(exprType, returnType, node.expression)) { error(node.expression, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); } } else if (func.type || isGetAccessorWithAnnotatedSetAccessor(func)) { if (isAsyncFunctionLike(func)) { const promisedType = getPromisedType(returnType); - const awaitedType = checkAwaitedType(exprType, node.expression, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member); + const awaitedType = checkAwaitedType(exprType, node.expression || node, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member); if (promisedType) { // If the function has a return type, but promisedType is // undefined, an error will be reported in checkAsyncFunctionReturnType // so we don't need to report one here. - checkTypeAssignableTo(awaitedType, promisedType, node.expression); + checkTypeAssignableTo(awaitedType, promisedType, node.expression || node); } } else { - checkTypeAssignableTo(exprType, returnType, node.expression); + checkTypeAssignableTo(exprType, returnType, node.expression || node); } } } From 129a4f190808ee3bda05fa76e32d1d92d8f667aa Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Mar 2016 16:16:19 -0800 Subject: [PATCH 45/59] Check return type includes 'undefined' in function with implicit return --- src/compiler/checker.ts | 3 +++ src/compiler/diagnosticMessages.json | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6d09cbffce8bf..f15af886c361c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11095,6 +11095,9 @@ namespace ts { // NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present error(func.type, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); } + else if (returnType && strictNullChecks && !isTypeAssignableTo(undefinedType, returnType)) { + error(func.type, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); + } else if (compilerOptions.noImplicitReturns) { if (!returnType) { // If return type annotation is omitted check if function has any explicit return statements. diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 0b70ab98ce7ff..bd8701d9cea25 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1091,6 +1091,10 @@ "category": "Error", "code": 2365 }, + "Function lacks ending return statement and return type does not include 'undefined'.": { + "category": "Error", + "code": 2366 + }, "Type parameter name cannot be '{0}'": { "category": "Error", "code": 2368 From 50d874e09d88a2f6948d246b25be7b5f2eea5810 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Mar 2016 16:55:54 -0800 Subject: [PATCH 46/59] Improve type relationship error reporting for nullable types --- src/compiler/checker.ts | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f15af886c361c..853ca8d07befd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5701,7 +5701,19 @@ namespace ts { function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { const targetTypes = target.types; - for (let i = 0, len = targetTypes.length; i < len; i++) { + let len = targetTypes.length; + // The null and undefined types are guaranteed to be at the end of the constituent type list. In order + // to produce the best possible errors we first check the nullable types, such that the last type we + // check and report errors from is a non-nullable type if one is present. + while (len >= 2 && targetTypes[len - 1].flags & TypeFlags.Nullable) { + const related = isRelatedTo(source, targetTypes[len - 1], /*reportErrors*/ false); + if (related) { + return related; + } + len--; + } + // Now check the non-nullable types and report errors on the last one. + for (let i = 0; i < len; i++) { const related = isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1); if (related) { return related; @@ -5725,7 +5737,19 @@ namespace ts { function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean): Ternary { const sourceTypes = source.types; - for (let i = 0, len = sourceTypes.length; i < len; i++) { + let len = sourceTypes.length; + // The null and undefined types are guaranteed to be at the end of the constituent type list. In order + // to produce the best possible errors we first check the nullable types, such that the last type we + // check and report errors from is a non-nullable type if one is present. + while (len >= 2 && sourceTypes[len - 1].flags & TypeFlags.Nullable) { + const related = isRelatedTo(sourceTypes[len - 1], target, /*reportErrors*/ false); + if (related) { + return related; + } + len--; + } + // Now check the non-nullable types and report errors on the last one. + for (let i = 0; i < len; i++) { const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1); if (related) { return related; From 0a25bb58a4e2cdbe20cb0b38cef5f58920c8264c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 6 Mar 2016 13:59:09 -0800 Subject: [PATCH 47/59] Make 'undefined' assignable to 'void' --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 853ca8d07befd..9d05bc60c271c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5507,7 +5507,7 @@ namespace ts { if (isTypeAny(target)) return Ternary.True; if (source.flags & TypeFlags.Undefined) { - if (!strictNullChecks || target.flags & TypeFlags.Undefined || source === emptyArrayElementType) return Ternary.True; + if (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void) || source === emptyArrayElementType) return Ternary.True; } if (source.flags & TypeFlags.Null) { if (!strictNullChecks || target.flags & TypeFlags.Null) return Ternary.True; From 187eaaee7f37ffffb92fa398d280abb527be6fa2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 7 Mar 2016 13:20:07 -0800 Subject: [PATCH 48/59] Fix issue with narrowing exported variables --- src/compiler/checker.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9d05bc60c271c..f18239d5e74ca 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7165,12 +7165,11 @@ namespace ts { if (!leftmostIdentifier) { return type; } - const leftmostSymbol = getResolvedSymbol(leftmostIdentifier); - if (!(leftmostSymbol.flags & SymbolFlags.Variable)) { + const declaration = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(leftmostIdentifier)).valueDeclaration; + if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) { return type; } - const declaration = getDeclarationOfKind(leftmostSymbol, SyntaxKind.VariableDeclaration); - const top = declaration && getDeclarationContainer(declaration); + const top = getDeclarationContainer(declaration); const originalType = type; const nodeStack: { node: Node, child: Node }[] = []; let node: Node = reference; From fbda0bdd94498d47cb4f5078565f2b6d1447e9c9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 8 Mar 2016 11:46:47 -0800 Subject: [PATCH 49/59] Adding another check for undefined --- src/compiler/checker.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f18239d5e74ca..e2c57ffeaa140 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7165,7 +7165,11 @@ namespace ts { if (!leftmostIdentifier) { return type; } - const declaration = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(leftmostIdentifier)).valueDeclaration; + const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(leftmostIdentifier)); + if (!leftmostSymbol) { + return type; + } + const declaration = leftmostSymbol.valueDeclaration; if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) { return type; } From 868e53df2578c18fe62a6b3924e457442e7732e5 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 8 Mar 2016 11:47:18 -0800 Subject: [PATCH 50/59] Accepting new baselines --- .../typeGuardsInExternalModule.types | 8 ++--- .../reference/typeGuardsInModule.types | 30 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/baselines/reference/typeGuardsInExternalModule.types b/tests/baselines/reference/typeGuardsInExternalModule.types index e20063719f5b6..940e7db831adf 100644 --- a/tests/baselines/reference/typeGuardsInExternalModule.types +++ b/tests/baselines/reference/typeGuardsInExternalModule.types @@ -44,13 +44,13 @@ if (typeof var2 === "string") { // export makes the var property and not variable strOrNum = var2; // string | number ->strOrNum = var2 : string | number +>strOrNum = var2 : string >strOrNum : string | number ->var2 : string | number +>var2 : string } else { strOrNum = var2; // number | string ->strOrNum = var2 : string | number +>strOrNum = var2 : number >strOrNum : string | number ->var2 : string | number +>var2 : number } diff --git a/tests/baselines/reference/typeGuardsInModule.types b/tests/baselines/reference/typeGuardsInModule.types index 6b6c552f0cced..7d3753037ad38 100644 --- a/tests/baselines/reference/typeGuardsInModule.types +++ b/tests/baselines/reference/typeGuardsInModule.types @@ -64,15 +64,15 @@ module m1 { >"string" : string strOrNum = var3; // string | number ->strOrNum = var3 : string | number +>strOrNum = var3 : string >strOrNum : string | number ->var3 : string | number +>var3 : string } else { strOrNum = var3; // string | number ->strOrNum = var3 : string | number +>strOrNum = var3 : number >strOrNum : string | number ->var3 : string | number +>var3 : number } } // local module @@ -116,14 +116,14 @@ module m2 { // exported variable from outer the module strOrNum = typeof var3 === "string" && var3; // string | number ->strOrNum = typeof var3 === "string" && var3 : string | number +>strOrNum = typeof var3 === "string" && var3 : string >strOrNum : string | number ->typeof var3 === "string" && var3 : string | number +>typeof var3 === "string" && var3 : string >typeof var3 === "string" : boolean >typeof var3 : string >var3 : string | number >"string" : string ->var3 : string | number +>var3 : string // variables in module declaration var var4: string | number; @@ -160,15 +160,15 @@ module m2 { >"string" : string strOrNum = var5; // string | number ->strOrNum = var5 : string | number +>strOrNum = var5 : string >strOrNum : string | number ->var5 : string | number +>var5 : string } else { strOrNum = var5; // string | number ->strOrNum = var5 : string | number +>strOrNum = var5 : number >strOrNum : string | number ->var5 : string | number +>var5 : number } } } @@ -225,15 +225,15 @@ module m3.m4 { >"string" : string strOrNum = var3; // string | number ->strOrNum = var3 : string | number +>strOrNum = var3 : string >strOrNum : string | number ->var3 : string | number +>var3 : string } else { strOrNum = var3; // string | number ->strOrNum = var3 : string | number +>strOrNum = var3 : number >strOrNum : string | number ->var3 : string | number +>var3 : number } } From 796613ce09dd25bdac8a3189f45f85210979126c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 10 Mar 2016 06:51:57 -0800 Subject: [PATCH 51/59] Better error message + fix assignment analysis of 'switch' statement --- src/compiler/checker.ts | 29 +++++++++++++++++++--------- src/compiler/diagnosticMessages.json | 4 ++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 117c66d3625ae..7eceab0956f3d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7222,6 +7222,7 @@ namespace ts { case SyntaxKind.ReturnStatement: case SyntaxKind.WithStatement: case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseBlock: case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: case SyntaxKind.LabeledStatement: @@ -7683,6 +7684,7 @@ namespace ts { case SyntaxKind.ReturnStatement: case SyntaxKind.WithStatement: case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseBlock: case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: case SyntaxKind.LabeledStatement: @@ -9541,6 +9543,15 @@ namespace ts { return true; } + function checkNonNullExpression(node: Expression | QualifiedName) { + const type = checkExpression(node); + if (strictNullChecks && getNullableKind(type)) { + error(node, Diagnostics.Object_is_possibly_null_or_undefined); + return getNonNullableType(type); + } + return type; + } + function checkPropertyAccessExpression(node: PropertyAccessExpression) { return checkPropertyAccessExpressionOrQualifiedName(node, node.expression, node.name); } @@ -9550,7 +9561,7 @@ namespace ts { } function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { - const type = checkExpression(left); + let type = checkNonNullExpression(left); if (isTypeAny(type)) { return type; } @@ -9661,7 +9672,7 @@ namespace ts { } // Obtain base constraint such that we can bail out if the constraint is an unknown type - const objectType = getApparentType(checkExpression(node.expression)); + const objectType = getApparentType(checkNonNullExpression(node.expression)); const indexType = node.argumentExpression ? checkExpression(node.argumentExpression) : unknownType; if (objectType === unknownType) { @@ -10676,7 +10687,7 @@ namespace ts { return resolveUntypedCall(node); } - const funcType = checkExpression(node.expression); + const funcType = checkNonNullExpression(node.expression); const apparentType = getApparentType(funcType); if (apparentType === unknownType) { @@ -10729,7 +10740,7 @@ namespace ts { } } - let expressionType = checkExpression(node.expression); + let expressionType = checkNonNullExpression(node.expression); // If expressionType's apparent type(section 3.8.1) is an object type with one or // more construct signatures, the expression is processed in the same manner as a @@ -10993,7 +11004,7 @@ namespace ts { return targetType; } - function checkNonNullExpression(node: NonNullExpression) { + function checkNonNullAssertion(node: NonNullExpression) { return getNonNullableType(checkExpression(node.expression)); } @@ -12178,7 +12189,7 @@ namespace ts { case SyntaxKind.AsExpression: return checkAssertion(node); case SyntaxKind.NonNullExpression: - return checkNonNullExpression(node); + return checkNonNullAssertion(node); case SyntaxKind.DeleteExpression: return checkDeleteExpression(node); case SyntaxKind.VoidExpression: @@ -14048,7 +14059,7 @@ namespace ts { } } - const rightType = checkExpression(node.expression); + const rightType = checkNonNullExpression(node.expression); // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved // in this case error about missing name is already reported - do not report extra one if (!isTypeAnyOrAllConstituentTypesHaveKind(rightType, TypeFlags.ObjectType | TypeFlags.TypeParameter)) { @@ -14068,7 +14079,7 @@ namespace ts { } function checkRightHandSideOfForOf(rhsExpression: Expression): Type { - const expressionType = getTypeOfExpression(rhsExpression); + const expressionType = checkNonNullExpression(rhsExpression); return checkIteratedTypeOrElementType(expressionType, rhsExpression, /*allowStringInput*/ true); } @@ -14339,7 +14350,7 @@ namespace ts { const signature = getSignatureFromDeclaration(func); const returnType = getReturnTypeOfSignature(signature); if (strictNullChecks || node.expression) { - const exprType = checkExpressionCached(node.expression); + const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; if (func.asteriskToken) { // A generator does not need its return expressions checked against its return type. diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 01005bffe7372..e47dc96fa225b 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1715,6 +1715,10 @@ "category": "Error", "code": 2530 }, + "Object is possibly 'null' or 'undefined'.": { + "category": "Error", + "code": 2531 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 From dad05642d71408a864e80e0aad34fc43091e38ac Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 10 Mar 2016 11:13:30 -0800 Subject: [PATCH 52/59] Support 'this' in type guards --- src/compiler/checker.ts | 79 ++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7eceab0956f3d..f2a41ea5de8bc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7144,6 +7144,9 @@ namespace ts { const symbol = getResolvedSymbol(node); return symbol !== unknownSymbol ? "" + getSymbolId(symbol) : undefined; } + if (node.kind === SyntaxKind.ThisKeyword) { + return "0"; + } if (node.kind === SyntaxKind.PropertyAccessExpression) { const key = getAssignmentKey((node).expression); return key && key + "." + (node).name.text; @@ -7242,10 +7245,12 @@ namespace ts { } function isReferenceAssignedWithin(reference: Node, node: Node): boolean { - const key = getAssignmentKey(reference); - if (key) { - const links = getNodeLinks(node); - return (links.assignmentMap || (links.assignmentMap = getAssignmentMap(node)))[key]; + if (reference.kind !== SyntaxKind.ThisKeyword) { + const key = getAssignmentKey(reference); + if (key) { + const links = getNodeLinks(node); + return (links.assignmentMap || (links.assignmentMap = getAssignmentMap(node)))[key]; + } } return false; } @@ -7267,47 +7272,53 @@ namespace ts { node.kind === SyntaxKind.Identifier && getResolvedSymbol(node) === undefinedSymbol; } - function getLeftmostIdentifier(node: Node): Identifier { + function getLeftmostIdentifierOrThis(node: Node): Node { switch (node.kind) { case SyntaxKind.Identifier: - return node; + case SyntaxKind.ThisKeyword: + return node; case SyntaxKind.PropertyAccessExpression: - return getLeftmostIdentifier((node).expression); + return getLeftmostIdentifierOrThis((node).expression); } return undefined; } function isMatchingReference(source: Node, target: Node): boolean { if (source.kind === target.kind) { - if (source.kind === SyntaxKind.Identifier) { - return getResolvedSymbol(source) === getResolvedSymbol(target); - } - if (source.kind === SyntaxKind.PropertyAccessExpression) { - return (source).name.text === (target).name.text && - isMatchingReference((source).expression, (target).expression); + switch (source.kind) { + case SyntaxKind.Identifier: + return getResolvedSymbol(source) === getResolvedSymbol(target); + case SyntaxKind.ThisKeyword: + return true; + case SyntaxKind.PropertyAccessExpression: + return (source).name.text === (target).name.text && + isMatchingReference((source).expression, (target).expression); } } return false; } // Get the narrowed type of a given symbol at a given location - function getNarrowedTypeOfReference(type: Type, reference: IdentifierOrPropertyAccess) { + function getNarrowedTypeOfReference(type: Type, reference: Node) { if (!(type.flags & (TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.Union | TypeFlags.TypeParameter))) { return type; } - const leftmostIdentifier = getLeftmostIdentifier(reference); - if (!leftmostIdentifier) { + const leftmostNode = getLeftmostIdentifierOrThis(reference); + if (!leftmostNode) { return type; } - const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(leftmostIdentifier)); - if (!leftmostSymbol) { - return type; - } - const declaration = leftmostSymbol.valueDeclaration; - if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) { - return type; + let top: Node; + if (leftmostNode.kind === SyntaxKind.Identifier) { + const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(leftmostNode)); + if (!leftmostSymbol) { + return type; + } + const declaration = leftmostSymbol.valueDeclaration; + if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) { + return type; + } + top = getDeclarationContainer(declaration); } - const top = getDeclarationContainer(declaration); const originalType = type; const nodeStack: { node: Node, child: Node }[] = []; let node: Node = reference; @@ -7322,11 +7333,12 @@ namespace ts { break; case SyntaxKind.SourceFile: case SyntaxKind.ModuleDeclaration: - // Stop at the first containing file or module declaration break loop; - } - if (node === top) { - break; + default: + if (node === top || isFunctionLikeKind(node.kind)) { + break loop; + } + break; } } @@ -7374,7 +7386,7 @@ namespace ts { return type; - function narrowTypeByTruthiness(type: Type, expr: Identifier, assumeTrue: boolean): Type { + function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { return strictNullChecks && assumeTrue && isMatchingReference(expr, reference) ? getNonNullableType(type) : type; } @@ -7551,7 +7563,8 @@ namespace ts { } } - if (isTypeAssignableTo(narrowedTypeCandidate, originalType)) { + const targetType = originalType.flags & TypeFlags.TypeParameter ? getApparentType(originalType) : originalType; + if (isTypeAssignableTo(narrowedTypeCandidate, targetType)) { // Narrow to the target type if it's assignable to the current type return narrowedTypeCandidate; } @@ -7592,8 +7605,9 @@ namespace ts { function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { switch (expr.kind) { case SyntaxKind.Identifier: + case SyntaxKind.ThisKeyword: case SyntaxKind.PropertyAccessExpression: - return narrowTypeByTruthiness(type, expr, assumeTrue); + return narrowTypeByTruthiness(type, expr, assumeTrue); case SyntaxKind.CallExpression: return narrowTypeByTypePredicate(type, expr, assumeTrue); case SyntaxKind.ParenthesizedExpression: @@ -8007,7 +8021,8 @@ namespace ts { if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); - return container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; + const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; + return getNarrowedTypeOfReference(type, node); } if (isInJavaScriptFile(node)) { From 6772c3451945dea5e4fdbad60ea991d82e49fffb Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 10 Mar 2016 11:14:40 -0800 Subject: [PATCH 53/59] Accepting new baselines --- ...ationCollidingNamesInAugmentation1.symbols | 57 --------- ...ntationCollidingNamesInAugmentation1.types | 69 ----------- .../reference/typeGuardInClass.errors.txt | 30 +++++ .../reference/typeGuardInClass.symbols | 29 ----- .../reference/typeGuardInClass.types | 34 ------ .../reference/typeGuardsDefeat.errors.txt | 52 ++++++++ .../reference/typeGuardsDefeat.symbols | 82 ------------- .../reference/typeGuardsDefeat.types | 105 ---------------- ...typeGuardsInFunctionAndModuleBlock.symbols | 16 +-- .../typeGuardsInFunctionAndModuleBlock.types | 36 +++--- .../reference/typeGuardsInProperties.types | 16 +-- .../typeGuardsOnClassProperty.errors.txt | 34 ------ .../typeGuardsOnClassProperty.symbols | 86 ++++++++++++++ .../reference/typeGuardsOnClassProperty.types | 112 ++++++++++++++++++ 14 files changed, 314 insertions(+), 444 deletions(-) delete mode 100644 tests/baselines/reference/moduleAugmentationCollidingNamesInAugmentation1.symbols delete mode 100644 tests/baselines/reference/moduleAugmentationCollidingNamesInAugmentation1.types create mode 100644 tests/baselines/reference/typeGuardInClass.errors.txt delete mode 100644 tests/baselines/reference/typeGuardInClass.symbols delete mode 100644 tests/baselines/reference/typeGuardInClass.types create mode 100644 tests/baselines/reference/typeGuardsDefeat.errors.txt delete mode 100644 tests/baselines/reference/typeGuardsDefeat.symbols delete mode 100644 tests/baselines/reference/typeGuardsDefeat.types delete mode 100644 tests/baselines/reference/typeGuardsOnClassProperty.errors.txt create mode 100644 tests/baselines/reference/typeGuardsOnClassProperty.symbols create mode 100644 tests/baselines/reference/typeGuardsOnClassProperty.types diff --git a/tests/baselines/reference/moduleAugmentationCollidingNamesInAugmentation1.symbols b/tests/baselines/reference/moduleAugmentationCollidingNamesInAugmentation1.symbols deleted file mode 100644 index ec933695900ad..0000000000000 --- a/tests/baselines/reference/moduleAugmentationCollidingNamesInAugmentation1.symbols +++ /dev/null @@ -1,57 +0,0 @@ -=== tests/cases/compiler/map1.ts === - -import { Observable } from "./observable" ->Observable : Symbol(Observable, Decl(map1.ts, 1, 8)) - -(Observable.prototype).map = function() { } ->Observable.prototype : Symbol(Observable.prototype) ->Observable : Symbol(Observable, Decl(map1.ts, 1, 8)) ->prototype : Symbol(Observable.prototype) - -declare module "./observable" { - interface I {x0} ->I : Symbol(I, Decl(map1.ts, 5, 31), Decl(map2.ts, 4, 31)) ->x0 : Symbol(x0, Decl(map1.ts, 6, 17)) -} - -=== tests/cases/compiler/map2.ts === -import { Observable } from "./observable" ->Observable : Symbol(Observable, Decl(map2.ts, 0, 8)) - -(Observable.prototype).map = function() { } ->Observable.prototype : Symbol(Observable.prototype) ->Observable : Symbol(Observable, Decl(map2.ts, 0, 8)) ->prototype : Symbol(Observable.prototype) - -declare module "./observable" { - interface I {x1} ->I : Symbol(I, Decl(map1.ts, 5, 31), Decl(map2.ts, 4, 31)) ->x1 : Symbol(x1, Decl(map2.ts, 5, 17)) -} - - -=== tests/cases/compiler/observable.ts === -export declare class Observable { ->Observable : Symbol(Observable, Decl(observable.ts, 0, 0)) ->T : Symbol(T, Decl(observable.ts, 0, 32)) - - filter(pred: (e:T) => boolean): Observable; ->filter : Symbol(filter, Decl(observable.ts, 0, 36)) ->pred : Symbol(pred, Decl(observable.ts, 1, 11)) ->e : Symbol(e, Decl(observable.ts, 1, 18)) ->T : Symbol(T, Decl(observable.ts, 0, 32)) ->Observable : Symbol(Observable, Decl(observable.ts, 0, 0)) ->T : Symbol(T, Decl(observable.ts, 0, 32)) -} - -=== tests/cases/compiler/main.ts === -import { Observable } from "./observable" ->Observable : Symbol(Observable, Decl(main.ts, 0, 8)) - -import "./map1"; -import "./map2"; - -let x: Observable; ->x : Symbol(x, Decl(main.ts, 4, 3)) ->Observable : Symbol(Observable, Decl(main.ts, 0, 8)) - diff --git a/tests/baselines/reference/moduleAugmentationCollidingNamesInAugmentation1.types b/tests/baselines/reference/moduleAugmentationCollidingNamesInAugmentation1.types deleted file mode 100644 index e87560c6a3d1e..0000000000000 --- a/tests/baselines/reference/moduleAugmentationCollidingNamesInAugmentation1.types +++ /dev/null @@ -1,69 +0,0 @@ -=== tests/cases/compiler/map1.ts === - -import { Observable } from "./observable" ->Observable : typeof Observable - -(Observable.prototype).map = function() { } ->(Observable.prototype).map = function() { } : () => void ->(Observable.prototype).map : any ->(Observable.prototype) : any ->Observable.prototype : any ->Observable.prototype : Observable ->Observable : typeof Observable ->prototype : Observable ->map : any ->function() { } : () => void - -declare module "./observable" { - interface I {x0} ->I : I ->x0 : any -} - -=== tests/cases/compiler/map2.ts === -import { Observable } from "./observable" ->Observable : typeof Observable - -(Observable.prototype).map = function() { } ->(Observable.prototype).map = function() { } : () => void ->(Observable.prototype).map : any ->(Observable.prototype) : any ->Observable.prototype : any ->Observable.prototype : Observable ->Observable : typeof Observable ->prototype : Observable ->map : any ->function() { } : () => void - -declare module "./observable" { - interface I {x1} ->I : I ->x1 : any -} - - -=== tests/cases/compiler/observable.ts === -export declare class Observable { ->Observable : Observable ->T : T - - filter(pred: (e:T) => boolean): Observable; ->filter : (pred: (e: T) => boolean) => Observable ->pred : (e: T) => boolean ->e : T ->T : T ->Observable : Observable ->T : T -} - -=== tests/cases/compiler/main.ts === -import { Observable } from "./observable" ->Observable : typeof Observable - -import "./map1"; -import "./map2"; - -let x: Observable; ->x : Observable ->Observable : Observable - diff --git a/tests/baselines/reference/typeGuardInClass.errors.txt b/tests/baselines/reference/typeGuardInClass.errors.txt new file mode 100644 index 0000000000000..aa86067576f5c --- /dev/null +++ b/tests/baselines/reference/typeGuardInClass.errors.txt @@ -0,0 +1,30 @@ +tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts(6,17): error TS2322: Type 'string | number' is not assignable to type 'string'. + Type 'number' is not assignable to type 'string'. +tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts(13,17): error TS2322: Type 'string | number' is not assignable to type 'number'. + Type 'string' is not assignable to type 'number'. + + +==== tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts (2 errors) ==== + let x: string | number; + + if (typeof x === "string") { + let n = class { + constructor() { + let y: string = x; + ~ +!!! error TS2322: Type 'string | number' is not assignable to type 'string'. +!!! error TS2322: Type 'number' is not assignable to type 'string'. + } + } + } + else { + let m = class { + constructor() { + let y: number = x; + ~ +!!! error TS2322: Type 'string | number' is not assignable to type 'number'. +!!! error TS2322: Type 'string' is not assignable to type 'number'. + } + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardInClass.symbols b/tests/baselines/reference/typeGuardInClass.symbols deleted file mode 100644 index cc0e745e4de9e..0000000000000 --- a/tests/baselines/reference/typeGuardInClass.symbols +++ /dev/null @@ -1,29 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts === -let x: string | number; ->x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3)) - -if (typeof x === "string") { ->x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3)) - - let n = class { ->n : Symbol(n, Decl(typeGuardInClass.ts, 3, 7)) - - constructor() { - let y: string = x; ->y : Symbol(y, Decl(typeGuardInClass.ts, 5, 15)) ->x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3)) - } - } -} -else { - let m = class { ->m : Symbol(m, Decl(typeGuardInClass.ts, 10, 7)) - - constructor() { - let y: number = x; ->y : Symbol(y, Decl(typeGuardInClass.ts, 12, 15)) ->x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3)) - } - } -} - diff --git a/tests/baselines/reference/typeGuardInClass.types b/tests/baselines/reference/typeGuardInClass.types deleted file mode 100644 index 93fe9f28c5e9e..0000000000000 --- a/tests/baselines/reference/typeGuardInClass.types +++ /dev/null @@ -1,34 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts === -let x: string | number; ->x : string | number - -if (typeof x === "string") { ->typeof x === "string" : boolean ->typeof x : string ->x : string | number ->"string" : string - - let n = class { ->n : typeof (Anonymous class) ->class { constructor() { let y: string = x; } } : typeof (Anonymous class) - - constructor() { - let y: string = x; ->y : string ->x : string - } - } -} -else { - let m = class { ->m : typeof (Anonymous class) ->class { constructor() { let y: number = x; } } : typeof (Anonymous class) - - constructor() { - let y: number = x; ->y : number ->x : number - } - } -} - diff --git a/tests/baselines/reference/typeGuardsDefeat.errors.txt b/tests/baselines/reference/typeGuardsDefeat.errors.txt new file mode 100644 index 0000000000000..d4006711d45c2 --- /dev/null +++ b/tests/baselines/reference/typeGuardsDefeat.errors.txt @@ -0,0 +1,52 @@ +tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts(21,20): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. +tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts(21,24): error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. +tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts(32,23): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. +tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts(32,27): error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. + + +==== tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts (4 errors) ==== + // Also note that it is possible to defeat a type guard by calling a function that changes the + // type of the guarded variable. + function foo(x: number | string) { + function f() { + x = 10; + } + if (typeof x === "string") { + f(); + return x.length; // string + } + else { + return x++; // number + } + } + function foo2(x: number | string) { + if (typeof x === "string") { + return x.length; // string + } + else { + var f = function () { + return x * x; + ~ +!!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. + ~ +!!! error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. + }; + } + x = "hello"; + f(); + } + function foo3(x: number | string) { + if (typeof x === "string") { + return x.length; // string + } + else { + var f = () => x * x; + ~ +!!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. + ~ +!!! error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. + } + x = "hello"; + f(); + } + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsDefeat.symbols b/tests/baselines/reference/typeGuardsDefeat.symbols deleted file mode 100644 index 388b69b578904..0000000000000 --- a/tests/baselines/reference/typeGuardsDefeat.symbols +++ /dev/null @@ -1,82 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts === -// Also note that it is possible to defeat a type guard by calling a function that changes the -// type of the guarded variable. -function foo(x: number | string) { ->foo : Symbol(foo, Decl(typeGuardsDefeat.ts, 0, 0)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 2, 13)) - - function f() { ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 2, 34)) - - x = 10; ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 2, 13)) - } - if (typeof x === "string") { ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 2, 13)) - - f(); ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 2, 34)) - - return x.length; // string ->x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 2, 13)) ->length : Symbol(String.length, Decl(lib.d.ts, --, --)) - } - else { - return x++; // number ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 2, 13)) - } -} -function foo2(x: number | string) { ->foo2 : Symbol(foo2, Decl(typeGuardsDefeat.ts, 13, 1)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) - - if (typeof x === "string") { ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) - - return x.length; // string ->x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) ->length : Symbol(String.length, Decl(lib.d.ts, --, --)) - } - else { - var f = function () { ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 19, 11)) - - return x * x; ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) - - }; - } - x = "hello"; ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) - - f(); ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 19, 11)) -} -function foo3(x: number | string) { ->foo3 : Symbol(foo3, Decl(typeGuardsDefeat.ts, 25, 1)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) - - if (typeof x === "string") { ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) - - return x.length; // string ->x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) ->length : Symbol(String.length, Decl(lib.d.ts, --, --)) - } - else { - var f = () => x * x; ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 31, 11)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) - } - x = "hello"; ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) - - f(); ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 31, 11)) -} - diff --git a/tests/baselines/reference/typeGuardsDefeat.types b/tests/baselines/reference/typeGuardsDefeat.types deleted file mode 100644 index cc655d3ce0f36..0000000000000 --- a/tests/baselines/reference/typeGuardsDefeat.types +++ /dev/null @@ -1,105 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts === -// Also note that it is possible to defeat a type guard by calling a function that changes the -// type of the guarded variable. -function foo(x: number | string) { ->foo : (x: number | string) => number ->x : number | string - - function f() { ->f : () => void - - x = 10; ->x = 10 : number ->x : number | string ->10 : number - } - if (typeof x === "string") { ->typeof x === "string" : boolean ->typeof x : string ->x : number | string ->"string" : string - - f(); ->f() : void ->f : () => void - - return x.length; // string ->x.length : number ->x : string ->length : number - } - else { - return x++; // number ->x++ : number ->x : number - } -} -function foo2(x: number | string) { ->foo2 : (x: number | string) => number ->x : number | string - - if (typeof x === "string") { ->typeof x === "string" : boolean ->typeof x : string ->x : number | string ->"string" : string - - return x.length; // string ->x.length : number ->x : string ->length : number - } - else { - var f = function () { ->f : () => number ->function () { return x * x; } : () => number - - return x * x; ->x * x : number ->x : number ->x : number - - }; - } - x = "hello"; ->x = "hello" : string ->x : number | string ->"hello" : string - - f(); ->f() : number ->f : () => number -} -function foo3(x: number | string) { ->foo3 : (x: number | string) => number ->x : number | string - - if (typeof x === "string") { ->typeof x === "string" : boolean ->typeof x : string ->x : number | string ->"string" : string - - return x.length; // string ->x.length : number ->x : string ->length : number - } - else { - var f = () => x * x; ->f : () => number ->() => x * x : () => number ->x * x : number ->x : number ->x : number - } - x = "hello"; ->x = "hello" : string ->x : number | string ->"hello" : string - - f(); ->f() : number ->f : () => number -} - diff --git a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols index 34810f303db3d..bf21641624e3e 100644 --- a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols +++ b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols @@ -27,9 +27,9 @@ function foo(x: number | string | boolean) { >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --)) : x.toString(); // number ->x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 2, 13)) ->toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) } (); } @@ -60,9 +60,9 @@ function foo2(x: number | string | boolean) { >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --)) : x.toString(); // number ->x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 12, 14)) ->toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) } (x); // x here is narrowed to number | boolean >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 12, 14)) @@ -91,9 +91,9 @@ function foo3(x: number | string | boolean) { >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --)) : x.toString(); // number ->x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 22, 14)) ->toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) })(); } @@ -123,9 +123,9 @@ function foo4(x: number | string | boolean) { >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --)) : x.toString(); // number ->x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 32, 14)) ->toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) })(x); // x here is narrowed to number | boolean >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 32, 14)) diff --git a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types index f7d56ed17d1a6..67d1816cfc373 100644 --- a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types +++ b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types @@ -21,14 +21,14 @@ function foo(x: number | string | boolean) { >f : () => string var b = x; // number | boolean ->b : number | boolean ->x : number | boolean +>b : number | string | boolean +>x : number | string | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : number | boolean +>x : number | string | boolean >"boolean" : string ? x.toString() // boolean @@ -40,7 +40,7 @@ function foo(x: number | string | boolean) { : x.toString(); // number >x.toString() : string >x.toString : (radix?: number) => string ->x : number +>x : number | string >toString : (radix?: number) => string } (); @@ -66,14 +66,14 @@ function foo2(x: number | string | boolean) { >a : number | boolean var b = x; // new scope - number | boolean ->b : number | boolean ->x : number | boolean +>b : number | string | boolean +>x : number | string | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : number | boolean +>x : number | string | boolean >"boolean" : string ? x.toString() // boolean @@ -85,7 +85,7 @@ function foo2(x: number | string | boolean) { : x.toString(); // number >x.toString() : string >x.toString : (radix?: number) => string ->x : number +>x : number | string >toString : (radix?: number) => string } (x); // x here is narrowed to number | boolean @@ -111,14 +111,14 @@ function foo3(x: number | string | boolean) { >() => { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number } : () => string var b = x; // new scope - number | boolean ->b : number | boolean ->x : number | boolean +>b : number | string | boolean +>x : number | string | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : number | boolean +>x : number | string | boolean >"boolean" : string ? x.toString() // boolean @@ -130,7 +130,7 @@ function foo3(x: number | string | boolean) { : x.toString(); // number >x.toString() : string >x.toString : (radix?: number) => string ->x : number +>x : number | string >toString : (radix?: number) => string })(); @@ -156,14 +156,14 @@ function foo4(x: number | string | boolean) { >a : number | boolean var b = x; // new scope - number | boolean ->b : number | boolean ->x : number | boolean +>b : number | string | boolean +>x : number | string | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : number | boolean +>x : number | string | boolean >"boolean" : string ? x.toString() // boolean @@ -175,7 +175,7 @@ function foo4(x: number | string | boolean) { : x.toString(); // number >x.toString() : string >x.toString : (radix?: number) => string ->x : number +>x : number | string >toString : (radix?: number) => string })(x); // x here is narrowed to number | boolean @@ -200,8 +200,8 @@ function foo5(x: number | string | boolean) { >foo : () => void var z = x; // string ->z : string ->x : string +>z : number | string | boolean +>x : number | string | boolean } } } diff --git a/tests/baselines/reference/typeGuardsInProperties.types b/tests/baselines/reference/typeGuardsInProperties.types index d6c9602a450d4..ef4cfbb5b051f 100644 --- a/tests/baselines/reference/typeGuardsInProperties.types +++ b/tests/baselines/reference/typeGuardsInProperties.types @@ -29,32 +29,32 @@ class C1 { >method : () => void strOrNum = typeof this.pp1 === "string" && this.pp1; // string | number ->strOrNum = typeof this.pp1 === "string" && this.pp1 : string | number +>strOrNum = typeof this.pp1 === "string" && this.pp1 : string >strOrNum : string | number ->typeof this.pp1 === "string" && this.pp1 : string | number +>typeof this.pp1 === "string" && this.pp1 : string >typeof this.pp1 === "string" : boolean >typeof this.pp1 : string >this.pp1 : string | number >this : this >pp1 : string | number >"string" : string ->this.pp1 : string | number +>this.pp1 : string >this : this ->pp1 : string | number +>pp1 : string strOrNum = typeof this.pp2 === "string" && this.pp2; // string | number ->strOrNum = typeof this.pp2 === "string" && this.pp2 : string | number +>strOrNum = typeof this.pp2 === "string" && this.pp2 : string >strOrNum : string | number ->typeof this.pp2 === "string" && this.pp2 : string | number +>typeof this.pp2 === "string" && this.pp2 : string >typeof this.pp2 === "string" : boolean >typeof this.pp2 : string >this.pp2 : string | number >this : this >pp2 : string | number >"string" : string ->this.pp2 : string | number +>this.pp2 : string >this : this ->pp2 : string | number +>pp2 : string strOrNum = typeof this.pp3 === "string" && this.pp3; // string | number >strOrNum = typeof this.pp3 === "string" && this.pp3 : string | number diff --git a/tests/baselines/reference/typeGuardsOnClassProperty.errors.txt b/tests/baselines/reference/typeGuardsOnClassProperty.errors.txt deleted file mode 100644 index 660962ec1a760..0000000000000 --- a/tests/baselines/reference/typeGuardsOnClassProperty.errors.txt +++ /dev/null @@ -1,34 +0,0 @@ -tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts(14,70): error TS2339: Property 'join' does not exist on type 'string | string[]'. - - -==== tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts (1 errors) ==== - // Note that type guards affect types of variables and parameters only and - // have no effect on members of objects such as properties. - - // Note that the class's property must be copied to a local variable for - // the type guard to have an effect - class D { - data: string | string[]; - getData() { - var data = this.data; - return typeof data === "string" ? data : data.join(" "); - } - - getData1() { - return typeof this.data === "string" ? this.data : this.data.join(" "); - ~~~~ -!!! error TS2339: Property 'join' does not exist on type 'string | string[]'. - } - } - - var o: { - prop1: number|string; - prop2: boolean|string; - } = { - prop1: "string" , - prop2: true - } - - if (typeof o.prop1 === "string" && o.prop1.toLowerCase()) {} - var prop1 = o.prop1; - if (typeof prop1 === "string" && prop1.toLocaleLowerCase()) { } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsOnClassProperty.symbols b/tests/baselines/reference/typeGuardsOnClassProperty.symbols new file mode 100644 index 0000000000000..f82df71e46ab1 --- /dev/null +++ b/tests/baselines/reference/typeGuardsOnClassProperty.symbols @@ -0,0 +1,86 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts === +// Note that type guards affect types of variables and parameters only and +// have no effect on members of objects such as properties. + +// Note that the class's property must be copied to a local variable for +// the type guard to have an effect +class D { +>D : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) + + data: string | string[]; +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) + + getData() { +>getData : Symbol(getData, Decl(typeGuardsOnClassProperty.ts, 6, 28)) + + var data = this.data; +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 8, 11)) +>this.data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) + + return typeof data === "string" ? data : data.join(" "); +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 8, 11)) +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 8, 11)) +>data.join : Symbol(Array.join, Decl(lib.d.ts, --, --)) +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 8, 11)) +>join : Symbol(Array.join, Decl(lib.d.ts, --, --)) + } + + getData1() { +>getData1 : Symbol(getData1, Decl(typeGuardsOnClassProperty.ts, 10, 5)) + + return typeof this.data === "string" ? this.data : this.data.join(" "); +>this.data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this.data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this.data.join : Symbol(Array.join, Decl(lib.d.ts, --, --)) +>this.data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>join : Symbol(Array.join, Decl(lib.d.ts, --, --)) + } +} + +var o: { +>o : Symbol(o, Decl(typeGuardsOnClassProperty.ts, 17, 3)) + + prop1: number|string; +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) + + prop2: boolean|string; +>prop2 : Symbol(prop2, Decl(typeGuardsOnClassProperty.ts, 18, 25)) + +} = { + prop1: "string" , +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 20, 5)) + + prop2: true +>prop2 : Symbol(prop2, Decl(typeGuardsOnClassProperty.ts, 21, 25)) + } + +if (typeof o.prop1 === "string" && o.prop1.toLowerCase()) {} +>o.prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) +>o : Symbol(o, Decl(typeGuardsOnClassProperty.ts, 17, 3)) +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) +>o.prop1.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --)) +>o.prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) +>o : Symbol(o, Decl(typeGuardsOnClassProperty.ts, 17, 3)) +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --)) + +var prop1 = o.prop1; +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 26, 3)) +>o.prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) +>o : Symbol(o, Decl(typeGuardsOnClassProperty.ts, 17, 3)) +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) + +if (typeof prop1 === "string" && prop1.toLocaleLowerCase()) { } +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 26, 3)) +>prop1.toLocaleLowerCase : Symbol(String.toLocaleLowerCase, Decl(lib.d.ts, --, --)) +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 26, 3)) +>toLocaleLowerCase : Symbol(String.toLocaleLowerCase, Decl(lib.d.ts, --, --)) + diff --git a/tests/baselines/reference/typeGuardsOnClassProperty.types b/tests/baselines/reference/typeGuardsOnClassProperty.types new file mode 100644 index 0000000000000..6d524ccf6744c --- /dev/null +++ b/tests/baselines/reference/typeGuardsOnClassProperty.types @@ -0,0 +1,112 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts === +// Note that type guards affect types of variables and parameters only and +// have no effect on members of objects such as properties. + +// Note that the class's property must be copied to a local variable for +// the type guard to have an effect +class D { +>D : D + + data: string | string[]; +>data : string | string[] + + getData() { +>getData : () => string + + var data = this.data; +>data : string | string[] +>this.data : string | string[] +>this : this +>data : string | string[] + + return typeof data === "string" ? data : data.join(" "); +>typeof data === "string" ? data : data.join(" ") : string +>typeof data === "string" : boolean +>typeof data : string +>data : string | string[] +>"string" : string +>data : string +>data.join(" ") : string +>data.join : (separator?: string) => string +>data : string[] +>join : (separator?: string) => string +>" " : string + } + + getData1() { +>getData1 : () => string + + return typeof this.data === "string" ? this.data : this.data.join(" "); +>typeof this.data === "string" ? this.data : this.data.join(" ") : string +>typeof this.data === "string" : boolean +>typeof this.data : string +>this.data : string | string[] +>this : this +>data : string | string[] +>"string" : string +>this.data : string +>this : this +>data : string +>this.data.join(" ") : string +>this.data.join : (separator?: string) => string +>this.data : string[] +>this : this +>data : string[] +>join : (separator?: string) => string +>" " : string + } +} + +var o: { +>o : { prop1: number | string; prop2: boolean | string; } + + prop1: number|string; +>prop1 : number | string + + prop2: boolean|string; +>prop2 : boolean | string + +} = { +>{ prop1: "string" , prop2: true } : { prop1: string; prop2: boolean; } + + prop1: "string" , +>prop1 : string +>"string" : string + + prop2: true +>prop2 : boolean +>true : boolean + } + +if (typeof o.prop1 === "string" && o.prop1.toLowerCase()) {} +>typeof o.prop1 === "string" && o.prop1.toLowerCase() : string +>typeof o.prop1 === "string" : boolean +>typeof o.prop1 : string +>o.prop1 : number | string +>o : { prop1: number | string; prop2: boolean | string; } +>prop1 : number | string +>"string" : string +>o.prop1.toLowerCase() : string +>o.prop1.toLowerCase : () => string +>o.prop1 : string +>o : { prop1: number | string; prop2: boolean | string; } +>prop1 : string +>toLowerCase : () => string + +var prop1 = o.prop1; +>prop1 : number | string +>o.prop1 : number | string +>o : { prop1: number | string; prop2: boolean | string; } +>prop1 : number | string + +if (typeof prop1 === "string" && prop1.toLocaleLowerCase()) { } +>typeof prop1 === "string" && prop1.toLocaleLowerCase() : string +>typeof prop1 === "string" : boolean +>typeof prop1 : string +>prop1 : number | string +>"string" : string +>prop1.toLocaleLowerCase() : string +>prop1.toLocaleLowerCase : () => string +>prop1 : string +>toLocaleLowerCase : () => string + From 9c58875f416177335932e1ff9292119955443aec Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 10 Mar 2016 11:27:20 -0800 Subject: [PATCH 54/59] Fix linting error --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f2a41ea5de8bc..52373640f5764 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9576,7 +9576,7 @@ namespace ts { } function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { - let type = checkNonNullExpression(left); + const type = checkNonNullExpression(left); if (isTypeAny(type)) { return type; } From 4c641c4147a87c87c83eb4e47cccc1557f38e266 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 10 Mar 2016 13:09:11 -0800 Subject: [PATCH 55/59] Add 'undefined' to type of parameter with default value in signature --- src/compiler/checker.ts | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 52373640f5764..3511305e16800 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5428,8 +5428,8 @@ namespace ts { const sourceParams = source.parameters; const targetParams = target.parameters; for (let i = 0; i < checkCount; i++) { - const s = i < sourceMax ? getTypeOfSymbol(sourceParams[i]) : getRestTypeOfSignature(source); - const t = i < targetMax ? getTypeOfSymbol(targetParams[i]) : getRestTypeOfSignature(target); + const s = i < sourceMax ? getTypeOfParameter(sourceParams[i]) : getRestTypeOfSignature(source); + const t = i < targetMax ? getTypeOfParameter(targetParams[i]) : getRestTypeOfSignature(target); const related = compareTypes(s, t, /*reportErrors*/ false) || compareTypes(t, s, reportErrors); if (!related) { if (reportErrors) { @@ -6409,8 +6409,8 @@ namespace ts { let result = Ternary.True; const targetLen = target.parameters.length; for (let i = 0; i < targetLen; i++) { - const s = isRestParameterIndex(source, i) ? getRestTypeOfSignature(source) : getTypeOfSymbol(source.parameters[i]); - const t = isRestParameterIndex(target, i) ? getRestTypeOfSignature(target) : getTypeOfSymbol(target.parameters[i]); + const s = isRestParameterIndex(source, i) ? getRestTypeOfSignature(source) : getTypeOfParameter(source.parameters[i]); + const t = isRestParameterIndex(target, i) ? getRestTypeOfSignature(target) : getTypeOfParameter(target.parameters[i]); const related = compareTypes(s, t); if (!related) { return Ternary.False; @@ -6777,8 +6777,8 @@ namespace ts { count = sourceMax < targetMax ? sourceMax : targetMax; } for (let i = 0; i < count; i++) { - const s = i < sourceMax ? getTypeOfSymbol(source.parameters[i]) : getRestTypeOfSignature(source); - const t = i < targetMax ? getTypeOfSymbol(target.parameters[i]) : getRestTypeOfSignature(target); + const s = i < sourceMax ? getTypeOfParameter(source.parameters[i]) : getRestTypeOfSignature(source); + const t = i < targetMax ? getTypeOfParameter(target.parameters[i]) : getRestTypeOfSignature(target); callback(s, t); } } @@ -11023,10 +11023,21 @@ namespace ts { return getNonNullableType(checkExpression(node.expression)); } + function getTypeOfParameter(symbol: Symbol) { + const type = getTypeOfSymbol(symbol); + if (strictNullChecks) { + const declaration = symbol.valueDeclaration; + if (declaration && (declaration).initializer) { + return addNullableKind(type, TypeFlags.Undefined); + } + } + return type; + } + function getTypeAtPosition(signature: Signature, pos: number): Type { return signature.hasRestParameter ? - pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) : - pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType; + pos < signature.parameters.length - 1 ? getTypeOfParameter(signature.parameters[pos]) : getRestTypeOfSignature(signature) : + pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : anyType; } function assignContextualParameterTypes(signature: Signature, context: Signature, mapper: TypeMapper) { From f774ecf4ec1e86b83f251720467d8f4732bf244f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 10 Mar 2016 14:30:42 -0800 Subject: [PATCH 56/59] Remove 'undefined' from type of binding element with non-undefined default value --- src/compiler/checker.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3511305e16800..10e4d50d9302a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2678,6 +2678,11 @@ namespace ts { type = createArrayType(elementType); } } + // In strict null checking mode, if a default value of a non-undefined type is specified, remove + // undefined from the final type. + if (strictNullChecks && declaration.initializer && !(getNullableKind(checkExpressionCached(declaration.initializer)) & TypeFlags.Undefined)) { + type = removeNullableKind(type, TypeFlags.Undefined); + } return type; } From b1bef15a1e05587f7b6a3471003ad2b773453b1f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 14 Mar 2016 15:11:27 -0700 Subject: [PATCH 57/59] Removing 'T?' type notation (use 'T | null | undefined' instead) --- src/compiler/checker.ts | 24 +----------------------- src/compiler/parser.ts | 23 ++++++----------------- src/compiler/types.ts | 9 +-------- 3 files changed, 8 insertions(+), 48 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 10e4d50d9302a..5360cb558f80b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2414,7 +2414,6 @@ namespace ts { case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: case SyntaxKind.ParenthesizedType: - case SyntaxKind.NullableType: return isDeclarationVisible(node.parent); // Default binding, import specifier and namespace import is visible @@ -4779,14 +4778,6 @@ namespace ts { return links.resolvedType; } - function getTypeFromNullableTypeNode(node: NullableTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getNullableType(getTypeFromTypeNode(node.type)); - } - return links.resolvedType; - } - interface TypeSet extends Array { containsAny?: boolean; containsUndefined?: boolean; @@ -5029,8 +5020,6 @@ namespace ts { return getTypeFromUnionTypeNode(node); case SyntaxKind.IntersectionType: return getTypeFromIntersectionTypeNode(node); - case SyntaxKind.NullableType: - return getTypeFromNullableTypeNode(node); case SyntaxKind.ParenthesizedType: case SyntaxKind.JSDocNullableType: case SyntaxKind.JSDocNonNullableType: @@ -6546,16 +6535,6 @@ namespace ts { return getNullableKind(type) === TypeFlags.Nullable; } - function getNullableType(type: Type): Type { - if (!strictNullChecks) { - return type; - } - if (!type.nullableType) { - type.nullableType = isNullableType(type) ? type : getUnionType([type, undefinedType, nullType]); - } - return type.nullableType; - } - function addNullableKind(type: Type, kind: TypeFlags): Type { if ((getNullableKind(type) & kind) !== kind) { const types = [type]; @@ -15792,8 +15771,7 @@ namespace ts { case SyntaxKind.IntersectionType: return checkUnionOrIntersectionType(node); case SyntaxKind.ParenthesizedType: - case SyntaxKind.NullableType: - return checkSourceElement((node).type); + return checkSourceElement((node).type); case SyntaxKind.FunctionDeclaration: return checkFunctionDeclaration(node); case SyntaxKind.Block: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 0ff1dafc78b49..a08c5755a3fc7 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -127,8 +127,7 @@ namespace ts { case SyntaxKind.IntersectionType: return visitNodes(cbNodes, (node).types); case SyntaxKind.ParenthesizedType: - case SyntaxKind.NullableType: - return visitNode(cbNode, (node).type); + return visitNode(cbNode, (node).type); case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: return visitNodes(cbNodes, (node).elements); @@ -2426,21 +2425,11 @@ namespace ts { function parseArrayTypeOrHigher(): TypeNode { let type = parseNonArrayType(); - while (!scanner.hasPrecedingLineBreak()) { - if (parseOptional(SyntaxKind.OpenBracketToken)) { - parseExpected(SyntaxKind.CloseBracketToken); - const node = createNode(SyntaxKind.ArrayType, type.pos); - node.elementType = type; - type = finishNode(node); - } - else if (parseOptional(SyntaxKind.QuestionToken)) { - const node = createNode(SyntaxKind.NullableType, type.pos); - node.type = type; - type = finishNode(node); - } - else { - break; - } + while (!scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) { + parseExpected(SyntaxKind.CloseBracketToken); + const node = createNode(SyntaxKind.ArrayType, type.pos); + node.elementType = type; + type = finishNode(node); } return type; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 12ff267356413..3c7a16da59f66 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -210,7 +210,6 @@ namespace ts { ParenthesizedType, ThisType, StringLiteralType, - NullableType, // Binding patterns ObjectBindingPattern, ArrayBindingPattern, @@ -357,7 +356,7 @@ namespace ts { FirstFutureReservedWord = ImplementsKeyword, LastFutureReservedWord = YieldKeyword, FirstTypeNode = TypePredicate, - LastTypeNode = NullableType, + LastTypeNode = StringLiteralType, FirstPunctuation = OpenBraceToken, LastPunctuation = CaretEqualsToken, FirstToken = Unknown, @@ -785,11 +784,6 @@ namespace ts { _stringLiteralTypeBrand: any; } - // @kind(SyntaxKind.NullableType) - export interface NullableTypeNode extends TypeNode { - type: TypeNode; - } - // @kind(SyntaxKind.StringLiteral) export interface StringLiteral extends LiteralExpression { _stringLiteralBrand: any; @@ -2152,7 +2146,6 @@ namespace ts { /* @internal */ id: number; // Unique ID symbol?: Symbol; // Symbol associated with type (if any) pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any) - nullableType?: Type; // Cached nullable form of this type } /* @internal */ From 09ad9c524334fd9dd9c57dd9f15a33dc1bac77b8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 14 Mar 2016 16:29:12 -0700 Subject: [PATCH 58/59] Remove 'T?' notation from type-to-string conversion --- src/compiler/checker.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5360cb558f80b..4aeb3c50c9295 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1885,10 +1885,6 @@ namespace ts { else if (type.flags & TypeFlags.Tuple) { writeTupleType(type); } - else if (isNullableType(type) && (type).types.length > 2) { - writeType(getNonNullableType(type), TypeFormatFlags.InElementType); - writePunctuation(writer, SyntaxKind.QuestionToken); - } else if (type.flags & TypeFlags.UnionOrIntersection) { writeUnionOrIntersectionType(type, flags); } @@ -6531,10 +6527,6 @@ namespace ts { getUnionType([nullType, undefinedType]) : nullType : undefinedType; } - function isNullableType(type: Type) { - return getNullableKind(type) === TypeFlags.Nullable; - } - function addNullableKind(type: Type, kind: TypeFlags): Type { if ((getNullableKind(type) & kind) !== kind) { const types = [type]; From fb6255a7e484ee7d6c1d826f92610ab4fbee6289 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 21 Mar 2016 16:28:09 -0700 Subject: [PATCH 59/59] Accepting new baselines --- ...ardOfFormTypeOfNotEqualHasNoEffect.symbols | 70 ------------------- .../typeGuardsOnClassProperty.symbols | 22 +++--- 2 files changed, 11 insertions(+), 81 deletions(-) delete mode 100644 tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols b/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols deleted file mode 100644 index 7988b7196ef3d..0000000000000 --- a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols +++ /dev/null @@ -1,70 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts === -class C { private p: string }; ->C : Symbol(C, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 0, 0)) ->p : Symbol(C.p, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 0, 9)) - -var strOrNum: string | number; ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) - -var strOrBool: string | boolean; ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) - -var numOrBool: number | boolean ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) - -var strOrC: string | C; ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) ->C : Symbol(C, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 0, 0)) - -// typeof x != s has not effect on typeguard -if (typeof strOrNum != "string") { ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) - - var r1 = strOrNum; // string | number ->r1 : Symbol(r1, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 9, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 12, 7)) ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) -} -else { - var r1 = strOrNum; // string | number ->r1 : Symbol(r1, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 9, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 12, 7)) ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) -} - -if (typeof strOrBool != "boolean") { ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) - - var r2 = strOrBool; // string | boolean ->r2 : Symbol(r2, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 16, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 19, 7)) ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) -} -else { - var r2 = strOrBool; // string | boolean ->r2 : Symbol(r2, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 16, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 19, 7)) ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) -} - -if (typeof numOrBool != "number") { ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) - - var r3 = numOrBool; // number | boolean ->r3 : Symbol(r3, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 23, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 26, 7)) ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) -} -else { - var r3 = numOrBool; // number | boolean ->r3 : Symbol(r3, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 23, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 26, 7)) ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) -} - -if (typeof strOrC != "Object") { ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) - - var r4 = strOrC; // string | C ->r4 : Symbol(r4, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 30, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 33, 7)) ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) -} -else { - var r4 = strOrC; // string | C ->r4 : Symbol(r4, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 30, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 33, 7)) ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) -} diff --git a/tests/baselines/reference/typeGuardsOnClassProperty.symbols b/tests/baselines/reference/typeGuardsOnClassProperty.symbols index f82df71e46ab1..5017c442b2f38 100644 --- a/tests/baselines/reference/typeGuardsOnClassProperty.symbols +++ b/tests/baselines/reference/typeGuardsOnClassProperty.symbols @@ -8,16 +8,16 @@ class D { >D : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) data: string | string[]; ->data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) getData() { ->getData : Symbol(getData, Decl(typeGuardsOnClassProperty.ts, 6, 28)) +>getData : Symbol(D.getData, Decl(typeGuardsOnClassProperty.ts, 6, 28)) var data = this.data; >data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 8, 11)) ->this.data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this.data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) >this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) ->data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) return typeof data === "string" ? data : data.join(" "); >data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 8, 11)) @@ -28,19 +28,19 @@ class D { } getData1() { ->getData1 : Symbol(getData1, Decl(typeGuardsOnClassProperty.ts, 10, 5)) +>getData1 : Symbol(D.getData1, Decl(typeGuardsOnClassProperty.ts, 10, 5)) return typeof this.data === "string" ? this.data : this.data.join(" "); ->this.data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this.data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) >this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) ->data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) ->this.data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this.data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) >this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) ->data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) >this.data.join : Symbol(Array.join, Decl(lib.d.ts, --, --)) ->this.data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this.data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) >this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) ->data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) >join : Symbol(Array.join, Decl(lib.d.ts, --, --)) } }