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

Global type references #983

Closed
ahejlsberg opened this issue Oct 28, 2014 · 38 comments · May be fixed by Woodpile37/TypeScript#10
Closed

Global type references #983

ahejlsberg opened this issue Oct 28, 2014 · 38 comments · May be fixed by Woodpile37/TypeScript#10
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@ahejlsberg
Copy link
Member

When a type declaration in an inner module has the same name as a type declaration in an outer module, the outer declaration becomes inaccessible. Programmers can typically work around this, for example by deriving a type with an alternate name in the outer module or, with our latest changes, by declaring a type alias. However, such manual work-arounds are not a good option for code generation tools such as the .d.ts generation facility provided by the compiler. We really need a fool proof way to ensure a lookup starts at the global scope. Consider:

function makeError(): Error {
    return new Error("We've got a problem");
}

module M {
    export class Error {
    }
    export var x = makeError();  // No way to name global Error type here
}

It is currently impossible to create a .d.ts file for the code above because there is no way to emit a type annotation for x that references the global interface Error.

Node.js uses the name global for the global scope. We could consider supporting something similar in type names. Specifically, if a qualified type name starts with the identifier global and if a lookup of global as a module name doesn't bind to anything (meaning the user didn't define a global module), then we consider it a globally qualified name. For the example above we could then generate the following .d.ts file:

declare module M {
    class Error {
    }
    var x: global.Error;
}

If course, if the user defines their own global module we now have a problem again--but I think it is fine to just say you shouldn't do that. We could even consider making it an error, or allowing it only under a compiler switch.

@ahejlsberg ahejlsberg added the Suggestion An idea for TypeScript label Oct 28, 2014
@ahejlsberg ahejlsberg added this to the Suggestions milestone Oct 28, 2014
@mhegazy
Copy link
Contributor

mhegazy commented Oct 31, 2014

