Skip to content

Corrected ES5 for-in destructuring binding emit #33337

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

Conversation

JoshuaKGoldberg
Copy link
Contributor

Although it's not permitted in the checker to use an array or object binding pattern ("destructuring") as the iterating variable in a for..in loop, TypeScript can still output the syntax. This corrects the emitter to correctly destructure the variables in the body of the loop. Previously they were incorrectly being bound in the iteration statement.

I'm not particularly satisfied with how this code shaped up. There's some duplication in the convertForInStatementForLoop* functions, but just small enough of it that I didn't extract into a shared function... does anyone see a cleaner approach?

Fixes #2272

Josh Goldberg added 2 commits September 9, 2019 17:45
Although it's not permitted in the checker to use an array or object binding pattern ("destructuring") as the iterating variable in a `for..in` loop, TypeScript can still output the syntax. This corrects the emitter to correctly destructure the variables in the body of the loop. Previously they were incorrectly being bound in the iteration statement.

I'm not particularly satisfied with how this code shaped up. There's some duplication in the `convertForInStatementForLoop*` functions, but just small enough of it that I didn't extract into a shared function... does anyone see a cleaner approach?
@JoshuaKGoldberg JoshuaKGoldberg marked this pull request as ready for review September 10, 2019 02:56
@sandersn sandersn added the For Backlog Bug PRs that fix a backlog bug label Feb 1, 2020
Copy link
Member

@rbuckton rbuckton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have an issue with these changes in general, but one of the reasons this is an error is that we don't correctly downlevel iterator destructuring for strings in ES5, as an ES2015 string iterator yields "code points" rather than "code units", so there would be differences in runtime behavior for ES2015 and ES5 for these two emits:

// --target ES2015
for (var [a, b] in { "🐛": 1 }) console.log(a, b);
// prints: 🐛 undefined
// --target ES5
for (var _a in { "🐛": 1 }) {
    var a = _a[0], b = _a[1];
    console.log(a, b);
}
// prints: � �

I can bring this up in a design meeting, but I'm somewhat wary of introducing a change that:

  • Only affects code we've labeled as an error
  • Produces incorrect results for unicode surrogate pairs.

}

function convertForOfStatementHead(node: ForOfStatement, boundValue: Expression, convertedLoopBodyStatements: Statement[]) {
function convertForStatementHead(node: ForInStatement | ForOfStatement, boundValue: Expression, convertedLoopBodyStatements: Statement[]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function convertForStatementHead(node: ForInStatement | ForOfStatement, boundValue: Expression, convertedLoopBodyStatements: Statement[]) {
function convertForInOrForOfStatementHead(node: ForInStatement | ForOfStatement, boundValue: Expression, convertedLoopBodyStatements: Statement[]) {

Only because a for statement is different from a for-in or for-of statement and its better to avoid confusion.

@@ -2433,6 +2445,77 @@ namespace ts {
);
}

function convertForInStatementForLoopDirectly(node: ForInStatement, outermostLabeledStatement: LabeledStatement, convertedLoopBodyStatements: Statement[]): Statement {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could possibly simplify this by using a 3rd function that optionally converts the iteration statement, or make the conversion function something that is passed in as an argument (and falls back to visitEachChild(node, visitor, context) when not converting).

@rbuckton
Copy link
Member

rbuckton commented Jan 8, 2021

We discussed this in the design meeting today, and while we appreciate the effort that went into putting together this PR, unfortunately we will not be taking this change. There are several factors involved:

  • The downlevel emit for ES5 is not compliant with ES2015 semantics with respect to surrogate pairs.
  • Supporting true ES2015 string iteration via a helper would overcomplicate the existing helpers and add a fairly large amount of extra code at the top of any file using array destructuring downlevel.
  • We already report an error for this, and it is not the only case where we may emit invalid JavaScript in the presence of a reported error (which is one of the reasons --noEmitOnError exists as a compiler option).

However, since this is legal ES2015, we may consider removing the error for --target ES2015 and higher.

@JoshuaKGoldberg JoshuaKGoldberg deleted the for-in-destructuring-emit branch January 8, 2021 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Emit destructuring patterns in 'for...in' statements for ES3/5
3 participants