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

Infer overload signatures from implementation bodies #10765

Closed
tekacs opened this issue Sep 7, 2016 · 7 comments
Closed

Infer overload signatures from implementation bodies #10765

tekacs opened this issue Sep 7, 2016 · 7 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@tekacs
Copy link

tekacs commented Sep 7, 2016

TypeScript Version: nightly (2.1.0-dev.20160906)

Code

function f(s: 'a'): string
function f(s: 'b'): number
function f(s: string): string | number {
    switch (s) {
        case 'a':
            return 'a';
        case 'b':
            return '2';
        default:
            return s;
    }
}

function g(x: string): number;
function g(x: number): string;
function g(x: string | number): string | number {
    if (typeof x === 'string') {
      return 'also a string';
    } else {
      return x.toString();
    }
}

// Math.sqrt(f('a')) // => Error
Math.sqrt(f('b')) //=> Compiles, to Math.sqrt(f('b')) == Math.sqrt('2')

Expected behavior: fails typechecking f(s: 'b') is annotated as returning a number, but returns a string in the implementation.

Actual behavior: typechecks successfully

I understand that the lattermost function in each case should typecheck as an isolated unit, only I would have hoped that this could be checked or at least warned about.

Given that we can throw an error in the below example, I think it should be possible to typecheck the above examples:

function h(x: string | number | boolean): number {
  if (typeof x === 'number') {
    return x;
  } else {
    return parseInt(x); // Error: Argument of type 'string | boolean' is not assignable to parameter of type 'string'.
  }
}

Edit: tsconfig.json is { "compilerOptions": { "strictNullChecks": true, "noImplicitAny": true } }

@tekacs
Copy link
Author

tekacs commented Sep 7, 2016

Happy to be pointed to an existing discussion/issue/explanation since this has surely come up before. I did try searching in earnest but didn't notice anything.

I would have hoped to be able to implement:

type S = 'a' | 'b';
function f(s: 'a'): string {
  return 'a';
}
function f(s: 'b'): number {
  return 2;
}

// Perhaps with a necessity for:
function f(s: S): never {
  throw new Exception("this can't happen");
}

Which allows all the functions to typecheck as units and has enough information to emit as a switch statement, but I imagine this is either stylistically inappropriate or doesn't work in all cases?

@tekacs tekacs changed the title Unsoundness in string literal function overloading? Unsoundness in function overload returns? Sep 7, 2016
@DanielRosenwasser
Copy link
Member

Function overloads are only related on signatures. The idea is that we trust the implementation signature as written. It's up to the user to deliver on the contract of each overload.

It's not obvious how we'd relate the implementation signature and the implementation body for every overload. It seems pretty non-trivial, even with our control flow analysis.

@tekacs
Copy link
Author

tekacs commented Sep 8, 2016

I see - thanks for responding. :)

Just to throw one proposal out there, I'd be curious to hear your comments on allowing overloading in the manner of my second comment here - that certainly provides a fairly clear cut path to relate the implementation and body.

I understand that there's definitely room for complexity around duplicate implementation bodies (I don't believe that's possible at all right now), but is there any chance that could work?


To add some context, I'm proposing that the syntax as specified there would compile to this singular implementation body:

function f(s: 'a' | 'b'): string | number {
  switch (s) {
    case 'a':
      return 'a';
    case 'b':
      return 2;
  }
}

In the case of string literal overloading (and tagged unions, as below), this seems to be completely possible to generate and for the implementation functions to be typechecked.

Relatedly, it would then be possible to provide overloading resembling that of other languages for tagged union members:

interface Cartesian {
  kind: 'cartesian';
  x: number;
  y: number;
}

interface Polar {
  kind: 'polar';
  r: number;
  theta: number;
}

type Coords = Cartesian | Polar;

function distance(c: Cartesian): number {
  return Math.sqrt(c.x*c.x + c.y*c.y);
}

function distance(p: Polar): number {
  return p.r;
}

// Again I'm including this because I don't know a good way to indicate the common parent type.
function distance(co: Coords): never {}

Which compiles to a switch statement as above, makes it easy to associate body and return type in solving the problem raised in this issue and reads clearly, resembling conventional overloading in many other languages.

(I'm aware this is a lot more than this issue started out accounting for - as this morphs towards suggestion I'm happy to move this elsewhere, if it gets to the point of being entertained.)

@yortus
Copy link
Contributor

yortus commented Sep 8, 2016

@tekacs the idea in your last comment was proposed and discussed quite a lot in #3442. It was closed as 'out of scope' with reasons given here.

@DanielRosenwasser DanielRosenwasser changed the title Unsoundness in function overload returns? Infer overload signatures from implementation bodies Sep 8, 2016
@DanielRosenwasser DanielRosenwasser added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Sep 8, 2016
@tekacs
Copy link
Author

tekacs commented Sep 8, 2016

Thanks for pointing me to that. I'll see if I can put together a proposal that doesn't trip any alarm bells, perhaps. What I propose above is dramatically simpler than that the general cases shot down in the thread and has no non-explicit RTTI or overhead.

Certainly in (say) Scala, some behaviour can only act on sealed traits, so a heavily restricted form of that proposal that gives many of the benefits without tripping not-pretty-code (4) and potentially the other two concerns might be possible (only allowing overloading on string literal types and tagged unions, say). :)

Given that without a fleshed out proposal the behaviour I highlighted in my opening post here is considered 'known', should this thread be closed?

@tekacs tekacs closed this as completed Sep 8, 2016
@tekacs tekacs reopened this Sep 8, 2016
@tekacs
Copy link
Author

tekacs commented Sep 8, 2016

I just noticed the title change that you made. I'll look into writing a thorough proposal in line with the guidelines and TypeScript's core set of language rules/intentions and bring it back to this thread.

@RyanCavanaugh RyanCavanaugh added Declined The issue was declined as something which matches the TypeScript vision and removed Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Jun 23, 2021
@RyanCavanaugh
Copy link
Member

There's at least a few duplicates out there, but the TL;DR is that we've decided to not implement this functionality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants