It shouldn't take a lot of thought to see why this is a somewhat user-hostile, passive-aggressive approach for a language. If something is ill-advised, the language should actively steer you away from it, not dissuade you with subtle bugs a few thousand lines of code down the road.
I think this scheme would be more workable if sigils or similar conventions of some kind were mandatory for top-level symbols. Then it would be much harder to accidentally wander into this problem. The language he seems to be drawing inspiration from, Ruby, does do this.
Like everything in languages (or APIs), there's a tradeoff here. By making scoping automatic, and (hopefully) forbidding shadowing, you can make the language conceptually simpler. Think of it as making variables be "referentially transparent" in terms of their lexical scope. Everywhere you see "A" within a given lexical scope -- you know that "A" always refers to the same thing. In a language with "var" and with shadowing, "A" could mean many different things within any given lexical scope, and you have to hunt for the nearest declaration to tell which one it is.
On the downside, you have what Armin describes: If you happen to try to use the same name for two different things within the same lexical scope, it won't work.
Since it's always the case that you are able to choose a more descriptive name for your variable, and gain clearer code by it, I think it's very much a tradeoff worth making.
But perhaps I'm still not getting at the answer you're looking for here...
That's not what I'm criticizing. Lexical shadowing is not what I'm advocating. I think your choice is fine in so far as it goes. It has its logic.
What I am criticizing is how it can fail. The top scope is different, quantitatively and qualitatively, from almost all nested scopes. It's much larger, and spread lexically over a larger area. If you have a team of developers, it will be modified concurrently. No one developer necessarily knows the full set of symbols defined in the top scope while they are writing an individual procedure.
And thus the problem: a developer thinks they've chosen a "different (better) name" for a some variable, but in fact they've chosen one that a different developer also thought was a "different (better) name", only one of them is in a lexically enclosing scope. This problem isn't likely to occur on the level of nested procedures or nested blocks, because the definitions would be visually close. But it's much more likely to happen when one of the symbols is defined in the top scope. Here, the definition could be many hundreds or thousands of lines away. It may even be in a separate commit, waiting to be merged, such that there's no way for either developer to know without closely reviewing every change.
And this is the criticism: the failure mode for this inadvertent reuse of a variable name is subtle bugs, as what one developer thought was a global symbol turns out to be modified and acquire strange values through unexpected codeflow, almost like the VM was corrupted and memory was behaving unreliably.
The qualitative difference of the top scope in situations like this is the reason why I suggested sigils or somesuch to disambiguate those scenarios. Perhaps top-level symbols can't be reassigned from nested scopes unless you use '$' as a prefix to their name; a visual shorthand that you are definitely not creating a new local symbol.
The reason I summarized your argument in the way I did is because your argument against this failure mode seems to be "don't create top scopes with lots of symbols". That's a fine argument (or rather, exhortation), but it isn't a realistic one. If the language is problematic with lots of symbols in the top scope, it should be unpleasant to use with lots of symbols in the top scope. And the unpleasantness shouldn't come from subtle bugs (the passive aggressiveness I mentioned); it should come from awkward and ugly sigils, or some other intrinsic way of discouraging those styles.
You should definitely go down this track. It would be a massive achievement.
If you want to have a constructive conversation, you could try dialing back the snark, and addressing my arguments directly.
To give an example, many CoffeeScript programmers do follow simple naming conventions to call out top-level variables, such as CamelCase for classes or ALL_CAPS for constants. When you follow these conventions, it's pretty easy to avoid naming collisions.
In certain cases, though, you want a top-level variable to be lowercase, perhaps for stylistic reasons. If your files are relatively small, it's pretty easy to check for naming collisions when you introduce top-level variables after the fact, so a developer might decide that the risk is acceptable, especially if there is good test coverage.
Another kind of user hostility is to optimize for safety at all costs. I don't think any scoping mechanism totally eliminates the possibility of bugs, but some schemes do err on the side of safety over convenience. There's nothing wrong with trading off convenience for safety, but, on the other hand, you can make judgment calls that convenience and/or simplicity of the scoping model outweigh the risk of naming collisions.
Obviously, I like CoffeeScript, so I think Jeremy's made the correct tradeoffs. All languages work a little different--JavaScript, CoffeeScript, Python2, Python3, and Ruby all have different rules--and none of them are perfect in all situations. In all of the languages, though, it's reasonably straightforward to write correct code once you adopt general good practices--be careful with your names, and understand the language's approach to scoping.
See: https://github.com/jashkenas/coffee-script/issues/712 https://github.com/jashkenas/coffee-script/issues/238
Whether he's right is another question. But if you don't like his decision you can use the Coco (https://github.com/satyr/coco) fork which fixes it by introducing := for nonlocal assignment.