Feature #8393

A class who's parent class is in a module can go wrong if files are required in the wrong order

Added by Daniel Lobato Garcia 11 months ago. Updated 11 months ago.

[ruby-core:54921]
Status:Rejected
Priority:Normal
Assignee:-
Category:-
Target version:-

Description

Hi,

I have found that inheritance is not done properly in a certain case. Let's say we have the following files:


animal.rb -

class Animal
def bark
puts 'fuck.'
end
end

dog.rb -

module Bark
class Dog < Animal
end
end

bark.rb -

module Bark
class Animal
def bark
puts 'woof'
end
end

end

If these files are required in that order (or any order where Bark::Animal is not required before Animal), Bark::Dog.new.bark will output "fuck.", showing the inheritance was done wrong, because in the case that there are two classes from which it can inherit (Animal and Bark::Animal), it should inherit from the class inside its module (Bark).

A workaround for this is defining Dog as Dog < Bark::Animal, that forces Dog to use the correct Animal class.

I found this on the latest 1.8.7, 1.9.2, 1.9.3 and 2.0.0dev, both using rvm and without using it. I could not find information about this on the issue tracker or on Google.

In my opinion a way to fix this is to check when a file is required if any of our current files could inherit from something in a module of the file that is imported, but that looks like it can be complicated with nested modules, etc.. so I'm all ears for better design decisions.

I would like to fix this myself as my first Ruby core contribution, but I was unsure if this is an actual bug. To me it looks like this behavior is totally unexpected, let me know if anything is wrong here.

Thanks!

History

#1 Updated by Jeremy Evans 11 months ago

This isn't a bug. If you load animal.rb then dog.rb, at the point Bark::Dog is defined, Bark::Animal does not exist, so it finds Animal at the top level (standard ruby constant lookup). If you want to force the behavior you desire, you should probably be specific about the superclass (class Dog < Bark::Animal) and require bark.rb at the top of dog.rb (to make sure Bark::Animal is defined before Bark::Dog).

#2 Updated by Nobuyoshi Nakada 11 months ago

  • Tracker changed from Bug to Feature
  • Status changed from Open to Feedback

It's a feature by the design.
I can't get what you expect from your code.
You will need more concrete proposal.

#3 Updated by Daniel Lobato Garcia 11 months ago

jeremyevans0: That's what I did when I found a problem caused by this on production code. Nonetheless you have to take into account that sometimes the order of the requires is not something you control.

nobu: In order to get these results, create the files, open IRB and run:

require 'animal.rb'
require 'dog.rb'
require 'bark.rb'

Bark::Dog.new.bark
fuck. <--- This should be woof because Bark::Dog should inherit from Bark::Animal.

Let me know if I am missing anything.

#4 Updated by Lee Jarvis 11 months ago

Nobu you don't need the requires to reproduce. This is simply the same thing:

#!/usr/bin/env ruby

class Animal
def bark
puts 'fuck.'
end
end

module Bark
class Dog < Animal
end
end

module Bark
class Animal
def bark
puts 'woof'
end
end
end

Bark::Dog.new.bark

I don't think this is unexpected at all. I think Jeremy pretty much nailed the reasons why.

#5 Updated by Nobuyoshi Nakada 11 months ago

I know the reason of course.

The point is how others reading your code can guess your intention from the code.
Can you explain why the result is "unexpected"?

#6 Updated by Nobuyoshi Nakada 11 months ago

One idea I'm thinking now is warning when a constant already introduced from the top-level is assigned.

#7 Updated by Daniel Lobato Garcia 11 months ago

This error showed up in a Rails app, on my code I had two different files (ProxyAPI::Resource and ProxyAPI::BMC < Resource), and somehow there was a separated Resource class defined by a loaded gem. Then ProxyAPI::BMC was inheriting from that one instead of ProxyAPI::Resource, which caused problems.

The warning could happen at initialization, but if a constant inside the module is added, IMO it should be inherited. Nonetheless I don't know too much about constant lookup at the moment, could you point me to the relevant parts of the code base or something like that?

My only concern with the warning showing up just when the class is defined, is that maybe it will be hard to notice, but it's surely better than no warning at all.

#8 Updated by Nobuyoshi Nakada 11 months ago

The warning could happen at initialization, but if a constant inside the module is added, IMO it should be inherited.

You can't inherit a constant which is not defined at that time yet.
Even if same name constant is defined later, it isn't concerned.

#9 Updated by Boris Stitnicky 11 months ago

OMG, why puts 'fuck'? ;_;

#10 Updated by Jon Forums 11 months ago

boris_stitnicky (Boris Stitnicky) wrote:

OMG, why puts 'fuck'? ;_;

It's bait to see who gets distracted by irrelevant minutiae.

People curse. Some even scamper promisingly close to wit. Who cares. Move on. ;)

#11 Updated by Nobuyoshi Nakada 11 months ago

OMG, why puts 'fuck'? ;_;

Ruby doesn't prevent you from fucking yourself.

#12 Updated by Konstantin Haase 11 months ago

eLobato (Daniel Lobato Garcia) wrote:

This error showed up in a Rails app, on my code I had two different files (ProxyAPI::Resource and ProxyAPI::BMC < Resource), and somehow there was a separated Resource class defined by a loaded gem. Then ProxyAPI::BMC was inheriting from that one instead of ProxyAPI::Resource, which caused problems.

The warning could happen at initialization, but if a constant inside the module is added, IMO it should be inherited. Nonetheless I don't know too much about constant lookup at the moment, could you point me to the relevant parts of the code base or something like that?

My only concern with the warning showing up just when the class is defined, is that maybe it will be hard to notice, but it's surely better than no warning at all.

Warnings would need to be really really smart or a lot of libraries will start printing warnings (for instance, rack defines Rack::File). I have to say, the whole issue would not have occurred if you had either required bark from dog (which is what require is for) or, if you want to use Rails' autoloading magic, used the qualified constant name (ie inherit from Bark::Animal).

#13 Updated by Daniel Lobato Garcia 11 months ago

Fair point rkh, feel free to close this unless there is something to avoid printing lots of warnings.

#14 Updated by Nobuyoshi Nakada 11 months ago

  • Status changed from Feedback to Rejected

Also available in: Atom PDF