-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Adding support for the ...spread operator on arrays and hashes #3839
Conversation
src/Lexer.php
Outdated
@@ -315,8 +315,13 @@ private function lexExpression(): void | |||
} | |||
} | |||
|
|||
// spread operator | |||
if ('.' === $this->code[$this->cursor] && '.' === $this->code[$this->cursor + 1] && '.' === $this->code[$this->cursor + 2]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if you have a .
near the end of the template ? Would this attempt to access $this->cursor + 2
past the end of the code ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note that the parsing of arrow functions seem to have the same issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great point. It's POSSIBLE, but I think highly unlikely. Since we're in lexExpression()
, we know we're already inside of Twig. So after the .
, the user will need valid closing tags }}
or %}
else they'll get an error related to those anyways before this code is run. So unless they customize their delimiters to a single character and do something like { set foo = .}
, i think it's not possible. I can cover for that if we want to - but it might be unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point is that we will try to access out of bounds indexes before reaching the point where Twig will throw the syntax error due to missing the closing tags (and in dev where notices are turned into ErrorException, they would get that ErrorException about a bad index access in Twig instead of getting the SyntaxError telling them that the template is invalid)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're totally right - I thought the syntax error due to the missing closing tag took precedence, but it doesn't. It should be covered now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the closing tag is expected after lexing the expression, not before it (parsing is done by moving through the file)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a Twig template feature available only depending on the PHP version being used looks wrong to me. I don't think we have any other such feature in Twig, and that would make documenting it harder
The alternative would be quite a lot of work - e.g. to convert the currently-dumped PHP |
Well, looking at tests, it seems like spread for hashes actually requires PHP 8.1+, not 7.4+ |
That's correct. I had originally planned to just allow the spread operator to work for hashes in 8.1 and allow PHP to throw the error, but it sounds like that won't do. Ok... let me see if I can get this working for all PHP versions. |
Another alternative might be to bump the min supported PHP version. But that requires a decision from @fabpot Note that the usage of |
Yea... it's worth a try at least for the benefit. Let me see what I can come up with :) |
// TODO: implementing hasSpreadItem is an exercise for the reader
$needsArrayMergeSpread = \PHP_VERSION_ID < 80100 && $this->hasSpreadItem($this->getKeyValuePairs());
if ($needsArrayMergeSpread) {
$compiler->raw('array_merge(');
}
$compiler->raw('[');
$first = true;
foreach ($this->getKeyValuePairs() as $pair) {
if ($needsArrayMergeSpread && $pair['value']->hasAttribute('spread')) {
$compiler->raw('], ')->subcompile($pair['value'])->raw(', [');
$first = true;
continue;
}
if (!$first) {
$compiler->raw(', ');
}
$first = false;
$compiler
->subcompile($pair['key'])
->raw(' => ')
->subcompile($pair['value'])
;
if ($pair['value']->hasAttribute('spread')) {
$compiler->raw('...')->subcompile($pair['value']);
} else {
$compiler
->subcompile($pair['key'])
->raw(' => ')
->subcompile($pair['value'])
;
}
}
$compiler->raw(']');
if ($needsArrayMergeSpread) {
$compiler->raw(')');
} |
note that the current implementation does not guarantee that the |
Woh. THANK YOU for getting me started with that snippet - I'm crunching on it right now.
I'm personally ok with this - it's the same behavior I think for the |
I'm also fine with that, given that PHP does not have separate data structures for lists and hashes. But the doc might maybe need to mention it. And the documentation needs to document that the spread operator is also supported in list and hash literals in Twig and not in function calls. |
Thanks to your help @stof, I've got things working on all php versions 🎆 . During that process, I DID notice one problem that affects all versions. Suppose you have
The PHP equivalent would result in a sequential series: 1, 2, 3, 4, 5, 6, 7. However, Twig currently assigns ALL array keys an index. So the above compiles to:
This messes up the merging, as PHP now thinks that we want the I'm not sure why Twig adds the explicit indexes, but removing them would be a BC break. But, keeping them causes bad If we're ok with this detail, this should be ready to go! Alternative solution would be to deprecate the old behavior and "opt into" the new behavior everywhere with an option. Both are fine for me. |
what would be the BC break here ? Without spread operator, wouldn't we have the same behavior at runtime ? |
You're probably right... I am being on the extreme safe-side here. If I remove my "BC layer" (i.e. I stop adding the integer indexes explicitly and allow PHP to do it), then the test suite almost passes. The only failures are "meaningless" - e.g.
As you can see, while the output has slightly changed, the underlying keys of that array would be the same. There are 4 such failures and they all look like this. To me, this change is NOT a BC break. But as Twig is so widely used, a low-level change like this still gives me pause. |
As this generates the equivalent PHP code, updating the assertion is OK IMO. I'm in favor of simplifying the code. |
Done! |
what's missing to merge this? |
Thank you @weaverryan. |
Apparently, this PR broke the test suite of Symfony's TwigBridge, especially the |
I'm having a look at the moment. |
This PR was merged into the 3.x branch. Discussion ---------- Fix spread operator implementation #3839 is too invasive, leading to failures like https://github.com/symfony/symfony/actions/runs/5612246672/jobs/10269741115 Commits ------- 75efa5e Fix spread operator implementation
Hi!
Long time user, first time contributor of a real future. I hope I didn't miss anything - but this seemed quite straightforward in the end.
Notes:
fabbot failures are unrelated, so I'm not touching them.
Cheers!