Skip to content

Add globalThis - #29332

Merged
Nathan Shively-Sanders (sandersn) merged 27 commits into
masterfrom
add-globalThis
Feb 27, 2019
Merged

Add globalThis#29332
Nathan Shively-Sanders (sandersn) merged 27 commits into
masterfrom
add-globalThis

Conversation

@sandersn

@sandersn Nathan Shively-Sanders (sandersn) commented Jan 9, 2019

Copy link
Copy Markdown
Member

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
Copy Markdown
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?

Comment thread src/compiler/checker.ts Outdated
Comment thread src/compiler/checker.ts Outdated
@Jessidhia

Jessica Franco (Jessidhia) commented Jan 10, 2019

Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown
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.
Comment thread src/compiler/checker.ts
Comment thread src/lib/esnext.globalthis.d.ts Outdated
//// function f(this) { return this; }

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

Copy link
Copy Markdown
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
Copy Markdown
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.

@weswigham Wesley Wigham (weswigham) Jan 18, 2019

Copy link
Copy Markdown
Member

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
Copy Markdown
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
Copy Markdown
Member Author

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

@sandersn

Copy link
Copy Markdown
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
Copy Markdown

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
Copy Markdown
Member Author

Ian Yates (@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
Copy Markdown
Member Author

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

Comment thread src/compiler/checker.ts Outdated
Comment thread src/testRunner/unittests/tsserver/projects.ts
Comment thread src/services/completions.ts Outdated
Comment thread tests/cases/fourslash/findAllRefsThisKeyword.ts Outdated