Project

General

Profile

Actions

Bug #20043

closed

`defined?` checks for method existence but only sometimes

Added by tenderlovemaking (Aaron Patterson) about 1 year ago. Updated 6 months ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 3.3.0dev (2023-12-05T21:25:34Z master 56eccb350b) [arm64-darwin23]
[ruby-core:115595]

Description

When an expression is passed to defined?, it will sometimes check if a method in a sub-expression is defined and sometimes it won't.

For example:

$ ./miniruby -e'p defined?(a)'
nil
$ ./miniruby -e'p defined?([a])'
nil

In the above case, Ruby will check whether or not the method a is defined, and it returns nil. However, if you use a splat, it will not check:

$ ./miniruby -e'p defined?([*a])'
"expression"

The same thing seems to happen with method parameters:

$ ./miniruby -e'p defined?(itself)'
"method"
$ ./miniruby -e'p defined?(itself(a))'
nil
$ ./miniruby -e'p defined?(itself(*a))'
"method"

Oddly, defined? will check contents of arrays, but won't check contents of hashes:

$ ./miniruby -e'p defined?([[[[a]]]])'
nil
$ ./miniruby -e'p defined?({ a => a })'
"expression"

I think all of the cases that refer to a should check whether or not a is defined regardless of splats or hashes.


Files

Updated by Eregon (Benoit Daloze) about 1 year ago

I wonder if defined? should only be defined for constant paths and method call without arguments, e.g. defined?(RubyVM.keep_script_lines).
Is there any use case for defined? besides those?

IOW, I think the simpler defined? is the better (less complexity in Ruby implementations, I think little value in practice).
That could mean don't recurse into nested nodes or so, except for constant paths.

Updated by byroot (Jean Boussier) about 1 year ago

Is there any use case for defined? besides those?

Instance variables for sure: defined?(@ivar).

Also checking for super: defined?(super).

Some people use it for a sequence of calls: defined?(foo.bar.baz).

Updated by Dan0042 (Daniel DeLorme) about 1 year ago

I didn't know about this way of using defined? and it's interesting to me that we can check if multiple things are defined at once. Although the 3rd case below is another example that seems odd. But both that and defined?([*a]) return "expression" since ruby 1.9, not 2.2

x = 1
defined?(x) and defined?(y) and defined?(z) #=> nil
defined?([x, y, z])                         #=> nil (shorter!)
defined?(x && y && z)                       #=> "expression" (???)
defined?(x & y & z)                         #=> nil

Updated by tenderlovemaking (Aaron Patterson) about 1 year ago

ko1 (Koichi Sasada) wrote in #note-3:

It seems a bug from Ruby 2.2.

I think itself was added in Ruby 2.2. Seems this bug is perhaps from Ruby 1.9?

Dan0042 (Daniel DeLorme) wrote in #note-4:

I didn't know about this way of using defined? and it's interesting to me that we can check if multiple things are defined at once.

Yes, I thought so too. I can understand the utility of defined?([x, y, z]), but I can't tell if that was intended? Especially considering the x && y && z case.

Also defined?([x, y, z]) will return "expression" if x, y, and z are defined. But [x, y, z] seems like an expression regardless of whether or not those are defined (the expression just might raise though).

Updated by jeremyevans0 (Jeremy Evans) 12 months ago

I submitted a pull request to fix this: https://github.com/ruby/ruby/pull/9500

Updated by ko1 (Koichi Sasada) 10 months ago

In future, should we deprecate defined? usecase which return expression?

Updated by mame (Yusuke Endoh) 7 months ago

Discussed at the dev meeting. @matz (Yukihiro Matsumoto) said defined?([*a]), defined?(itself(*a)), and defined?({ a => a }) should all return nil.

Matz was not positive about restricting the expression of defined?. @ko1 (Koichi Sasada) said he will investigate the use case in public gem code base.

Updated by ko1 (Koichi Sasada) 7 months ago

Here is a survey which node (in Prism) are used in defined?(expr) (the node of expr)
https://gist.github.com/ko1/b31517a5037d55bbe50e7f12d79b9fc1

I understand the usage of :constant_read_node, :constant_path_node, :instance_variable_read_node, :call_node, :global_variable_read_node, :class_variable_read_node, :forwarding_super_node, :numbered_reference_read_node, :yield_node, :super_node, but not sure other nodes.

https://gist.github.com/ko1/fcf98c0fec3aecfdae7f1a4d91ee5626
is a survey of other nodes.

There are misusages like defined? C ? x : y (expected defined?(C) ? x : y but defined?(C ? x : y) which always returns "expression".

Actions #10

Updated by jeremyevans (Jeremy Evans) 6 months ago

  • Status changed from Open to Closed

Applied in changeset git|ae0c7faa79029ebe615487d23c8ee1ca44ce4a36.


Handle hash and splat nodes in defined?

This supports the nodes in both in the parse.y and prism compilers.

Fixes [Bug #20043]

Co-authored-by: Kevin Newton

Actions

Also available in: Atom PDF

Like1
Like0Like1Like0Like0Like0Like0Like0Like0Like0Like0