phlex-ruby / phlex-rails

An object-oriented alternative to ActionView for Ruby on Rails.
https://www.phlex.fun
MIT License
215 stars 34 forks source link

Single method block passed as proc errors "Can't create Binding from C level Proc" #49

Closed dhnaranjo closed 1 year ago

dhnaranjo commented 1 year ago

Aight you have:

class Thing < Phlex::HTML
  def template
    div { "Dang!" }
  end
end

class Things < Phlex::HTML
  def template(&things)
    div(&things)
  end

  def thing
    render(Thing)
  end
end

# This works
render Things.new do |things|
  things.thing
end

# This returns an error: Can't create Binding from C level Proc
render(Things.new, &:thing)

Weird.

joeldrapper commented 1 year ago

Hey @dhnaranjo, thanks for the bug report. I wrote a reproduction based on your example here joeldrapper/phlex#479, but I can't seem to reproduce it on any of the versions of Ruby we test against. You can see the tests run against various versions of MRI, JRuby and TruffleRuby on Linux and macOS here. https://github.com/joeldrapper/phlex/actions/runs/4122921860

One thing we don't test against is Windows. Is this happening on Windows? Otherwise, I wonder if this issue is caused by some other gem you have installed. 🤔

joeldrapper commented 1 year ago

Update: I was able to reproduce this issue in phlex-rails, where yield_content calls block.binding. I will try to find a fix.

joeldrapper commented 1 year ago

The fix for this had to be reverted. See #61

joelmoss commented 1 year ago

I am now just getting this same exception after upgrading to phlex-rails 0.7.0. 0.6.1 works fine.

Running the code from @dhnaranjo above reproduces it reliably for me.

Here's a trace if it helps...

        âš  ArgumentError: Can't create Binding from C level Proc
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/phlex-rails-0.7.0/lib/phlex/rails/sgml/overrides.rb:110 binding
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/phlex-rails-0.7.0/lib/phlex/rails/sgml/overrides.rb:110 yield_content_with_args
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/bundler/gems/phlex-bd14a8720c0b/lib/phlex/sgml.rb:59 block (2 levels) in __final_call__
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/bundler/gems/phlex-bd14a8720c0b/lib/phlex/sgml.rb:205 yield_content
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/phlex-rails-0.7.0/lib/phlex/rails/sgml/overrides.rb:101 yield_content
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/bundler/gems/phlex-bd14a8720c0b/lib/phlex/elements.rb:26 div
                test/hue/components/description_list.rb:178 template
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/bundler/gems/phlex-bd14a8720c0b/lib/phlex/sgml.rb:57 block in __final_call__
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/bundler/gems/phlex-bd14a8720c0b/lib/phlex/sgml.rb:186 around_template
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/bundler/gems/phlex-bd14a8720c0b/lib/phlex/sgml.rb:51 __final_call__
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/bundler/gems/phlex-bd14a8720c0b/lib/phlex/testing/view_helper.rb:10 render
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/phlex-testing-capybara-0.1.0/lib/phlex/testing/capybara.rb:22 render
                test/hue/components/description_list.rb:192 block (4 levels) in <top (required)>
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/it.rb:49 block in handle_skip
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/it.rb:48 catch
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/it.rb:48 handle_skip
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/it.rb:42 block (2 levels) in call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/base.rb:28 around
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/it.rb:41 block in call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/assertions.rb:296 block in nested
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/output/buffered.rb:64 indented
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/assertions.rb:294 nested
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/it.rb:38 call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:52 block (2 levels) in call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:51 each
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:51 block in call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/assertions.rb:296 block in nested
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/output/null.rb:32 indented
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/assertions.rb:294 nested
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:50 call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:52 block (2 levels) in call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:51 each
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:51 block in call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/assertions.rb:296 block in nested
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/output/null.rb:32 indented
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/assertions.rb:294 nested
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:50 call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:52 block (2 levels) in call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:51 each
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:51 block in call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/assertions.rb:296 block in nested
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/output/null.rb:32 indented
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/assertions.rb:294 nested
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:50 call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:52 block (2 levels) in call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:51 each
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:51 block in call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/assertions.rb:296 block in nested
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/output/null.rb:32 indented
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/assertions.rb:294 nested
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/context.rb:50 call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/registry.rb:58 call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/lib/sus/filter.rb:77 call
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/sus-0.19.1/bin/sus:23 <top (required)>
                /Users/joelmoss/.rbenv/versions/3.2.1/bin/sus:25 load
                /Users/joelmoss/.rbenv/versions/3.2.1/bin/sus:25 <top (required)>
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/lib/bundler/cli/exec.rb:58 load
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/lib/bundler/cli/exec.rb:58 kernel_load
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/lib/bundler/cli/exec.rb:23 run
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/lib/bundler/cli.rb:491 exec
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/lib/bundler/vendor/thor/lib/thor/command.rb:27 run
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/lib/bundler/vendor/thor/lib/thor/invocation.rb:127 invoke_command
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/lib/bundler/vendor/thor/lib/thor.rb:392 dispatch
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/lib/bundler/cli.rb:34 dispatch
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/lib/bundler/vendor/thor/lib/thor/base.rb:485 start
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/lib/bundler/cli.rb:28 start
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/exe/bundle:45 block in <top (required)>
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/lib/bundler/friendly_errors.rb:117 with_friendly_errors
                /Users/joelmoss/.rbenv/versions/3.2.1/lib/ruby/gems/3.2.0/gems/bundler-2.4.1/exe/bundle:33 <top (required)>
                /Users/joelmoss/.rbenv/versions/3.2.1/bin/bundle:25 load
                /Users/joelmoss/.rbenv/versions/3.2.1/bin/bundle:25 <main>
joeldrapper commented 1 year ago

Thanks @joelmoss. The problem is calling binding on a C-level proc, e.g. foo(&:bar) or :bar.to_proc raises an error and it's too generic of an error to rescue from. There's no sensible way to detect if a given proc is a C-level proc either.

joelmoss commented 1 year ago

Ok so do we now why it raises an error?

joeldrapper commented 1 year ago

It raises an error because C-level Procs don't have a binding. I wish it would just return nil, but they decided to make it raise. Fortunately, I found a way to avoid calling binding altogether. #70 should fix this issue and make things faster too.