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

Open
wants to merge 21 commits into
base: master
from

Conversation

Projects
None yet
5 participants
@sandersn
Copy link
Member

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 #22891.

sandersn added some commits Jan 7, 2019

Restore original code from bind-toplevel-this
With one or two additional comments
Working in JS, but the symbol is not right.
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.
Check in TS also; update some tests
Lots of tests still fail, but all but 1 change so far has been correct.
Update baselines
A couple of tests still fail and need to be fixed.
Handle type references to globalThis
The type reference must be `typeof globalThis`. Just `globalThis` will
be treated as a value reference in type position -- an error.
Restore former behaviour of implicitThis errors
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.
Test values with type globalThis
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.
Switch to symbol-based approach
I decided I didn't like the import-type-based approach.

Update baselines to reflect the difference.
@weswigham

This comment has been minimized.

Copy link
Member

weswigham commented Jan 9, 2019

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?

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Member Author

sandersn commented Jan 14, 2019

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

This comment has been minimized.

Copy link
Member Author

sandersn commented Jan 14, 2019

@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.

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

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

This comment has been minimized.

@weswigham

weswigham Jan 16, 2019

Member

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

This comment has been minimized.

@sandersn

sandersn Jan 18, 2019

Author Member

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.

This comment has been minimized.

@weswigham

weswigham Jan 18, 2019

Member

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.

This comment has been minimized.

@sandersn

sandersn Jan 18, 2019

Author Member

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

This comment has been minimized.

Copy link
Member Author

sandersn commented Jan 18, 2019

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

@sandersn

This comment has been minimized.

Copy link
Member Author

sandersn commented Jan 18, 2019

I think we're going to discuss this PR in the design meeting today or next week. Currently, this PR has 4 items to follow up on:

  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, 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.

@ljharb ljharb referenced this pull request Jan 22, 2019

Open

Path to Stage 4! #12

23 of 30 tasks complete
@sandersn

This comment has been minimized.

Copy link
Member Author

sandersn commented Jan 25, 2019

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

This comment has been minimized.

Copy link

IanYates commented Jan 30, 2019

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

This comment has been minimized.

Copy link
Member Author

sandersn commented Jan 30, 2019

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment