From: "ufuk (Ufuk Kayserilioglu) via ruby-core" Date: 2023-10-30T20:00:33+00:00 Subject: [ruby-core:115206] [Ruby master Feature#19979] Allow methods to declare that they don't accept a block via `&nil` Issue #19979 has been updated by ufuk (Ufuk Kayserilioglu). > Is this a frequent error? I would not say that it is frequent but for the case where it happens the resulting behaviour is confusing and misleading. I have caught some instances in Shopify's code base, most of them in tests for mocking as @zverok says, where folks expected to add mock behaviour via a block, but the mocking method didn't expect one. That rendered the tests to end up testing the wrong thing, which is worse than not having tests at all. An explicit error or warning for such cases would be for the benefit of everyone. > I think the only times I saw it is due to confusion of the priority of `do/end` vs `{/}` (which needs to be learned separately anyway), and for some poorly-designed `open` methods which don't take a block. Yes, this is another source of such errors. > Note that this can already be done in a way that runs on older Rubies too with `raise ArgumentError, 'this method does not accept a block' if block_given?`. True, it can be, but making it explicit in the method signature will also allow tooling to take such behaviour into account as well. A static analyzer will be able to consume a `&nil` annotation and act on it, but won't be able to do the same for implicit `raise ArgumentError` code. > I think even if it was accepted it would be very unlikely for gems to use `&nil`, first they would need to require Ruby 3.3+ and second it's extra noise in the source code with almost no benefit for the gem. > So I think in practice this would not achieve much. Of course, but that is true for any new syntax. In my opinion, if we had adopted the `&nil` annotation back in 2019 when it was originally suggested, we would have had it in all supported versions today. I would like us to be there for Ruby 3.6, at least. > It's also a bit similar to `**nil`, which is almost never used. The intention isn't for this to be frequently used, but useful when an API author wants to make sure that the API is defined strictly. > Regarding #15554 I think that has the significant advantage that it does not require any changes in gems to be useful. It just automatically knows if a method accepts a block or not. Yes, doing it automatically would be my preference as well, but it seems that are many caveats to doing proper detection of calls to a block that makes automation not feasible. For example, we would have to forbid the ability to do `eval("yield")` which is a big ask. > Also, as a negative consequence, I can imagine a linter-enforced rule to "always add this declaration when the method doesn't need/accept block," which will make a lot of code worse. Again, my intention isn't to make this be the default state of affairs for general Ruby code. If I am writing a "mocking library" for example and my mocks don't accept any blocks for mocking behaviour, I would like to make sure my public mock methods are annotated with `&nil` so that end users don't use it incorrectly. I can also see Ruby stdlib/core methods using the annotation as well. Doing so would make calls to `Time.freeze { some code }` error out, for example. ---------------------------------------- Feature #19979: Allow methods to declare that they don't accept a block via `&nil` https://bugs.ruby-lang.org/issues/19979#change-105118 * Author: ufuk (Ufuk Kayserilioglu) * Status: Open * Priority: Normal ---------------------------------------- ## Abstract This feature proposes new syntax to allow methods to explicitly declare that they don't accept blocks, and makes passing of a block to such methods an error. ## Background In #15554, it was proposed to automatically detect methods that do not use the block passed to them, and to error if a block was passed to such methods. As far as I can tell, it was later on closed since #10499 solved a large part of the problem. That proposal has, as part of [a dev meeting discussion](https://github.com/ruby/dev-meeting-log/blob/b4357853c03dfe71b6eab320d5642d463854f50f/2019/DevMeeting-2019-01-10.md?plain=1#L110-L120), a proposal from @matz to allow methods to use `&nil` to explicitly declare that they don't accept a block. At the time, the proposal was trying to solve a bigger problem, so this sub-proposal was never considered seriously. However, notes in the proposal say: > It is explicit, but it is tough to add this `&nil` parameter declaration to all of methods (do you want to add it to `def []=(i, e, &nil)`?). (I agree `&nil` is valuable on some situations) This proposal extracts that sub-proposal to make this a new language feature. ## Proposal In Ruby, it is always valid for the caller to pass a block to a method call, even if the callee is not expecting a block to be passed. This leads to subtle user errors, where the author of some code assumes a method call uses a block, but the block passed to the method call is silently ignored. The proposal is to introduce `&nil` at method declaration sites to mean "This method does not accept a block". This is symmetric to the ability to pass `&nil` at call sites to mean "I am not passing a block to this method call", which is sometimes useful when making `super` calls (since blocks are always implicitly passed). Explicitly, the proposal is to make the following behaviour be a part of Ruby: ```ruby def find(item = nil, &nil) # some implementation that doesn't call `yield` or `block_given?` end find { |i| i == 42 } # => ArgumentError: passing block to the method `find' that does not accept a block. ``` ## Implementation I assume the implementation would be a grammar change to make `&nil` valid at method declaration sites, as well as raising an `ArgumentError` for methods that are called with a block but are declared with `&nil`. ## Evaluation Since I don't have an implementation, I can't make a proper evaluation of the feature proposal. However, I would expect the language changes to be minimal with no runtime costs for methods that don't use the `&nil` syntax. ## Discussion This proposal has much smaller scope than #15554 so that the Ruby language can start giving library authors the ability to explicitly mark their methods as not accepting a block. This is fully backward compatible, since it is an opt-in behaviour and not an opt-out one. Future directions after this feature proposal could be a way to signal to the VM that any method in a file that doesn't explicitly use `yield`/`block_given?` or explicitly declared a block parameter should be treated as not accepting a block. This can be done via some kind of pragma similar to `frozen_string_literal`, or through other means. However, such future directions are beyond the scope of this proposal. ## Summary Adding the ability for methods to declare that they don't accept a block will make writing code against such methods safer and more resilient, and will prevent silently ignored behaviour that is often hard to catch or troubleshoot. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/