Repository: TypeScript
Issue: Enable returning locally defined type in signature overloads
Labels: Suggestion,Awaiting More Feedback

# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## 🔍 Search Terms

<!--
  💡 Did you know? TypeScript has over 2,000 open suggestions!
  🔎 Please search thoroughly before logging new feature requests as most common ideas already have a proposal in progress.
  The "Common Feature Requests" section of the FAQ lists many popular requests: https://github.com/Microsoft/TypeScript/wiki/FAQ#common-feature-requests

  Replace the text below:
-->

signature, method, function, overload, return, local, scope, type, define, declare, variable, anonymous, class

## ✅ Viability Checklist

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## ⭐ Suggestion

<!-- A summary of what you'd like to see added or changed -->

Signature overloads should allow the user to return types of objects/functions/classes defined inside the function body, as is currently allowed for non-overloads.

## 📃 Motivating Example

<!--
  If you were announcing this feature in a blog post, what's a short explanation that shows
  a developer why this feature improves the language?
-->

Currently, using type inference, we're able to return unique types only declared locally:
```ts
export class Foo {
  something() {
    return class Bar {};
  }
}
```

Writing a return type is also possible by using `typeof` on a variable:
```ts
export class Foo {
  something(): typeof Bar {
    const Bar = class {};

    return Bar;
  }
}
```

However, when using signature overloading, providing no return type would not fallback to inference (returning `any`); attempting to provide a reference to a local variable in the return type would throw a compile error:

```ts
export class Foo {
  something(): void;
  something(): typeof Bar; // Cannot find name 'Bar'. ts(2304)
  something(): typeof Bar | void {
    const Bar = class {};

    return Bar;
  }
}
```

> NOTE: I could only get the above return type code compiling on my local machine, the playground would throw the below error even with (from what I could tell) matching compiler versions (4.6.2) & configs.
> ```
> Return type of public method from exported class has or is using private name 'Bar'
> ```
> Any help in resolving this to get a valid example would be great.

## 💻 Use Cases

<!--
  What do you want to use this for?
  What shortcomings exist with current approaches?
  What workarounds are you using in the meantime?
-->

### Better editor completion with fallback types:
> NOTE: This below example will work without the overloads, however, the editor won't suggest anything.
```ts
export class Foo {
  something<T extends 'qux' | 'quux'>(arg: T): typeof Bar;
  something<T extends string>(arg: T): typeof Bar;
  something<T extends string>(arg: T): typeof Bar {
    const Bar = class {
      public baz = arg;
      public isQux = (arg === 'qux') as
        | string extends T
        ? boolean
        : T extends 'qux'
          ? true
          : false
    };

    return Bar;
  }
}

/* -------------------- Usage -------------------- */

// typing `new Foo().something('`
// the editor would give suggestions for `'qux' | 'quux'`
// whist accepting other `string` inputs

const qux = new (new Foo().something('qux'))();
qux.baz;        // asserts qux.baz is 'qux'
qux.isQux;      // asserts qux.isQux is true

const quux = new (new Foo().something('quux'))();
quux.baz;       // asserts quux.baz is 'quux'
quux.isQux;     // asserts quux.isQux is false

const hello = new (new Foo().something('hello'))();
hello.baz;      // asserts hello.baz is 'hello'
hello.isQux;    // asserts hello.isQux is false

const string = new (new Foo().something(String()))();
string.baz;     // asserts string.baz is string
string.isQux;   // asserts string.isQux is boolean
```

> NOTE: Currently, this behaviour is actually already possible for properties inside of objects using the `&` operator:
> ```ts
> export class Foo {
>   // new Foo().map.set('hello', 'works');
>   map:
>   | Map<'qux' | 'quux', unknown>
>     & Map<string, unknown>;
> }
> ```
> I, however, haven't found anything for methods; if there indeed is a more elegant solution, please do ridicule & educate me (::

### Differing constructed classes depending on a string argument:
```ts
export class Foo {
  something<T extends 'qux' | 'quux'>(arg: T): typeof Qux;
  something<T extends string>(arg: T):
    | string extends T
      ? typeof Qux | typeof NotQux
      : T extends 'qux'
        ? typeof Qux
        : typeof NotQux;
  something<T extends string>(arg: T): 
    | typeof Qux
    | typeof NotQux {
    const Bar = class {
      public baz = arg;
    };

    const Qux = class extends Bar {
      public override baz = arg;
      public readonly isQux = true;

      // ...more complicated props
    };

    const NotQux = class extends Bar {
      public override baz = arg;
      public readonly isNotQux = true;

      // ...more complicated props
    };

    return (arg === 'qux' ? Qux : NotQux) as
      | string extends T
        ? (typeof Qux | typeof NotQux)
        : T extends 'qux'
          ? typeof Qux
          : typeof NotQux;
  }
}

/* -------------------- Usage -------------------- */

const qux = new (new Foo().something('qux'))();
qux;              // asserts qux is Qux
qux.baz;          // asserts qux.baz is 'qux'
qux.isQux;        // asserts qux.isQux is true

const quux = new (new Foo().something('quux'))();
quux;             // asserts quux is NotQux
quux.baz;         // asserts quux.baz is 'quux'
quux.isNotQux;    // asserts quux.isNotQux is true

const hello = new (new Foo().something('hello'))();
hello;            // asserts hello is NotQux
hello.baz;        // asserts hello.baz is 'hello'
hello.isNotQux;   // asserts hello.isNotQux is true

const string = new (new Foo().something(String()))();
string;           // asserts string is NotQux
string.baz;       // asserts string.baz is string
string.isNotQux;  // asserts string.isNotQux is true
```