-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Splat values correctly inside return/break/next statements #10193
Splat values correctly inside return/break/next statements #10193
Conversation
node.raise "argument to splat must be a tuple, not #{type}" | ||
end | ||
end | ||
end |
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.
I am starting to think if we could do the following instead, and remove similar checks in other places:
module Crystal
class MainVisitor
def end_visit(node : Splat)
node.bind_to node.exp
if (type = node.exp.type?) && !type.is_a?(TupleInstanceType)
node.raise "argument to splat must be a tuple, not #{type}"
end
end
end
end
Running all the semantic tests locally reveals exactly two failing test cases which only affect error messages. The first one is a slight wording change:
it "errors if splatting a non-tuple (#9853)" do
assert_error %(
Array(*Int32)
),
"argument to splat must be a tuple type, not Int32"
# got: "argument to splat must be a tuple, not Int32"
end
The second is
it "errors if splatting union" do
assert_error %(
a = {1} || {1, 2}
foo *a
),
"not yet supported"
# got: "argument to splat must be a tuple, not (Tuple(Int32) | Tuple(Int32, Int32))"
end
This is also probably acceptable since Crystal already merges unions of tuples of the same arity (not that I agree with it), so the unions that aren't reduced to tuples are precisely ones containing tuples of different arities.
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.
This can probably be done in CleanUpTransformer (or something like that) where all types are known
Since this is a syntax change we also need to add specs for the formatter to verify these still format well. |
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.
I rebase locally on master and compiling a 2nd generator of the compiler in release mode seems to lead to almost double time in the Codegen (bc+obj) (14min vs 24 min).
Can someone verify/confirm?
The memory didn't increase. I am not sure if there are many occurrences of what I mention in the inline comment.
Anyone care to test the build time again? |
I am not seeing a difference in build times, either with the latest change or not.
|
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.
This one falls in the obscure middle ground between bug and breaking change, but I'd like to see it merged. Thanks @HertzDevil
Return/break/next statements in Crystal are parsed as calls, and therefore accept single splats, but nothing is done to those splats. This is okay for returning a single tuple, since multiple return values are combined into a tuple expression by the parser (c.f. #10181 (comment)):
But if there are non-splat return values, the results become different:
Contrast this to Ruby where splats in return statements are also permitted:
This PR fixes this so that
*{1, 2}, 3
in Crystal returns a flattened 3-tuple. It also rejects statements such asreturn *1
that were previously silently accepted (since the splats did nothing after all). Most notably, this brings the splat behaviour of return statements on par withyield
:Internally, Crystal wraps the multiple return values inside a
TupleLiteral
, which means there is already a way to get splats inside them; if we extend the parser to also accept splats inside tuple literals in actual code, then #3718 could be implemented on top of this PR (without delegating{...}
toTuple.new(...)
). However, the code paths between tuple literal parsing and return argument parsing seem considerably different, so that issue is not pursued here.For simplicity, returning a single splat will be considered multiple return values after this PR, which may affect macros and formatters: