opal / opal

Ruby ♥︎ JavaScript
https://opalrb.com
MIT License
4.84k stars 330 forks source link

Module scoping of classes #1085

Closed djboardman closed 5 years ago

djboardman commented 9 years ago

There seem to be a couple of differences in how Opal handles classes scoped by modules vs how native Ruby handles them.

The first one is when the class does not exist inside the module but is initialized as if it did:

class MyClass
  def initialize
    puts "MyClass initialized as #{self.class}" 
  end
end

module MyModule
end

c = MyModule::MyClass.new
puts "Is a MyMod::MyClass? #{c.is_a?(MyModule::MyClass)}" rescue puts "MyMod::MyClass is unknown"
puts "Is a MyClass? #{c.is_a?(::MyClass)}"

When this is run in Ruby:

uninitialized constant MyModule::MyClass (NameError)

When the same thing is run in Opal:

MyClass initialized as MyClass
Is a MyMod::MyClass? true
Is a MyClass? true

The second example is when there is when the class does exist in the module:

class MyClass
  def initialize
    puts "MyClass initialized as #{self.class}" 
  end
end

module MyModule
  class MyClass
    def initialize
      puts "MyMod::MyClass initialized as #{self.class}"
    end
  end
end

c = MyModule::MyClass.new
puts "Is a MyMod::MyClass? #{c.is_a?(MyModule::MyClass)}" rescue puts "MyMod::MyClass is unknown"
puts "Is a MyClass? #{c.is_a?(::MyClass)}"

When run in Ruby then #class gives the fully scoped name:

MyMod::MyClass initialized as MyModule::MyClass
Is a MyMod::MyClass? true
Is a MyClass? false

While Opal responds to #class with only the name of the class without the module:

MyMod::MyClass initialized as MyClass
Is a MyMod::MyClass? true
Is a MyClass? false

The first example in particular is causing an issue for me because if the module with the scoped class is accidentally not required then Opal silently creates an unscoped class.

meh commented 9 years ago

This is gonna be hard to fix.

ryanstout commented 9 years ago

@meh is it related to $scope not having the right information?

meh commented 9 years ago

It's deeper than that. The Object constants are always looked up, because when you're inside a class definition you want to be able to access any constants that go up to Object.

But when getting a constant directly with a different base, you don't want anything but the class/module scopes constants to come up. And you want both to use #const_missing.

Constant handling probably has to be rewritten from scratch, it would help if rubyspec/language/constants_spec could run, but they crash even before running, just to tell you how broken constants are right now.

meh commented 9 years ago

It also would help if Ruby constant semantics weren't absolutely insane. There are some corners of Ruby that are semantical nightmares, but only people who want to implement Ruby will know.

ryanstout commented 9 years ago

@meh yea, thats too bad. Its funny that I haven't run into this issue until now. Guess its just an issue if your shadowing a higher up class. Well, maybe for now we'll just pick a different class name. :-) Let me know if I can help on this. (Sounds like a big project though)

meh commented 9 years ago

@ryanstout it only happens if you forget to declare an inner constant, i.e. when you forget to require a file, and there's a constant with the same name up.

wied03 commented 9 years ago

@djboardman @meh @ryanstout - the fix from #1110 appears to have fixed the 2nd case but it does not fix the 1st.

elia commented 5 years ago

Work's on latest release 🎉

opal:master ⤑ pbpaste | ruby -v -                                                                                                                      ~/C/opal
ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin18]
Traceback (most recent call last):
-:10:in `<main>': uninitialized constant MyModule::MyClass (NameError)
opal:master ⤑ pbpaste | opal -v -                                                                                                                      ~/C/opal
Opal v1.0.0

/private/var/folders/w0/yjfqr9n94ld7ft4j3hlz_fsm0000gn/T/opal-nodejs-runner-20190731-21526-15ekt5o:4679
      throw exception;
      ^
MyClass: uninitialized constant MyModule::MyClass
    at Function.$$const_missing (/private/var/folders/w0/yjfqr9n94ld7ft4j3hlz_fsm0000gn/T/opal-nodejs-runner-20190731-21526-15ekt5o:3066:52)
    at const_missing (/private/var/folders/w0/yjfqr9n94ld7ft4j3hlz_fsm0000gn/T/opal-nodejs-runner-20190731-21526-15ekt5o:224:32)
    at Opal.const_get_qualified (/private/var/folders/w0/yjfqr9n94ld7ft4j3hlz_fsm0000gn/T/opal-nodejs-runner-20190731-21526-15ekt5o:271:38)
    at /private/var/folders/w0/yjfqr9n94ld7ft4j3hlz_fsm0000gn/T/opal-nodejs-runner-20190731-21526-15ekt5o:23478:7
    at Object.<anonymous> (/private/var/folders/w0/yjfqr9n94ld7ft4j3hlz_fsm0000gn/T/opal-nodejs-runner-20190731-21526-15ekt5o:23489:3)
    at Module._compile (internal/modules/cjs/loader.js:759:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:770:10)
    at Module.load (internal/modules/cjs/loader.js:628:32)
    at Function.Module._load (internal/modules/cjs/loader.js:555:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:822:10)
opal:master ⤑