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

Use static interfaces for Ambient declarations in lib.d.ts #182

Closed
basarat opened this Issue Jul 22, 2014 · 21 comments

Comments

Projects
None yet
@basarat
Copy link
Contributor

basarat commented Jul 22, 2014

Ported from : https://typescript.codeplex.com/workitem/1085

Some core ambient vars already do this when the only interface is the static one e.g. JSON : https://github.com/Microsoft/TypeScript/blob/master/src/lib/core.d.ts#L916-L959

Would be nice to do the name for others like String, Date perhaps calling them StringStatic and DateStatic. It is need for definitions for quite a few JavaScript libraries.

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

RyanCavanaugh commented Jul 22, 2014

One reason we didn't do this in v1.0 was because we had a hard time deciding on good names for the types (StringStatic ?) that are otherwise unnamed. What do people like here?

@vvakame

This comment has been minimized.

Copy link
Contributor

vvakame commented Jul 22, 2014

I think we need declaration merging for object type literal. (like as interface)

declare var String: {
  concat(other: string): string;
};
declare var String: {
  trim(): string;
};
@basarat

This comment has been minimized.

Copy link
Contributor

basarat commented Jul 22, 2014

For jQuery the original (and current) def uses jQueryStatic

@Bartvds

This comment has been minimized.

Copy link

Bartvds commented Jul 22, 2014

@vvakame And class declaration should be re-openable.

As class in TS is a prototype in JS, and prototypes in JS are extensible then classes in TS should be too right? It sounds a bit horrible but it it matches reality.

Although extending native types is generally considered a bad idea it is possible, and it is done a lot, so it's the user choice and the system should support it.

Then make String and Date and Array a class and be able to append static and instance members to it.

@johnnyreilly

This comment has been minimized.

Copy link

johnnyreilly commented Jul 22, 2014

I agree with the points made here by @vvakame and @Bartvds. We need to be able to model reality - not just good practice.

I think the naming suggestions by @basarat are fine @RyanCavanaugh. Hopefully with the existing example of jQueryStatic this would seem like a pretty seemless and sensible change.

@Bartvds

This comment has been minimized.

Copy link

Bartvds commented Jul 22, 2014

Nice practical example just now:

While migrating some JavaScript code to TypeScript I try to extend Error: first I cannot extend my class from Error because in the def it is not a class (weird).

So I just re-implement the interface but then the Error.captureStackTrace() static method is not defined and I cannot add it to the Error type definition because it is closed.

@RandScullard

This comment has been minimized.

Copy link

RandScullard commented Jul 31, 2014

Let's keep it simple for now and just change lib.d.ts to use interfaces for Array, String, etc. This would solve the problem of integrating with these third-party libraries without needing to change the language. I vote for ArrayStatic, StringStatic, and so on, since it already seems to be a common pattern.

@bgever

This comment has been minimized.

Copy link

bgever commented Oct 7, 2014

@series0ne

This comment has been minimized.

Copy link

series0ne commented Nov 27, 2014

Personally I agree with the comments stating that classes and declare var should be open-ended, like interfaces.

In the short term I think using interfaces as a workaround for extending static capability of built in objects works. For my own project I've defined these like so:

interface _Object {
    new (value?: any): Object;
    (): any;
    (value: any): any;
    ....
}
interface Object {
    constructor: Function;
    toString(): string;
    toLocaleString(): string;
    ....
}
declare var Object: _Object;

In the long term however, I don't think that using interfaces for static functions is a good idea (not very OO in my opinion), so open-ending classes and declare var seems appropriate

@saschanaz

This comment has been minimized.

Copy link
Contributor

saschanaz commented Nov 30, 2014

Are there still some big problems other than the naming one?

@mhegazy

This comment has been minimized.

Copy link
Contributor

mhegazy commented Nov 30, 2014

should be handled in #987

@series0ne

This comment has been minimized.

Copy link

series0ne commented Nov 30, 2014

Again, I don't think you should use interfaces to declare static members...

Doesn't seem very OOP
Pollutes intellisense with interfaces that aren't proper interfaces
Can't (and shouldn't) be implemented by 3rd party code

Seems more appropriate to either just open-end declare var or extend the language spec - in this respect, consider the following code:

extern class Object {
    static new (value?: any): Object;
    static (): any;
    static (value: any): any;
    ...
    constructor: Function;
    toString(): string;
    toLocaleString(): string;
    ...
}

The example above illustrates the potential use of an "extern" keyword, which would in this case, notify typescript compiler that the implementation is external (or built in) rather than provided in the source code.

In this respect, "extern class" would alleviate the need for an interface and a declare var for a particular object, since both would be handled within the "extern class" definition - In this respect "extern class" would need to be open ended to allow 3rd party definitions to be added. Below is an example of how this might be achieved:

