From 54a83b9901d25cfd895a46b6c6d5e2cb2b54431f Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Tue, 16 Jul 2024 15:01:26 -0700 Subject: [PATCH] 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