Skip to content

Commit 4054469

Browse files
committed
[GR-19220] Anonymous block forwarding (#2698)
PullRequest: truffleruby/3450
2 parents b4aa93e + 059c09c commit 4054469

File tree

7 files changed

+3911
-3798
lines changed

7 files changed

+3911
-3798
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ New features:
55
* Foreign strings now have all methods of Ruby `String`. They are treated as `#frozen?` UTF-8 Ruby Strings.
66
* Add `Java.add_to_classpath` method to add jar paths at runtime (#2693, @bjfish).
77
* Add support for Ruby 3.1's Hash shorthand/punning syntax (@nirvdrum).
8+
* Add support for Ruby 3.1's anonymous block forwarding syntax (@nirvdrum).
89

910
Bug fixes:
1011

spec/ruby/language/block_spec.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,3 +983,77 @@ def a; 1; end
983983
end
984984
end
985985
end
986+
987+
describe "Anonymous block forwarding" do
988+
ruby_version_is "3.1" do
989+
it "forwards blocks to other functions that formally declare anonymous blocks" do
990+
eval <<-EOF
991+
def b(&); c(&) end
992+
def c(&); yield :non_null end
993+
EOF
994+
995+
b { |c| c }.should == :non_null
996+
end
997+
998+
it "requires the anonymous block parameter to be declared if directly passing a block" do
999+
-> { eval "def a; b(&); end; def b; end" }.should raise_error(SyntaxError)
1000+
end
1001+
1002+
it "works when it's the only declared parameter" do
1003+
eval <<-EOF
1004+
def inner; yield end
1005+
def block_only(&); inner(&) end
1006+
EOF
1007+
1008+
block_only { 1 }.should == 1
1009+
end
1010+
1011+
it "works alongside positional parameters" do
1012+
eval <<-EOF
1013+
def inner; yield end
1014+
def pos(arg1, &); inner(&) end
1015+
EOF
1016+
1017+
pos(:a) { 1 }.should == 1
1018+
end
1019+
1020+
it "works alongside positional arguments and splatted keyword arguments" do
1021+
eval <<-EOF
1022+
def inner; yield end
1023+
def pos_kwrest(arg1, **kw, &); inner(&) end
1024+
EOF
1025+
1026+
pos_kwrest(:a, arg: 3) { 1 }.should == 1
1027+
end
1028+
1029+
it "works alongside positional arguments and disallowed keyword arguments" do
1030+
eval <<-EOF
1031+
def inner; yield end
1032+
def no_kw(arg1, **nil, &); inner(&) end
1033+
EOF
1034+
1035+
no_kw(:a) { 1 }.should == 1
1036+
end
1037+
end
1038+
1039+
ruby_version_is "3.2" do
1040+
it "works alongside explicit keyword arguments" do
1041+
eval <<-EOF
1042+
def inner; yield end
1043+
def rest_kw(*a, kwarg: 1, &); inner(&) end
1044+
def kw(kwarg: 1, &); inner(&) end
1045+
def pos_kw_kwrest(arg1, kwarg: 1, **kw, &); inner(&) end
1046+
def pos_rkw(arg1, kwarg1:, &); inner(&) end
1047+
def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &); inner(&) end
1048+
def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &); inner(&) end
1049+
EOF
1050+
1051+
rest_kw { 1 }.should == 1
1052+
kw { 1 }.should == 1
1053+
pos_kw_kwrest(:a) { 1 }.should == 1
1054+
pos_rkw(:a, kwarg1: 3) { 1 }.should == 1
1055+
all(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1
1056+
all_kwrest(:a, :b, :c, :d, :e, okw1: 'x', okw2: 'y') { 1 }.should == 1
1057+
end
1058+
end
1059+
end

spec/truffleruby.mspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class MSpecScript
103103

104104
# Use spec/ruby/core/nil/nil_spec.rb as a dummy file to avoid being empty
105105
set :next, %w[
106+
spec/ruby/language/block_spec.rb
106107
spec/ruby/language/hash_spec.rb
107108
spec/ruby/core/nil/nil_spec.rb
108109
spec/ruby/core/gc/measure_total_time_spec.rb

src/main/java/org/truffleruby/parser/parser/ParserSupport.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ public class ParserSupport {
158158
.usAsciiString(FORWARD_ARGS_KWREST_VAR);
159159
/** The local variable to store the block from ... in */
160160
public static final String FORWARD_ARGS_BLOCK_VAR = Layouts.TEMP_PREFIX + "forward_block";
161+
public static final TruffleString FORWARD_ARGS_BLOCK_VAR_TSTRING = TStringUtils
162+
.usAsciiString(FORWARD_ARGS_BLOCK_VAR);
161163

162164
// Parser states:
163165
protected StaticScope currentScope;
@@ -1536,6 +1538,11 @@ public void warning(SourceIndexLength position, String message) {
15361538
warnings.warning(file, position.toSourceSection(lexer.getSource()).getStartLine(), message);
15371539
}
15381540

1541+
public boolean local_id(String value) {
1542+
// FIXME: local_id_ref is more complicated and we just blanket look for a scope var of the same name.
1543+
return currentScope.isDefined(value) >= 0;
1544+
}
1545+
15391546
// ENEBO: Totally weird naming (in MRI is not allocated and is a local var name) [1.9]
15401547
public boolean is_local_id(TruffleString name) {
15411548
return lexer.isIdentifierChar(name.readByteUncached(0, lexer.tencoding));

0 commit comments

Comments
 (0)