Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow overload signatures to have different access levels #58316

Open
6 tasks done
Howard-Lam-UnitedVanning opened this issue Apr 25, 2024 · 3 comments
Open
6 tasks done
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@Howard-Lam-UnitedVanning

πŸ” Search Terms

error TS2385: Overload signatures must all be public, private or protected

βœ… Viability Checklist

⭐ Suggestion

What I would like to achieve with that is, that tsc checks, that I can only call public signatures from outside of a class, while I can also call private signatures from inside the class and protected ones from inside a child class.

Originally posted by @0815fox in #7577 (comment)

See also the full posts below:
#7577 (comment)
#58303

πŸ“ƒ Motivating Example

export default class NetworkService<R, G = undefined, Q = G, D = Q, RD=R> {
  get(query?: Q|null): Promise<AxiosResponse<R>>
  get(param: G|null, query: Q|null): Promise<AxiosResponse<R>>;
  protected get(param?: G|Q|null, query?: Q|null): Promise<AxiosResponse<R>>;
  async get(params?: G|Q|null, query?: Q|null): Promise<AxiosResponse<R>> {
  }
 }
 export default class FCNetworkService<R, G=undefined, Q=G, D=Q, RD=R> extends NetworkService<R,G,Q,D,RD> {
    override async get(params?: G|Q|null, query?: Q|null) {
        const result = await super.get(params, query);
    }
}

If I don't add an extra line

get(param?: G|Q|null, query?: Q|null): Promise<AxiosResponse<R>>;

I get the error
"Argument of type 'G | Q | null | undefined' is not assignable to parameter of type 'G | null'."

But that function signature should not be ever called from the outside because the function body cannot tell if params is actually a query object if the second input is undefined, which is why I have the restriction on the second overlord. That is why I need protected for the third overload.

πŸ’» Use Cases

  1. What do you want to use this for? For generic class inheritance overloaded functions override
  2. What shortcomings exist with current approaches? When inheriting and overriding the function with overload signatures, the final combined signature must be provided for the override to call the super method but that signature should not be exposed because it may contain input patterns that logic of the function cannot distinguish.
  3. What workarounds are you using in the meantime? I disable the error on the super method call with @ts-ignore without adding the extra signature needed. If parent changes API later on, the child will fail to detect the change
    super.method.apply doesn't work because I get
    Argument of type 'IArguments' is not assignable to parameter of type '[params: G | null, data: D]'
@MartinJohns
Copy link
Contributor

Duplicate of #9592. It's fairly old tho, things might have changed. In 2021 the reasoning was still true.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Apr 25, 2024
@RyanCavanaugh
Copy link
Member

It's still pretty problematic, because let's say you write this:

class Foo {
    foo(s: string): void;
    foo(n: number): void;
    protected foo(b: boolean): void;
    foo(a: any) {
    }

    bar() {
        return this.foo;
    }

    baz() {
        this.bar()("hello");
        this.bar()(true);
    }
}

const p = (new Foo()).bar();
p("hello");
p(true);

There's not really an obvious way to write down the type of bar's return type, nor is it clear if baz's invocations are both legal. Today it's a basic syntactic analysis to see if a protected or private call is legal -- we just check the lexical position of the call. But if the overloads are conditionally visible, then there's unknown new machinery to represent that from a type perspective.

@Howard-Lam-UnitedVanning
Copy link
Author

I think in your case, it doesn't matter, all three overloads would just become public anyway if you return it with a public method. See below code, If you make all of them private, all three of the overloads are still accessible via p.

class Foo {
    private foo(s: string): void;
    private foo(n: number): void;
    private foo(n: boolean): void;
    private foo(a: any) {
    }

    bar() {
        return this.foo;
    }
}

const p = (new Foo()).bar();
p("hello");
p(true); // no problem on any of these 3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants