Skip to content

Support Symbol#to_proc block inference#452

Open
pvcresin wants to merge 3 commits into
ruby:masterfrom
pvcresin:symbol-to-proc-block-inference
Open

Support Symbol#to_proc block inference#452
pvcresin wants to merge 3 commits into
ruby:masterfrom
pvcresin:symbol-to-proc-block-inference

Conversation

@pvcresin
Copy link
Copy Markdown
Contributor

@pvcresin pvcresin commented May 8, 2026

Summary

Fixes #414

Support Symbol#to_proc blocks such as map(&:to_i).

This treats a symbol passed as a block as a method call where the first yielded block argument is the receiver and the remaining yielded arguments are method arguments.

Changes

  • Infer ["1", "2"].map(&:to_i) as Array[Integer]
  • Support the same Symbol#to_proc behavior for Ruby yield
  • Add scenario coverage for RBS block calls and Ruby method block calls

Co-authored-by: Codex <codex@openai.com>
@sinsoku
Copy link
Copy Markdown
Collaborator

sinsoku commented May 11, 2026

I tried this branch against Redmine 6.1.1 with this script, and the analysis hangs (master finishes in under 20s). Could you take a look?

Comment thread lib/typeprof/core/graph/box.rb Outdated
resolve(genv, changes) do |me, ty, mid, orig_ty|
if !me
if @node.is_a?(AST::YieldNode) && mid == :call && orig_ty.is_a?(Type::Symbol)
box = add_symbol_proc_call_box(changes, genv, orig_ty.sym, @a_args.positionals)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yield with keyword arguments is valid Ruby, e.g.:

def foo
  yield 1, key: "v"
end
foo { |x, key:| puts "x=#{x}, key=#{key}" } # => x=1, key=v

So I think @a_args.keywords should also be passed through to add_symbol_proc_call_box here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 7b8e9b0. @a_args.keywords is now forwarded, with a kwarg-yield scenario added.

pvcresin added 2 commits May 12, 2026 13:58
Allocating a fresh ActualArguments / MethodCallBox on every parent
re-run produced fresh `box.ret` vertex identities, which forced
`typecheck`'s edge to be re-added/removed each cycle. The resulting
on_type_added / on_type_removed re-scheduled the parent endlessly,
hanging analysis on large codebases (e.g. Redmine 6.1.1 never
terminates).

Memoize the sub-box on the parent box itself, keyed by
(recv, sym, *positionals), and tear it down in the parent's destroy.
This keeps `box.ret` stable across re-runs so the change-set edges
converge, matching the pattern already used by `@generics`.
`yield 1, key: "v"` is valid Ruby and, when received as `&:sym`,
desugars to `1.sym(key: "v")`. The previous implementation dropped
the keywords, leaving inference incomplete for symbol-proc blocks
that take keyword arguments.

Add a `caller_keywords` parameter to `add_symbol_proc_call_box` and
pass `@a_args.keywords` from the yield path. Cover the behavior with
a yield-with-kwargs scenario.
@pvcresin
Copy link
Copy Markdown
Contributor Author

@sinsoku Reproduced the Redmine 6.1.1 hang locally.
Each parent re-run allocated a fresh sub-box, so box.ret had a new identity every cycle and the typecheck edge churned the parent into an infinite re-run.
Fixed in 70adf42 by memoizing the sub-box on the parent — Redmine now finishes in ~5s, matching master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Symbol#to_proc pattern (&:method_name) is not inferred correctly

2 participants