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

Add globalThis #29332

Merged
merged 27 commits into from
Feb 27, 2019
Merged

Add globalThis #29332

merged 27 commits into from
Feb 27, 2019

Conversation

sandersn
Copy link
Member

@sandersn sandersn commented Jan 9, 2019

globalThis now has a name and has advanced to stage 3, so this PR adds it to Typescript. It's based on #22891, which we delayed until the proposal had come up with a name.

This change injects a global namespace symbol globalThis whose exports is the global symbol table. This enables much better checking.

// @Filename: one.ts
var a = 1;
var b = 2;
// @Filename: two.js
this.c = 3;
const total = globalThis.a + this.b + window.c + this.unknown;

If you need to refer to the global type, use typeof globalThis.

Open items:

  1. Targets below esnext need to emit a helper. I think we should use the non-eval helper specified in the spec.
  2. window should have type Window & typeof globalThis, and window property assignments (window.x = 1) should add to globals, at least at top-level, or when window is otherwise still bound to the global object. This is probably a big breaking change (in TSJS-lib-generator no less), and should be tested separately.
  3. I'm not sure whether globalThis should print specially, at least on its own. Right now it just prints like any other namespace as far as I can tell.
  4. In modules, this: undefined is correct, not this: typeof globalThis. However, this is probably a big breaking change. It should be tested separately.

Notes:

  1. The symbol injection approach means that globalThis is always available, no matter what the target emit is. I'll add code to downlevel globalThis to this (or a helper) in a future PR.
  2. Because globalThis is a namespace, typeof globalThis doesn't have an index signature even though Typescript currently allows arbitrary property and element accesses on global this. I added special-case code in checkPropertyAccessExpression to allow arbitrary property accesses, and it gives an error when noImplicitAny is on.
  3. I kept a noImplicitThis error when an arrow function uses the global this. Strictly speaking this isn't of type any, but typeof globalThis, but it's still not good code; people should use globalThis.x instead of this.x because it's less confusing.
  4. This PR also adds binder code to treat this.x = 12 property assignments as an equivalent declaration to var x = 12 at the global scope in JS. This code is based on the previous PR Bind toplevel this assignments as global declarations #22891.

With one or two additional comments
Still need to

1. Make it work in Typescript.
2. Add test (and make them work) for the other uses of GlobalThis:
window, globalThis, etc.
Lots of tests still fail, but all but 1 change so far has been correct.
A couple of tests still fail and need to be fixed.
The type reference must be `typeof globalThis`. Just `globalThis` will
be treated as a value reference in type position -- an error.
I left the noImplicitThis rule for captured use of global this in an
arrow function, even though technically it isn't `any` any more --
it's typeof globalThis.  However, you should still use some other method
to access globals inside an arrow, because captured-global-this is super
confusing there.
I ran into a problem with intersecting `Window & typeof globalThis`:

1. This adds a new index signature to Window, which is probably not
desired. In fact, with noImplicitAny, it's not desired on globalThis
either I think.
2. Adding this type requires editing TSJS-lib-generator, not this repo.

So I added the test cases and will probably update them later, when
those two problems are fixed.
I decided I didn't like the import-type-based approach.

Update baselines to reflect the difference.
@weswigham
Copy link
Member

It also means that globalThis can be shadowed, but because of the way I injected it, you cannot say globalThis.globalThis.globalThis.a

(typeof globalThis)["globalThis"] should work fine, right?

src/compiler/checker.ts Outdated Show resolved Hide resolved
src/compiler/checker.ts Outdated Show resolved Hide resolved
@Jessidhia
Copy link

Jessidhia commented Jan 10, 2019

I kept a noImplicitThis error when an arrow function uses the global this. Strictly speaking this isn't of type any, but typeof globalThis, but it's still not good code; people should use globalThis.x instead of this.x because it's less confusing.

I assume this is specifically about sloppy mode code, right? In strict mode, or in modules, the global this should be undefined, not any or globalThis.

@sandersn
Copy link
Member Author

From later discussion, I noticed that window.x = 12, at least at top-level in a script, should result in binding a global variable 'x'.

@sandersn
Copy link
Member Author

@Kovensky you are right but that would probably be a big breaking change. It'll be easier to test in a separate PR, so I will do it that way. It would be a good idea to ship that change in the same version as this one, though.

globalThis is no longer constructed lazily. Its synthetic Identifier
node is also now more realistic.
src/compiler/checker.ts Outdated Show resolved Hide resolved
src/lib/esnext.globalthis.d.ts Outdated Show resolved Hide resolved
@@ -12,4 +12,4 @@
//// // different 'this'
//// function f(this) { return this; }

verify.singleReferenceGroup("this");
verify.singleReferenceGroup("module globalThis\nthis: typeof globalThis");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. We might want a custom way to display the symbol associated with the global scope. Like just (global) globalThis?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the current state, but isn't there some value to printing typeof globalThis ? It teaches people what type they have to write to refer to the type of globalThis.

Copy link
Member

@weswigham weswigham Jan 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eh? I don't really feel too strongly about it. It just felt like for something as core as a reference to the global scope, something a little more clear then module globalThis; this: typeof globalThis may be warranted. It's a unique object and so maybe deserves unique output.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't expect, and don't like, module globalThis. I'll figure out why it appears.

