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

Lib files leak "support" types into global namespace #15529

Closed
ericdrobinson opened this issue May 2, 2017 · 11 comments
Closed

Lib files leak "support" types into global namespace #15529

ericdrobinson opened this issue May 2, 2017 · 11 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@ericdrobinson
Copy link

TypeScript Version: 2.2.1 / nightly (2.2.0-dev.201xxxxx)

Description

Core ECMAScript lib declaration files (e.g. lib.es5.d.ts) leak "internal" interface types into the global namespace. A common pattern employed in the lib files is that "class"-static features are placed in a [TypeName]Constructor interface and then a const variable of that type is declared with the same TypeName as the [TypeName] interface. Example:

interface Object {
    constructor: Function;
    // ...
}

interface ObjectConstructor {
    new (value?: any): Object;
    (): any;
    (value: any): any;
    // ...
}

declare const Object: ObjectConstructor;

The problem is that the ObjectConstructor type is leaked into the global namespace. No such type is defined in the ECMAScript 5.1 language spec, and yet it clutters up auto-complete suggestions.

The [TypeName]Constructor pattern seems like a workaround to enable certain features to be expressed in the lib files. The leaking of these types seems to be a side-effect of this construction.

Expected Behavior

Including a core ECMAScript lib file into a project (whether by default or compilerOption) only adds types found in the associated ECMAScript specifications into the global namespace.

Actual Behavior

[TypeName]Constructor types end up in the global namespace for any project that includes a library utilizing such a pattern.

@ahejlsberg
Copy link
Member

The xxxConstructor types are only present in the global namespace for types which is a TypeScript specific concept and completely separate from the global namespace for values. Anything defined by the ECMAScript spec lives in the value namespace and the lib.d.ts files accurately reflect that. In other words, there is no "leakage". For example:

let x: ObjectConstructor;  // Ok, referenced as type name
let y = ObjectConstructor;  // Error, can't use a type name as a value

Declarations often introduce names in both namespaces. For example, a class declaration introduces a name in the value namespace (the constructor function) and the type namespace (the class instance type). This provides the illusion of a connection between the two, but they're really completely separate.

@ahejlsberg ahejlsberg added the Working as Intended The behavior described is the intended behavior; this is not a bug label May 2, 2017
@ericdrobinson
Copy link
Author

I'm still learning my way around declaration files, but wouldn't it be possible to put the [TypeName]Constructor interface declarations and const [TypeName] declarations into a "lib.es5.support.d.ts" module-based declarations file and then import the constants into the main libs?

Something like:

In lib.es5.support.d.ts:

declare interface ObjectConstructor {
    new (value?: any): Object;
    (): any;
    (value: any): any;
    // ...
}

export declare const Object: ObjectConstructor;

and in lib.es5.d.ts:

import { Object } from './lib.es5.support';

interface Object {
    constructor: Function;
    // ...
}

Something similar to this appears to work with a simple local test file (admittedly a declaration file and a TypeScript file, not declaration→declaration→JavaScript... though it seems portable)...

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented May 2, 2017

Not at the moment, because as soon as you include an import inside of a file, it becomes a module. That means lib.es5.d.ts would become a module (rather than being a true global, which it is).

That said, we could add support for referencing types within a global context (@yuit was looking into this), or we could even put all of these types within a global namespace called TS_Helper_Types or something. But at this point, moving these would be a breaking change.

I do share your concern. As the person who introduced TemplateStringsArray, I wasn't really happy about the fact that some global type would just always be present even though there's no runtime value that uses it. In reality, this whole thing turns out not to be not as much of a problem as you'd think for most TS users anyhow.

@ericdrobinson
Copy link
Author

The xxxConstructor types are only present in the global namespace for types which is a TypeScript specific concept and completely separate from the global namespace for values.

Yes, understood.

Anything defined by the ECMAScript spec lives in the value namespace and the lib.d.ts files accurately reflect that. In other words, there is no "leakage".

@ahejlsberg This I don't understand. When using the latest VSCode, I can type var x = new Obj and see both Object and ObjectConstructor in the auto-complete suggestions. This works for both JavaScript and TypeScript. With TypeScript, I see the same suggestions when writing let x:Obj.

As a user, I expect that including the ECMAScript spec in a project ("lib": ["es5"]) would fill the global "typespace" with ECMAScript-defined types only - I shouldn't see the "helper" types.

@DanielRosenwasser
Copy link
Member

@ericdrobinson, @mhegazy can better answer, but we introduce type-only suggestions to not be overly-strict. A lot of the time you'll end up in a state where it makes no sense semantically to suggest a type - however, the reality of it is that people write code in weird ways to take shortcuts (including myself). We could be more rigid about this.

@ericdrobinson
Copy link
Author

