-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Description
Suggestion
π Search Terms
signature, method, function, overload, return, local, scope, type, define, declare, variable, anonymous, class
β Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
β Suggestion
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
Currently, using type inference, we're able to return unique types only declared locally:
export class Foo {
something() {
return class Bar {};
}
}Writing a return type is also possible by using typeof on a variable:
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:
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
Better editor completion with fallback types:
NOTE: This below example will work without the overloads, however, the editor won't suggest anything.
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 booleanNOTE: Currently, this behaviour is actually already possible for properties inside of objects using the
&operator: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:
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