Skip to content

Commit

Permalink
Improve detection of containing object literals to improve contextual…
Browse files Browse the repository at this point in the history
… `this` typing
  • Loading branch information
Andarist committed Dec 31, 2024
1 parent 56a0825 commit 858bdf1
Show file tree
Hide file tree
Showing 4 changed files with 305 additions and 5 deletions.
49 changes: 44 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
88 changes: 88 additions & 0 deletions tests/baselines/reference/thisInObjectLiterals2.symbols
Original file line number Diff line number Diff line change
@@ -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))

},
};
}
137 changes: 137 additions & 0 deletions tests/baselines/reference/thisInObjectLiterals2.types
Original file line number Diff line number Diff line change
@@ -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
> : ^^^^^^

},
};
}
Original file line number Diff line number Diff line change
@@ -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);
},
};
}

0 comments on commit 858bdf1

Please sign in to comment.