Narrowing from truthy unknown to object (#37507)

* For x && typeof x === 'object', narrow x to just type object

* Add tests

* Allow arbitrary nesting / add comments

* Add additional tests
This commit is contained in:
Anders Hejlsberg
2020-03-20 17:09:24 -07:00
committed by GitHub
parent e3ec7b18b8
commit fde9c7f555
6 changed files with 379 additions and 3 deletions

View File

@@ -19013,6 +19013,13 @@ namespace ts {
return false;
}
// Given a source x, check if target matches x or is an && operation with an operand that matches x.
function containsTruthyCheck(source: Node, target: Node): boolean {
return isMatchingReference(source, target) ||
(target.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>target).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken &&
(containsTruthyCheck(source, (<BinaryExpression>target).left) || containsTruthyCheck(source, (<BinaryExpression>target).right)));
}
function getAccessedPropertyName(access: AccessExpression): __String | undefined {
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
@@ -20409,15 +20416,23 @@ namespace ts {
if (type.flags & TypeFlags.Any && literal.text === "function") {
return type;
}
if (assumeTrue && type.flags & TypeFlags.Unknown && literal.text === "object") {
// The pattern x && typeof x === 'object', where x is of type unknown, narrows x to type object. We don't
// need to check for the reverse typeof x === 'object' && x since that already narrows correctly.
if (typeOfExpr.parent.parent.kind === SyntaxKind.BinaryExpression) {
const expr = <BinaryExpression>typeOfExpr.parent.parent;
if (expr.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && expr.right === typeOfExpr.parent && containsTruthyCheck(reference, expr.left)) {
return nonPrimitiveType;
}
}
return getUnionType([nonPrimitiveType, nullType]);
}
const facts = assumeTrue ?
typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject :
typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject;
return getTypeWithFacts(assumeTrue ? mapType(type, narrowTypeForTypeof) : type, facts);
function narrowTypeForTypeof(type: Type) {
if (type.flags & TypeFlags.Unknown && literal.text === "object") {
return getUnionType([nonPrimitiveType, nullType]);
}
// We narrow a non-union type to an exact primitive type if the non-union type
// is a supertype of that primitive type. For example, type 'any' can be narrowed
// to one of the primitive types.