Skip to content

Commit 7b7591d

Browse files
committed
Emit a warning when it call without arguments is used in a block without parameters
1 parent 49a4475 commit 7b7591d

File tree

7 files changed

+76
-9
lines changed

7 files changed

+76
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Compatibility:
2727
* Add `rb_gc_mark_locations()` (#3704, @andrykonchin).
2828
* Implement `rb_str_format()` (#3716, @andrykonchin).
2929
* Add `IO#{pread, pwrite}` methods (#3718, @andrykonchin).
30+
* Emit a warning when `it` call without arguments is used in a block without parameters (#3681, @andrykonchin).
3031

3132
Performance:
3233

spec/ruby/language/block_spec.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,3 +1072,32 @@ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **
10721072
end
10731073
end
10741074
end
1075+
1076+
describe "`it` calls without arguments in a block with no ordinary parameters" do
1077+
ruby_version_is "3.3"..."3.4" do
1078+
it "emits a deprecation warning" do
1079+
-> {
1080+
eval "proc { it }"
1081+
}.should complain(/warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it\(\) or self.it/)
1082+
end
1083+
1084+
it "does not emit a deprecation warning when a block has parameters" do
1085+
-> { eval "proc { |a, b| it }" }.should_not complain
1086+
-> { eval "proc { |*rest| it }" }.should_not complain
1087+
-> { eval "proc { |*| it }" }.should_not complain
1088+
-> { eval "proc { |a:, b:| it }" }.should_not complain
1089+
-> { eval "proc { |**kw| it }" }.should_not complain
1090+
-> { eval "proc { |**| it }" }.should_not complain
1091+
-> { eval "proc { |&block| it }" }.should_not complain
1092+
-> { eval "proc { |&| it }" }.should_not complain
1093+
end
1094+
1095+
it "does not emit a deprecation warning when `it` calls with arguments" do
1096+
-> { eval "proc { it(42) }" }.should_not complain
1097+
end
1098+
1099+
it "does not emit a deprecation warning when `it` calls with explicit empty arguments list" do
1100+
-> { eval "proc { it() }" }.should_not complain
1101+
end
1102+
end
1103+
end

src/main/java/org/truffleruby/language/methods/Arity.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ public boolean acceptsKeywords() {
115115
return hasKeywords() || hasKeywordsRest();
116116
}
117117

118+
public boolean acceptsPositionalArguments() {
119+
return getRequired() > 0 || getOptional() > 0 || hasRest();
120+
}
121+
118122
public boolean hasKeywords() {
119123
return keywordArguments.length != 0;
120124
}

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,17 @@
4343
public final class YARPBlockNodeTranslator extends YARPTranslator {
4444

4545
private final Arity arity;
46+
/** Whether a block itself accepts a block parameter (&block). The Arity class does not contain this information. */
47+
private final boolean acceptsBlockParameter;
4648

47-
public YARPBlockNodeTranslator(TranslatorEnvironment environment, Arity arity) {
48-
super(environment);
49+
public YARPBlockNodeTranslator(
50+
TranslatorEnvironment environment,
51+
Arity arity,
52+
boolean acceptsBlockParameter,
53+
RubyDeferredWarnings rubyWarnings) {
54+
super(environment, rubyWarnings);
4955
this.arity = arity;
56+
this.acceptsBlockParameter = acceptsBlockParameter;
5057
}
5158

5259
public RubyNode compileBlockNode(Nodes.Node body, Nodes.ParametersNode parameters, String[] locals,
@@ -310,4 +317,22 @@ private boolean shouldConsiderDestructuringArrayArg(Arity arity) {
310317
}
311318
}
312319

320+
@Override
321+
public RubyNode visitCallNode(Nodes.CallNode node) {
322+
// emit a warning if `it` is called without arguments in a block without ordinal parameters
323+
boolean noBlockParameters = !arity.acceptsKeywords() && !arity.acceptsPositionalArguments() &&
324+
!acceptsBlockParameter;
325+
if (node.name.equals("it") && node.receiver == null && node.isVariableCall() && noBlockParameters) {
326+
SourceSection section = rubySource.getSource().createSection(node.startOffset, node.length);
327+
328+
String sourcePath = rubySource.getSourcePath(language).intern();
329+
int lineNumber = section.getStartLine() + rubySource.getLineOffset();
330+
String message = "`it` calls without arguments will refer to the first block param in Ruby 3.4; use it() or self.it";
331+
332+
rubyWarnings.warn(sourcePath, lineNumber, message);
333+
}
334+
335+
return super.visitCallNode(node);
336+
}
337+
313338
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ public final class YARPDefNodeTranslator extends YARPTranslator {
2424

2525
public YARPDefNodeTranslator(
2626
RubyLanguage language,
27-
TranslatorEnvironment environment) {
28-
super(environment);
27+
TranslatorEnvironment environment,
28+
RubyDeferredWarnings rubyWarnings) {
29+
super(environment, rubyWarnings);
2930

3031
if (parseEnvironment.parserContext.isEval() || parseEnvironment.isCoverageEnabled()) {
3132
shouldLazyTranslate = false;

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ public class YARPTranslator extends YARPBaseTranslator {
175175
public static final RescueNode[] EMPTY_RESCUE_NODE_ARRAY = new RescueNode[0];
176176

177177
public Deque<Integer> frameOnStackMarkerSlotStack = new ArrayDeque<>();
178+
179+
protected RubyDeferredWarnings rubyWarnings;
180+
178181
/** whether a while-loop body is translated; needed to check correctness of operators like break/next/etc */
179182
private boolean translatingWhile = false;
180183
/** whether a for-loop body is translated; needed to enforce variables in the for-loop body to be declared outside
@@ -198,8 +201,9 @@ public class YARPTranslator extends YARPBaseTranslator {
198201
/** all the encountered BEGIN {} blocks; they will be added finally at the beginning of the program AST */
199202
private final ArrayList<RubyNode> beginBlocks = new ArrayList<>();
200203

201-
public YARPTranslator(TranslatorEnvironment environment) {
204+
public YARPTranslator(TranslatorEnvironment environment, RubyDeferredWarnings rubyWarnings) {
202205
super(environment);
206+
this.rubyWarnings = rubyWarnings;
203207
}
204208

205209
public RubyNode[] getBeginBlocks() {
@@ -537,7 +541,9 @@ private RubyNode translateBlockAndLambda(Nodes.Node node, Nodes.Node parametersN
537541

538542
final YARPBlockNodeTranslator methodCompiler = new YARPBlockNodeTranslator(
539543
newEnvironment,
540-
arity);
544+
arity,
545+
parameters.block != null,
546+
rubyWarnings);
541547

542548
methodCompiler.frameOnStackMarkerSlotStack = frameOnStackMarkerSlotStack;
543549

@@ -1548,7 +1554,8 @@ public RubyNode visitDefNode(Nodes.DefNode node) {
15481554

15491555
final var defNodeTranslator = new YARPDefNodeTranslator(
15501556
language,
1551-
newEnvironment);
1557+
newEnvironment,
1558+
rubyWarnings);
15521559
var callTargetSupplier = defNodeTranslator.buildMethodNodeCompiler(node, parameters, arity);
15531560

15541561
final boolean isDefSingleton = singletonClassNode != null;
@@ -3562,7 +3569,7 @@ private RubyNode openModule(Nodes.Node moduleNode, RubyNode defineOrGetNode, Str
35623569
newEnvironment.declareVar(name);
35633570
}
35643571

3565-
final YARPTranslator moduleTranslator = new YARPTranslator(newEnvironment);
3572+
final YARPTranslator moduleTranslator = new YARPTranslator(newEnvironment, rubyWarnings);
35663573
final ModuleBodyDefinition definition = moduleTranslator.compileClassNode(moduleNode, bodyNode);
35673574
return new RunModuleDefinitionNode(definition, defineOrGetNode);
35683575
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ public RootCallTarget parse(RubySource rubySource, ParserContext parserContext,
222222
// Translate to Ruby Truffle nodes
223223

224224
// use source encoding detected by manually, before source file is fully parsed
225-
final YARPTranslator translator = new YARPTranslator(environment);
225+
final YARPTranslator translator = new YARPTranslator(environment, rubyWarnings);
226226

227227
RubyNode truffleNode;
228228
printParseTranslateExecuteMetric("before-translate", context, source);

0 commit comments

Comments
 (0)