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 to add a call signature to the Mapped Type OR to remove all Function.prototype methods #27575

Open
4 tasks done
anurbol opened this issue Oct 5, 2018 · 11 comments
Open
4 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

@anurbol
Copy link

anurbol commented Oct 5, 2018

Search Terms

add a call signature to the Mapped Type; remove all Function.prototype methods

Suggestion

Being able to create a callable type, but without Function.prototype methods.

Use Cases

Imagine one creates some API that exposes a function that is (because function is an object) also have some custom properties. This function is not supposed to be call-ed, apply-ed, bind-ed, etc, in fact all the Function.prototype methods are removed from it (e.g. with setPrototypeOf..null). However IDE will still suggest the Function.prototype methods.

Desired behavior:

let callableObject = () => 'foo'
Object.setPrototypeOf(callableObject, null)
callableObject.bar = 'baz'

callableObject() // 'foo'
callableObject // {bar: 'baz'}
callableObject.call // <---------- error

Current behavior:

let callableObject = () => 'foo'
Object.setPrototypeOf(callableObject, null)
callableObject.bar = 'baz'

callableObject() // 'foo'
callableObject // {bar: 'baz'}
callableObject.call // <---------- OK

What I tried:

type ExcludeFunctionPrototypeMethods<T extends () => any> = {
    [K in Exclude<keyof T, keyof Function>]: T[K]
}
// the type above "lacks a call signature" and I have no Idea how to add it there.

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. new expression-level syntax)
@RyanCavanaugh
Copy link
Member

type Unfunction<T> = T & { call: null; apply: null; bind: null; }

function getUnfunction<T extends Function>(callableObject: T): Unfunction<T> {
    Object.setPrototypeOf(callableObject, null);
    return callableObject as Unfunction<T>;
}



let callableObject = getUnfunction(() => 'foo');

callableObject() // 'foo'
callableObject // {bar: 'baz'}
callableObject.call() // <---------- error

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Oct 8, 2018
@RyanCavanaugh
Copy link
Member

You could also use never in place of null

@anurbol anurbol closed this as completed Oct 17, 2018
@trickeyd
Copy link

trickeyd commented Jan 13, 2020

The problem with this method is that all the prototype props still exist on the type, just as 'never'. I feel there should be a way to explicitly 'mask' types out completely so that they don't appear in the type at all - for the sake of a clean api.

Been talking about it here:
https://stackoverflow.com/questions/59700434/typescript-omitting-a-function-prototype-from-a-type-extending-any?noredirect=1#comment105570732_59700434

this is kinda linked too:
#29261

@fwh1990
Copy link

fwh1990 commented Jul 10, 2020

I agree with @anurbol . Sometimes we have custom property on function like fn.foo = 123, and we want user see this property from IDE tips, however, so many unexpected properties like apply | call | bind | arguments ... are shown, in other words fn.foo is hidden from them, user is very hard to find custom things in a second.

@RyanCavanaugh Maybe this issue should not be closed.

@nth-commit
Copy link

+1

Trying to create a concise function builder, and having the interesting methods polluted by the function prototype.

@anurbol anurbol reopened this Sep 16, 2020
@RyanCavanaugh RyanCavanaugh added 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 and removed Question An issue which isn't directly actionable in code labels Sep 16, 2020
@zachkirsch
Copy link

+1 would love to exclude extra these function properties to make the IDE experience better

@atablash
Copy link

We could really use a feature where mapped types would not strip call and construct signatures or there would be a new mapping type that would perform mapping on call and/or construct signatures instead of (or in addition to) regular property mapping.

For callables, this would be very useful for implementing any sort of callable interfaces.

For newables, perhaps more common, you could perform property mappings on constructors with additional properties (classes with static fields) - without loosing the construct signature.

@Krombik
Copy link

Krombik commented May 20, 2023

I found a workaround:

declare class SomeName {
  static someField: any;
}

type Identity<T> = T;

interface SomeName extends Identity<typeof SomeName> {
  (...args: any[]): any;
}

const someName: SomeName

this solution allows someName to be called and the someField property to be first in the suggestion list

@geoffreytools
Copy link

geoffreytools commented Sep 3, 2023

I like @Krombik's workaround and I think this should be the default display order for properties on callable objects.

A big limitation though is that someField needs to be static, so it cannot reference any type parameter on SomeName.

I feel that it is generally a terrible idea to remove properties and methods from the function prototype just to get cleaner tooltips, unless the implementer truly has a good reason to prescribe how users should call their functions.

@rotu
Copy link

rotu commented Dec 25, 2023

@RyanCavanaugh this breaks in TS 4.3.5, where it deduces the type of callableObject as never (I think it's actually broken in 3.9.7, but there is no visible error on calling never). playground link

Is there an existing issue for this?

type Unfunction<T> = T & { call: null; apply: null; bind: null; }

function getUnfunction<T extends Function>(callableObject: T): Unfunction<T> {
    Object.setPrototypeOf(callableObject, null);
    return callableObject as Unfunction<T>;
}



let callableObject = getUnfunction(() => 'foo');

callableObject() // 'foo'
callableObject // {bar: 'baz'}
callableObject.call() // <---------- error

@jcalz
Copy link
Contributor

jcalz commented Jul 31, 2024

We could really use a feature where mapped types would not strip call and construct signatures or there would be a new mapping type that would perform mapping on call and/or construct signatures instead of (or in addition to) regular property mapping.

For callables, this would be very useful for implementing any sort of callable interfaces.

For newables, perhaps more common, you could perform property mappings on constructors with additional properties (classes with static fields) - without loosing the construct signature.

#29261 seems to be the place for that

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