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

Can typeof be used with block-bound variables #49

Closed
nzakas opened this issue Apr 10, 2014 · 7 comments
Closed

Can typeof be used with block-bound variables #49

nzakas opened this issue Apr 10, 2014 · 7 comments
Labels

Comments

@nzakas
Copy link
Owner

nzakas commented Apr 10, 2014

This came up on #48, should this throw an error or not?

if (foo) {
    console.log(typeof bar);
    let bar = "hi";
}

@getify @rwaldron

@anba
Copy link

anba commented Apr 13, 2014

It prints "undefined" in sloppy mode and throws a ReferenceError in strict mode.

Spec sections:

  • Block Declaration Instantiation creates, but does not initialise the lexical binding.
  • In typeof Evaluation, the reference is not unresolvable (because the binding was found), so GetValue is executed. The reference's base value is an environment record, so GetBindingValue gets called. And step 3.a in that method either returns undefined or throws a ReferenceError, depending on the strict mode setting.

@getify
Copy link
Contributor

getify commented Apr 13, 2014

@anba

It is quite surprising that typeof would behave differently for undeclared variables vs. TDZ let variables:

"use strict";
console.log(typeof a);
console.log(typeof b); // ReferenceError
let b = 2;

Wouldn't it have made more sense for it to behave the same in both cases? What's the justification for the error in the b case but not with a?

@anba
Copy link

anba commented Apr 13, 2014

The typeof result for a is legacy behaviour and cannot be changed, regardless of whether it makes sense or not. And the result for b actually derives from that legacy behaviour. For example you can achieve the same effect without TDZ variables if you use global variables:

let global = this;
Object.setPrototypeOf(global, new Proxy(Object.create(null), {
  _has: false,
  has(t, pk) {
    // Switch the HasBinding() result for the global object environment on every access
    return this._has = !this._has;
  }
}));

typeof a; // undefined
"use strict"; typeof a; // ReferenceError

@getify
Copy link
Contributor

getify commented Apr 13, 2014

@anba

Thanks for the clarification, but I was actually asking the reverse question. Perhaps I'm missing it, but I'm not sure I understand why there needed to be an error on typeof b while in the TDZ. I would have thought, to preserve consistency with the typeof a case, that typeof b would always return undefined regardless of strict mode. What I'm wondering is if there's some other reason/justification for this error, since it breaks consistency and thus surprises (at least me)?

To put it another way, I see it as a feature, not a bug, that typeof is "safe" to use with variables if you're not sure if they are declared or not. This typeof a !== "undefined" type of check is used many times in real-world code to check if a variable (usually a global) exists or not, where just checking for the variable by name would in fact throw an error if it wasn't declared.

It would seem at quick glance that being able to do similar checks in a safe way against TDZ'd let declarations could be similarly useful.

@nzakas
Copy link
Owner Author

nzakas commented Apr 13, 2014

Thanks @anba for the explanation. Seems like some good fodder for es-discuss.

@anba
Copy link

anba commented Apr 13, 2014

The typeof operator applied on the (internal) Reference type only differentiates between unresolvable and resolvable references, cf. step 2 in typeof - Runtime Semantics: Evaluation. For typeof a the unresolvable reference case applies, whereas in typeof b the reference to b was successfully resolved and its value is retrieved. The same behaviour is specified in ECMAScript 5. That's what I mean when I speak of legacy behaviour.
(Even for ES5 the ReferenceError in strict mode is specified, except that user code can never actually create references to uninitialised bindings in ES5...!)
That being said, it is certainly possible to extend ES6 to catch the resolved but not yet initialised case and make it return undefined.

@nzakas nzakas closed this as completed in f22a2c5 May 24, 2014
@getify
Copy link
Contributor

getify commented Jul 17, 2014

Dunno what if anything will come of it, but I filed this as a ES6 bug: https://bugs.ecmascript.org/show_bug.cgi?id=3009

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

No branches or pull requests

3 participants