From: Michael Edgar Date: 2011-04-21T06:46:56+09:00 Subject: Re: To Yield or Not to Yield: An Inferable Question On Apr 20, 2011, at 4:59 AM, Brian Candler wrote: > However there are some cases where this is done unnecessarily, > net/telnet.rb being the prime example. e.g. > > if block_given? > waitfor({"Prompt" => match, "Timeout" => time_out}){|c| yield c > } > else > waitfor({"Prompt" => match, "Timeout" => time_out}) > end > > could have been written simply as: > > waitfor({"Prompt" => match, "Timeout" => time_out}, &blk) I don't know much about the Telnet library, so I'll comment only on how this affects analysis. If you rewrote the code you provided as you sugested, the question of whether the block is used then simply depends on whether `waitfor` calls it. No matter how pathologically you write that method, if you introduce the current block as a variable (either via Proc::new or as an explicit block argument, or ...) an analyzer should assume that `waitfor(..., &blk)` may refer to the currently active block, unless it can prove otherwise. So the question becomes: how are blocks used by Net::Telnet#waitfor, and all overrides of #waitfor by subclasses which in turn do not override #cmd without invoking super? In other words, resolve the call to #waitfor, and recursively analyze the yield behavior of all possible targets of that method call. If analysis worked on one method, it will work on #waitfor ! Indeed, the only definition of #waitfor I could find in the standard library has only two calls to yield: yield buf if block_given? # telnet.rb:594 and yield nil if block_given? # telnet.rb:599 The hard part is "resolve the call to #waitfor". My belief is that method resolution is undecidable in Ruby, though I haven't proven it just yet. The compiler writers live with this fact and haven't yet gone nuts, for which we owe them our sincerest gratitude. But in designing a linter, one is permitted to occasionally take shortcuts, perhaps even opinionated ones! While I must do my best to accommodate dynamic behavior, I personally have no issue with giving an incorrect analysis if you are nondeterministically creating a subclass of Net::Telnet and overriding methods. Somewhere down the line, it may be reasonable to turn off certain optimistic assumptions such as "by the time I analyze this method, I have seen definitions (using `def`, `eval(constant_string)`, `define_method(constant)`, ...) of all possible methods it may call." For now though, purely conservative inference is not yet my focus. Michael Edgar adgar@carboni.ca http://carboni.ca/