ericdrobinson commented May 2, 2017

In reality, this whole thing turns out not to be not as much of a problem as you'd think for most TS users anyhow.

@DanielRosenwasser I guess this speaks to one of the main reasons that I am so attracted to TypeScript as a language. When using it alongside JavaScript (or, more accurately, an ECMAScript-based language) it can significantly improve usability and discoverability of the language features. This (discoverability, especially) becomes incredibly muddied when you litter the global namespace with types like this.

@ericdrobinson
Copy link
Author

A lot of the time you'll end up in a state where it makes no sense semantically to suggest a type - however, the reality of it is that people write code in weird ways to take shortcuts (including myself). We could be more rigid about this.

@DanielRosenwasser When you say "be more rigid", do you mean on the IntelliSense side? The declaration implementation side? Or the TypeScript language feature side?

I don't have a problem with the flexibility. What's confusing, especially as someone new to the language, is why all these unexpected types show up. If you've only had JavaScript experience you may start poking around for ObjectConstructor when it comes up during autocomplete. A default google search for that reveals unhelpful information. Unfortunately, adding "typescript" to that search doesn't help much.

Personally, I didn't actually understand what ObjectConstructor was until I asked on a different issue. ( @mhegazy cleared it up for me ;D)

@mhegazy
Copy link
Contributor

mhegazy commented May 19, 2017

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@mhegazy mhegazy closed this as completed May 19, 2017
@ericdrobinson
Copy link
Author

ericdrobinson commented May 19, 2017

@mhegazy As this has issue has been closed because it was labeled as "Working as Intended", what is the appropriate way to issue a feature request?

Specifically, as a user, I'd prefer to have an option that would allow me to set up my environment in such a way that, by default, it only fills the type and value namespaces with content as outlined in the various specs associated with the libraries specified using the --lib compiler option. Providing the ability to create "helper types" that do not end up in the global namespaces to users would also be useful.

This will improve target language API discoverability and usability as listed types would theoretically have some form of useful results when google'd. This would also free up folks like @DanielRosenwasser to create new helper types like TemplateStringsArrayBecauseHowElseWouldYouDoThis without fear of others seeing his poetry. ;)

In short, I cannot think of a time that I would actually care about ObjectConstructor. Seeing it every time I try to specify a plain old Object gets old and only serves to increase the amount of filtering I have to do to get down to what I care about.

So... feature requests? Should I open a new ticket or is there another way to handle this?

@mhegazy
Copy link
Contributor

mhegazy commented May 19, 2017

yet it clutters up auto-complete suggestions.

For auto complete clutter, #15750 tracks filtering it.

Specifically, as a user, I'd prefer to have an option that would allow me to set up my environment in such a way that, by default, it only fills the type and value namespaces with content as outlined in the various specs associated with the libraries specified using the --lib compiler option.

the library file is not special, it is a rather normal .d.ts file. just create your own lib.d.ts file, and include it in your complication with --noLib.

@ericdrobinson
Copy link
Author

ericdrobinson commented May 22, 2017

Autocompletion Clutter

Thanks, @mhegazy, for the replies!

For auto complete clutter, #15750 tracks filtering it.
It seems to partially track it. Should I add follow-up suggestions/wishes? The issue I'm trying to raise is that many types created in declaration files are support types; types that are used to build other types which themselves are intended for actual consumption outside of the declaration files. This appears to be the case with the Class Decomposition approach to creating a consumable type that matches, for instance, an ECMAScript spec.

Your clarification appears to imply that the issue I'm attempting to raise here is not covered by #15750:

To clarify, this issue tracks filtering the entries in the completion list based on the location where the completion is requested. i.e. in a value postilion, type names are not returned, and in a type position, values names are not returned

Addressing this issue would not stop me from seeing ObjectConstructor show up when looking for type suggestions in a type position. Unless I'm missing something?

Global Declaration Library Files that Keep Implementation Details Private

the library file is not special, it is a rather normal .d.ts file. just create your own lib.d.ts file, and include it in your complication with --noLib.

I think you may have misunderstood my request. I understand that the library files are not special - they are "pre-built" global declaration files that attempt to describe the associated ECMAScript spec). The problem is that adding them brings in a whole bunch of mostly useless helper-types - types that typically come with no documentation as they are generally intended as declaration-"internal".

As a concrete example, if I specify es6 with the --lib option, then I expect to only see the values and types outlined in the ECMAScript spec itself appear in the list of suggested values and types. I do not expect (nor want) to see ObjectConstructor, which is not a type or value outlined in the spec.

Put another way: I'd like some way to mark a type as an "implementation detail" and not something that should bleed into the global type/value contexts. I think @DanielRosenwasser's comment was starting to get at this.

@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants