Module: ForwardableAccessor
- Defined in:
- lib/opal_patches.rb
Overview
Forwardable#def_instance_delegator — support for expression accessors
CRuby’s Forwardable evaluates the ‘accessor` argument as a Ruby expression when it is neither an ivar (`@foo`) nor a plain method name — so `def_delegator ’self.class’, :foo` delegates to the current class’s #foo method. Opal’s simplified Forwardable (stdlib/forwardable.rb) just treats the accessor literally and calls ‘__send__(’self.class’)‘, which raises method_missing.
Mustermann’s AST::Pattern relies on this exact CRuby behaviour:
instance_delegate %i[parser compiler ...] => 'self.class'
so without this patch the first Mustermann::Pattern#compile call from Sinatra::Base#route aborts the whole require chain. The fix below re-implements def_instance_delegator / def_single_delegator so that non-ivar accessors that look like Ruby expressions go through ‘instance_eval` / `class_eval` instead of `__send__`. homura patch: Cloudflare Workers disallows `new Function($code)` / `eval($code)` (Workers’ “Code generation from strings disallowed” rule). Opal’s ‘instance_eval(String)` compiles to a `new Function($code)` call, so any Forwardable delegation with an expression accessor (Mustermann’s ‘instance_delegate %i[parser compiler] => ’self.class’‘ is the real-world trigger) crashes on Workers at first dispatch.
The helper below walks a small subset of dot-separated accessor expressions — ‘self`, `self.class`, `@ivar`, `@ivar.method`, plain `method_name`, `method_name.other_method` — without going through `instance_eval`. Mustermann (and the Ruby stdlib itself) only uses identifiers from that subset, so we never need the full Ruby parser.
Class Method Summary collapse
Class Method Details
.resolve(instance, expr) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/opal_patches.rb', line 152 def resolve(instance, expr) expr = expr.to_s current = instance expr.split('.').each do |part| current = if part == 'self' instance elsif part.start_with?('@') instance.instance_variable_get(part) else current.__send__(part) end end current end |