From 91c0d7ff9b3e43de673ee9fe539754146387832a Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:12:28 -0700 Subject: [PATCH] Avoid resolving objects in getTypeFacts when caller doesn't need that info (#55459) --- src/compiler/checker.ts | 76 +++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3eb6ce3681e..392883ae41f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10651,7 +10651,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { parentType = getNonNullableType(parentType); } // Filter `undefined` from the type we check against if the parent has an initializer and that initializer is not possibly `undefined` - else if (strictNullChecks && pattern.parent.initializer && !(getTypeFacts(getTypeOfInitializer(pattern.parent.initializer)) & TypeFacts.EQUndefined)) { + else if (strictNullChecks && pattern.parent.initializer && !(hasTypeFacts(getTypeOfInitializer(pattern.parent.initializer), TypeFacts.EQUndefined))) { parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); } @@ -10710,7 +10710,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) { // In strict null checking mode, if a default value of a non-undefined type is specified, remove // undefined from the final type. - return strictNullChecks && !(getTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal)) & TypeFacts.IsUndefined) ? getNonUndefinedType(type) : type; + return strictNullChecks && !(hasTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal), TypeFacts.IsUndefined)) ? getNonUndefinedType(type) : type; } return widenTypeInferredFromInitializer(declaration, getUnionType([getNonUndefinedType(type), checkDeclarationInitializer(declaration, CheckMode.Normal)], UnionReduction.Subtype)); } @@ -20332,7 +20332,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType)); const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && - (getTypeFacts(sourceType) & TypeFacts.IsUndefinedOrNull) === (getTypeFacts(targetType) & TypeFacts.IsUndefinedOrNull); + getTypeFacts(sourceType, TypeFacts.IsUndefinedOrNull) === getTypeFacts(targetType, TypeFacts.IsUndefinedOrNull); let related = callbacks ? compareSignaturesRelated(targetSig, sourceSig, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); @@ -23894,7 +23894,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function removeDefinitelyFalsyTypes(type: Type): Type { - return filterType(type, t => !!(getTypeFacts(t) & TypeFacts.Truthy)); + return filterType(type, t => hasTypeFacts(t, TypeFacts.Truthy)); } function extractDefinitelyFalsyTypes(type: Type): Type { @@ -26141,7 +26141,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType)); } - function getTypeFacts(type: Type): TypeFacts { + function getTypeFacts(type: Type, mask: TypeFacts): TypeFacts { + return getTypeFactsWorker(type, mask) & mask; + } + + function hasTypeFacts(type: Type, mask: TypeFacts): boolean { + return getTypeFacts(type, mask) !== 0; + } + + function getTypeFactsWorker(type: Type, callerOnlyNeeds: TypeFacts): TypeFacts { if (type.flags & (TypeFlags.Intersection | TypeFlags.Instantiable)) { type = getBaseConstraintOfType(type) || unknownType; } @@ -26182,6 +26190,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { (type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; } if (flags & TypeFlags.Object) { + const possibleFacts = strictNullChecks + ? TypeFacts.EmptyObjectStrictFacts | TypeFacts.FunctionStrictFacts | TypeFacts.ObjectStrictFacts + : TypeFacts.EmptyObjectFacts | TypeFacts.FunctionFacts | TypeFacts.ObjectFacts; + + if ((callerOnlyNeeds & possibleFacts) === 0) { + // If the caller doesn't care about any of the facts that we could possibly produce, + // return zero so we can skip resolving members. + return 0; + } + return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ? strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : isFunctionObjectType(type as ObjectType) ? @@ -26207,15 +26225,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return TypeFacts.None; } if (flags & TypeFlags.Union) { - return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFacts(t), TypeFacts.None); + return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFactsWorker(t, callerOnlyNeeds), TypeFacts.None); } if (flags & TypeFlags.Intersection) { - return getIntersectionTypeFacts(type as IntersectionType); + return getIntersectionTypeFacts(type as IntersectionType, callerOnlyNeeds); } return TypeFacts.UnknownFacts; } - function getIntersectionTypeFacts(type: IntersectionType): TypeFacts { + function getIntersectionTypeFacts(type: IntersectionType, callerOnlyNeeds: TypeFacts): TypeFacts { // When an intersection contains a primitive type we ignore object type constituents as they are // presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type. const ignoreObjects = maybeTypeOfKind(type, TypeFlags.Primitive); @@ -26225,7 +26243,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let andedFacts = TypeFacts.All; for (const t of type.types) { if (!(ignoreObjects && t.flags & TypeFlags.Object)) { - const f = getTypeFacts(t); + const f = getTypeFactsWorker(t, callerOnlyNeeds); oredFacts |= f; andedFacts &= f; } @@ -26234,7 +26252,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getTypeWithFacts(type: Type, include: TypeFacts) { - return filterType(type, t => (getTypeFacts(t) & include) !== 0); + return filterType(type, t => hasTypeFacts(t, include)); } // This function is similar to getTypeWithFacts, except that in strictNullChecks mode it replaces type @@ -26245,12 +26263,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (strictNullChecks) { switch (facts) { case TypeFacts.NEUndefined: - return mapType(reduced, t => getTypeFacts(t) & TypeFacts.EQUndefined ? getIntersectionType([t, getTypeFacts(t) & TypeFacts.EQNull && !maybeTypeOfKind(reduced, TypeFlags.Null) ? getUnionType([emptyObjectType, nullType]) : emptyObjectType]) : t); + return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQUndefined) ? getIntersectionType([t, hasTypeFacts(t, TypeFacts.EQNull) && !maybeTypeOfKind(reduced, TypeFlags.Null) ? getUnionType([emptyObjectType, nullType]) : emptyObjectType]) : t); case TypeFacts.NENull: - return mapType(reduced, t => getTypeFacts(t) & TypeFacts.EQNull ? getIntersectionType([t, getTypeFacts(t) & TypeFacts.EQUndefined && !maybeTypeOfKind(reduced, TypeFlags.Undefined) ? getUnionType([emptyObjectType, undefinedType]) : emptyObjectType]) : t); + return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQNull) ? getIntersectionType([t, hasTypeFacts(t, TypeFacts.EQUndefined) && !maybeTypeOfKind(reduced, TypeFlags.Undefined) ? getUnionType([emptyObjectType, undefinedType]) : emptyObjectType]) : t); case TypeFacts.NEUndefinedOrNull: case TypeFacts.Truthy: - return mapType(reduced, t => getTypeFacts(t) & TypeFacts.EQUndefinedOrNull ? getGlobalNonNullableTypeInstantiation(t) : t); + return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQUndefinedOrNull) ? getGlobalNonNullableTypeInstantiation(t) : t); } } return reduced; @@ -27818,14 +27836,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // the constituent based on its type facts. We use the strict subtype relation because it treats `object` // as a subtype of `{}`, and we need the type facts check because function types are subtypes of `object`, // but are classified as "function" according to `typeof`. - isTypeRelatedTo(t, impliedType, strictSubtypeRelation) ? getTypeFacts(t) & facts ? t : neverType : + isTypeRelatedTo(t, impliedType, strictSubtypeRelation) ? hasTypeFacts(t, facts) ? t : neverType : // We next check if the consituent is a supertype of the implied type. If so, we substitute the implied // type. This handles top types like `unknown` and `{}`, and supertypes like `{ toString(): string }`. isTypeSubtypeOf(impliedType, t) ? impliedType : // Neither the constituent nor the implied type is a subtype of the other, however their domains may still // overlap. For example, an unconstrained type parameter and type `string`. If the type facts indicate // possible overlap, we form an intersection. Otherwise, we eliminate the constituent. - getTypeFacts(t) & facts ? getIntersectionType([t, impliedType]) : + hasTypeFacts(t, facts) ? getIntersectionType([t, impliedType]) : neverType); } @@ -27840,7 +27858,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (hasDefaultClause) { // In the default clause we filter constituents down to those that are not-equal to all handled cases. const notEqualFacts = getNotEqualFactsFromTypeofSwitch(clauseStart, clauseEnd, witnesses); - return filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts); + return filterType(type, t => getTypeFacts(t, notEqualFacts) === notEqualFacts); } // In the non-default cause we create a union of the type narrowed by each of the listed cases. const clauseWitnesses = witnesses.slice(clauseStart, clauseEnd); @@ -28023,7 +28041,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } if ( strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) && - !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined) + !(hasTypeFacts(predicate.type, TypeFacts.EQUndefined)) ) { type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } @@ -28184,7 +28202,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return true; } - const containsUndefined = !!(getTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal)) & TypeFacts.IsUndefined); + const containsUndefined = !!(hasTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal), TypeFacts.IsUndefined)); if (!popTypeResolution()) { reportCircularityError(declaration.symbol); @@ -28202,7 +28220,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const removeUndefined = strictNullChecks && declaration.kind === SyntaxKind.Parameter && declaration.initializer && - getTypeFacts(declaredType) & TypeFacts.IsUndefined && + hasTypeFacts(declaredType, TypeFacts.IsUndefined) && !parameterInitializerContainsUndefined(declaration); return removeUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType; @@ -31781,7 +31799,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function isNullableType(type: Type) { - return !!(getTypeFacts(type) & TypeFacts.IsUndefinedOrNull); + return hasTypeFacts(type, TypeFacts.IsUndefinedOrNull); } function getNonNullableTypeIfNeeded(type: Type) { @@ -31845,7 +31863,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { error(node, Diagnostics.Object_is_of_type_unknown); return errorType; } - const facts = getTypeFacts(type); + const facts = getTypeFacts(type, TypeFacts.IsUndefinedOrNull); if (facts & TypeFacts.IsUndefinedOrNull) { reportError(node, facts); const t = getNonNullableType(type); @@ -36307,7 +36325,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return (TypeFacts.AllTypeofNE & notEqualFacts) === TypeFacts.AllTypeofNE; } // A missing not-equal flag indicates that the type wasn't handled by some case. - return !someType(operandConstraint, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts); + return !someType(operandConstraint, t => getTypeFacts(t, notEqualFacts) === notEqualFacts); } const type = checkExpressionCached(node.expression); if (!isLiteralType(type)) { @@ -36725,7 +36743,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if ( strictNullChecks && !(type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Never)) && - !(exactOptionalPropertyTypes ? symbol.flags & SymbolFlags.Optional : getTypeFacts(type) & TypeFacts.IsUndefined) + !(exactOptionalPropertyTypes ? symbol.flags & SymbolFlags.Optional : hasTypeFacts(type, TypeFacts.IsUndefined)) ) { error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_optional); } @@ -36871,7 +36889,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getUnaryResultType(operandType); case SyntaxKind.ExclamationToken: checkTruthinessOfType(operandType, node.operand); - const facts = getTypeFacts(operandType) & (TypeFacts.Truthy | TypeFacts.Falsy); + const facts = getTypeFacts(operandType, TypeFacts.Truthy | TypeFacts.Falsy); return facts === TypeFacts.Truthy ? falseType : facts === TypeFacts.Falsy ? trueType : booleanType; @@ -37162,7 +37180,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // undefined from the final type. if ( strictNullChecks && - !(getTypeFacts(checkExpression(prop.objectAssignmentInitializer)) & TypeFacts.IsUndefined) + !(hasTypeFacts(checkExpression(prop.objectAssignmentInitializer), TypeFacts.IsUndefined)) ) { sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); } @@ -37638,7 +37656,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return checkInExpression(left, right, leftType, rightType); case SyntaxKind.AmpersandAmpersandToken: case SyntaxKind.AmpersandAmpersandEqualsToken: { - const resultType = getTypeFacts(leftType) & TypeFacts.Truthy ? + const resultType = hasTypeFacts(leftType, TypeFacts.Truthy) ? getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) : leftType; if (operator === SyntaxKind.AmpersandAmpersandEqualsToken) { @@ -37648,7 +37666,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } case SyntaxKind.BarBarToken: case SyntaxKind.BarBarEqualsToken: { - const resultType = getTypeFacts(leftType) & TypeFacts.Falsy ? + const resultType = hasTypeFacts(leftType, TypeFacts.Falsy) ? getUnionType([getNonNullableType(removeDefinitelyFalsyTypes(leftType)), rightType], UnionReduction.Subtype) : leftType; if (operator === SyntaxKind.BarBarEqualsToken) { @@ -37658,7 +37676,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } case SyntaxKind.QuestionQuestionToken: case SyntaxKind.QuestionQuestionEqualsToken: { - const resultType = getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ? + const resultType = hasTypeFacts(leftType, TypeFacts.EQUndefinedOrNull) ? getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) : leftType; if (operator === SyntaxKind.QuestionQuestionEqualsToken) { @@ -41917,7 +41935,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const type = location === condExpr ? condType : checkTruthinessExpression(location); const isPropertyExpressionCast = isPropertyAccessExpression(location) && isTypeAssertion(location.expression); - if (!(getTypeFacts(type) & TypeFacts.Truthy) || isPropertyExpressionCast) return; + if (!hasTypeFacts(type, TypeFacts.Truthy) || isPropertyExpressionCast) return; // While it technically should be invalid for any known-truthy value // to be tested, we de-scope to functions and Promises unreferenced in