[#122900] [Ruby Bug#21529] Deprecate the /o modifier and warn against using it — "jpcamara (JP Camara) via ruby-core" <ruby-core@...>

Issue #21529 has been reported by jpcamara (JP Camara).

10 messages 2025/08/03

[#122925] [Ruby Feature#21533] Introduce `Time#am?` and `Time#pm?` — "matheusrich (Matheus Richard) via ruby-core" <ruby-core@...>

SXNzdWUgIzIxNTMzIGhhcyBiZWVuIHJlcG9ydGVkIGJ5IG1hdGhldXNyaWNoIChNYXRoZXVzIFJp

10 messages 2025/08/06

[#122932] [Ruby Bug#21534] ppc64le Ractor ractor_port_receive aborted (core dumped) — "jaruga (Jun Aruga) via ruby-core" <ruby-core@...>

Issue #21534 has been reported by jaruga (Jun Aruga).

12 messages 2025/08/07

[#122953] [Ruby Bug#21540] prism allows `foo && return bar` when parse.y doesn't — "Earlopain (Earlopain _) via ruby-core" <ruby-core@...>

Issue #21540 has been reported by Earlopain (Earlopain _).

12 messages 2025/08/12

[#122964] [Ruby Feature#21543] Point ArgumentError to the call site — "mame (Yusuke Endoh) via ruby-core" <ruby-core@...>

Issue #21543 has been reported by mame (Yusuke Endoh).

8 messages 2025/08/14

[#122969] [Ruby Feature#21545] `#try_dig`: a dig that returns early if it cannot dig deeper — "cb341 (Daniel Bengl) via ruby-core" <ruby-core@...>

Issue #21545 has been reported by cb341 (Daniel Bengl).

10 messages 2025/08/15

[#122987] [Ruby Bug#21547] SEGV after 2083fa commit — "watson1978 (Shizuo Fujita) via ruby-core" <ruby-core@...>

Issue #21547 has been reported by watson1978 (Shizuo Fujita).

10 messages 2025/08/20

[#123042] [Ruby Feature#21550] Ractor.sharable_proc/sharable_lambda to make sharable Proc object — "ko1 (Koichi Sasada) via ruby-core" <ruby-core@...>

SXNzdWUgIzIxNTUwIGhhcyBiZWVuIHJlcG9ydGVkIGJ5IGtvMSAoS29pY2hpIFNhc2FkYSkuDQoN

16 messages 2025/08/21

[#123122] [Ruby Feature#21556] Add true? and false? methods to NilClass, TrueClass, FalseClass, and String — "Phalado (Raphael Cordeiro) via ruby-core" <ruby-core@...>

Issue #21556 has been reported by Phalado (Raphael Cordeiro).

9 messages 2025/08/29

[#123146] [Ruby Bug#21559] Unicode normalization nfd -> nfc -> nfd is not reversible — "tompng (tomoya ishida) via ruby-core" <ruby-core@...>

Issue #21559 has been reported by tompng (tomoya ishida).

8 messages 2025/08/31

[ruby-core:123100] [Ruby Feature#21543] Point ArgumentError to the call site

From: "mame (Yusuke Endoh) via ruby-core" <ruby-core@...>
Date: 2025-08-28 04:03:26 UTC
List: ruby-core #123100
Issue #21543 has been updated by mame (Yusuke Endoh).

Status changed from Closed to Assigned
Assignee set to mame (Yusuke Endoh)

Thank you everyone. I also talked with @matz, and he said "give it a try."

I have prepared https://github.com/ruby/error_highlight/pull/61 . Matz was reluctant to change the backtrace itself, so I implemented it without modifying the backtrace for now, displaying only caller/callee snippets.

I will merge it soon. Please give it a try, and let me know if you encounter any unusual or inconvenient cases.

----------------------------------------
Feature #21543: Point ArgumentError to the call site
https://bugs.ruby-lang.org/issues/21543#change-114415

* Author: mame (Yusuke Endoh)
* Status: Assigned
* Assignee: mame (Yusuke Endoh)
----------------------------------------
Consider this buggy code:

```ruby
def foo(x, y)
end

foo(1)
```

The resulting error is:

```
$ ruby test.rb
test.rb:1:in `foo': wrong number of arguments (given 1, expected 2) (ArgumentError)
	from test.rb:4:in `<main>'
```

I want this to be:

```
$ ruby test.rb
test.rb:4:in `<main>': wrong number of arguments (given 1, expected 2) (ArgumentError)
for `foo' at test.rb:1
```

## Problem

In my experience, the code that needs fixing is almost always the caller, not the callee. In the above case, one would change `foo(1)` to something like `foo(1, 2)`.

My typical debugging workflow starts with opening the first location shown in the backtrace. This takes me to the `def` line of the method. However, I can barely remember a single time I've ever had to fix the method definition itself in this scenario. I often find myself sighing, going back to the backtrace, finding the second location (the caller), and opening that file instead.

I think this inconvenience is a significant loss of productivity.

I understand that the current behavior is designed because developers need to check the callee (the method definition) to see what arguments are required for the fix.

However, I believe the primary location in a backtrace should point to the code that is most likely to need modification. There are cases where developers can fix the call site without re-checking the method definition. Even when they do need to check the callee to recall the expected arguments, it feels more natural to first understand "what arguments am I currently passing?" at the call site before asking "what arguments should I be passing?". This workflow makes it easier to spot the mistake.

## Proposal

I propose that the backtrace for an ArgumentError should point to the caller's location (the call site) as shown in the above. The location of the callee (the method definition) could instead be included within the error message itself.

For a longer backtrace, the output would look like this:

```ruby
class TestClass
  def foo(x, y)
  end

  def bar
    foo(1)
  end

  def main
    bar
  end
end

TestClass.new.main
```

```
$ ruby test.rb
test.rb:6:in 'TestClass#bar': wrong number of arguments (given 1, expected 2) (ArgumentError)
for TestClass#foo at test.rb:2
        from test.rb:10:in 'TestClass#main'
        from test.rb:14:in '<main>'
```

What do you think of this proposal? I have attached a proof-of-concept patch, though it will require more work before it is merge-ready.

```diff
diff --git a/vm_args.c b/vm_args.c
index 44be6f54c5..986cb3e4c1 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -980,17 +980,7 @@ raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb
 {
     VALUE at;

-    if (iseq) {
-        vm_push_frame(ec, iseq, VM_FRAME_MAGIC_DUMMY | VM_ENV_FLAG_LOCAL, Qnil /* self */,
-                      VM_BLOCK_HANDLER_NONE /* specval*/, (VALUE) cme /* me or cref */,
-                      ISEQ_BODY(iseq)->iseq_encoded,
-                      ec->cfp->sp, 0, 0 /* stack_max */);
-        at = rb_ec_backtrace_object(ec);
-        rb_vm_pop_frame(ec);
-    }
-    else {
-        at = rb_ec_backtrace_object(ec);
-    }
+    at = rb_ec_backtrace_object(ec);

     rb_ivar_set(exc, idBt_locations, at);
     rb_exc_set_backtrace(exc, at);
@@ -1000,7 +990,15 @@ raise_argument_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb
 static void
 argument_arity_error(rb_execution_context_t *ec, const rb_iseq_t *iseq, const rb_callable_method_entry_t *cme, const int miss_argc, const int min_argc, const int max_argc)
 {
-    VALUE exc = rb_arity_error_new(miss_argc, min_argc, max_argc);
+    VALUE rb_gen_method_name(VALUE owner, VALUE name);
+    VALUE mname = rb_gen_method_name(cme->owner, rb_id2str(cme->def->original_id));
+    const rb_iseq_t *miseq = cme->def->body.iseq.iseqptr;
+    VALUE file = Qfalse, lineno = 0;
+    if (miseq) {
+        file = rb_iseq_path(miseq);
+        lineno = rb_iseq_first_lineno(miseq);
+    }
+    VALUE exc = rb_arity_error_new(miss_argc, min_argc, max_argc, mname, file, lineno);
     if (ISEQ_BODY(iseq)->param.flags.has_kw) {
         const struct rb_iseq_param_keyword *const kw = ISEQ_BODY(iseq)->param.keyword;
         const ID *keywords = kw->table;
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 3aca1bc24f..a5ad2f0562 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -484,7 +484,7 @@ rb_vm_push_frame_fname(rb_execution_context_t *ec, VALUE fname)

 /* method dispatch */
 static inline VALUE
-rb_arity_error_new(int argc, int min, int max)
+rb_arity_error_new(int argc, int min, int max, VALUE mname, VALUE file, VALUE lineno)
 {
     VALUE err_mess = rb_sprintf("wrong number of arguments (given %d, expected %d", argc, min);
     if (min == max) {
@@ -497,13 +497,19 @@ rb_arity_error_new(int argc, int min, int max)
         rb_str_catf(err_mess, "..%d", max);
     }
     rb_str_cat_cstr(err_mess, ")");
+    if (mname) {
+        rb_str_catf(err_mess, "\nfor %"PRIsVALUE, mname);
+        if (file) {
+            rb_str_catf(err_mess, " at %"PRIsVALUE":%"PRIsVALUE, file, lineno);
+        }
+    }
     return rb_exc_new3(rb_eArgError, err_mess);
 }

 void
 rb_error_arity(int argc, int min, int max)
 {
-    rb_exc_raise(rb_arity_error_new(argc, min, max));
+    rb_exc_raise(rb_arity_error_new(argc, min, max, Qfalse, Qfalse, INT2FIX(0)));
 }

 /* lvar */
```
```

---Files--------------------------------
poc.patch (6.31 KB)


-- 
https://bugs.ruby-lang.org/
______________________________________________
 ruby-core mailing list -- [email protected]
 To unsubscribe send an email to [email protected]
 ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/


In This Thread