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

Add unlimited block unpacking #11597

Merged
merged 17 commits into from
Jul 25, 2023
Merged

Conversation

asterite
Copy link
Member

@asterite asterite commented Dec 15, 2021

Fixes #8237

What's this?

This PR allows unlimited levels of block unpacking. For example, taking #8237 as a reference, this now works:

boxes = [
  { {0, 0}, {15, 10} },
  { {30, 10}, {40, 20} },
  { {9, 14}, {99, 58} },
]
boxes.each_with_index do |((x1, x2), (y1, y2)), i|
  puts "#{i}: (#{x1}, #{x2}), (#{y1}, #{y2})"
end

That's just one nested level, but it actually works with arbitrary nest levels. For example:

ary = [
  {1, {2, {3, 4}}},
]

ary.each do |(x, (y, (z, w)))|
  puts x
  puts y
  puts z
  puts w
end

Also, splats are now supported inside these unpacks!

ary = [
  [1, 2, 3, 4, 5],
]

ary.each do |(x, *y, z)|
  puts x
  puts y
  puts z
end

I believe this makes the language much more consistent with everything else.

How?

It's relevant to know how this was implemented before.

When the parser encountered something like this:

foo do |(x, y)|
end

it would actually produce an AST node that's already expanded to unpack the values.

You can see this is the case in specs, but also with this code:

macro show(node)
  {% puts node %}
end

show(foo do |(x, y)|
end)

Output:

foo do |__arg0|
  x = __arg0[0]
  y = __arg0[1]
end

That also means that up until now, a Block AST node was only able to hold a list of names (actually a list of Var nodes) as the block arguments.

Ideally, that would need to change so that each block argument can be one of two things:

  1. a variable
  2. an unpacked group of things, which can in turn be variables or group of things

This would require introducing a new AST node for the second point. But I believe that would be a breaking change, because AST nodes are exposed in macro code. If existing macro code relies on the fact the block arguments can only be variables, we might break things if we don't keep this guarantee.

That's why, this new implementation has the same property: the block that you get when you pass things to macros is already "expanded".

That said, the current PR improves things a bit: instead of letting the parser do the expansion, the parser will produce a Block node that has information about what to unpack. Then, right before semantic analysis, those are expanded.

Apart from the fact that user code might have relied on the fact that block arguments were only Var nodes, the existing code also relies on Block @args being an Array(Var). If we change that, we'll have to change the logic in a lot of places. That's why that didn't change: a new unpacks property was added to Block, and that is eventually put into the @args to keep backwards compatiblity.

If we eventually find that changing this representation is probably not a breaking change, we can do it. And with this PR in place it should be much simpler to do.

@asterite asterite marked this pull request as ready for review December 16, 2021 11:09
@HertzDevil
Copy link
Contributor

The following will now break under -Dstrict_multi_assign, since the previous expansion didn't use MultiAssigns and this PR does (without the trailing *_):

{1, 2, 3}.try { |(x, y)| }

which is fine by me.

Do you have a corresponding PR for the reference manual?

@asterite
Copy link
Member Author

Oh, right, the reference manual... no, I don't have a PR for that. I wonder if the manual should live in this repo too, somewhere, so that they can be kept in-sync, and we could change things in the same PR.

I won't have time to update the reference manual now, but maybe next year (I mean it)

@oprypin oprypin changed the title Feature/unlimited block unpacking Feature: unlimited block unpacking Dec 16, 2021
@asterite
Copy link
Member Author

Too bad this didn't get merged when I sent it. Now the merge conflict is a bit hard to fix. And this is a nice feature.

@asterite
Copy link
Member Author

I'll open another PR for this.

@asterite asterite closed this Sep 27, 2022
@asterite asterite deleted the feature/unlimited-block-unpacking branch September 27, 2022 10:47
@asterite asterite restored the feature/unlimited-block-unpacking branch September 27, 2022 11:13
@asterite asterite reopened this Sep 27, 2022
@asterite
Copy link
Member Author

Ah, it's a mess... and now with arg renamed to param. It's a lot of work 😢

If anyone wants to take a stab at this, please go ahead. I don't know why it wasn't merged back then. It's been almost a year 😢

@oprypin
Copy link
Member

oprypin commented Sep 27, 2022

and now with arg renamed to param. It's a lot of work 😢

If anyone wants to take a stab at this,

I'm very good at merging, that part doesn't scare me, I can do it. If you merge to the commit right before the rename (git merge e05516f00~), I'll then definitely follow up and merge to latest.

@asterite
Copy link
Member Author

@oprypin Thanks! I think I managed to do the merge after all. We'll see what CI says... I'll also check the final diff now.

@asterite
Copy link
Member Author

I think it looks good.

This would have been nice for 1.6... 😢 I'm sure going to need this for the next Advent of Code, which I'll likely stream, trying to also use the interpreter. But I think the 1.7 release will come on January next year.

src/compiler/crystal/semantic/main_visitor.cr Outdated Show resolved Hide resolved
src/compiler/crystal/semantic/normalizer.cr Outdated Show resolved Hide resolved
src/compiler/crystal/syntax/to_s.cr Outdated Show resolved Hide resolved
@asterite asterite force-pushed the feature/unlimited-block-unpacking branch from 5d0b521 to c3dc983 Compare October 6, 2022 10:58
@asterite
Copy link
Member Author

@straight-shoota If this turns green, is there any reason not to merge this?

Copy link
Member

@straight-shoota straight-shoota left a comment

Choose a reason for hiding this comment

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

LGTM. I'm happy to merge this 👍

@straight-shoota straight-shoota added this to the 1.10.0 milestone Jul 24, 2023
@straight-shoota straight-shoota changed the title Feature: unlimited block unpacking Add unlimited block unpacking Jul 25, 2023
@straight-shoota straight-shoota merged commit b69838c into master Jul 25, 2023
106 checks passed
@straight-shoota straight-shoota deleted the feature/unlimited-block-unpacking branch July 25, 2023 09:03
veelenga added a commit to crystal-ameba/ameba that referenced this pull request Oct 6, 2023
veelenga added a commit to crystal-ameba/ameba that referenced this pull request Oct 6, 2023
veelenga added a commit to crystal-ameba/ameba that referenced this pull request Oct 6, 2023
straight-shoota added a commit to crystal-lang/crystal-book that referenced this pull request Oct 9, 2023
Unlimited block unpacking was introduced in crystal-lang/crystal#11597
Blacksmoke16 pushed a commit to Blacksmoke16/crystal that referenced this pull request Dec 11, 2023
Co-authored-by: Johannes Müller <straightshoota@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Nested block arguments can't be unpacked
5 participants