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

Typescript Autocompletion Not Working For Function Overloads #26892

Open
mjbvz opened this issue Sep 5, 2018 · 10 comments
Open

Typescript Autocompletion Not Working For Function Overloads #26892

mjbvz opened this issue Sep 5, 2018 · 10 comments
Labels
Experience Enhancement Noncontroversial enhancements Help Wanted You can do this Suggestion An idea for TypeScript
Milestone

Comments

@mjbvz
Copy link
Contributor

mjbvz commented Sep 5, 2018

From @MartinLoeper on August 29, 2018 13:27

  • VSCode Version: 1.25.1 1dfc5e557209371715f655691b1235b6b26a06be x64
  • OS Version: Linux version 4.15.0-32-generic Ubuntu

Steps to Reproduce:
I provided a small example of the issue (see screenshot below).

  1. Create a function with two overloads
  2. Try to invoke this function
  3. Once you typed in the first parameter, the IDE recognizes that it matches the first overload's signature.
    However, the auto-completion suggest the literals for both overloads.

I expect the auto-completion to show "BW" and "BY" only!

_005

Does this issue occur when all extensions are disabled?: Yes

The code:

export class MomentHolidayFactory {
    public a(input: Germany, second: GermanState): string;
    public a(input: Austria, second: AustrianState): string;

    public a(input: Country, second: State): string {
        return "test";
    }

    public holidays() {
        this.a("de", ""
    }
}

type Country = Germany & Austria;
type Germany = "de" | "De" | "DE" | "germany";
type Austria = "Au" | "au" | "AU" | "austria";

type State = GermanState & AustrianState;
type GermanState = "BW" | "BY";
type AustrianState = "Stmk" | "Vbg";

Copied from original issue: microsoft/vscode#57509

@mjbvz mjbvz self-assigned this Sep 5, 2018
@mjbvz mjbvz removed the typescript label Sep 5, 2018
@mjbvz mjbvz removed their assignment Sep 5, 2018
@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Experience Enhancement Noncontroversial enhancements labels Sep 17, 2018
@RyanCavanaugh RyanCavanaugh added this to the Community milestone Sep 17, 2018
@RyanCavanaugh RyanCavanaugh added the Help Wanted You can do this label Sep 17, 2018
@ChuckJonas
Copy link

ChuckJonas commented Nov 8, 2018

I'm also having an issue with autocomplete on overloaded code:

