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.
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;
global:: is interesting. C++ has ::x to retrieve an x in the global namespace, so it sort of reminds me of that.
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.
:+1: for a way to explicitly resolve global scope
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
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.
I could get behind either global::foo or ::foo
:+1: 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
The main objection to :: is that it looks bad in a type position:
var x: ::Error;
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.
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).
I guess the problem with using window.X, global.X or any other
global:: :+1:
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) {
}
}
}
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;
}
global sounds like a useful way of controlling what /which lib.d.ts
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.
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.
Internal modules seem to cause more pain than joy. So I am :cool: with this.
@basarat I'm curious why you say that. I've found internal modules to be the only sane way to have a library with the following characteristics:
d.ts files work with no modificationgenerated d.ts files work with no modification
This will be true also for external modules once we have https://github.com/Microsoft/TypeScript/issues/2338#issuecomment-95006284 i.e. the .d.ts:
export interface SomeOldInterface {
foo: string;
}
export declare function something(): SomeOldInterface;
Will be requireable.
painless process of compile -> cat -> minify -> distribute
For _small_ libs yes. But bigger look at : https://github.com/TypeStrong/atom-typescript/blob/master/docs/out.md
And a recent example : https://github.com/Microsoft/TypeScript/pull/2917#issuecomment-96289551 in the TypeScript compiler src (which isn't too many files).
For small libs yes. But bigger look at : https://github.com/TypeStrong/atom-typescript/blob/master/docs/out.md
Oh, I'm aware of that pain :)
https://github.com/phosphorjs/phosphor/blob/master/gulpfile.js#L26
But, #2338 isn't available yet, and manually patching a .d.ts file is far more work than simply listing sources in the correct order. An argument could also be made for --out to automatically order the input files as well.
An argument could also be made for --out to automatically order the input files as well.
I guess we both agree though that #2338 is more important than this and then this is less _useful_ once #2338 is there.
We need to reconsider this. Consider the case in #3857 where you have a third-party external module X and a third-party internal module Y that declares types at the global scope. It's impossible to augment Y's types with types from X.
Reclose and open a new one on global type augmentation problem
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...
@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.
@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..
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};
@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.
@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
@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.
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 https://github.com/Microsoft/TypeScript/issues/1166
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?
no, but types are not an ES6 either.
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.
Yeah, where are we at with this? Is there a way to reference a global that collides with something inside the namespace or no?
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
Any progress on this front?
Most helpful comment
Yeah, where are we at with this? Is there a way to reference a global that collides with something inside the namespace or no?