TypeScript
TypeScript copied to clipboard
Protected and private functions are not visible when 'this' context "arg" is provided if it doesn't explicitly define a return type
Bug Report
🔎 Search Terms
private type is not visible
🕗 Version & Regression Information
- This is the behavior in every version I tried (3.3.3 -> Nightly), and I reviewed the FAQ for entries about types and not-a-bugs.
⏯ Playground Link
Playground link with relevant code
💻 Code
interface Interface1 {}
interface Interface2 {}
class Mixin {
private field: any;
protected field2: any;
protected doOneThing() {}
protected doOneThingVoid(): void {}
private doTwoThings() {}
private doTwoThingsVoid(): void {}
public doThreeThings() {}
public doThreeThingsVoid(): void {}
doThing(this: Mixin & (Interface1 | Interface2)) {
// Not fine
this.field2;
this.doOneThing();
this.doOneThingVoid();
this.doTwoThings();
// Fine
this.field;
this.doTwoThingsVoid();
this.doThreeThings();
this.doThreeThingsVoid();
}
}
🙁 Actual behavior
The protected fields and functions are declared as not visible. The private functions that implicitly return void are not visible.
🙂 Expected behavior
Protected fields are visible. Functions that return an implicit void are visible and therefore treated the same as functions that return an explicit void.
Further Notes
It doesn't matter if I return void, any return value will do. I'm also finding it odd that private works, but protected doesn't even though protected should have greater visibility here. However it may be trying to say that it doesn't think that we're inside of a Mixin class instance, in which case neither private nor protected should be visible in that case, even though that's not my preferred outcome. At minimum there shouldn't be a difference between implicit and explicit void.
Yes this is used as a mixin with the ts-mixer library, however, the type errors are all contained within the class itself like the example above, and so are a problem without involvement of any mixin libraries.
Also this seems related to #29132 except there's no generics used here. I also found #10637 when researching this, but that involves inheritance which isn't in play here.
Not to mention that the fine/not-fine lists change if you switch to this & (Interface1 | Interface2)
@DanielRosenwasser Not so; the only thing that seems to change is that this.doTwoThings(); is not an error anymore in your playground. Definitely weird in any case.
👀👀👀👀👀👀👀👀👀👀
Sorry, to clarify I didn't mean that they swap - just that a single method changes (which you called out in more detail).
Digged into this problem (for educational reasons). For now only checked this.field2; use-case...
Discovered, that bug happens in hasBaseType function in checker.ts: it just does not handleTypeFlags.Union. Only Intersection. so if you change Interface1 | Interface2 in the example to Interface1 & Interface2 - everything works fine.
function hasBaseType(type: Type, checkBase: Type | undefined) {
return check(type);
function check(type: Type): boolean {
if (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) {
const target = getTargetType(type) as InterfaceType;
return target === checkBase || some(getBaseTypes(target), check);
}
else if (type.flags & TypeFlags.Intersection) {
return some((type as IntersectionType).types, check);
}
// No "else if (type.flags & TypeFlags.Union)" here
return false;
}
}