how about a global modifier instead of reserving the name something like ``global::`:

declare module M {
    export class Error {
    }
    var x: global::Error;
}
var e: global::M.Error;

@DanielRosenwasser
Copy link
Member

global:: is interesting. C++ has ::x to retrieve an x in the global namespace, so it sort of reminds me of that.

@RyanCavanaugh
Copy link
Member

We just as much need to solve the problem for external modules, so we'd need another special identifier that meant "the containing external module". global makes sense for internal modules but I think we'll have a hard time finding a non-conflicting equivalent for external modules. Just using a different syntax seems like a cleaner solution at that point.

@sccolbert
Copy link

👍 for a way to explicitly resolve global scope

@basarat
Copy link
Contributor

basarat commented Dec 11, 2014

It is possible with a bit of _ab_use of declare var and typeof:

function makeError(): Error {
    return new Error("We've got a problem");
}

declare var GlobalError:Error;

module M {
    export class Error {
    }
    export var x:typeof GlobalError = makeError();  // captured global type Error
}

Related stackoverflow question: http://stackoverflow.com/a/27433864/390330

@ghost
Copy link

ghost commented Jan 12, 2015

I think the fact that these kinds of ugly workarounds are necessary illustrates the need for a supported method of accessing the global scope.

I could get behind either global::foo or ::foo.

@basarat
Copy link
Contributor

basarat commented Jan 13, 2015

I could get behind either global::foo or ::foo

👍 for ::foo

Reason: I don't like the confusion global will create with respect to something actually called global e.g. http://nodejs.org/api/globals.html#globals_global

@RyanCavanaugh RyanCavanaugh added the In Discussion Not yet reached consensus label Jan 13, 2015
@mhegazy
Copy link
Contributor

mhegazy commented Jan 14, 2015

The main objection to :: is that it looks bad in a type position:
var x: ::Error;

@cseufert
Copy link

Could we just use a leading . so .File or .Error, in the vein of PHP with \BuiltInClass.

Alternatively, when in a browser context, use window.File, or window.Error, since whats were they are declared, rather than adding a new syntax.

@basarat
Copy link
Contributor

basarat commented Feb 16, 2015

when in a browser context

They/I would really like to support the same syntax for browser/node/anything else. Runtime shouldn't define TypeScript semantics (or as little as possible).

I don't see a reason to not use . though (although there very well might be a technical reason out there).

@cseufert
Copy link

I guess the problem with using window.X, global.X or any other . is that there is always the possibility of someone clobbering the namespace, with a parent module. Especially when these modules can be declared in another file. (Just tested this is the case).

@basarat
Copy link
Contributor

basarat commented Feb 16, 2015

global:: 👍

@cseufert
Copy link

Does global still make sense to access a parent modules child? It is kind of foreign to typescript/javascript though.

// Strings.d.ts
module Strings {
  module Validator {
    class Text {
    }
  }
}

// Vendor1String.ts
module Vendor {
  module Strings {
    module Validator  {
      class Markdown extends global::Strings.Validator.Text {
      }
    }
  }
  class Go {
    runValidator(validator: global::Strings.Validator.Text) {
    }
  }
}

@jbondc
Copy link
Contributor

jbondc commented Mar 5, 2015

Like the idea but what does 'global' resolve to?

Could be interesting to be explicit about the 'global' being imported.

module M {
    import global from "@es6";

    class Error {
    }
    var x: global.Error;
}

@basarat
Copy link
Contributor

basarat commented Mar 5, 2015

global sounds like a useful way of controlling what /which lib.d.ts

@mhegazy mhegazy modified the milestone: Suggestions Apr 22, 2015
@RyanCavanaugh RyanCavanaugh added Declined The issue was declined as something which matches the TypeScript vision and removed In Discussion Not yet reached consensus labels Apr 27, 2015
@RyanCavanaugh
Copy link
Member

Declined, at least for now.

We're willing to revisit this if we see some more compelling cases where this is really necessary. Declaration file generation could be made marginally easier/more possible, but this doesn't seem to actually come up all that often.

@sccolbert
Copy link

The decision to decline this is disappointing. The name shadowing problem is not limited to types, run time entities are shadowed as well. If a particular symbol can be reached from regular Javascript code, (IMHO) it should also be reachable from Typescript.

@basarat
Copy link
Contributor

basarat commented Apr 28, 2015

Internal modules seem to cause more pain than joy. So I am 🆒 with this.

@RyanCavanaugh
Copy link
Member

Reclose and open a new one on global type augmentation problem

@bartzy
Copy link

bartzy commented Mar 1, 2016

What is the proposed solution for this right now in 1.8...?

I have my own custom Touch class which I import under the name Touch. And I also need to access the global Touch type in the same file...

@RyanCavanaugh
Copy link
Member

@bartzy you're fundamentally out of luck because JavaScript has lexical scoping. There's nothing TypeScript can do to let you access an outer thing of the same name.

@bartzy
Copy link

bartzy commented Mar 1, 2016

@RyanCavanaugh

Well, it must have some kind of solution besides just naming things differently.
What I did which worked on TS 1.7 but now fails on 1.8, is having a BrowserTypes.ts file where I only re-export:

export {Touch as BrowserTouch};

But on 1.8 I get this error: error TS2661: Cannot re-export name that is not defined in the module..

@mhegazy
Copy link
Contributor

mhegazy commented Mar 1, 2016

But on 1.8 I get this error: error TS2661: Cannot re-export name that is not defined in the module..

this is in accordance with the ES6 spec. see https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#exporting-non-local-names-from-a-module for more details.

To work around this, you need to have a local declaration, e.g:

const localTouch = Touch;
export {Touch as BrowserTouch};

@bartzy
Copy link

bartzy commented Mar 2, 2016

@mhegazy Thanks.

But now when I import it, I get this error: error TS2304: Cannot find name 'BrowserTouchEvent'.

This is my import line:

import {BrowserTouch, BrowserTouchEvent} from './BrowserTypes';

And this is BrowserTypes.ts:

const localTouch = Touch;
const localTouchEvent = TouchEvent;

export {localTouch as BrowserTouch, localTouchEvent as BrowserTouchEvent};

I also tried:

const BrowserTouch = Touch;
const BrowserTouchEvent = TouchEvent;
export {BrowserTouch, BrowserTouchEvent};

Thanks.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 2, 2016

@bartzy, I can not get a repro for your scenario.

c:\test\983>type BrowserTypes.ts
const localTouch = Touch;
const localTouchEvent = TouchEvent;

export {localTouch as BrowserTouch, localTouchEvent as BrowserTouchEvent};

c:\test\983>type app.ts
import {BrowserTouch, BrowserTouchEvent} from './BrowserTypes';

c:\test\983>tsc --v
Version 1.8.7

c:\test\983>tsc --m commonjs app.ts

c:\test\983>echo %ERRORLEVEL%
0

@bartzy
Copy link

bartzy commented Mar 2, 2016

@mhegazy, Try to use BrowserTouchEvent as a type to an argument in a function in app.ts.

I get error TS2304: Cannot find name 'BrowserTouchEvent'. for that.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 2, 2016

aah, it is not a type, it is a value.. see https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Declaration%20Merging.md#basic-concepts for more information..

What you want is to export both:

const localTouchEvent = TouchEvent; // only value
type  localTouchEvent  = TouchEvent; // only type

export {localTouchEvent as BrowserTouchEvent}; // export both value and type

ideally you would use import localTouchEvent = TouchEvent which will get you "all" meanings of TouchEvent, but this is not supported at the moment, see #1166

@bartzy
Copy link

bartzy commented Mar 2, 2016

Thanks, it works!

I was under the impression that const localTouchEvent = TouchEvent does capture the type as well.
I also didn't get what syntax is import localTouchEvent = TouchEvent? It's not ES6 syntax, right?

@mhegazy
Copy link
Contributor

mhegazy commented Mar 2, 2016

no, but types are not an ES6 either.

@rjamesnw
Copy link

rjamesnw commented Apr 5, 2017

Just checking - is this still an issue, or is there a solution to this yet? I used to have issues access types using some kind of fully qualified naming approach (as Anders suggested), but not sure where things stand now after much time has passed.

@mnpenner
Copy link

mnpenner commented Aug 3, 2017

Yeah, where are we at with this? Is there a way to reference a global that collides with something inside the namespace or no?

@fly-studio
Copy link

As this problem

// a 3rd lib
namespace eui {
    class IAssetAdapter {}
}
//My lib
namespace layer.eui {

   // the eui below, is not a global namespace,
   // it defines to layer.eui.IAssetAdapter
   class AssetAdapter extends eui.IAssetAdapter {  
   }
}

I known how to close this error, like this, but it'll export a global variant euiIAssetAdapter

import euiIAssetAdapter = eui.IAssetAdapter
namespace layer.eui {
  class XXX entends euiIAssetAdapter  {
 }
}

Do this have a root namespace symbol, like PHP \eui\IAssetAdapter or ::eui.IAssetAdapter

aomarks added a commit to Polymer/polymer that referenced this issue Feb 1, 2018
Polymer defines its own `Element` class, shadowing the standard global
`Element` class. This means that references to `Element` within the
`Polymer` namespace inadvertently reference `Polymer.Element`. Here we
define an alias of the global `Element`, so that we can reference it
from declarations within the `Polymer` namespace.

See microsoft/TypeScript#983 for general
discussion of this shadowing problem in TypeScript.

Fixes #5074
aomarks added a commit to Polymer/polymer that referenced this issue Feb 1, 2018
Polymer defines its own `Element` class, shadowing the standard global
`Element` class. This means that references to `Element` within the
`Polymer` namespace inadvertently reference `Polymer.Element`. Here we
define an alias of the global `Element`, so that we can reference it
from declarations within the `Polymer` namespace.

See microsoft/TypeScript#983 for general
discussion of this shadowing problem in TypeScript.

Fixes #5074
aomarks added a commit to Polymer/polymer that referenced this issue Feb 1, 2018
Polymer defines its own `Element` class, shadowing the standard global
`Element` class. This means that references to `Element` within the
`Polymer` namespace inadvertently reference `Polymer.Element`. Here we
define an alias of the global `Element`, so that we can reference it
from declarations within the `Polymer` namespace.

See microsoft/TypeScript#983 for general
discussion of this shadowing problem in TypeScript.

Fixes #5074
@inad9300
Copy link

Any progress on this front?

@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.