Skip to content

Commit

Permalink
Use contextual type to determine "this" when determining member visib…
Browse files Browse the repository at this point in the history
…ility (#56105)
  • Loading branch information
RyanCavanaugh authored Aug 2, 2024
1 parent 269219f commit aafdfe5
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 2 deletions.
16 changes: 14 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33667,10 +33667,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function getEnclosingClassFromThisParameter(node: Node): InterfaceType | undefined {
// 'this' type for a node comes from, in priority order...
// 1. The type of a syntactic 'this' parameter in the enclosing function scope
const thisParameter = getThisParameterFromNodeContext(node);
let thisType = thisParameter?.type && getTypeFromTypeNode(thisParameter.type);
if (thisType && thisType.flags & TypeFlags.TypeParameter) {
thisType = getConstraintOfTypeParameter(thisType as TypeParameter);
if (thisType) {
// 2. The constraint of a type parameter used for an explicit 'this' parameter
if (thisType.flags & TypeFlags.TypeParameter) {
thisType = getConstraintOfTypeParameter(thisType as TypeParameter);
}
}
else {
// 3. The 'this' parameter of a contextual type
const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
if (isFunctionLike(thisContainer)) {
thisType = getContextualThisParameterType(thisContainer);
}
}
if (thisType && getObjectFlags(thisType) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) {
return getTargetType(thisType) as InterfaceType;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
protectedAccessThroughContextualThis.ts(13,20): error TS2341: Property 'privat' is private and only accessible within class 'Foo'.
protectedAccessThroughContextualThis.ts(20,20): error TS2341: Property 'privat' is private and only accessible within class 'Foo'.


==== protectedAccessThroughContextualThis.ts (2 errors) ====
class Foo {
protected protec = 'bar';
private privat = '';
copy!: string
constructor() {
bindCopy.call(this)
bindCopy2.call(this)
}
}

function bindCopy(this: Foo) {
this.copy = this.protec; // Should OK
console.log(this.privat); // Should error
~~~~~~
!!! error TS2341: Property 'privat' is private and only accessible within class 'Foo'.
}

type BindingFunction = (this: Foo) => void;

const bindCopy2: BindingFunction = function () {
this.copy = this.protec; // Should OK
console.log(this.privat); // Should error
~~~~~~
!!! error TS2341: Property 'privat' is private and only accessible within class 'Foo'.
}
44 changes: 44 additions & 0 deletions tests/baselines/reference/protectedAccessThroughContextualThis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//// [tests/cases/compiler/protectedAccessThroughContextualThis.ts] ////

//// [protectedAccessThroughContextualThis.ts]
class Foo {
protected protec = 'bar';
private privat = '';
copy!: string
constructor() {
bindCopy.call(this)
bindCopy2.call(this)
}
}

function bindCopy(this: Foo) {
this.copy = this.protec; // Should OK
console.log(this.privat); // Should error
}

type BindingFunction = (this: Foo) => void;

const bindCopy2: BindingFunction = function () {
this.copy = this.protec; // Should OK
console.log(this.privat); // Should error
}

//// [protectedAccessThroughContextualThis.js]
"use strict";
var Foo = /** @class */ (function () {
function Foo() {
this.protec = 'bar';
this.privat = '';
bindCopy.call(this);
bindCopy2.call(this);
}
return Foo;
}());
function bindCopy() {
this.copy = this.protec; // Should OK
console.log(this.privat); // Should error
}
var bindCopy2 = function () {
this.copy = this.protec; // Should OK
console.log(this.privat); // Should error
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//// [tests/cases/compiler/protectedAccessThroughContextualThis.ts] ////

=== protectedAccessThroughContextualThis.ts ===
class Foo {
>Foo : Symbol(Foo, Decl(protectedAccessThroughContextualThis.ts, 0, 0))

protected protec = 'bar';
>protec : Symbol(Foo.protec, Decl(protectedAccessThroughContextualThis.ts, 0, 11))

private privat = '';
>privat : Symbol(Foo.privat, Decl(protectedAccessThroughContextualThis.ts, 1, 27))

copy!: string
>copy : Symbol(Foo.copy, Decl(protectedAccessThroughContextualThis.ts, 2, 22))

constructor() {
bindCopy.call(this)
>bindCopy.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>bindCopy : Symbol(bindCopy, Decl(protectedAccessThroughContextualThis.ts, 8, 1))
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>this : Symbol(Foo, Decl(protectedAccessThroughContextualThis.ts, 0, 0))

bindCopy2.call(this)
>bindCopy2.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>bindCopy2 : Symbol(bindCopy2, Decl(protectedAccessThroughContextualThis.ts, 17, 5))
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>this : Symbol(Foo, Decl(protectedAccessThroughContextualThis.ts, 0, 0))
}
}

function bindCopy(this: Foo) {
>bindCopy : Symbol(bindCopy, Decl(protectedAccessThroughContextualThis.ts, 8, 1))
>this : Symbol(this, Decl(protectedAccessThroughContextualThis.ts, 10, 18))
>Foo : Symbol(Foo, Decl(protectedAccessThroughContextualThis.ts, 0, 0))

this.copy = this.protec; // Should OK
>this.copy : Symbol(Foo.copy, Decl(protectedAccessThroughContextualThis.ts, 2, 22))
>this : Symbol(this, Decl(protectedAccessThroughContextualThis.ts, 10, 18))
>copy : Symbol(Foo.copy, Decl(protectedAccessThroughContextualThis.ts, 2, 22))
>this.protec : Symbol(Foo.protec, Decl(protectedAccessThroughContextualThis.ts, 0, 11))
>this : Symbol(this, Decl(protectedAccessThroughContextualThis.ts, 10, 18))
>protec : Symbol(Foo.protec, Decl(protectedAccessThroughContextualThis.ts, 0, 11))

console.log(this.privat); // Should error
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>this.privat : Symbol(Foo.privat, Decl(protectedAccessThroughContextualThis.ts, 1, 27))
>this : Symbol(this, Decl(protectedAccessThroughContextualThis.ts, 10, 18))
>privat : Symbol(Foo.privat, Decl(protectedAccessThroughContextualThis.ts, 1, 27))
}

type BindingFunction = (this: Foo) => void;
>BindingFunction : Symbol(BindingFunction, Decl(protectedAccessThroughContextualThis.ts, 13, 1))
>this : Symbol(this, Decl(protectedAccessThroughContextualThis.ts, 15, 24))
>Foo : Symbol(Foo, Decl(protectedAccessThroughContextualThis.ts, 0, 0))

const bindCopy2: BindingFunction = function () {
>bindCopy2 : Symbol(bindCopy2, Decl(protectedAccessThroughContextualThis.ts, 17, 5))
>BindingFunction : Symbol(BindingFunction, Decl(protectedAccessThroughContextualThis.ts, 13, 1))

this.copy = this.protec; // Should OK
>this.copy : Symbol(Foo.copy, Decl(protectedAccessThroughContextualThis.ts, 2, 22))
>this : Symbol(this, Decl(protectedAccessThroughContextualThis.ts, 15, 24))
>copy : Symbol(Foo.copy, Decl(protectedAccessThroughContextualThis.ts, 2, 22))
>this.protec : Symbol(Foo.protec, Decl(protectedAccessThroughContextualThis.ts, 0, 11))
>this : Symbol(this, Decl(protectedAccessThroughContextualThis.ts, 15, 24))
>protec : Symbol(Foo.protec, Decl(protectedAccessThroughContextualThis.ts, 0, 11))

console.log(this.privat); // Should error
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>this.privat : Symbol(Foo.privat, Decl(protectedAccessThroughContextualThis.ts, 1, 27))
>this : Symbol(this, Decl(protectedAccessThroughContextualThis.ts, 15, 24))
>privat : Symbol(Foo.privat, Decl(protectedAccessThroughContextualThis.ts, 1, 27))
}
133 changes: 133 additions & 0 deletions tests/baselines/reference/protectedAccessThroughContextualThis.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//// [tests/cases/compiler/protectedAccessThroughContextualThis.ts] ////

=== protectedAccessThroughContextualThis.ts ===
class Foo {
>Foo : Foo
> : ^^^

protected protec = 'bar';
>protec : string
> : ^^^^^^
>'bar' : "bar"
> : ^^^^^

private privat = '';
>privat : string
> : ^^^^^^
>'' : ""
> : ^^

copy!: string
>copy : string
> : ^^^^^^

constructor() {
bindCopy.call(this)
>bindCopy.call(this) : void
> : ^^^^
>bindCopy.call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
>bindCopy : (this: Foo) => void
> : ^ ^^ ^^^^^^^^^
>call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
>this : this
> : ^^^^

bindCopy2.call(this)
>bindCopy2.call(this) : void
> : ^^^^
>bindCopy2.call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
>bindCopy2 : BindingFunction
> : ^^^^^^^^^^^^^^^
>call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
>this : this
> : ^^^^
}
}

function bindCopy(this: Foo) {
>bindCopy : (this: Foo) => void
> : ^ ^^ ^^^^^^^^^
>this : Foo
> : ^^^

this.copy = this.protec; // Should OK
>this.copy = this.protec : string
> : ^^^^^^
>this.copy : string
> : ^^^^^^
>this : Foo
> : ^^^
>copy : string
> : ^^^^^^
>this.protec : string
> : ^^^^^^
>this : Foo
> : ^^^
>protec : string
> : ^^^^^^

console.log(this.privat); // Should error
>console.log(this.privat) : void
> : ^^^^
>console.log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>console : Console
> : ^^^^^^^
>log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>this.privat : string
> : ^^^^^^
>this : Foo
> : ^^^
>privat : string
> : ^^^^^^
}

type BindingFunction = (this: Foo) => void;
>BindingFunction : BindingFunction
> : ^^^^^^^^^^^^^^^
>this : Foo
> : ^^^

const bindCopy2: BindingFunction = function () {
>bindCopy2 : BindingFunction
> : ^^^^^^^^^^^^^^^
>function () { this.copy = this.protec; // Should OK console.log(this.privat); // Should error} : (this: Foo) => void
> : ^ ^^ ^^^^^^^^^

this.copy = this.protec; // Should OK
>this.copy = this.protec : string
> : ^^^^^^
>this.copy : string
> : ^^^^^^
>this : Foo
> : ^^^
>copy : string
> : ^^^^^^
>this.protec : string
> : ^^^^^^
>this : Foo
> : ^^^
>protec : string
> : ^^^^^^

console.log(this.privat); // Should error
>console.log(this.privat) : void
> : ^^^^
>console.log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>console : Console
> : ^^^^^^^
>log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>this.privat : string
> : ^^^^^^
>this : Foo
> : ^^^
>privat : string
> : ^^^^^^
}
23 changes: 23 additions & 0 deletions tests/cases/compiler/protectedAccessThroughContextualThis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @strict: true

class Foo {
protected protec = 'bar';
private privat = '';
copy!: string
constructor() {
bindCopy.call(this)
bindCopy2.call(this)
}
}

function bindCopy(this: Foo) {
this.copy = this.protec; // Should OK
console.log(this.privat); // Should error
}

type BindingFunction = (this: Foo) => void;

const bindCopy2: BindingFunction = function () {
this.copy = this.protec; // Should OK
console.log(this.privat); // Should error
}

0 comments on commit aafdfe5

Please sign in to comment.