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

[GR-59866] Emit a warning when it call without arguments is used in a block without parameters #3734

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Compatibility:
* Add `rb_gc_mark_locations()` (#3704, @andrykonchin).
* Implement `rb_str_format()` (#3716, @andrykonchin).
* Add `IO#{pread, pwrite}` methods (#3718, @andrykonchin).
* Emit a warning when `it` call without arguments is used in a block without parameters (#3681, @andrykonchin).

Performance:

Expand Down
29 changes: 29 additions & 0 deletions spec/ruby/language/block_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1072,3 +1072,32 @@ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **
end
end
end

describe "`it` calls without arguments in a block with no ordinary parameters" do
ruby_version_is "3.3"..."3.4" do
it "emits a deprecation warning" do
-> {
eval "proc { it }"
}.should complain(/warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it\(\) or self.it/)
end

it "does not emit a deprecation warning when a block has parameters" do
-> { eval "proc { |a, b| it }" }.should_not complain
-> { eval "proc { |*rest| it }" }.should_not complain
-> { eval "proc { |*| it }" }.should_not complain
-> { eval "proc { |a:, b:| it }" }.should_not complain
-> { eval "proc { |**kw| it }" }.should_not complain
-> { eval "proc { |**| it }" }.should_not complain
-> { eval "proc { |&block| it }" }.should_not complain
-> { eval "proc { |&| it }" }.should_not complain
end

it "does not emit a deprecation warning when `it` calls with arguments" do
-> { eval "proc { it(42) }" }.should_not complain
end

it "does not emit a deprecation warning when `it` calls with explicit empty arguments list" do
-> { eval "proc { it() }" }.should_not complain
end
end
end
4 changes: 4 additions & 0 deletions src/main/java/org/truffleruby/language/methods/Arity.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ public boolean acceptsKeywords() {
return hasKeywords() || hasKeywordsRest();
}

public boolean acceptsPositionalArguments() {
return getRequired() > 0 || getOptional() > 0 || hasRest();
}

public boolean hasKeywords() {
return keywordArguments.length != 0;
}
Expand Down
29 changes: 27 additions & 2 deletions src/main/java/org/truffleruby/parser/YARPBlockNodeTranslator.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,17 @@
public final class YARPBlockNodeTranslator extends YARPTranslator {

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

public YARPBlockNodeTranslator(TranslatorEnvironment environment, Arity arity) {
super(environment);
public YARPBlockNodeTranslator(
TranslatorEnvironment environment,
Arity arity,
boolean acceptsBlockParameter,
RubyDeferredWarnings rubyWarnings) {
super(environment, rubyWarnings);
this.arity = arity;
this.acceptsBlockParameter = acceptsBlockParameter;
}

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

@Override
public RubyNode visitCallNode(Nodes.CallNode node) {
// emit a warning if `it` is called without arguments in a block without ordinal parameters
boolean noBlockParameters = !arity.acceptsKeywords() && !arity.acceptsPositionalArguments() &&
!acceptsBlockParameter;
if (node.name.equals("it") && node.receiver == null && node.isVariableCall() && noBlockParameters) {
SourceSection section = rubySource.getSource().createSection(node.startOffset, node.length);

String sourcePath = rubySource.getSourcePath(language).intern();
int lineNumber = section.getStartLine() + rubySource.getLineOffset();
String message = "`it` calls without arguments will refer to the first block param in Ruby 3.4; use it() or self.it";

rubyWarnings.warn(sourcePath, lineNumber, message);
}

return super.visitCallNode(node);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ public final class YARPDefNodeTranslator extends YARPTranslator {

public YARPDefNodeTranslator(
RubyLanguage language,
TranslatorEnvironment environment) {
super(environment);
TranslatorEnvironment environment,
RubyDeferredWarnings rubyWarnings) {
super(environment, rubyWarnings);

if (parseEnvironment.parserContext.isEval() || parseEnvironment.isCoverageEnabled()) {
shouldLazyTranslate = false;
Expand Down
15 changes: 11 additions & 4 deletions src/main/java/org/truffleruby/parser/YARPTranslator.java
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ public class YARPTranslator extends YARPBaseTranslator {
public static final RescueNode[] EMPTY_RESCUE_NODE_ARRAY = new RescueNode[0];

public Deque<Integer> frameOnStackMarkerSlotStack = new ArrayDeque<>();

protected RubyDeferredWarnings rubyWarnings;

/** whether a while-loop body is translated; needed to check correctness of operators like break/next/etc */
private boolean translatingWhile = false;
/** whether a for-loop body is translated; needed to enforce variables in the for-loop body to be declared outside
Expand All @@ -198,8 +201,9 @@ public class YARPTranslator extends YARPBaseTranslator {
/** all the encountered BEGIN {} blocks; they will be added finally at the beginning of the program AST */
private final ArrayList<RubyNode> beginBlocks = new ArrayList<>();

public YARPTranslator(TranslatorEnvironment environment) {
public YARPTranslator(TranslatorEnvironment environment, RubyDeferredWarnings rubyWarnings) {
super(environment);
this.rubyWarnings = rubyWarnings;
}

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

final YARPBlockNodeTranslator methodCompiler = new YARPBlockNodeTranslator(
newEnvironment,
arity);
arity,
parameters.block != null,
rubyWarnings);

methodCompiler.frameOnStackMarkerSlotStack = frameOnStackMarkerSlotStack;

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

final var defNodeTranslator = new YARPDefNodeTranslator(
language,
newEnvironment);
newEnvironment,
rubyWarnings);
var callTargetSupplier = defNodeTranslator.buildMethodNodeCompiler(node, parameters, arity);

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

final YARPTranslator moduleTranslator = new YARPTranslator(newEnvironment);
final YARPTranslator moduleTranslator = new YARPTranslator(newEnvironment, rubyWarnings);
final ModuleBodyDefinition definition = moduleTranslator.compileClassNode(moduleNode, bodyNode);
return new RunModuleDefinitionNode(definition, defineOrGetNode);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public RootCallTarget parse(RubySource rubySource, ParserContext parserContext,
// Translate to Ruby Truffle nodes

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

RubyNode truffleNode;
printParseTranslateExecuteMetric("before-translate", context, source);
Expand Down