[#114348] [Ruby master Feature#19832] Method#destructive?, UnboundMethod#destructive? — "sawa (Tsuyoshi Sawada) via ruby-core" <ruby-core@...>

Issue #19832 has been reported by sawa (Tsuyoshi Sawada).

15 messages 2023/08/06

[#114365] [Ruby master Bug#19834] Segmentation fault while running in docker — "ramachandran@... (Ramachandran A) via ruby-core" <ruby-core@...>

Issue #19834 has been reported by ramachandran@mallow-tech.com (Ramachandran A).

7 messages 2023/08/09

[#114380] [Ruby master Bug#19837] Concurrent calls to Process.waitpid2 misbehave on Ruby 3.1 & 3.2 — "kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core" <ruby-core@...>

Issue #19837 has been reported by kjtsanaktsidis (KJ Tsanaktsidis).

7 messages 2023/08/11

[#114399] [Ruby master Feature#19839] Need a method to check if two ranges overlap — "shouichi (Shouichi KAMIYA) via ruby-core" <ruby-core@...>

Issue #19839 has been reported by shouichi (Shouichi KAMIYA).

27 messages 2023/08/18

[#114410] [Ruby master Bug#19841] Marshal.dump stack overflow with recursive Time — "segiddins (Samuel Giddins) via ruby-core" <ruby-core@...>

Issue #19841 has been reported by segiddins (Samuel Giddins).

9 messages 2023/08/18

[#114422] [Ruby master Feature#19842] Intorduce M:N threads — "ko1 (Koichi Sasada) via ruby-core" <ruby-core@...>

Issue #19842 has been reported by ko1 (Koichi Sasada).

30 messages 2023/08/21

[#114590] [Ruby master Bug#19857] Eval coverage is reset after each `eval`. — "ioquatix (Samuel Williams) via ruby-core" <ruby-core@...>

Issue #19857 has been reported by ioquatix (Samuel Williams).

21 messages 2023/08/30

[ruby-core:114361] [Ruby master Feature#19832] Method#destructive?, UnboundMethod#destructive?

From: "Dan0042 (Daniel DeLorme) via ruby-core" <ruby-core@...>
Date: 2023-08-08 19:28:19 UTC
List: ruby-core #114361
Issue #19832 has been updated by Dan0042 (Daniel DeLorme).


This is a great idea. No doubt there are many cases where it's impossible to know *for sure* if a method is destructive or not, but it should be possible to make it good enough to be useful.

> Consider for instance Array#map. Is this method destructive? Well it could be used in a destructive way. But who knows.

Can you explain that? I don't see how Array#map could be destructive; even if the array is modified in the block, that is not a property of the #map method itself.

> Whether a method call modifies its receiver or not ultimately does not fix until that method call actually modifies its receiver. This is how the language is designed to be.

I must ask, is that really relevant? `"x".freeze.concat("")` raises a FrozenError even though the receiver would not have been modified. Because String#concat is considered a destructive method no matter if it doesn't actually modify its receiver. We want to know if the method is generally destructive, not if a particular call is destructive.

For core class methods I think this 'destructive' flag can be inferred from the presence of `rb_check_frozen`. In fact this would mesh very well with the way method signatures are defined in pseudo-ruby with the body defined via `Primitive`. Then `rb_check_frozen` could be pulled out of the C code and expressed as part of the method signature. For example something like
```
def concat(ary)
  Primitive.modify!  #=> call rb_check_frozen, and also mark this method as destructive
  Primitive.rb_ary_concat(ary)
end
```

For regular ruby code, probably the only way to know if a method is destructive is to check for instance variable assignments. It's not perfect but it should serve as a good enough definition of 'destructive'. It's almost certainly impossible to propagate the 'destructive' flag transitively (#foo would be considered non-destructive even if it calls #bar destructive method). But if a method has the `super` keyword it may be possible to inherit the 'destructive' flag from up the call chain.

----------------------------------------
Feature #19832: Method#destructive?, UnboundMethod#destructive?
https://bugs.ruby-lang.org/issues/19832#change-104104

* Author: sawa (Tsuyoshi Sawada)
* Status: Open
* Priority: Normal
----------------------------------------
I propose to add `destructive?` property to `Method` and `UnboundMethod` instances, which shall behave like:

```ruby
String.instance_method(:<<).destructive? # => true
String.instance_method(:+).destructive? # => false
```

One main purpose of using these classes is to inspect and make sure how a certain method behaves. Besides arity and owner, whether a method is destructive or not is one important piece of information, but currently, you cannot achieve that from `Method` or `UnboundMethod` instances.

The problem is how to implement this. It is best if this information (whether or not a method is destructive) can be extracted automatically from the method definition.

Unlike owner and arity, it may or may not be straightforward by statically analyzing the code. I think that, if a method definition defined at the ruby level does not call a destructive method anywhere within its own definition, and no dynamic method calls (`send`, `eval`, etc.) are made, then we can say that the method is non-destructive. If it does call, then the method is most likely a destructive method (it would not be destructive if the internally-called destructive method is applied to a different object. Or, we could rather call that a destructive method in the sense that it has a destructive side effect).

If doing that turns out to be difficult for some or all cases, then a practical approach for the difficult cases is to label the methods as destructive or not, manually. We can perhaps have methods `Module#destructive` and `Module#non_destructive` which take (a) symbol/string argument(s) and return the method name(s) in symbol so that they can be used like:

```ruby
class A
  destructive private def some_destructive_private_method
    ...
  end
end
```

or

```ruby
class A
  def foo; ... end
  def bar; ... end
  def baz; ... end

  non_destructive :foo, :baz
  destructive :bar
end
```

or

```ruby
class A
  non_destructive

  def foo; ... end
  def baz; ... end

  destructive

  def bar; ... end
end
```

When the method is not (yet) specified whether destructive or not, the return value can be `"unknown"` (or `:unknown` or `nil`) by default.

```ruby
String.instance_method(:<<).destructive? # => "unknown"
```




-- 
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/

In This Thread