//Declared in lib.d.ts
extern class Object {
    static new (value?: any): Object;
    static (): any;
    static (value: any): any;
    ...
    constructor: Function;
    toString(): string;
    toLocaleString(): string;
    ...
}
// Declared in 3rd party code
extern class Object {
    // Defines the ES6 experimental "is" function
    static checked is(value1: any, value2: any): boolean {
        // provides ES6 Object.is polyfill implementation
    }
    // Implements a custom user method
    static test(value: string) {
        console.log(value);
    }
}

You can see in the code above, two slightly different static implementations; This is to allow developers to bind 3rd party functions to objects in different ways.

Firstly, "Object.is" is defined with a "checked" keyword, to signify to the TypeScript compiler that this should be implemented as a polyfill, i.e.

if(!Object.is) {
    Object.is = function(value1, value2) {
        return someBool;
    }
}

Secondly "Object.test" is implemented in a non polyfill manner, i.e.

Object.test = function(value) {
    console.log(value);
}

Whilst I understand that there may be some unseen caveats with these examples, I'm hoping it might spark some new ideas into how these issues might be resolved, and in doing so, also add some more functionality to the typescript platform, with regards to polyfills and custom implementations on existing (extern) objects.

@saschanaz

This comment has been minimized.

Copy link
Contributor

saschanaz commented Dec 1, 2014

@series0ne You may want to see these: #9 #819

I like checked keyword anyway 😄

@danquirk

This comment has been minimized.

Copy link
Member

danquirk commented Dec 1, 2014

Your extern keyword is just the existing declare modifier. The 'unseen caveats' you mention are the primary thing blocking this work (#819) from happening already, things like how overloads, constructors, instance vars, etc are combined/added.

@basarat

This comment has been minimized.

Copy link
Contributor

basarat commented Dec 9, 2014

Just noticed thanks to @DickvdBrink's answer on stackoverflow the master branch has StringConstructor and ObjectConstructor and others. Thanks guys! https://github.com/Microsoft/TypeScript/blob/master/src/lib/core.d.ts

interface StringConstructor {
    new (value?: any): String;
    (value?: any): string;
    prototype: String;
    fromCharCode(...codes: number[]): string;
}

/** 
  * Allows manipulation and formatting of text strings and determination and location of substrings within strings. 
  */
declare var String: StringConstructor;
@DickvdBrink

This comment has been minimized.

Copy link
Contributor

DickvdBrink commented Dec 10, 2014

@basarat, +1 :)

@series0ne

This comment has been minimized.

Copy link

series0ne commented Dec 10, 2014

@basarat alarm bells ring when I see something that is meant to be implemented into a class being use this way, however just out of curiosity, can StringConstructor be implemented as part of a class or does it contain special functions that can't be implemented?

@DanielRosenwasser

This comment has been minimized.

Copy link
Member

DanielRosenwasser commented Apr 21, 2015

It looks like this is fixed.

@basarat

This comment has been minimized.

Copy link
Contributor

basarat commented Apr 21, 2015

StringConstructor be implemented as part of a class or does it contain special functions that can't be implemented

@series0ne Can't be implemented by a class. But stuff like this can be provided by a function : http://stackoverflow.com/questions/25340601/typescript-hybrid-type-implementation

👍 For closing. Haven't had any issues lately 🌹

@andrewvarga

This comment has been minimized.

Copy link

andrewvarga commented May 4, 2015

This looks like fixed, but how do I go about adding a custom static property on an inbuilt class?
The concrete example would be to add type info to be able to use this:
Element.ALLOW_KEYBOARD_INPUT

While I've decided not to use this any more (only worked in safari, seemingly doesn't even work there any more), it's a good example because Element doesn't have this defined in lib.d.ts.

@Draccoz

This comment has been minimized.

Copy link

Draccoz commented Sep 3, 2017

Hey there. I know this issue is long closed now, but don't know if I should open a new issue for that or maybe I'm just missing something.
I wanted to extend CustomEvent constructor, to add a custom interface for detail when given specific event name (need to do that for addEventListener too). For example:

interface CustomEvent {
  new(typeArg: 'my-event', eventInitDict?: CustomEventInit & { detail: MyEventDetail }): CustomEvent;
}
interface GlobalEventHandlersEventMap {
  "my-event": Event & { detail: MyEventDetail };
}

Is there a chance to achieve that? As far as I can see CustomEvent is declared as var:

declare var CustomEvent: {
    prototype: CustomEvent;
    new(typeArg: string, eventInitDict?: CustomEventInit): CustomEvent;
};

So far I have played around but couldn't make it to work :(.

@Microsoft Microsoft locked and limited conversation to collaborators Jun 18, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.