@@ -704,6 +704,7 @@ import {
704
704
isStringOrNumericLiteralLike,
705
705
isSuperCall,
706
706
isSuperProperty,
707
+ isSyntheticExpression,
707
708
isTaggedTemplateExpression,
708
709
isTemplateSpan,
709
710
isThisContainerOrFunctionBlock,
@@ -27463,6 +27464,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
27463
27464
return type;
27464
27465
}
27465
27466
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
+ }
27466
27479
if (!isTypeDerivedFrom(rightType, globalFunctionType)) {
27467
27480
return type;
27468
27481
}
@@ -27560,8 +27573,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
27560
27573
function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type {
27561
27574
// Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function'
27562
27575
if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) {
27563
- const predicateArgument = getTypePredicateArgument(predicate, callExpression);
27576
+ let predicateArgument = getTypePredicateArgument(predicate, callExpression);
27564
27577
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
+ }
27565
27587
if (isMatchingReference(reference, predicateArgument)) {
27566
27588
return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ false);
27567
27589
}
@@ -33153,7 +33175,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
33153
33175
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount);
33154
33176
}
33155
33177
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 {
33157
33179
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
33158
33180
const isDecorator = node.kind === SyntaxKind.Decorator;
33159
33181
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
@@ -33266,6 +33288,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
33266
33288
chain = chainDiagnosticMessages(chain, Diagnostics.The_last_overload_gave_the_following_error);
33267
33289
chain = chainDiagnosticMessages(chain, Diagnostics.No_overload_matches_this_call);
33268
33290
}
33291
+ const headMessage = headMessageCallback?.();
33269
33292
if (headMessage) {
33270
33293
chain = chainDiagnosticMessages(chain, headMessage);
33271
33294
}
@@ -33311,6 +33334,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
33311
33334
let chain = chainDiagnosticMessages(
33312
33335
map(diags, createDiagnosticMessageChainFromDiagnostic),
33313
33336
Diagnostics.No_overload_matches_this_call);
33337
+ const headMessage = headMessageCallback?.();
33314
33338
if (headMessage) {
33315
33339
chain = chainDiagnosticMessages(chain, headMessage);
33316
33340
}
@@ -33330,18 +33354,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
33330
33354
}
33331
33355
}
33332
33356
else if (candidateForArgumentArityError) {
33333
- diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args, headMessage ));
33357
+ diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args, headMessageCallback?.() ));
33334
33358
}
33335
33359
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?.() );
33337
33361
}
33338
33362
else {
33339
33363
const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments));
33340
33364
if (signaturesWithCorrectTypeArgumentArity.length === 0) {
33341
- diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!, headMessage ));
33365
+ diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!, headMessageCallback?.() ));
33342
33366
}
33343
33367
else {
33344
- diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args, headMessage ));
33368
+ diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args, headMessageCallback?.() ));
33345
33369
}
33346
33370
}
33347
33371
}
@@ -33689,7 +33713,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
33689
33713
return resolveErrorCall(node);
33690
33714
}
33691
33715
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);
33693
33722
}
33694
33723
33695
33724
function isGenericFunctionReturningFunction(signature: Signature) {
@@ -34072,7 +34101,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
34072
34101
return resolveErrorCall(node);
34073
34102
}
34074
34103
34075
- return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage);
34104
+ return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, () => headMessage);
34076
34105
}
34077
34106
34078
34107
function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature {
@@ -36445,6 +36474,52 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
36445
36474
return (symbol.flags & SymbolFlags.ConstEnum) !== 0;
36446
36475
}
36447
36476
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
+
36448
36523
function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type {
36449
36524
if (leftType === silentNeverType || rightType === silentNeverType) {
36450
36525
return silentNeverType;
@@ -36458,9 +36533,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
36458
36533
allTypesAssignableToKind(leftType, TypeFlags.Primitive)) {
36459
36534
error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter);
36460
36535
}
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
+ }
36464
36561
}
36465
36562
return booleanType;
36466
36563
}
0 commit comments