Skip to content

Commit

Permalink
Avoid resolving objects in getTypeFacts when caller doesn't need that…
Browse files Browse the repository at this point in the history
… info (microsoft#55459)
  • Loading branch information
jakebailey authored and snovader committed Sep 23, 2023
1 parent f3abfc4 commit 69a14cc
Showing 1 changed file with 47 additions and 29 deletions.
76 changes: 47 additions & 29 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) ?
Expand All @@ -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);
Expand All @@ -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;
}
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 69a14cc

Please sign in to comment.