@@ -704,6 +704,7 @@ import {
704704 isStringOrNumericLiteralLike,
705705 isSuperCall,
706706 isSuperProperty,
707+ isSyntheticExpression,
707708 isTaggedTemplateExpression,
708709 isTemplateSpan,
709710 isThisContainerOrFunctionBlock,
@@ -27463,6 +27464,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2746327464 return type;
2746427465 }
2746527466 const rightType = getTypeOfExpression(expr.right);
27467+ // if rightType is an object type with a custom `[Symbol.hasInstance]` method, and that method has a type
27468+ // predicate, use the type predicate to perform narrowing. This allows normal `object` types to participate
27469+ // in `instanceof`, as per Step 2 of https://tc39.es/ecma262/#sec-instanceofoperator.
27470+ const customHasInstanceMethodType = getCustomSymbolHasInstanceMethodOfObjectType(rightType);
27471+ if (customHasInstanceMethodType) {
27472+ const syntheticCall = createSyntheticHasInstanceMethodCall(left, expr.right, type, customHasInstanceMethodType);
27473+ const signature = getEffectsSignature(syntheticCall);
27474+ const predicate = signature && getTypePredicateOfSignature(signature);
27475+ if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) {
27476+ return narrowTypeByTypePredicate(type, predicate, syntheticCall, assumeTrue);
27477+ }
27478+ }
2746627479 if (!isTypeDerivedFrom(rightType, globalFunctionType)) {
2746727480 return type;
2746827481 }
@@ -27560,8 +27573,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2756027573 function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type {
2756127574 // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function'
2756227575 if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) {
27563- const predicateArgument = getTypePredicateArgument(predicate, callExpression);
27576+ let predicateArgument = getTypePredicateArgument(predicate, callExpression);
2756427577 if (predicateArgument) {
27578+ // If the predicate argument is synthetic and is the first argument of a synthetic call to
27579+ // `[Symbol.hasInstance]`, replace the synthetic predicate argument with the actual argument from
27580+ // the original `instanceof` expression which is stored as the synthetic argument's `parent`.
27581+ if (isSyntheticExpression(predicateArgument) &&
27582+ predicate.parameterIndex === 0 &&
27583+ isSyntheticHasInstanceMethodCall(callExpression)) {
27584+ Debug.assertNode(predicateArgument.parent, isExpression);
27585+ predicateArgument = predicateArgument.parent;
27586+ }
2756527587 if (isMatchingReference(reference, predicateArgument)) {
2756627588 return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ false);
2756727589 }
@@ -33153,7 +33175,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3315333175 return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount);
3315433176 }
3315533177
33156- function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, headMessage ?: DiagnosticMessage): Signature {
33178+ function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, headMessageCallback ?: () => DiagnosticMessage | undefined ): Signature {
3315733179 const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
3315833180 const isDecorator = node.kind === SyntaxKind.Decorator;
3315933181 const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
@@ -33266,6 +33288,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3326633288 chain = chainDiagnosticMessages(chain, Diagnostics.The_last_overload_gave_the_following_error);
3326733289 chain = chainDiagnosticMessages(chain, Diagnostics.No_overload_matches_this_call);
3326833290 }
33291+ const headMessage = headMessageCallback?.();
3326933292 if (headMessage) {
3327033293 chain = chainDiagnosticMessages(chain, headMessage);
3327133294 }
@@ -33311,6 +33334,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3331133334 let chain = chainDiagnosticMessages(
3331233335 map(diags, createDiagnosticMessageChainFromDiagnostic),
3331333336 Diagnostics.No_overload_matches_this_call);
33337+ const headMessage = headMessageCallback?.();
3331433338 if (headMessage) {
3331533339 chain = chainDiagnosticMessages(chain, headMessage);
3331633340 }
@@ -33330,18 +33354,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3333033354 }
3333133355 }
3333233356 else if (candidateForArgumentArityError) {
33333- diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args, headMessage ));
33357+ diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args, headMessageCallback?.() ));
3333433358 }
3333533359 else if (candidateForTypeArgumentError) {
33336- checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, headMessage );
33360+ checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, headMessageCallback?.() );
3333733361 }
3333833362 else {
3333933363 const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments));
3334033364 if (signaturesWithCorrectTypeArgumentArity.length === 0) {
33341- diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!, headMessage ));
33365+ diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!, headMessageCallback?.() ));
3334233366 }
3334333367 else {
33344- diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args, headMessage ));
33368+ diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args, headMessageCallback?.() ));
3334533369 }
3334633370 }
3334733371 }
@@ -33689,7 +33713,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3368933713 return resolveErrorCall(node);
3369033714 }
3369133715
33692- return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags);
33716+ // If the call expression is a synthetic call to a `[Symbol.hasInstance]` method then we will produce a head
33717+ // message when reporting diagnostics that explains how we got to `right[Symbol.hasInstance](left)` from
33718+ // `left instanceof right`, as it pertains to "Argument" related messages reported for the call.
33719+ return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags, () => isSyntheticHasInstanceMethodCall(node) ?
33720+ Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_assignable_to_the_first_argument_of_the_right_hand_side_s_Symbol_hasInstance_method :
33721+ undefined);
3369333722 }
3369433723
3369533724 function isGenericFunctionReturningFunction(signature: Signature) {
@@ -34072,7 +34101,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3407234101 return resolveErrorCall(node);
3407334102 }
3407434103
34075- return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage);
34104+ return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, () => headMessage);
3407634105 }
3407734106
3407834107 function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature {
@@ -36445,6 +36474,52 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3644536474 return (symbol.flags & SymbolFlags.ConstEnum) !== 0;
3644636475 }
3644736476
36477+ /**
36478+ * Get the type of the `[Symbol.hasInstance]` method of an object type, but only if it is not the
36479+ * `[Symbol.hasInstance]` method inherited from the global `Function` type.
36480+ */
36481+ function getCustomSymbolHasInstanceMethodOfObjectType(type: Type) {
36482+ const hasInstancePropertyName = getPropertyNameForKnownSymbolName("hasInstance");
36483+ const hasInstanceProperty = getPropertyOfObjectType(type, hasInstancePropertyName);
36484+ if (hasInstanceProperty && hasInstanceProperty !== getPropertyOfObjectType(globalFunctionType, hasInstancePropertyName)) {
36485+ const hasInstancePropertyType = getTypeOfSymbol(hasInstanceProperty);
36486+ if (hasInstancePropertyType && getSignaturesOfType(hasInstancePropertyType, SignatureKind.Call).length !== 0) {
36487+ return hasInstancePropertyType;
36488+ }
36489+ }
36490+ }
36491+
36492+ /**
36493+ * Creates a synthetic `CallExpression` that reinterprets `left instanceof right` as `right[Symbol.hasInstance](left)`
36494+ * per the `InstanceofOperator` algorithm in the ECMAScript specification.
36495+ * @param left The left-hand expression of `instanceof`
36496+ * @param right The right-hand expression of `instanceof`
36497+ * @param leftType The type of the left-hand expression of `instanceof`.
36498+ * @param hasInstanceMethodType The type of the `[Symbol.hasInstance]` method of the right-hand expression of `instanceof`.
36499+ */
36500+ function createSyntheticHasInstanceMethodCall(left: Expression, right: Expression, leftType: Type, hasInstanceMethodType: Type) {
36501+ const syntheticExpression = createSyntheticExpression(right, hasInstanceMethodType);
36502+ const syntheticArgument = createSyntheticExpression(left, leftType);
36503+ const syntheticCall = parseNodeFactory.createCallExpression(syntheticExpression, /*typeArguments*/ undefined, [syntheticArgument]);
36504+ setParent(syntheticCall, left.parent);
36505+ setTextRange(syntheticCall, left.parent);
36506+ return syntheticCall;
36507+ }
36508+
36509+ /**
36510+ * Tests whether a `CallExpression` is a synthetic call to a `[Symbol.hasInstance]` method as would be produced by
36511+ * {@link createSyntheticHasInstanceMethodCall}.
36512+ */
36513+ function isSyntheticHasInstanceMethodCall(node: CallExpression) {
36514+ return isSyntheticExpression(node.expression) &&
36515+ node.arguments.length === 1 &&
36516+ isSyntheticExpression(node.arguments[0]) &&
36517+ isBinaryExpression(node.parent) &&
36518+ node.parent.operatorToken.kind === SyntaxKind.InstanceOfKeyword &&
36519+ node.parent.right === node.expression.parent &&
36520+ node.parent.left === node.arguments[0].parent;
36521+ }
36522+
3644836523 function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type {
3644936524 if (leftType === silentNeverType || rightType === silentNeverType) {
3645036525 return silentNeverType;
@@ -36458,9 +36533,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3645836533 allTypesAssignableToKind(leftType, TypeFlags.Primitive)) {
3645936534 error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
3646036535 }
36461- // NOTE: do not raise error if right is unknown as related error was already reported
36462- if (!(isTypeAny(rightType) || typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) {
36463- error(right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_of_type_any_or_of_a_type_assignable_to_the_Function_interface_type);
36536+ if (!isTypeAny(rightType)) {
36537+ // if rightType is an object type with a custom `[Symbol.hasInstance]` method, then it is potentially
36538+ // valid on the right-hand side of the `instanceof` operator. This allows normal `object` types to
36539+ // participate in `instanceof`, as per Step 2 of https://tc39.es/ecma262/#sec-instanceofoperator.
36540+ const customHasInstanceMethodType = getCustomSymbolHasInstanceMethodOfObjectType(rightType);
36541+ if (customHasInstanceMethodType) {
36542+ // If rightType has a `[Symbol.hasInstance]` method that is not the default [Symbol.hasInstance]() method on `Function`, check
36543+ // that left is assignable to the first parameter.
36544+ const syntheticCall = createSyntheticHasInstanceMethodCall(left, right, leftType, customHasInstanceMethodType);
36545+ const returnType = getReturnTypeOfSignature(getResolvedSignature(syntheticCall));
36546+
36547+ // Also verify that the return type of the `[Symbol.hasInstance]` method is assignable to `boolean`. The spec
36548+ // will perform `ToBoolean` on the result, but this is more type-safe.
36549+ checkTypeAssignableTo(returnType, booleanType, right, Diagnostics.An_object_s_Symbol_hasInstance_method_must_return_a_boolean_value_for_it_to_be_used_on_the_right_hand_side_of_an_instanceof_expression);
36550+ }
36551+ // NOTE: do not raise error if right is unknown as related error was already reported
36552+ if (!(customHasInstanceMethodType || typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) {
36553+ // Do not indicate that `[Symbol.hasInstance]` is a valid option if it's not known to be present on `SymbolConstructor`.
36554+ const globalESSymbolConstructorSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false);
36555+ const hasInstanceProp = globalESSymbolConstructorSymbol && getMembersOfSymbol(globalESSymbolConstructorSymbol).get(getPropertyNameForKnownSymbolName("hasInstance"));
36556+ const message = hasInstanceProp ?
36557+ Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_either_of_type_any_of_an_object_type_with_a_Symbol_hasInstance_method_or_of_a_type_assignable_to_the_Function_interface_type :
36558+ Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_of_type_any_or_of_a_type_assignable_to_the_Function_interface_type;
36559+ error(right, message);
36560+ }
3646436561 }
3646536562 return booleanType;
3646636563 }
0 commit comments