microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
98.27k stars 12.21k forks source link

Error when accessing properties on `globalThis` with the same name of a global variable #58345

Open nicolo-ribaudo opened 2 weeks ago

nicolo-ribaudo commented 2 weeks ago

🔎 Search Terms

globalThis redefined error script property does not exist

🕗 Version & Regression Information

⏯ Playground Link

https://www.typescriptlang.org/play/?noImplicitAny=false&ts=5.5.0-beta#code/DYUwLgBAlgdgbgQ2FAJgRggXggc2AewCMkAVACygGcA6WRZdAbgChRIBPLXA44cq2vCSo0LNhABeXPEVIUadYSgBMLIA

💻 Code

let invalid1 = globalThis.invalid1;
let y = globalThis.invalid1;
let z = globalThis.invalid2;

🙁 Actual behavior

Property 'invalid1' does not exist on type 'typeof globalThis'.

🙂 Expected behavior

It should either complain about both invalid1 and invalid2, or neither

Additional information about the issue

It only happens in scripts and not in modules (if I add export {}, there are no reported errors).

RyanCavanaugh commented 1 week ago

I think what you should actually be seeing here is a circularity error

nicolo-ribaudo commented 1 week ago

let invalid1 = 1 doesn't define globalThis.invalid1, so there is no circularity going on (it only happens with var).

Note that the code above has actually valid use cases. I spotted it while doing

let SuppressedError = globalThis.SuppressedError ?? class SuppressedError extends Error {
  // ...
}
Strapazzon commented 5 days ago

Hello @nicolo-ribaudo.

Attempting to use the variable before declaration will result in a ReferenceError. This is known as the "temporal dead zone," where the variable exists in the scope but cannot be accessed.

But, When you declare a variable with var, the variable declaration is "hoisted" (or "raised") to the top of your current scope. This means that, regardless of where you declared the variable in the code, JavaScript treats it as if you had declared it at the beginning of the scope. However, only the declaration is hoisted, not the initialization. Therefore, if you try to access the variable before initializing it, the value will be undefined, but it will not cause an error.

On the other hand, when you declare a variable with let, the declaration is also hoisted, but JavaScript puts these variables in a "temporal dead zone" from the start of the block until the declaration is processed. During this "dead zone", if you try to access the variable, you will receive a ReferenceError.

So, in your case, when you try to do let invalid1 = globalThis.invalid1; without globalThis.invalid1 being defined, invalid1 is undefined. This is not a problem in itself, but if you try to access invalid1 somewhere in the code before this line, you will receive a ReferenceError because of the "temporal dead zone" of let.

However, if you use var instead of let, as in var invalid1 = globalThis.invalid1;, even if globalThis.invalid1 is not defined, invalid1 will simply be undefined and will not cause an error, even if you access invalid1 before this line, thanks to the hoisting of var.

nicolo-ribaudo commented 5 days ago

In the example above no variables are in temporal dead zone -- you can try running that code and it does not error.