Repository: soutaro/steep (Ruby)

Issue:
optional method arg named `_` implodes `steep check`
does not affect and mandatory `_` arg and optional not-`_` args

---

Ruby:
```ruby
def m(x = nil) = nil
```

RBS:
```rbs
class Object
  def m: (?untyped) -> void
end
```

<details><summary><code>steep check</code>:</summary>

```
# Type checking files:

......................................................................[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)] Unexpected error: #<RuntimeError: Unexpected variable name: _>
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_inference/type_env.rb:92:in `[]'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:1308:in `block (2 levels) in synthesize'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   <internal:kernel>:148:in `yield_self'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:1304:in `block in synthesize'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:694:in `synthesize'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:3245:in `block in synthesize_children'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/node_helper.rb:7:in `block in each_child_node'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/node_helper.rb:5:in `each'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/node_helper.rb:5:in `each_child_node'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:3243:in `synthesize_children'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:963:in `block (3 levels) in synthesize'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:280:in `block in with_method_constr'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/subtyping/check.rb:51:in `push_variable_bounds'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:279:in `with_method_constr'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:942:in `block (2 levels) in synthesize'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   <internal:kernel>:148:in `yield_self'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:937:in `block in synthesize'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:694:in `synthesize'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/services/type_check_service.rb:393:in `type_check'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/services/type_check_service.rb:329:in `block in type_check_file'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/services/type_check_service.rb:327:in `type_check_file'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/services/type_check_service.rb:283:in `block (2 levels) in typecheck_source'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep.rb:180:in `measure'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/services/type_check_service.rb:277:in `block in typecheck_source'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/services/type_check_service.rb:276:in `typecheck_source'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/server/type_check_worker.rb:198:in `handle_job'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/server/base_worker.rb:56:in `block (2 levels) in run'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 1.5.3] [typecheck:typecheck@6] [background] [#typecheck_source(path=underscore_arg.rb)] [#type_check_file(underscore_arg.rb@lib)] [synthesize:(1:1)] [synthesize:(1:7)]   /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/server/base_worker.rb:46:in `block in run'
F.............

underscore_arg.rb:1:6: [error] UnexpectedError: Unexpected variable name: _(RuntimeError)
│ 1. /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_inference/type_env.rb:92:in `[]'
│ 2. /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:1308:in `block (2 levels) in synthesize'
│ 3. <internal:kernel>:148:in `yield_self'
│ 4. /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:1304:in `block in synthesize'
│ 5. /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `block in tagged'
│ 6. /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:37:in `tagged'
│ 7. /usr/local/lib/ruby/gems/3.2.0/gems/activesupport-7.0.8/lib/active_support/tagged_logging.rb:99:in `tagged'
│ 8. /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:694:in `synthesize'
│ 9. /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:3245:in `block in synthesize_children'
│ 10. /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/node_helper.rb:7:in `block in each_child_node'
│ 11. /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/node_helper.rb:5:in `each'
│ 12. /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/node_helper.rb:5:in `each_child_node'
│ 13. /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:3243:in `synthesize_children'
│ 14. /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:963:in `block (3 levels) in synthesize'
│ 15. /usr/local/lib/ruby/gems/3.2.0/gems/steep-1.5.3/lib/steep/type_construction.rb:280:in `block in with_method_constr'
│   (28 more backtrace)
│
│ Diagnostic ID: Ruby::UnexpectedError
│
└ def m(_ = nil) = nil
        ~~~~~~~

Detected 1 problem from 1 file
```

</details>


Discussion:
Related: #659



Patch:
diff --git a/lib/steep/type_construction.rb b/lib/steep/type_construction.rb
index 6ddb1ccf4..e2429da42 100644
--- a/lib/steep/type_construction.rb
+++ b/lib/steep/type_construction.rb
@@ -1305,29 +1305,39 @@ def synthesize(node, hint: nil, condition: false)
             var = node.children[0]
             rhs = node.children[1]
 
-            var_type = context.type_env[var]
+            if SPECIAL_LVAR_NAMES.include?(var)
+              synthesize(rhs)
+              add_typing(node, type: AST::Builtin.any_type)
+            else
+              var_type = context.type_env[var]
 
-            if var_type
-              type, constr = check(rhs, var_type) do |expected_type, actual_type, result|
-                typing.add_error(
-                  Diagnostic::Ruby::IncompatibleAssignment.new(
-                    node: node,
-                    lhs_type: expected_type,
-                    rhs_type: actual_type,
-                    result: result
+              if var_type
+                type, constr = check(rhs, var_type) do |expected_type, actual_type, result|
+                  typing.add_error(
+                    Diagnostic::Ruby::IncompatibleAssignment.new(
+                      node: node,
+                      lhs_type: expected_type,
+                      rhs_type: actual_type,
+                      result: result
+                    )
                   )
-                )
+                end
+              else
+                type, constr = synthesize(rhs)
               end
-            else
-              type, constr = synthesize(rhs)
-            end
 
-            constr.add_typing(node, type: type)
+              constr.add_typing(node, type: type)
+            end
           end
 
         when :restarg
           yield_self do
             var = node.children[0]
+
+            if SPECIAL_LVAR_NAMES.include?(var)
+              return add_typing(node, type: AST::Builtin.any_type)
+            end
+
             type = context.type_env[var]
 
             unless type
@@ -1344,6 +1354,11 @@ def synthesize(node, hint: nil, condition: false)
         when :kwrestarg
           yield_self do
             var = node.children[0]
+
+            if SPECIAL_LVAR_NAMES.include?(var)
+              return add_typing(node, type: AST::Builtin.any_type)
+            end
+
             type = context.type_env[var]
             unless type
               if context.method_context&.method_type


Test patch:
diff --git a/test/type_check_test.rb b/test/type_check_test.rb
index c6fd06a08..e86d50530 100644
--- a/test/type_check_test.rb
+++ b/test/type_check_test.rb
@@ -1714,4 +1714,35 @@ def []=: (String, String) -> Integer
       YAML
     )
   end
+
+  def test_underscore_opt_param
+    run_type_check_test(
+      signatures: {
+        "a.rbs" => <<~RBS
+          class Foo
+            def foo: (?String, *untyped, **untyped) -> void
+
+            def bar: () { (?String, *untyped, **untyped) -> void } -> void
+          end
+        RBS
+      },
+      code: {
+        "a.rb" => <<~RUBY
+          class Foo
+            def foo(_ = "", *_, **_)
+              bar {|_ = "", *_, **_| }
+            end
+
+            def bar
+            end
+          end
+        RUBY
+      },
+      expectations: <<~YAML
+        ---
+        - file: a.rb
+          diagnostics: []
+      YAML
+    )
+  end
 end