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

New with spread #3066

Merged
merged 11 commits into from
May 18, 2015
Merged

New with spread #3066

merged 11 commits into from
May 18, 2015

Conversation

tinganho
Copy link
Contributor

@tinganho tinganho commented May 7, 2015

Implements #2369. Adds ES5 support for spread operator in new expressions. ES3 targets should still output warning as before, because it doesn't support Function.prototype.bind which I use in this ES5 implementation. I refactored some of the code from previous call with spread implementation #1931 so I could reuse it to on my implementation.

I have also manually tested the permutations of the outputted JavaScript.

@tinganho tinganho force-pushed the newWithSpread branch 3 times, most recently from d34674a to ce95286 Compare May 7, 2015 12:23
@tinganho
Copy link
Contributor Author

tinganho commented May 7, 2015

I had some serious trouble with crlf and lf line endings.

I have to turn off autocrlf:

git config --global core.autocrlf false

In order for my test to pass. Have there been any experiencing the same problem as me? using Mac?

@CyrusNajmabadi
Copy link
Contributor

Yes. Don't use autocrlf :) It's not appropriate for our project because line endings are an important thing we track and use in our tests.

@@ -21,6 +21,7 @@ tests/services/baselines/local/*
tests/baselines/prototyping/local/*
tests/baselines/rwc/*
tests/baselines/test262/*
tests/baselines/reference/projectOutput/*
Copy link
Contributor

Choose a reason for hiding this comment

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

why was this added to the .gitignore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I got multiple files outputted there every time I run jake runtests:

screen shot 2015-05-07 at 9 49 45 pm

Copy link
Contributor

Choose a reason for hiding this comment

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

@CyrusNajmabadi i think this is a fair one to add, ppl have made this mistake multiple times already. mainly because of a mocha issue on node 0.10.*

Copy link
Member

Choose a reason for hiding this comment

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

yeah I don't think there's any real harm in having it

@mhegazy
Copy link
Contributor

mhegazy commented May 7, 2015

👍
@vladima and @JsonFreeman can you take a look.

@tinganho
Copy link
Contributor Author

tinganho commented May 8, 2015

I probably should re-style my tests to use foo, bar, baz, x, y, z and A, B, C.

@@ -1881,13 +1860,77 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
}
}

function emitCallOrNewExpressionTarget(node: Expression): CallTarget {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it is a little subtle that this function emits one thing (the target), and then returns two things, one of which it emitted and one of which it did not. Can you make it just return the target and the accessor, and then the caller will emit the target?

Copy link
Contributor

Choose a reason for hiding this comment

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

+1. It should be named properly as well to convey that it's just computing hte call target, and not actually emitting anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@JsonFreeman sound like a good idea. I will address this later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just tried to address this. The reason I did it was so that I don't need to have multiple node kind checks — one for the getter and one for the emitter. We already have in emitCallOrNewExpressionTarget() the node kind checks:

if (node.kind === SyntaxKind.PropertyAccessExpression) {
    ...
}
else if (node.kind === SyntaxKind.ElementAccessExpression) {
    ...
}
else if (node.kind === SyntaxKind.SuperKeyword) {
    ...
}
else {
    ...
}

If we would have one for getter and emitter the same node kind checks need to be applied mulitple times.

I don't have any perfect solution to this. Any idea how to fix this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just found a better way to do this. We can just call it emitCallTarget() and return the target and have a separate function for the emit of accessor or just inline the emit logic in emitNewExpression since we only use it there.

The problem is though, that it already exists one function with emitCallTarget(). Any chance I can just rename it to emitCallTargetWithOptionalTempAssignment()?

function emitCallTarget(node: Expression): Expression {
    if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.SuperKeyword) {
        emit(node);
        return node;
    }
    let temp = createAndRecordTempVariable(TempFlags.Auto);

    write("(");
    emit(temp);
    write(" = ");
    emit(node);
    write(")");
    return temp;
}

Copy link
Member

Choose a reason for hiding this comment

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

Well the functionality below looks a lot like emitCallWithSpread, which calls the emitCallTarget you've mentioned above, and you want to now call this emitCallTarget, so we should find some unifying scheme.

Also, on the issue of subtlety in behavior - while the name is important, I'd rather see documentation, which we are currently lacking in. Regardless of what this is called, please add a nice JSDoc comment to point out its peculiar behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just tested my latest idea. It also requires additional node checks. emitCallWithSpread() also emits the accessor and writing the extra node checks is not worth it IMO. Can't we just rename it to emitCallTargetAndAccessor()? It optionally emits and optionally returns the target and accessor. It is the best in terms of DRY:ness and also number of operations/comparison for the job.

Copy link
Member

Choose a reason for hiding this comment

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

@tinganho I don't think we're opposed to it, but let's see what the change looks like; it's hard to get a feel of it without seeing the end result. Worst-case scenario, we'll just revert the commits

@tinganho
Copy link
Contributor Author

Revision 2

I've just updated the function name to emitCallTargetAndAccessor to reflect the accessor too. It optionally emits and optionally returns the call target and accessor. I also added some comments about some very specific cases with the implementation. This also addresses a bug I found. I also changed the passed in object for the bind method from null to void 0. And the outputted JavaScript have been tested manually from my side.

Bug1 (accessor is a string literal)

new object["a-b"](1, 2, a..)

new (a_ = object)["a-b"].bind.apply(a_."a-b", [null]); // should be a["a-b"];

Bug2 (accessor is a numeric literal)

new object[1](1, 2, a..)

new (a_ = object)[1].bind.apply(a_.1, [null]); // should be a[1];

Old comments:
.gitignore
emitter.ts
types.ts

@tinganho tinganho force-pushed the newWithSpread branch 7 times, most recently from acfae7d to 94dc37f Compare May 13, 2015 02:07
@tinganho
Copy link
Contributor Author

@JsonFreeman I addressed the problems in the old revision.

I only use Function now and it works.

Also there was no wrong with the new expression call syntax new object.Array()(). But I added a test for it just in case.

Travis is pulling in a wrong commit that's why it is failing. But this one has the right commit. (I think I can only fix this with a new PR though) I contacted travis to address this.

@JsonFreeman
Copy link
Contributor

@tinganho Thanks for iterating on this implementation so well. One thing that would be helpful in the future is when you change something, push the changes as new commits instead of amending your old commits. That way it is very easy to see what changed over the course of the discussion.

// Call expression
new f(1, 2, "string")();
new (Function.bind.apply(f, [void 0].concat([1, 2].concat(a))))();
new (Function.bind.apply(f, [void 0].concat([1, 2].concat(a, ["string"]))))();
Copy link
Contributor

Choose a reason for hiding this comment

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

I do think that these two lines are incorrect because these really translate to

new f(1, 2, ...a);

and not

new f(1, 2, ...a)();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think I understand here can you elaborate? They have () at the end? Or does the outer parentheses (Function...) messing with it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, the outer parentheses are messing with it, because it causes the final () to be consumed as a part of the new expression. You need an extra () to make it a call expression. So you should emit:

new (Function.bind.apply(f, [void 0].concat([1, 2].concat(a))))()();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@JsonFreeman I guess if the call have arguments it should be compiled to:

new (Function.bind.apply(f, [void 0].concat([1, 2].concat(a))))()(a, b);

@JsonFreeman
Copy link
Contributor

The one caveat with using Function as I suggested is that there could be a local declaration called Function that shadows the global function. Maybe the Function idea won't work then 😞. Unless we emit some sort of capture, which is overkill I think. Do you think we should go back to the temp idea? Sorry to be fickle.

@tinganho
Copy link
Contributor Author

Do you think we should go back to the temp idea? Sorry to be fickle.

No problem, Ok I can change to your other idea with using temp for "target+accessor".

I will push them as new commits instead of cleaned ones.

@JsonFreeman
Copy link
Contributor

Thanks. In terms of the implementation code, I guess my suggestion is that instead of using emitCallTargetAndAccessor, you just call emitCallTarget (the already existing one), and pass in node.expression as the argument (where node is the new expression).

That way you don't need to worry about the target and accessor.

@tinganho
Copy link
Contributor Author

hmm, I will try it out and see if there are any problem.

@JsonFreeman
Copy link
Contributor

I think it should still give you (a_ = object.array) as the temp

@tinganho
Copy link
Contributor Author

@JsonFreeman it is still pulling in the wrong commit bde2491 instead of b88d542

@JsonFreeman
Copy link
Contributor

Let's ask @DanielRosenwasser, since he knows more about Travis

@DanielRosenwasser
Copy link
Member

@tinganho I pulled down your branch and I'm getting the same error:

node bin/tsc.js --module commonjs -noImplicitAny --preserveConstEnums --out built\local\tsc.js -sourcemap -mapRoot file:///C:\Users\drosen\tinganho\built\local src\compiler\core.ts src\compiler\sys.ts src\compiler\types.ts src\compiler\scanner.ts src\compiler\parser.ts src\compiler\utilities.ts src\compiler\binder.ts src\compiler\checker.ts src\compiler\declarationEmitter.ts src\compiler\emitter.ts src\compiler\program.ts src\compiler\commandLineParser.ts src\compiler\tsc.ts src\compiler\diagnosticInformationMap.generated.ts

src/compiler/emitter.ts(1916,21): error TS2346: Supplied parameters do not match any signature of call target.

This is related to some recent changes; you need to add the parameter alwaysCopy for emitListWithSpread.

write(".bind.apply(");
emit(target);
write(", [void 0].concat(");
emitListWithSpread(node.arguments, /*multiline*/false, /*trailingComma*/false);
Copy link
Member

