From 5cf79d79025729159a9df69a7ea60fec1b87db4a Mon Sep 17 00:00:00 2001 From: PaulyBearCoding Date: Wed, 12 Nov 2025 16:29:24 -0800 Subject: [PATCH] Allow protected access with explicit 'this' parameters Fixes #62738 When accessing a base class protected member via .call(this) or .apply(this) with an explicit this parameter typed as a derived class, TypeScript now correctly allows the access. Previously, accessing BaseClass.prototype.method.call(this) inside a function with this: DerivedClass incorrectly raised error TS2446. The error claimed the access was invalid, but this pattern is type-safe because the explicit this parameter ensures the correct type. Changes: - Check explicit 'this' parameters before enclosing class hierarchy for instance members only (static members unaffected) - Track whether enclosingClass came from explicit 'this' parameter - Allow bidirectional type relationship when explicit 'this' present The fix maintains protected member security while allowing the valid pattern confirmed by maintainer in the issue discussion. --- src/compiler/checker.ts | 43 +++- ...dAccessViaExplicitThisParameter.errors.txt | 60 +++++ ...protectedAccessViaExplicitThisParameter.js | 100 ++++++++ ...ctedAccessViaExplicitThisParameter.symbols | 137 +++++++++++ ...tectedAccessViaExplicitThisParameter.types | 225 ++++++++++++++++++ ...protectedAccessViaExplicitThisParameter.ts | 53 +++++ 6 files changed, 606 insertions(+), 12 deletions(-) create mode 100644 tests/baselines/reference/protectedAccessViaExplicitThisParameter.errors.txt create mode 100644 tests/baselines/reference/protectedAccessViaExplicitThisParameter.js create mode 100644 tests/baselines/reference/protectedAccessViaExplicitThisParameter.symbols create mode 100644 tests/baselines/reference/protectedAccessViaExplicitThisParameter.types create mode 100644 tests/cases/conformance/classes/members/accessibility/protectedAccessViaExplicitThisParameter.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fa2a56e2fdfa6..1c5fb2556396f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -34451,24 +34451,40 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return true; } - // Find the first enclosing class that has the declaring classes of the protected constituents - // of the property as base classes - let enclosingClass = forEachEnclosingClass(location, enclosingDeclaration => { - const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfDeclaration(enclosingDeclaration)) as InterfaceType; - return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing); - }); - // A protected property is accessible if the property is within the declaring class or classes derived from it - if (!enclosingClass) { - // allow PropertyAccessibility if context is in function with this parameter - // static member access is disallowed + // For instance members, first check for explicit 'this' parameter which takes precedence. + // For static members, only use enclosing class hierarchy (explicit 'this' doesn't apply to statics). + let enclosingClass: InterfaceType | undefined; + let fromExplicitThis = false; + + if (!(flags & ModifierFlags.Static)) { enclosingClass = getEnclosingClassFromThisParameter(location); + fromExplicitThis = !!enclosingClass; enclosingClass = enclosingClass && isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing); - if (flags & ModifierFlags.Static || !enclosingClass) { + } + + // If no explicit 'this' parameter (or static member), find the first enclosing class that has + // the declaring classes of the protected constituents of the property as base classes + if (!enclosingClass) { + fromExplicitThis = false; + enclosingClass = forEachEnclosingClass(location, enclosingDeclaration => { + const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfDeclaration(enclosingDeclaration)) as InterfaceType; + return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing); + }); + } + + // A protected property is accessible if the property is within the declaring class or classes derived from it + if (!enclosingClass) { + if (flags & ModifierFlags.Static) { if (errorNode) { error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, symbolToString(prop), typeToString(getDeclaringClass(prop) || containingType)); } return false; } + // No enclosing class found at all + if (errorNode) { + error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, symbolToString(prop), typeToString(getDeclaringClass(prop) || containingType)); + } + return false; } // No further restrictions for static properties if (flags & ModifierFlags.Static) { @@ -34478,7 +34494,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // get the original type -- represented as the type constraint of the 'this' type containingType = (containingType as TypeParameter).isThisType ? getConstraintOfTypeParameter(containingType as TypeParameter)! : getBaseConstraintOfType(containingType as TypeParameter)!; // TODO: GH#18217 Use a different variable that's allowed to be undefined } - if (!containingType || !hasBaseType(containingType, enclosingClass)) { + // Allow protected access if containingType is enclosingClass or its subclass (standard behavior). + // Also allow if enclosingClass came from explicit 'this' parameter and is a subclass of containingType. + // This handles cases like: BaseClass.prototype.method.call(this) where this: DerivedClass + if (!containingType || !(hasBaseType(containingType, enclosingClass) || (fromExplicitThis && hasBaseType(enclosingClass, containingType)))) { if (errorNode) { error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1_This_is_an_instance_of_class_2, symbolToString(prop), typeToString(enclosingClass), typeToString(containingType)); } diff --git a/tests/baselines/reference/protectedAccessViaExplicitThisParameter.errors.txt b/tests/baselines/reference/protectedAccessViaExplicitThisParameter.errors.txt new file mode 100644 index 0000000000000..3f6162631d235 --- /dev/null +++ b/tests/baselines/reference/protectedAccessViaExplicitThisParameter.errors.txt @@ -0,0 +1,60 @@ +protectedAccessViaExplicitThisParameter.ts(43,22): error TS2445: Property 'baseMethod' is protected and only accessible within class 'Base' and its subclasses. +protectedAccessViaExplicitThisParameter.ts(50,10): error TS2445: Property 'baseMethod' is protected and only accessible within class 'Derived' and its subclasses. + + +==== protectedAccessViaExplicitThisParameter.ts (2 errors) ==== + // Accessing base class protected methods via explicit 'this' parameter + // Should compile when 'this' type is a derived class + + class Base { + protected baseMethod() { + return "Base.baseMethod"; + } + } + + class Derived extends Base { + protected override baseMethod() { + return "Derived.baseMethod"; + } + + // Test case 1: Static block with explicit 'this' parameter + static { + this.prototype.baseMethod = function(this: Derived) { + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived + return "override"; + } + } + + // Test case 2: Regular method with explicit 'this' parameter + testExplicitThis() { + const fn = function(this: Derived) { + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived + }; + fn.call(this); + } + + // Test case 3: Should still error with wrong explicit 'this' type + testWrongExplicitThis() { + const fn = function(this: Base) { + Base.prototype.baseMethod.call(this); // Error: this: Base, not compatible + }; + } + } + + // Test case 4: Should error without derived relationship + class Unrelated { + testUnrelated() { + const fn = function(this: Unrelated) { + Base.prototype.baseMethod.call(this); // Error: Unrelated not related to Base + ~~~~~~~~~~ +!!! error TS2445: Property 'baseMethod' is protected and only accessible within class 'Base' and its subclasses. + }; + } + } + + // Test case 5: Should still error for external access + const instance = new Derived(); + instance.baseMethod(); // Error: external access to protected member + ~~~~~~~~~~ +!!! error TS2445: Property 'baseMethod' is protected and only accessible within class 'Derived' and its subclasses. + \ No newline at end of file diff --git a/tests/baselines/reference/protectedAccessViaExplicitThisParameter.js b/tests/baselines/reference/protectedAccessViaExplicitThisParameter.js new file mode 100644 index 0000000000000..95f32eaf5173b --- /dev/null +++ b/tests/baselines/reference/protectedAccessViaExplicitThisParameter.js @@ -0,0 +1,100 @@ +//// [tests/cases/conformance/classes/members/accessibility/protectedAccessViaExplicitThisParameter.ts] //// + +//// [protectedAccessViaExplicitThisParameter.ts] +// Accessing base class protected methods via explicit 'this' parameter +// Should compile when 'this' type is a derived class + +class Base { + protected baseMethod() { + return "Base.baseMethod"; + } +} + +class Derived extends Base { + protected override baseMethod() { + return "Derived.baseMethod"; + } + + // Test case 1: Static block with explicit 'this' parameter + static { + this.prototype.baseMethod = function(this: Derived) { + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived + return "override"; + } + } + + // Test case 2: Regular method with explicit 'this' parameter + testExplicitThis() { + const fn = function(this: Derived) { + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived + }; + fn.call(this); + } + + // Test case 3: Should still error with wrong explicit 'this' type + testWrongExplicitThis() { + const fn = function(this: Base) { + Base.prototype.baseMethod.call(this); // Error: this: Base, not compatible + }; + } +} + +// Test case 4: Should error without derived relationship +class Unrelated { + testUnrelated() { + const fn = function(this: Unrelated) { + Base.prototype.baseMethod.call(this); // Error: Unrelated not related to Base + }; + } +} + +// Test case 5: Should still error for external access +const instance = new Derived(); +instance.baseMethod(); // Error: external access to protected member + + +//// [protectedAccessViaExplicitThisParameter.js] +"use strict"; +// Accessing base class protected methods via explicit 'this' parameter +// Should compile when 'this' type is a derived class +class Base { + baseMethod() { + return "Base.baseMethod"; + } +} +class Derived extends Base { + baseMethod() { + return "Derived.baseMethod"; + } + // Test case 1: Static block with explicit 'this' parameter + static { + this.prototype.baseMethod = function () { + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived + return "override"; + }; + } + // Test case 2: Regular method with explicit 'this' parameter + testExplicitThis() { + const fn = function () { + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived + }; + fn.call(this); + } + // Test case 3: Should still error with wrong explicit 'this' type + testWrongExplicitThis() { + const fn = function () { + Base.prototype.baseMethod.call(this); // Error: this: Base, not compatible + }; + } +} +// Test case 4: Should error without derived relationship +class Unrelated { + testUnrelated() { + const fn = function () { + Base.prototype.baseMethod.call(this); // Error: Unrelated not related to Base + }; + } +} +// Test case 5: Should still error for external access +const instance = new Derived(); +instance.baseMethod(); // Error: external access to protected member diff --git a/tests/baselines/reference/protectedAccessViaExplicitThisParameter.symbols b/tests/baselines/reference/protectedAccessViaExplicitThisParameter.symbols new file mode 100644 index 0000000000000..6e24b8fe33079 --- /dev/null +++ b/tests/baselines/reference/protectedAccessViaExplicitThisParameter.symbols @@ -0,0 +1,137 @@ +//// [tests/cases/conformance/classes/members/accessibility/protectedAccessViaExplicitThisParameter.ts] //// + +=== protectedAccessViaExplicitThisParameter.ts === +// Accessing base class protected methods via explicit 'this' parameter +// Should compile when 'this' type is a derived class + +class Base { +>Base : Symbol(Base, Decl(protectedAccessViaExplicitThisParameter.ts, 0, 0)) + + protected baseMethod() { +>baseMethod : Symbol(Base.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 3, 12)) + + return "Base.baseMethod"; + } +} + +class Derived extends Base { +>Derived : Symbol(Derived, Decl(protectedAccessViaExplicitThisParameter.ts, 7, 1)) +>Base : Symbol(Base, Decl(protectedAccessViaExplicitThisParameter.ts, 0, 0)) + + protected override baseMethod() { +>baseMethod : Symbol(Derived.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 9, 28)) + + return "Derived.baseMethod"; + } + + // Test case 1: Static block with explicit 'this' parameter + static { + this.prototype.baseMethod = function(this: Derived) { +>this.prototype.baseMethod : Symbol(Derived.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 9, 28)) +>this.prototype : Symbol(Derived.prototype) +>this : Symbol(Derived, Decl(protectedAccessViaExplicitThisParameter.ts, 7, 1)) +>prototype : Symbol(Derived.prototype) +>baseMethod : Symbol(Derived.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 9, 28)) +>this : Symbol(this, Decl(protectedAccessViaExplicitThisParameter.ts, 16, 41)) +>Derived : Symbol(Derived, Decl(protectedAccessViaExplicitThisParameter.ts, 7, 1)) + + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived +>Base.prototype.baseMethod.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --)) +>Base.prototype.baseMethod : Symbol(Base.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 3, 12)) +>Base.prototype : Symbol(Base.prototype) +>Base : Symbol(Base, Decl(protectedAccessViaExplicitThisParameter.ts, 0, 0)) +>prototype : Symbol(Base.prototype) +>baseMethod : Symbol(Base.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 3, 12)) +>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --)) +>this : Symbol(this, Decl(protectedAccessViaExplicitThisParameter.ts, 16, 41)) + + return "override"; + } + } + + // Test case 2: Regular method with explicit 'this' parameter + testExplicitThis() { +>testExplicitThis : Symbol(Derived.testExplicitThis, Decl(protectedAccessViaExplicitThisParameter.ts, 20, 3)) + + const fn = function(this: Derived) { +>fn : Symbol(fn, Decl(protectedAccessViaExplicitThisParameter.ts, 24, 9)) +>this : Symbol(this, Decl(protectedAccessViaExplicitThisParameter.ts, 24, 24)) +>Derived : Symbol(Derived, Decl(protectedAccessViaExplicitThisParameter.ts, 7, 1)) + + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived +>Base.prototype.baseMethod.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --)) +>Base.prototype.baseMethod : Symbol(Base.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 3, 12)) +>Base.prototype : Symbol(Base.prototype) +>Base : Symbol(Base, Decl(protectedAccessViaExplicitThisParameter.ts, 0, 0)) +>prototype : Symbol(Base.prototype) +>baseMethod : Symbol(Base.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 3, 12)) +>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --)) +>this : Symbol(this, Decl(protectedAccessViaExplicitThisParameter.ts, 24, 24)) + + }; + fn.call(this); +>fn.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --)) +>fn : Symbol(fn, Decl(protectedAccessViaExplicitThisParameter.ts, 24, 9)) +>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --)) +>this : Symbol(Derived, Decl(protectedAccessViaExplicitThisParameter.ts, 7, 1)) + } + + // Test case 3: Should still error with wrong explicit 'this' type + testWrongExplicitThis() { +>testWrongExplicitThis : Symbol(Derived.testWrongExplicitThis, Decl(protectedAccessViaExplicitThisParameter.ts, 28, 3)) + + const fn = function(this: Base) { +>fn : Symbol(fn, Decl(protectedAccessViaExplicitThisParameter.ts, 32, 9)) +>this : Symbol(this, Decl(protectedAccessViaExplicitThisParameter.ts, 32, 24)) +>Base : Symbol(Base, Decl(protectedAccessViaExplicitThisParameter.ts, 0, 0)) + + Base.prototype.baseMethod.call(this); // Error: this: Base, not compatible +>Base.prototype.baseMethod.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --)) +>Base.prototype.baseMethod : Symbol(Base.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 3, 12)) +>Base.prototype : Symbol(Base.prototype) +>Base : Symbol(Base, Decl(protectedAccessViaExplicitThisParameter.ts, 0, 0)) +>prototype : Symbol(Base.prototype) +>baseMethod : Symbol(Base.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 3, 12)) +>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --)) +>this : Symbol(this, Decl(protectedAccessViaExplicitThisParameter.ts, 32, 24)) + + }; + } +} + +// Test case 4: Should error without derived relationship +class Unrelated { +>Unrelated : Symbol(Unrelated, Decl(protectedAccessViaExplicitThisParameter.ts, 36, 1)) + + testUnrelated() { +>testUnrelated : Symbol(Unrelated.testUnrelated, Decl(protectedAccessViaExplicitThisParameter.ts, 39, 17)) + + const fn = function(this: Unrelated) { +>fn : Symbol(fn, Decl(protectedAccessViaExplicitThisParameter.ts, 41, 9)) +>this : Symbol(this, Decl(protectedAccessViaExplicitThisParameter.ts, 41, 24)) +>Unrelated : Symbol(Unrelated, Decl(protectedAccessViaExplicitThisParameter.ts, 36, 1)) + + Base.prototype.baseMethod.call(this); // Error: Unrelated not related to Base +>Base.prototype.baseMethod.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --)) +>Base.prototype.baseMethod : Symbol(Base.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 3, 12)) +>Base.prototype : Symbol(Base.prototype) +>Base : Symbol(Base, Decl(protectedAccessViaExplicitThisParameter.ts, 0, 0)) +>prototype : Symbol(Base.prototype) +>baseMethod : Symbol(Base.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 3, 12)) +>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --)) +>this : Symbol(this, Decl(protectedAccessViaExplicitThisParameter.ts, 41, 24)) + + }; + } +} + +// Test case 5: Should still error for external access +const instance = new Derived(); +>instance : Symbol(instance, Decl(protectedAccessViaExplicitThisParameter.ts, 48, 5)) +>Derived : Symbol(Derived, Decl(protectedAccessViaExplicitThisParameter.ts, 7, 1)) + +instance.baseMethod(); // Error: external access to protected member +>instance.baseMethod : Symbol(Derived.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 9, 28)) +>instance : Symbol(instance, Decl(protectedAccessViaExplicitThisParameter.ts, 48, 5)) +>baseMethod : Symbol(Derived.baseMethod, Decl(protectedAccessViaExplicitThisParameter.ts, 9, 28)) + diff --git a/tests/baselines/reference/protectedAccessViaExplicitThisParameter.types b/tests/baselines/reference/protectedAccessViaExplicitThisParameter.types new file mode 100644 index 0000000000000..31599853f0bca --- /dev/null +++ b/tests/baselines/reference/protectedAccessViaExplicitThisParameter.types @@ -0,0 +1,225 @@ +//// [tests/cases/conformance/classes/members/accessibility/protectedAccessViaExplicitThisParameter.ts] //// + +=== protectedAccessViaExplicitThisParameter.ts === +// Accessing base class protected methods via explicit 'this' parameter +// Should compile when 'this' type is a derived class + +class Base { +>Base : Base +> : ^^^^ + + protected baseMethod() { +>baseMethod : () => string +> : ^^^^^^^^^^^^ + + return "Base.baseMethod"; +>"Base.baseMethod" : "Base.baseMethod" +> : ^^^^^^^^^^^^^^^^^ + } +} + +class Derived extends Base { +>Derived : Derived +> : ^^^^^^^ +>Base : Base +> : ^^^^ + + protected override baseMethod() { +>baseMethod : () => string +> : ^^^^^^^^^^^^ + + return "Derived.baseMethod"; +>"Derived.baseMethod" : "Derived.baseMethod" +> : ^^^^^^^^^^^^^^^^^^^^ + } + + // Test case 1: Static block with explicit 'this' parameter + static { + this.prototype.baseMethod = function(this: Derived) { +>this.prototype.baseMethod = function(this: Derived) { Base.prototype.baseMethod.call(this); // OK: explicit this: Derived return "override"; } : (this: Derived) => string +> : ^ ^^ ^^^^^^^^^^^ +>this.prototype.baseMethod : () => string +> : ^^^^^^^^^^^^ +>this.prototype : Derived +> : ^^^^^^^ +>this : typeof Derived +> : ^^^^^^^^^^^^^^ +>prototype : Derived +> : ^^^^^^^ +>baseMethod : () => string +> : ^^^^^^^^^^^^ +>function(this: Derived) { Base.prototype.baseMethod.call(this); // OK: explicit this: Derived return "override"; } : (this: Derived) => string +> : ^ ^^ ^^^^^^^^^^^ +>this : Derived +> : ^^^^^^^ + + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived +>Base.prototype.baseMethod.call(this) : string +> : ^^^^^^ +>Base.prototype.baseMethod.call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>Base.prototype.baseMethod : () => string +> : ^^^^^^^^^^^^ +>Base.prototype : Base +> : ^^^^ +>Base : typeof Base +> : ^^^^^^^^^^^ +>prototype : Base +> : ^^^^ +>baseMethod : () => string +> : ^^^^^^^^^^^^ +>call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>this : Derived +> : ^^^^^^^ + + return "override"; +>"override" : "override" +> : ^^^^^^^^^^ + } + } + + // Test case 2: Regular method with explicit 'this' parameter + testExplicitThis() { +>testExplicitThis : () => void +> : ^^^^^^^^^^ + + const fn = function(this: Derived) { +>fn : (this: Derived) => void +> : ^ ^^ ^^^^^^^^^ +>function(this: Derived) { Base.prototype.baseMethod.call(this); // OK: explicit this: Derived } : (this: Derived) => void +> : ^ ^^ ^^^^^^^^^ +>this : Derived +> : ^^^^^^^ + + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived +>Base.prototype.baseMethod.call(this) : string +> : ^^^^^^ +>Base.prototype.baseMethod.call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>Base.prototype.baseMethod : () => string +> : ^^^^^^^^^^^^ +>Base.prototype : Base +> : ^^^^ +>Base : typeof Base +> : ^^^^^^^^^^^ +>prototype : Base +> : ^^^^ +>baseMethod : () => string +> : ^^^^^^^^^^^^ +>call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>this : Derived +> : ^^^^^^^ + + }; + fn.call(this); +>fn.call(this) : void +> : ^^^^ +>fn.call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>fn : (this: Derived) => void +> : ^ ^^ ^^^^^^^^^ +>call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>this : this +> : ^^^^ + } + + // Test case 3: Should still error with wrong explicit 'this' type + testWrongExplicitThis() { +>testWrongExplicitThis : () => void +> : ^^^^^^^^^^ + + const fn = function(this: Base) { +>fn : (this: Base) => void +> : ^ ^^ ^^^^^^^^^ +>function(this: Base) { Base.prototype.baseMethod.call(this); // Error: this: Base, not compatible } : (this: Base) => void +> : ^ ^^ ^^^^^^^^^ +>this : Base +> : ^^^^ + + Base.prototype.baseMethod.call(this); // Error: this: Base, not compatible +>Base.prototype.baseMethod.call(this) : string +> : ^^^^^^ +>Base.prototype.baseMethod.call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>Base.prototype.baseMethod : () => string +> : ^^^^^^^^^^^^ +>Base.prototype : Base +> : ^^^^ +>Base : typeof Base +> : ^^^^^^^^^^^ +>prototype : Base +> : ^^^^ +>baseMethod : () => string +> : ^^^^^^^^^^^^ +>call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>this : Base +> : ^^^^ + + }; + } +} + +// Test case 4: Should error without derived relationship +class Unrelated { +>Unrelated : Unrelated +> : ^^^^^^^^^ + + testUnrelated() { +>testUnrelated : () => void +> : ^^^^^^^^^^ + + const fn = function(this: Unrelated) { +>fn : (this: Unrelated) => void +> : ^ ^^ ^^^^^^^^^ +>function(this: Unrelated) { Base.prototype.baseMethod.call(this); // Error: Unrelated not related to Base } : (this: Unrelated) => void +> : ^ ^^ ^^^^^^^^^ +>this : Unrelated +> : ^^^^^^^^^ + + Base.prototype.baseMethod.call(this); // Error: Unrelated not related to Base +>Base.prototype.baseMethod.call(this) : string +> : ^^^^^^ +>Base.prototype.baseMethod.call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>Base.prototype.baseMethod : () => string +> : ^^^^^^^^^^^^ +>Base.prototype : Base +> : ^^^^ +>Base : typeof Base +> : ^^^^^^^^^^^ +>prototype : Base +> : ^^^^ +>baseMethod : () => string +> : ^^^^^^^^^^^^ +>call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^ +>this : Unrelated +> : ^^^^^^^^^ + + }; + } +} + +// Test case 5: Should still error for external access +const instance = new Derived(); +>instance : Derived +> : ^^^^^^^ +>new Derived() : Derived +> : ^^^^^^^ +>Derived : typeof Derived +> : ^^^^^^^^^^^^^^ + +instance.baseMethod(); // Error: external access to protected member +>instance.baseMethod() : string +> : ^^^^^^ +>instance.baseMethod : () => string +> : ^^^^^^^^^^^^ +>instance : Derived +> : ^^^^^^^ +>baseMethod : () => string +> : ^^^^^^^^^^^^ + diff --git a/tests/cases/conformance/classes/members/accessibility/protectedAccessViaExplicitThisParameter.ts b/tests/cases/conformance/classes/members/accessibility/protectedAccessViaExplicitThisParameter.ts new file mode 100644 index 0000000000000..32b9d9b683a6c --- /dev/null +++ b/tests/cases/conformance/classes/members/accessibility/protectedAccessViaExplicitThisParameter.ts @@ -0,0 +1,53 @@ +// @strict: true +// @target: ES2022 + +// Accessing base class protected methods via explicit 'this' parameter +// Should compile when 'this' type is a derived class + +class Base { + protected baseMethod() { + return "Base.baseMethod"; + } +} + +class Derived extends Base { + protected override baseMethod() { + return "Derived.baseMethod"; + } + + // Test case 1: Static block with explicit 'this' parameter + static { + this.prototype.baseMethod = function(this: Derived) { + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived + return "override"; + } + } + + // Test case 2: Regular method with explicit 'this' parameter + testExplicitThis() { + const fn = function(this: Derived) { + Base.prototype.baseMethod.call(this); // OK: explicit this: Derived + }; + fn.call(this); + } + + // Test case 3: Should still error with wrong explicit 'this' type + testWrongExplicitThis() { + const fn = function(this: Base) { + Base.prototype.baseMethod.call(this); // Error: this: Base, not compatible + }; + } +} + +// Test case 4: Should error without derived relationship +class Unrelated { + testUnrelated() { + const fn = function(this: Unrelated) { + Base.prototype.baseMethod.call(this); // Error: Unrelated not related to Base + }; + } +} + +// Test case 5: Should still error for external access +const instance = new Derived(); +instance.baseMethod(); // Error: external access to protected member