    public select<P extends NonReferenceNonFunctionPropNames<T>>(f: P): string;
    public select<P extends NonReferenceNonFunctionPropNames<T>>(f: FunctionField<T>): string;
    public select<P extends NonReferenceNonFunctionPropNames<T>>(f: P[]): string[];
    public select<P extends NonReferenceNonFunctionPropNames<T>>(f: P | P[] | FunctionField<T>): string | string[] {
        let relations = this._traversed.map(r => r.apiName);
        if (Array.isArray(f)) {
            let fieldArr: string[];
            fieldArr = f.map(field => {
                return this._obj.FIELDS[field as string].apiName
            });
            return fieldArr.map(field => {
                return [...relations, ...[field]].join('.');
            });
        } else if (typeof f === 'object') {
            let apiName = this._obj.FIELDS[f.field as string].apiName
            return renderComplexTypeText([...relations, ...[apiName]].join('.'), f.func, f.alias)
        } else {
            return [...relations, ...[this._obj.FIELDS[f as string].apiName]].join('.');
        }
    }

The auto-compete does work on the instances with P instances, but does NOT work when calling the P[] or FunctionField<T> (typechecking does still work, but it's very hard to remember these key names)

@Kingwl
Copy link
Contributor

Kingwl commented Dec 28, 2018

What is the expected behavior?
implement a partial signature resolve or fill parameters with correct type?

@fuafa
Copy link
Contributor

fuafa commented Feb 18, 2019

The signature help is wrong either if the first argument is Austria, the signature help still shows the first overload.

@RyanCavanaugh RyanCavanaugh modified the milestones: Community, Backlog Mar 7, 2019
@aloifolia
Copy link

I recently encountered the same problem where I wanted the compiler to restrict arguments based on previous arguments for an overloaded function type. Also, I came up with a solution that works quite well, in my opinion.

First of all, your types need a little fix:

// intersection types do not make much sense, you actually want union types
type Country = Germany | Austria;
type State = GermanState | AustrianState;

With this fix in mind, you can change the function signature in such a manner that you will basically be able to have partial application of your function:

// I removed the class "wrapper" to keep the example small - this should work for methods, as well.
function b(input: Germany): (second: GermanState) => string;
function b(input: Austria): (second: AustrianState) => string;
function b(input: Country): (second: State) => string {
    return second => "test";
}

Now, you are forced to apply the function b twice. For the second application, VSCode will suggest more fitting values because the inference of the compiler is "locked" into the return type of the first application:

Screenshot 2020-07-06 at 12 59 18

In this playground you will find both approaches. Just try the autocompletion for the calls of both a and b.

@jueinin
Copy link

jueinin commented Nov 18, 2020

any updates on this? I have a function with 100+ overloads. needs this feature very much!

@Kingwl
Copy link
Contributor

Kingwl commented Nov 18, 2020

I have a function with 100+ overloads.

Good luck....

@jueinin
Copy link

jueinin commented Nov 18, 2020

I have a function with 100+ overloads.

Good luck....

@Kingwl
sad...
I write 100+ overloads in one go
and finally, i find that the autocomplete has been totally destroyed.

@dani-ras
Copy link

dani-ras commented Oct 6, 2022

Is there any update on this?
I just tried using an overloaded function and found the autocomplete is not narrowing after providing partial argument..

@ethanresnick
Copy link
Contributor

@RyanCavanaugh I'd like to help with this, as fixing it would enable a library API design that isn't really viable right now, while autocomplete is suggesting invalid candidates. However, I've never contributed to the language server before and don't know how it's set up. Do you have any sense of how hard this would be? And, if not too bad, any pointers on how to get started?

@dwjohnston
Copy link

dwjohnston commented Oct 9, 2024

Here is another simple example of this behaviour:

type UserTypes = {
 userType: "admin" | "user" | "unauthorized"; 
}


type SessionData = {
  sessionId: string; 
  userData?: {
    username: string; 
  }
}

type AuthedSessionData = Required<SessionData>;
function getUserData(userType: {userType: "unauthorized"}) : SessionData 
function getUserData(userType: {userType: "admin" | "user" }) : AuthedSessionData 
function getUserData(userType: {userType: "unauthorized"} | {userType: "admin" | "user"}) : SessionData | AuthedSessionData {
  if(userType.userType === "unauthorized"){
    return {
      sessionId: "123"
    }
  }

  return {
    sessionId: "123", 
    userData: {
      username: "foo"
    }
  }
}



const result1 = getUserData({"userType": "unauthorized"})
const result2 = getUserData({"userType": "admin"}); // <-- Valid, but no autocomplete 

We can get the autocomplete behaviour back by converting to a generic:

type UserTypes<T extends "admin" | "user" | "unauthorized">  = {
 userType: T ; 
}


type SessionData = {
  sessionId: string; 
  userData?: {
    username: string; 
  }
}

type AuthedSessionData = Required<SessionData>;

function getUserData<T extends "admin" | "user" | "unauthorized"> (userType: UserTypes<T>) : T extends "unauthorized" ? SessionData : AuthedSessionData {
  if(userType.userType === "unauthorized"){
    return {
      sessionId: "123"
    } as T extends "unauthorized" ? SessionData : AuthedSessionData
  }

  return {
    sessionId: "123", 
    userData: {
      username: "foo"
    }
  }
}



const result1 = getUserData({"userType": "unauthorized"})
const result2 = getUserData({"userType": "admin"});

But as a rule of thumb, I prefer to use function overloads over generics - tends to be simpler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Experience Enhancement Noncontroversial enhancements Help Wanted You can do this Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

10 participants