Choose a reason for hiding this comment

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

Can you add spaces after each of these comments?

@tinganho
Copy link
Contributor Author

@DanielRosenwasser I could get the errors now. Let me just fix it.

@JsonFreeman
Copy link
Contributor

Oh wait, I noticed that

new f(...a, ...b);

will emit as

new (f.bind.apply(f, [void 0].concat(a.concat(b)))();

And with alwaysCopy set to true, it would do

new f(...a)

as

new (f.bind.apply(f, [void 0].concat(a.slice()))();

Can you confirm that this is what happens? This is not what we want, we don't want the slice or the second concat.

Can you add tests for this if it is not already affected?

write(".bind.apply(");
emit(target);
write(", [void 0].concat(");
emitListWithSpread(node.arguments, /*alwaysCopy*/true, /*multiline*/false, /*trailingComma*/false);
Copy link
Member

Choose a reason for hiding this comment

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

Can you actually rename alwaysCopy to needsUniqueCopy and replace the relevant comments elsewhere? I realized that we don't actually always copy if the initial element is an array literal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I can rename it.

@JsonFreeman
Copy link
Contributor

I think the reason the tests did not catch this is because all the tests have at least one non-spread argument before the spread.

@tinganho
Copy link
Contributor Author

@JsonFreeman I was a little bit fast with the the push button. I think alwaysCopy should be set to false instead of true. Because it was false before.

I can add a test for it.

@JsonFreeman
Copy link
Contributor

Sure, but you will still have the double concat problem.

@tinganho
Copy link
Contributor Author

@JsonFreeman is that doable to do f(...a, ...b)? It throws an error now that a spread parameter can only be at the end of a parameter list.

If it is doable what should the outputted code be like?

@JsonFreeman
Copy link
Contributor

Regarding f(...a, ...b). It is not legal to define a function with a rest parameter that is not at the end. So you can't define a function like this:

function f(...a, ...b) { }

But I meant if you have a function called f, and you call it with two spread arguments:

function f(...p: any[]) { }
var a: string[];
var b: string[];
f(...a, ...b);

And that is legal.

@tinganho
Copy link
Contributor Author

Sure, but you will still have the double concat problem.

@JsonFreeman I just tested this and it is outputting:

new (f.bind.apply(f, [void 0].concat(a.concat(b)))();

I can't see what's wrong with it? What is the proposed emit? Or should b not be concatenated?

@JsonFreeman
Copy link
Contributor

That emit works, but I think it's nicer to emit:

new (f.bind.apply(f, [void 0].concat(a, b)))();

You can achieve this by passing a boolean to emitListWithSpread to make it suppress the concat.

@tinganho
Copy link
Contributor Author

Ok I fixed it on a new commit.

@JsonFreeman
Copy link
Contributor

Sweet! Thanks @tinganho! I sign off. Please fix my one nit, and then I can merge it in.

@tinganho
Copy link
Contributor Author

OK @JsonFreeman I fixed it on a new commit. Sorry for not seeing it earlier.

JsonFreeman added a commit that referenced this pull request May 18, 2015
@JsonFreeman JsonFreeman merged commit 5bea6a9 into microsoft:master May 18, 2015
@JsonFreeman
Copy link
Contributor

Thanks @tinganho!

@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants