From dad05642d71408a864e80e0aad34fc43091e38ac Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 10 Mar 2016 11:13:30 -0800 Subject: [PATCH] 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 7eceab0956f..f2a41ea5de8 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; + 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 declaration = leftmostSymbol.valueDeclaration; - if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) { - return type; - } - 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)) {