On the other hand, globalThis is a unique object, but one whose binding acts like any other global variable, so it weirds me out a little to say that this should display as globalThis at top-level. I kind of like saying that this has the type typeof globalThis instead.

Let's discuss at the design meeting. I could go either way.

@sandersn
Copy link
Member Author

I just ran the user tests and nothing failed there. That's a good sign.

@sandersn
Copy link
Member Author

Notes from Design meeting:

  1. Need to forbid assignments to nested globalThis references: globalThis.globalThis = 1. This applies to other global consts; they should be readonly properties.
  2. window property assignments seem weird and should definitely only work for JS.
  3. A good, general polyfill seems out of reach, so we will ship without one at first. So I need to add a test that ensures that var globalThis = this/window/self works.
  4. The type of globalThis should print as typeof globalThis. (this isn't my memory of our decision, but it's what Daniel wrote?)

@IanYates
Copy link

Is this the same as what global::: can solve in C#?

That is, the outerWobbler variable in namespace PluginA can now be defined of type IWobbler with the help of globalThis below?

namespace Data {

    export namespace Components {
        export interface IWobbler {
            wobble(): void;
        }
    }
}

////IN SOME OTHER FILE

namespace PluginA {

    var outerWobbler: globalThis.Data.Components.IWobbler = undefined;

    //this namespace breaks outerWobbler since you can't reference Data.Components.IWobbler
    export namespace Data {

        //other stuff for PluginA's Data 

    }

}

Workaround at the moment is something like

////IN SOME OTHER FILE - HACKY WORKAROUND

type AliasForIWobbler = Data.Components.IWobbler;

namespace PluginB {
    var outerWobbler: AliasForIWobbler = undefined;
    export namespace Data {
        //other stuff for PluginB's Data 

    }
}

@sandersn
Copy link
Member Author

@IanYates This is already available as global on node and this or window (sort of mostly) in the browser. globalThis is just a standard name for it.

This PR adds a type for the global namespace so you get that instead of any. That is, the example you posted works today if you substitute global for globalThis, except you don't get completions. After this PR, you will.

@sandersn
Copy link
Member Author

All right, I think this is ready to go. @weswigham Mind taking another look?

src/compiler/checker.ts Outdated Show resolved Hide resolved
src/testRunner/unittests/tsserver/projects.ts Show resolved Hide resolved
src/services/completions.ts Outdated Show resolved Hide resolved
tests/cases/fourslash/findAllRefsThisKeyword.ts Outdated Show resolved Hide resolved
1. Add parameter to tryGetThisTypeAt to exclude globalThis.
2. Use combined Module flag instead combining them in-place.
3. SymbolDisplay doesn't print 'module globalThis' for this expressions
anymore.
if (outsideThis) {
addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container));
const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container);
if (noImplicitThis) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than switching on noImplicitThis out here, shouldn't we use errorOrSuggestion (switching on noImplicitThis) instead of error so we get suggestions for these issues even when noImplicitThis is off?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestions are only used for triggering codefixes, I think. And there aren't any codefixes for these errors. If both of these are true, then let's just wait until we have a codefix for them.

And I can't think of a good codefix for any of the errors except perhaps "The containing arrow function captures the global value of this", which would convert the arrow function to a function expression.

@@ -19343,6 +19362,12 @@ namespace ts {
if (isJSLiteralType(leftType)) {
return anyType;
}
if (leftType.symbol === globalThisSymbol) {
if (noImplicitAny) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, but for noImplicitAny.

@dperetti
Copy link

Apparently the current implementation only works when variables are defined in a global file.
But doesn't this mean that we cannot use globalThis with imported types?

Currently, I'm using window as follows (like everyone apparently!). How can this be achieved with globalThis?

declare global {
  interface Window {
    MyAppStores: {
      FileStore: FileStoreI;
    }
  }
}
...
window.MyAppStore = { FileStore }

@diondirza
Copy link

diondirza commented Feb 25, 2021

Apparently the current implementation only works when variables are defined in a global file.
But doesn't this mean that we cannot use globalThis with imported types?

Currently, I'm using window as follows (like everyone apparently!). How can this be achieved with globalThis?

declare global {
  interface Window {
    MyAppStores: {
      FileStore: FileStoreI;
    }
  }
}
...
window.MyAppStore = { FileStore }

@dperetti you can try this

interface AppStore  {
    FileStore: FileStoreI;
}

declare global {
  var MyAppStores: AppStore;
}

Both window and globalThis will recognize the type for that variable. For typing globalThis you should use var not let or const

@smac89
Copy link

smac89 commented Jun 16, 2022

It doesn't seem there is a good way to extend globalThis using an interface.

interface globalThis extends Window {}

The above keeps failing with:

Declaration name conflicts with built-in global identifier 'globalThis'

I'm on typescript version 4.1.

Also I was expecting that extending Window will propagate the properties to globalThis, but it doesn't:

interface RuntimeGlobals {}
interface Window extends RuntimeGlobals {}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants