From 54a83b9901d25cfd895a46b6c6d5e2cb2b54431f Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Tue, 16 Jul 2024 15:01:26 -0700 Subject: [PATCH 1/3] Allow `this` when it appears in `this is T` positions Fixes #59252 --- src/compiler/checker.ts | 4 +- ...declarationEmitThisPredicates02.errors.txt | 18 --- .../declarationEmitThisPredicates02.types | 1 - ...ThisPredicatesWithPrivateName02.errors.txt | 18 --- ...nEmitThisPredicatesWithPrivateName02.types | 1 - ...edTypeWithAsClauseAndLateBoundProperty2.js | 110 ------------------ .../reference/thisPredicateInObjectLiteral.js | 18 +++ .../thisPredicateInObjectLiteral.symbols | 14 +++ .../thisPredicateInObjectLiteral.types | 22 ++++ ...peGuardFunctionOfFormThisErrors.errors.txt | 5 +- .../compiler/thisPredicateInObjectLiteral.ts | 8 ++ 11 files changed, 66 insertions(+), 153 deletions(-) delete mode 100644 tests/baselines/reference/declarationEmitThisPredicates02.errors.txt delete mode 100644 tests/baselines/reference/declarationEmitThisPredicatesWithPrivateName02.errors.txt create mode 100644 tests/baselines/reference/thisPredicateInObjectLiteral.js create mode 100644 tests/baselines/reference/thisPredicateInObjectLiteral.symbols create mode 100644 tests/baselines/reference/thisPredicateInObjectLiteral.types create mode 100644 tests/cases/compiler/thisPredicateInObjectLiteral.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 86702764c9585..5df9f0388f98d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19673,7 +19673,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { return getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(container)).thisType!; } - error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); + if (node.parent.kind !== SyntaxKind.TypePredicate) { + error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); + } return errorType; } diff --git a/tests/baselines/reference/declarationEmitThisPredicates02.errors.txt b/tests/baselines/reference/declarationEmitThisPredicates02.errors.txt deleted file mode 100644 index 396e12c0b6ffe..0000000000000 --- a/tests/baselines/reference/declarationEmitThisPredicates02.errors.txt +++ /dev/null @@ -1,18 +0,0 @@ -declarationEmitThisPredicates02.ts(8,10): error TS2526: A 'this' type is available only in a non-static member of a class or interface. - - -==== declarationEmitThisPredicates02.ts (1 errors) ==== - export interface Foo { - a: string; - b: number; - c: boolean; - } - - export const obj = { - m(): this is Foo { - ~~~~ -!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface. - let dis = this as {} as Foo; - return dis.a != null && dis.b != null && dis.c != null; - } - } \ No newline at end of file diff --git a/tests/baselines/reference/declarationEmitThisPredicates02.types b/tests/baselines/reference/declarationEmitThisPredicates02.types index 790e599a60486..4baa55abd85f4 100644 --- a/tests/baselines/reference/declarationEmitThisPredicates02.types +++ b/tests/baselines/reference/declarationEmitThisPredicates02.types @@ -33,7 +33,6 @@ export const obj = { >this as {} : {} > : ^^ >this : any -> : ^^^ return dis.a != null && dis.b != null && dis.c != null; >dis.a != null && dis.b != null && dis.c != null : boolean diff --git a/tests/baselines/reference/declarationEmitThisPredicatesWithPrivateName02.errors.txt b/tests/baselines/reference/declarationEmitThisPredicatesWithPrivateName02.errors.txt deleted file mode 100644 index b720ce2d66824..0000000000000 --- a/tests/baselines/reference/declarationEmitThisPredicatesWithPrivateName02.errors.txt +++ /dev/null @@ -1,18 +0,0 @@ -declarationEmitThisPredicatesWithPrivateName02.ts(8,10): error TS2526: A 'this' type is available only in a non-static member of a class or interface. - - -==== declarationEmitThisPredicatesWithPrivateName02.ts (1 errors) ==== - interface Foo { - a: string; - b: number; - c: boolean; - } - - export const obj = { - m(): this is Foo { - ~~~~ -!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface. - let dis = this as {} as Foo; - return dis.a != null && dis.b != null && dis.c != null; - } - } \ No newline at end of file diff --git a/tests/baselines/reference/declarationEmitThisPredicatesWithPrivateName02.types b/tests/baselines/reference/declarationEmitThisPredicatesWithPrivateName02.types index 6927ce897786a..906206df1141c 100644 --- a/tests/baselines/reference/declarationEmitThisPredicatesWithPrivateName02.types +++ b/tests/baselines/reference/declarationEmitThisPredicatesWithPrivateName02.types @@ -33,7 +33,6 @@ export const obj = { >this as {} : {} > : ^^ >this : any -> : ^^^ return dis.a != null && dis.b != null && dis.c != null; >dis.a != null && dis.b != null && dis.c != null : boolean diff --git a/tests/baselines/reference/mappedTypeWithAsClauseAndLateBoundProperty2.js b/tests/baselines/reference/mappedTypeWithAsClauseAndLateBoundProperty2.js index b143900a08085..28910e4b3adb8 100644 --- a/tests/baselines/reference/mappedTypeWithAsClauseAndLateBoundProperty2.js +++ b/tests/baselines/reference/mappedTypeWithAsClauseAndLateBoundProperty2.js @@ -107,113 +107,3 @@ export declare const thing: { readonly [Symbol.unscopables]?: boolean; }; }; - - -//// [DtsFileErrors] - - -mappedTypeWithAsClauseAndLateBoundProperty2.d.ts(27,118): error TS2526: A 'this' type is available only in a non-static member of a class or interface. - - -==== mappedTypeWithAsClauseAndLateBoundProperty2.d.ts (1 errors) ==== - export declare const thing: { - [x: number]: number; - toString: () => string; - toLocaleString: { - (): string; - (locales: string | string[], options?: Intl.NumberFormatOptions & Intl.DateTimeFormatOptions): string; - }; - pop: () => number; - push: (...items: number[]) => number; - concat: { - (...items: ConcatArray[]): number[]; - (...items: (number | ConcatArray)[]): number[]; - }; - join: (separator?: string) => string; - reverse: () => number[]; - shift: () => number; - slice: (start?: number, end?: number) => number[]; - sort: (compareFn?: (a: number, b: number) => number) => number[]; - splice: { - (start: number, deleteCount?: number): number[]; - (start: number, deleteCount: number, ...items: number[]): number[]; - }; - unshift: (...items: number[]) => number; - indexOf: (searchElement: number, fromIndex?: number) => number; - lastIndexOf: (searchElement: number, fromIndex?: number) => number; - every: { - (predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): this is S[]; - ~~~~ -!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface. - (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): boolean; - }; - some: (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any) => boolean; - forEach: (callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void; - map: (callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any) => U[]; - filter: { - (predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): S[]; - (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): number[]; - }; - reduce: { - (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; - (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; - (callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; - }; - reduceRight: { - (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; - (callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; - (callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; - }; - find: { - (predicate: (value: number, index: number, obj: number[]) => value is S, thisArg?: any): S; - (predicate: (value: number, index: number, obj: number[]) => unknown, thisArg?: any): number; - }; - findIndex: (predicate: (value: number, index: number, obj: number[]) => unknown, thisArg?: any) => number; - fill: (value: number, start?: number, end?: number) => number[]; - copyWithin: (target: number, start: number, end?: number) => number[]; - entries: () => IterableIterator<[number, number]>; - keys: () => IterableIterator; - values: () => IterableIterator; - includes: (searchElement: number, fromIndex?: number) => boolean; - flatMap: (callback: (this: This, value: number, index: number, array: number[]) => U | readonly U[], thisArg?: This) => U[]; - flat: (this: A, depth?: D) => FlatArray[]; - [Symbol.iterator]: () => IterableIterator; - readonly [Symbol.unscopables]: { - [x: number]: boolean; - length?: boolean; - toString?: boolean; - toLocaleString?: boolean; - pop?: boolean; - push?: boolean; - concat?: boolean; - join?: boolean; - reverse?: boolean; - shift?: boolean; - slice?: boolean; - sort?: boolean; - splice?: boolean; - unshift?: boolean; - indexOf?: boolean; - lastIndexOf?: boolean; - every?: boolean; - some?: boolean; - forEach?: boolean; - map?: boolean; - filter?: boolean; - reduce?: boolean; - reduceRight?: boolean; - find?: boolean; - findIndex?: boolean; - fill?: boolean; - copyWithin?: boolean; - entries?: boolean; - keys?: boolean; - values?: boolean; - includes?: boolean; - flatMap?: boolean; - flat?: boolean; - [Symbol.iterator]?: boolean; - readonly [Symbol.unscopables]?: boolean; - }; - }; - \ No newline at end of file diff --git a/tests/baselines/reference/thisPredicateInObjectLiteral.js b/tests/baselines/reference/thisPredicateInObjectLiteral.js new file mode 100644 index 0000000000000..84cccc313717c --- /dev/null +++ b/tests/baselines/reference/thisPredicateInObjectLiteral.js @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/thisPredicateInObjectLiteral.ts] //// + +//// [thisPredicateInObjectLiteral.ts] +// Should be OK +const foo2 = { + isNumber(): this is { b: string } { + return true; + }, +}; + +//// [thisPredicateInObjectLiteral.js] +"use strict"; +// Should be OK +var foo2 = { + isNumber: function () { + return true; + }, +}; diff --git a/tests/baselines/reference/thisPredicateInObjectLiteral.symbols b/tests/baselines/reference/thisPredicateInObjectLiteral.symbols new file mode 100644 index 0000000000000..e59f573530251 --- /dev/null +++ b/tests/baselines/reference/thisPredicateInObjectLiteral.symbols @@ -0,0 +1,14 @@ +//// [tests/cases/compiler/thisPredicateInObjectLiteral.ts] //// + +=== thisPredicateInObjectLiteral.ts === +// Should be OK +const foo2 = { +>foo2 : Symbol(foo2, Decl(thisPredicateInObjectLiteral.ts, 1, 5)) + + isNumber(): this is { b: string } { +>isNumber : Symbol(isNumber, Decl(thisPredicateInObjectLiteral.ts, 1, 14)) +>b : Symbol(b, Decl(thisPredicateInObjectLiteral.ts, 2, 25)) + + return true; + }, +}; diff --git a/tests/baselines/reference/thisPredicateInObjectLiteral.types b/tests/baselines/reference/thisPredicateInObjectLiteral.types new file mode 100644 index 0000000000000..7e5d02c2abde0 --- /dev/null +++ b/tests/baselines/reference/thisPredicateInObjectLiteral.types @@ -0,0 +1,22 @@ +//// [tests/cases/compiler/thisPredicateInObjectLiteral.ts] //// + +=== thisPredicateInObjectLiteral.ts === +// Should be OK +const foo2 = { +>foo2 : { isNumber(): this is { b: string; }; } +> : ^^^^^^^^^^^^^^ ^^^ +>{ isNumber(): this is { b: string } { return true; },} : { isNumber(): this is { b: string; }; } +> : ^^^^^^^^^^^^^^ ^^^ + + isNumber(): this is { b: string } { +>isNumber : () => this is { b: string; } +> : ^^^^^^ +>b : string +> : ^^^^^^ + + return true; +>true : true +> : ^^^^ + + }, +}; diff --git a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.errors.txt b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.errors.txt index bb8daf5500d05..8c8da810d4748 100644 --- a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.errors.txt +++ b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.errors.txt @@ -10,12 +10,11 @@ typeGuardFunctionOfFormThisErrors.ts(26,1): error TS2322: Type '() => this is Le typeGuardFunctionOfFormThisErrors.ts(27,1): error TS2322: Type '() => this is FollowerGuard' is not assignable to type '() => this is LeadGuard'. Type predicate 'this is FollowerGuard' is not assignable to 'this is LeadGuard'. Property 'lead' is missing in type 'FollowerGuard' but required in type 'LeadGuard'. -typeGuardFunctionOfFormThisErrors.ts(29,32): error TS2526: A 'this' type is available only in a non-static member of a class or interface. typeGuardFunctionOfFormThisErrors.ts(55,7): error TS2339: Property 'follow' does not exist on type 'RoyalGuard'. typeGuardFunctionOfFormThisErrors.ts(58,7): error TS2339: Property 'lead' does not exist on type 'RoyalGuard'. -==== typeGuardFunctionOfFormThisErrors.ts (7 errors) ==== +==== typeGuardFunctionOfFormThisErrors.ts (6 errors) ==== class RoyalGuard { isLeader(): this is LeadGuard { return this instanceof LeadGuard; @@ -65,8 +64,6 @@ typeGuardFunctionOfFormThisErrors.ts(58,7): error TS2339: Property 'lead' does n !!! related TS2728 typeGuardFunctionOfFormThisErrors.ts:11:5: 'lead' is declared here. function invalidGuard(c: any): this is number { - ~~~~ -!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface. return false; } diff --git a/tests/cases/compiler/thisPredicateInObjectLiteral.ts b/tests/cases/compiler/thisPredicateInObjectLiteral.ts new file mode 100644 index 0000000000000..594ec873d47ef --- /dev/null +++ b/tests/cases/compiler/thisPredicateInObjectLiteral.ts @@ -0,0 +1,8 @@ +// @strict: true + +// Should be OK +const foo2 = { + isNumber(): this is { b: string } { + return true; + }, +}; \ No newline at end of file From 08a65b1d0587bd2a0d4e20b33081ea76371c0e4e Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 17 Jul 2024 10:32:58 -0700 Subject: [PATCH 2/3] Only applies to left operand --- src/compiler/checker.ts | 2 +- .../thisPredicateInObjectLiteral.errors.txt | 20 ++++++++++++++++++ .../reference/thisPredicateInObjectLiteral.js | 16 +++++++++++++- .../thisPredicateInObjectLiteral.symbols | 14 +++++++++++++ .../thisPredicateInObjectLiteral.types | 21 +++++++++++++++++++ .../compiler/thisPredicateInObjectLiteral.ts | 9 +++++++- 6 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/thisPredicateInObjectLiteral.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5df9f0388f98d..2997c69b65a79 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19673,7 +19673,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { return getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(container)).thisType!; } - if (node.parent.kind !== SyntaxKind.TypePredicate) { + if (!(node.parent.kind === SyntaxKind.TypePredicate && node === (node.parent as TypePredicateNode).parameterName)) { error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); } return errorType; diff --git a/tests/baselines/reference/thisPredicateInObjectLiteral.errors.txt b/tests/baselines/reference/thisPredicateInObjectLiteral.errors.txt new file mode 100644 index 0000000000000..117a47720d602 --- /dev/null +++ b/tests/baselines/reference/thisPredicateInObjectLiteral.errors.txt @@ -0,0 +1,20 @@ +thisPredicateInObjectLiteral.ts(10,28): error TS2526: A 'this' type is available only in a non-static member of a class or interface. + + +==== thisPredicateInObjectLiteral.ts (1 errors) ==== + // Should be OK + const foo2 = { + isNumber(): this is { b: string } { + return true; + }, + }; + + // Still an error + const foo3 = { + isNumber(x: any): x is this { + ~~~~ +!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface. + return true; + }, + }; + \ No newline at end of file diff --git a/tests/baselines/reference/thisPredicateInObjectLiteral.js b/tests/baselines/reference/thisPredicateInObjectLiteral.js index 84cccc313717c..f2c9e45740756 100644 --- a/tests/baselines/reference/thisPredicateInObjectLiteral.js +++ b/tests/baselines/reference/thisPredicateInObjectLiteral.js @@ -6,7 +6,15 @@ const foo2 = { isNumber(): this is { b: string } { return true; }, -}; +}; + +// Still an error +const foo3 = { + isNumber(x: any): x is this { + return true; + }, +}; + //// [thisPredicateInObjectLiteral.js] "use strict"; @@ -16,3 +24,9 @@ var foo2 = { return true; }, }; +// Still an error +var foo3 = { + isNumber: function (x) { + return true; + }, +}; diff --git a/tests/baselines/reference/thisPredicateInObjectLiteral.symbols b/tests/baselines/reference/thisPredicateInObjectLiteral.symbols index e59f573530251..ed78d8775bdf1 100644 --- a/tests/baselines/reference/thisPredicateInObjectLiteral.symbols +++ b/tests/baselines/reference/thisPredicateInObjectLiteral.symbols @@ -12,3 +12,17 @@ const foo2 = { return true; }, }; + +// Still an error +const foo3 = { +>foo3 : Symbol(foo3, Decl(thisPredicateInObjectLiteral.ts, 8, 5)) + + isNumber(x: any): x is this { +>isNumber : Symbol(isNumber, Decl(thisPredicateInObjectLiteral.ts, 8, 14)) +>x : Symbol(x, Decl(thisPredicateInObjectLiteral.ts, 9, 13)) +>x : Symbol(x, Decl(thisPredicateInObjectLiteral.ts, 9, 13)) + + return true; + }, +}; + diff --git a/tests/baselines/reference/thisPredicateInObjectLiteral.types b/tests/baselines/reference/thisPredicateInObjectLiteral.types index 7e5d02c2abde0..96963ea5061e6 100644 --- a/tests/baselines/reference/thisPredicateInObjectLiteral.types +++ b/tests/baselines/reference/thisPredicateInObjectLiteral.types @@ -20,3 +20,24 @@ const foo2 = { }, }; + +// Still an error +const foo3 = { +>foo3 : { isNumber(x: any): x is this; } +> : ^^^^^^^^^^^ ^^ ^^^ ^^^ +>{ isNumber(x: any): x is this { return true; },} : { isNumber(x: any): x is this; } +> : ^^^^^^^^^^^ ^^ ^^^ ^^^ + + isNumber(x: any): x is this { +>isNumber : (x: any) => x is this +> : ^ ^^ ^^^^^ +>x : any +> : ^^^ + + return true; +>true : true +> : ^^^^ + + }, +}; + diff --git a/tests/cases/compiler/thisPredicateInObjectLiteral.ts b/tests/cases/compiler/thisPredicateInObjectLiteral.ts index 594ec873d47ef..54c5ce2f974be 100644 --- a/tests/cases/compiler/thisPredicateInObjectLiteral.ts +++ b/tests/cases/compiler/thisPredicateInObjectLiteral.ts @@ -5,4 +5,11 @@ const foo2 = { isNumber(): this is { b: string } { return true; }, -}; \ No newline at end of file +}; + +// Still an error +const foo3 = { + isNumber(x: any): x is this { + return true; + }, +}; From a26f4350d6f589e8d69ec2000e7e53512eae54e7 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Tue, 23 Jul 2024 12:37:47 -0700 Subject: [PATCH 3/3] There's no reason to call this function here --- src/compiler/checker.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2997c69b65a79..b6e7fa9ed0e03 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19673,9 +19673,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { return getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(container)).thisType!; } - if (!(node.parent.kind === SyntaxKind.TypePredicate && node === (node.parent as TypePredicateNode).parameterName)) { - error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); - } + error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); return errorType; } @@ -40773,10 +40771,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkSourceElement(node.type); const { parameterName } = node; - if (typePredicate.kind === TypePredicateKind.This || typePredicate.kind === TypePredicateKind.AssertsThis) { - getTypeFromThisTypeNode(parameterName as ThisTypeNode); - } - else { + if (typePredicate.kind !== TypePredicateKind.This && typePredicate.kind !== TypePredicateKind.AssertsThis) { if (typePredicate.parameterIndex >= 0) { if (signatureHasRestParameter(signature) && typePredicate.parameterIndex === signature.parameters.length - 1) { error(parameterName, Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter);