diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 80b61edd3657a..5fc9b3a0890de 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -31184,12 +31184,51 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function getContainingPropertyAssignment(node: Node): PropertyAssignment | undefined { + const parent = node.parent; + switch (parent.kind) { + case SyntaxKind.PropertyAssignment: + return parent as PropertyAssignment; + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.ConditionalExpression: + return getContainingPropertyAssignment(parent); + case SyntaxKind.BinaryExpression: { + const binaryExpression = parent as BinaryExpression; + switch (binaryExpression.operatorToken.kind) { + case SyntaxKind.AmpersandAmpersandToken: + case SyntaxKind.BarBarToken: + case SyntaxKind.QuestionQuestionToken: + return getContainingPropertyAssignment(parent); + case SyntaxKind.EqualsToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: + case SyntaxKind.BarBarEqualsToken: + case SyntaxKind.QuestionQuestionEqualsToken: + case SyntaxKind.CommaToken: + if (node === binaryExpression.left) { + return; + } + return getContainingPropertyAssignment(parent); + } + } + } + } + function getContainingObjectLiteral(func: SignatureDeclaration): ObjectLiteralExpression | undefined { - return (func.kind === SyntaxKind.MethodDeclaration || - func.kind === SyntaxKind.GetAccessor || - func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? func.parent : - func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? func.parent.parent as ObjectLiteralExpression : - undefined; + switch (func.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + if (func.parent.kind !== SyntaxKind.ObjectLiteralExpression) { + return; + } + return func.parent; + case SyntaxKind.FunctionExpression: + const prop = getContainingPropertyAssignment(func); + if (!prop) { + return; + } + return prop.parent; + } } function getThisTypeArgument(type: Type): Type | undefined { diff --git a/tests/baselines/reference/thisInObjectJs.symbols b/tests/baselines/reference/thisInObjectJs.symbols index 093fbcd9533c7..4e9c339117832 100644 --- a/tests/baselines/reference/thisInObjectJs.symbols +++ b/tests/baselines/reference/thisInObjectJs.symbols @@ -39,9 +39,13 @@ let obj = { >f3 : Symbol(f3, Decl(index.js, 11, 4)) this.x = 1 +>this.x : Symbol(x, Decl(index.js, 1, 11)) +>this : Symbol(obj, Decl(index.js, 1, 9)) >x : Symbol((Anonymous function).x, Decl(index.js, 12, 19)) this/*3*/ +>this : Symbol(obj, Decl(index.js, 1, 9)) + }), } diff --git a/tests/baselines/reference/thisInObjectJs.types b/tests/baselines/reference/thisInObjectJs.types index 7b631c78bfd23..d9960fa0101cc 100644 --- a/tests/baselines/reference/thisInObjectJs.types +++ b/tests/baselines/reference/thisInObjectJs.types @@ -75,16 +75,18 @@ let obj = { this.x = 1 >this.x = 1 : 1 > : ^ ->this.x : any ->this : any -> : ^^^ ->x : any -> : ^^^ +>this.x : number +> : ^^^^^^ +>this : { x: number; y: number[]; fun: typeof fun; f2: () => void; f3: typeof (Anonymous function); } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>x : number +> : ^^^^^^ >1 : 1 > : ^ this/*3*/ ->this : any +>this : { x: number; y: number[]; fun: typeof fun; f2: () => void; f3: typeof (Anonymous function); } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ }), } diff --git a/tests/baselines/reference/thisInObjectLiterals2.symbols b/tests/baselines/reference/thisInObjectLiterals2.symbols new file mode 100644 index 0000000000000..923f7213797f1 --- /dev/null +++ b/tests/baselines/reference/thisInObjectLiterals2.symbols @@ -0,0 +1,88 @@ +//// [tests/cases/conformance/expressions/thisKeyword/thisInObjectLiterals2.ts] //// + +=== thisInObjectLiterals2.ts === +// https://github.com/microsoft/TypeScript/issues/54723 + +interface State { +>State : Symbol(State, Decl(thisInObjectLiterals2.ts, 0, 0)) + + value: string; +>value : Symbol(State.value, Decl(thisInObjectLiterals2.ts, 2, 17)) + + matches(value: string): boolean; +>matches : Symbol(State.matches, Decl(thisInObjectLiterals2.ts, 3, 16)) +>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 4, 10)) +} + +declare function macthesState(state: { value: string }, value: string): boolean; +>macthesState : Symbol(macthesState, Decl(thisInObjectLiterals2.ts, 5, 1)) +>state : Symbol(state, Decl(thisInObjectLiterals2.ts, 7, 30)) +>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 7, 38)) +>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 7, 55)) + +declare function isState(state: unknown): state is State; +>isState : Symbol(isState, Decl(thisInObjectLiterals2.ts, 7, 80)) +>state : Symbol(state, Decl(thisInObjectLiterals2.ts, 8, 25)) +>state : Symbol(state, Decl(thisInObjectLiterals2.ts, 8, 25)) +>State : Symbol(State, Decl(thisInObjectLiterals2.ts, 0, 0)) + +function test(config: unknown, prevConfig: unknown) { +>test : Symbol(test, Decl(thisInObjectLiterals2.ts, 8, 57)) +>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 10, 14)) +>prevConfig : Symbol(prevConfig, Decl(thisInObjectLiterals2.ts, 10, 30)) + + if (isState(config)) { +>isState : Symbol(isState, Decl(thisInObjectLiterals2.ts, 7, 80)) +>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 10, 14)) + + return { + ...config, +>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 10, 14)) + + matches: isState(prevConfig) +>matches : Symbol(matches, Decl(thisInObjectLiterals2.ts, 13, 16)) +>isState : Symbol(isState, Decl(thisInObjectLiterals2.ts, 7, 80)) +>prevConfig : Symbol(prevConfig, Decl(thisInObjectLiterals2.ts, 10, 30)) + + ? prevConfig.matches +>prevConfig.matches : Symbol(State.matches, Decl(thisInObjectLiterals2.ts, 3, 16)) +>prevConfig : Symbol(prevConfig, Decl(thisInObjectLiterals2.ts, 10, 30)) +>matches : Symbol(State.matches, Decl(thisInObjectLiterals2.ts, 3, 16)) + + : function (value: string) { +>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 16, 20)) + + return macthesState(this, value); +>macthesState : Symbol(macthesState, Decl(thisInObjectLiterals2.ts, 5, 1)) +>this : Symbol(__object, Decl(thisInObjectLiterals2.ts, 12, 10)) +>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 16, 20)) + + }, + }; + } + + return config; +>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 10, 14)) +} + +function test2(config: State) { +>test2 : Symbol(test2, Decl(thisInObjectLiterals2.ts, 23, 1)) +>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 25, 15)) +>State : Symbol(State, Decl(thisInObjectLiterals2.ts, 0, 0)) + + return { + ...config, +>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 25, 15)) + + matches: function (value: string) { +>matches : Symbol(matches, Decl(thisInObjectLiterals2.ts, 27, 14)) +>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 28, 23)) + + return macthesState(this, value); +>macthesState : Symbol(macthesState, Decl(thisInObjectLiterals2.ts, 5, 1)) +>this : Symbol(__object, Decl(thisInObjectLiterals2.ts, 26, 8)) +>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 28, 23)) + + }, + }; +} diff --git a/tests/baselines/reference/thisInObjectLiterals2.types b/tests/baselines/reference/thisInObjectLiterals2.types new file mode 100644 index 0000000000000..715fca93efa66 --- /dev/null +++ b/tests/baselines/reference/thisInObjectLiterals2.types @@ -0,0 +1,137 @@ +//// [tests/cases/conformance/expressions/thisKeyword/thisInObjectLiterals2.ts] //// + +=== thisInObjectLiterals2.ts === +// https://github.com/microsoft/TypeScript/issues/54723 + +interface State { + value: string; +>value : string +> : ^^^^^^ + + matches(value: string): boolean; +>matches : (value: string) => boolean +> : ^ ^^ ^^^^^ +>value : string +> : ^^^^^^ +} + +declare function macthesState(state: { value: string }, value: string): boolean; +>macthesState : (state: { value: string; }, value: string) => boolean +> : ^ ^^ ^^ ^^ ^^^^^ +>state : { value: string; } +> : ^^^^^^^^^ ^^^ +>value : string +> : ^^^^^^ +>value : string +> : ^^^^^^ + +declare function isState(state: unknown): state is State; +>isState : (state: unknown) => state is State +> : ^ ^^ ^^^^^ +>state : unknown +> : ^^^^^^^ + +function test(config: unknown, prevConfig: unknown) { +>test : (config: unknown, prevConfig: unknown) => unknown +> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^ +>config : unknown +> : ^^^^^^^ +>prevConfig : unknown +> : ^^^^^^^ + + if (isState(config)) { +>isState(config) : boolean +> : ^^^^^^^ +>isState : (state: unknown) => state is State +> : ^ ^^ ^^^^^ +>config : unknown +> : ^^^^^^^ + + return { +>{ ...config, matches: isState(prevConfig) ? prevConfig.matches : function (value: string) { return macthesState(this, value); }, } : { matches: (value: string) => boolean; value: string; } +> : ^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^ ^^^ + + ...config, +>config : State +> : ^^^^^ + + matches: isState(prevConfig) +>matches : (value: string) => boolean +> : ^ ^^ ^^^^^ +>isState(prevConfig) ? prevConfig.matches : function (value: string) { return macthesState(this, value); } : (value: string) => boolean +> : ^ ^^ ^^^^^ +>isState(prevConfig) : boolean +> : ^^^^^^^ +>isState : (state: unknown) => state is State +> : ^ ^^ ^^^^^ +>prevConfig : unknown +> : ^^^^^^^ + + ? prevConfig.matches +>prevConfig.matches : (value: string) => boolean +> : ^ ^^ ^^^^^ +>prevConfig : State +> : ^^^^^ +>matches : (value: string) => boolean +> : ^ ^^ ^^^^^ + + : function (value: string) { +>function (value: string) { return macthesState(this, value); } : (value: string) => boolean +> : ^ ^^ ^^^^^^^^^^^^ +>value : string +> : ^^^^^^ + + return macthesState(this, value); +>macthesState(this, value) : boolean +> : ^^^^^^^ +>macthesState : (state: { value: string; }, value: string) => boolean +> : ^ ^^ ^^ ^^ ^^^^^ +>this : { matches: (value: string) => boolean; value: string; } +> : ^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^ ^^^ +>value : string +> : ^^^^^^ + + }, + }; + } + + return config; +>config : unknown +> : ^^^^^^^ +} + +function test2(config: State) { +>test2 : (config: State) => { matches: (value: string) => boolean; value: string; } +> : ^ ^^ ^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ +>config : State +> : ^^^^^ + + return { +>{ ...config, matches: function (value: string) { return macthesState(this, value); }, } : { matches: (value: string) => boolean; value: string; } +> : ^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ + + ...config, +>config : State +> : ^^^^^ + + matches: function (value: string) { +>matches : (value: string) => boolean +> : ^ ^^ ^^^^^^^^^^^^ +>function (value: string) { return macthesState(this, value); } : (value: string) => boolean +> : ^ ^^ ^^^^^^^^^^^^ +>value : string +> : ^^^^^^ + + return macthesState(this, value); +>macthesState(this, value) : boolean +> : ^^^^^^^ +>macthesState : (state: { value: string; }, value: string) => boolean +> : ^ ^^ ^^ ^^ ^^^^^ +>this : { matches: (value: string) => boolean; value: string; } +> : ^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ +>value : string +> : ^^^^^^ + + }, + }; +} diff --git a/tests/cases/conformance/expressions/thisKeyword/thisInObjectLiterals2.ts b/tests/cases/conformance/expressions/thisKeyword/thisInObjectLiterals2.ts new file mode 100644 index 0000000000000..a14503b9eb0a2 --- /dev/null +++ b/tests/cases/conformance/expressions/thisKeyword/thisInObjectLiterals2.ts @@ -0,0 +1,36 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/54723 + +interface State { + value: string; + matches(value: string): boolean; +} + +declare function macthesState(state: { value: string }, value: string): boolean; +declare function isState(state: unknown): state is State; + +function test(config: unknown, prevConfig: unknown) { + if (isState(config)) { + return { + ...config, + matches: isState(prevConfig) + ? prevConfig.matches + : function (value: string) { + return macthesState(this, value); + }, + }; + } + + return config; +} + +function test2(config: State) { + return { + ...config, + matches: function (value: string) { + return macthesState(this, value); + }, + }; +} \ No newline at end of file