From: Michael Edgar Date: 2011-04-18T21:26:33+09:00 Subject: Re: [ANN] To Yield or Not to Yield: An Inferable Question Robert, Excellent, and thank you! It seems I had let myself be tricked by the more common use of "block_given?" into forgetting the actual semanticsof a failed raise. My current analysis is equivalent to just skipping the exception handling part, and assuming the exception isn't handled. In fact, I realize now that I should not even have a special "yield" instruction, but instead lower it even further to something roughly like this: %temp = HiddenAnalyzerMagic.current_block_if_any if %temp %temp.call(...) else raise LocalJumpError.new('no block given (yield)', __FILE__, __LINE__) end In the "failed yield" case, constant propagation would take over, prune the if-true "%temp.call(...)" branch above, and then the only remaining branch does a raise. Raise just sets $! to the LocalJumpError constant and unconditionally jumps to a copy of the rescue handler. $! is read-only, so constant propagation works on it too, even though it's a global. A rescue handler is just a bunch of #=== calls and jumps, so a *sane* rescue handler which has constants in its rescue clause is actually just a bunch of (some_constant === $!) calls and branches on the result. Constant propagation can handle that, eliminating any rescue handlers that fail to match the LocalJumpError. If the exception is caught and not re-raised, the only path left in the rescue handler will lead out of the handler: it's optional! If it fails to be caught (either always or sometimes), there will be a path left to the next exception handler (or the Exit), via an error-path, and if that LJE continues to have an uncaught path until the Exit, then the yield is a required one. Some graphviz graphs would help illustrate this, but I don't want to spam up the list too much with big PNGs. As usual, that's if you have constants and pure methods as your rescue handler. It all hopes you don't do something like this (Ruby 1.9 only): Handler = Object.new def Handler.===(other) # analyzer definitely not smart enough to know rand(10) always < 10 other.message.size > rand(10) end def foo begin yield rescue Handler # always caught, because the LJE message is longer than 10 chars. end end foo is block-optional, but the analyzer as implemented would say it is block-required, as it can't prove the exception is always caught. Ouch. Michael Edgar adgar@carboni.ca http://carboni.ca/ On Apr 18, 2011, at 7:16 AM, Robert Klemme wrote: Now I see it. Thanks! This looks interesting and I think this issomething to muse about further. On first glance I only noticed thecomplete absence of another case of "optional block" apart from callsguarded by block_given? or tests for &b parameter to be non nil: caught exceptions