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 ea64fda
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 11 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
4 changes: 4 additions & 0 deletions tests/baselines/reference/thisInObjectJs.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -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))

}),
}

14 changes: 8 additions & 6 deletions tests/baselines/reference/thisInObjectJs.types
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

}),
}
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 ea64fda

Please sign in to comment.