Help, Ruby 1.8.6-p287 broke my "use" library and I don't know why

Hi everyone,

I need help!

I’ve got a library called “use” that let’s you mixin methods from
modules in a fine-grained fashion. Unfortunately, something broke it
between Ruby 1.8.6-p111 and Ruby 1.8.6-p287.

It may have something to do with the way I autogenerate some methods in
the windows-api library, as simple cases still work fine, but I’m not
sure how or why.

Here’s some sample code:

test.rb

require ‘use’
require ‘windows/path’
require ‘windows/msvcrt/io’

class Foo
use Windows::Path
use Windows::MSVCRT::IO, :tmpfile

def initialize
   p PathIsRoot("C:\\")
   p tmpfile()
end

end

Foo.new

Using Ruby 1.8.6-p111 I get:

true
2009464032

Using Ruby 1.8.6-p287 I get:

(eval):3:in PathIsRoot': uninitialized constant Class::PathIsRoot (NameError) from test.rb:10:ininitialize’
from test.rb:15:in `new’
from test.rb:15

What the heck happened between p 111 and p 287?

Below is the full source for use.rb.

Regards,

Dan

class Class
# The version of the ‘use’ library
USE_VERSION = ‘1.2.2’

# Allows you to include mixins in a fine grained manner. Instead of
# including all methods from a given module, you can instead mixin
# only those methods you want through a combination of the :include,
# :exclude, and :alias options.
#
# Examples:
#
#  # Defines a 'bar' and 'baz' method
#  module Foo
#     def bar
#        "hello"
#     end
#     def baz
#       "world"
#     end
#  end
#
#  # Defines a 'bar', 'blah', and 'zap' methods
#  module Test
#     def bar
#        "goodbye"
#     end
#     def blah
#        "new york"
#     end
#     def zap
#        "zap"
#     end
#  end
#
#  # From the Foo module, only mixin the 'bar' method. From the Test
#  # module exclude the 'bar' and 'zap' methods.
#  class Zap
#     use Foo, :bar
#     use Test, :exclude => [:bar, :zap]
#  end
#
#  z = Zap.new
#
#  z.bar  # => "hello"
#  z.baz  # => NoMethodError - wasn't mixed in
#  z.zap  # => NoMethodError - wasn't mixed in
#  z.blah # =>"new york"
#
#  # Alias a method on the fly
#  class MyKlass
#     use Foo :alias => {:bar, :test}
#  end
#
#  m = MyKlass.new
#  m.test # => "hello"
#  m.bar  # => NoMethodError - was aliased to 'test'
#
#  If no options follow the module name this method is identical
#  to a standard include.
#
def use(*args)
   valid_keys = %w/include exclude alias/
   excluded = []
   included = []
   aliased  = []

   mod = args.shift.clone

   # If no arguments follow the mod name, treat as a standard 

include
if args.empty?
included.concat(mod.instance_methods)
end

   m = Module.new

   args.each{ |arg|
      if arg.kind_of?(Hash)
         arg.each{ |key, val|
            case key.to_s
               when "include"
                  if val.respond_to?(:each)
                     val.each{ |arg| included.push(arg.to_s) }
                  else
                     included.push(val.to_s)
                  end
               when "exclude"
                  if val.respond_to?(:each)
                     val.each{ |arg| excluded.push(arg.to_s) }
                  else
                     excluded.push(val.to_s)
                  end
               when "alias"
                  aliased.push(val)
               else
                  raise "invalid key '#{key}'"
            end
         }
      else
         included.push(arg.to_s)
      end
   }

   unless included.empty? || excluded.empty?
      err = "you cannot include and exclude in the same statement"
      raise ArgumentError, err
   end

   imethods = mod.instance_methods

   # Remove excluded methods
   unless excluded.empty?
      (imethods & excluded).each{ |meth|
         mod.module_eval{ remove_method(meth) }
      }
   end

   # Alias methods
   aliased.each{ |pair|
      pair.each{ |old, new|
         included.push(old) # Aliased methods automatically included
         mod.module_eval{
            alias_method(new, old)
            remove_method(old)
         }
      }
   }

   # Remove methods not specifically included.  The rescue was 

needed
# for cases where a module included another module. Also, don’t
# remove methods that already exist unless specifically included.
unless included.empty?
(imethods - included).each{ |meth|
if superclass.instance_methods.include?(meth)
if included.include?(meth)
mod.module_eval{ undef_method(meth) rescue nil }
else
mod.module_eval{ remove_method(meth) rescue nil }
end
else
mod.module_eval{ undef_method(meth) rescue nil }
end
}
end

   m.module_eval{ include mod }

   # Raise a warning if methods are shadowed (in $VERBOSE mode)
   if $VERBOSE
      imethods = instance_methods(true)
      m.instance_methods.each{ |meth|
         next unless imethods.include?(meth)
         warn "method '#{meth}' aliased, shadows old '#{meth}'"
      }
   end

   include m
end

end

Hi,

At Sun, 28 Sep 2008 05:42:59 +0900,
Daniel B. wrote in [ruby-talk:316199]:

It may have something to do with the way I autogenerate some methods in
the windows-api library, as simple cases still work fine, but I’m not
sure how or why.

Reproduced with:

module X
XXX = 1
def xxx
XXX
end
end

class Foo
use X
end

Foo.new.xxx

And worked fine with the SVN 1.8 branch head.

Hi Dan –

On Sun, 28 Sep 2008, Daniel B. wrote:

why.
Do you have a non-Windows example where it does this?

David

On Sep 27, 3:14 pm, “David A. Black” [email protected] wrote:

It may have something to do with the way I autogenerate some methods in the
windows-api library, as simple cases still work fine, but I’m not sure how or
why.

Do you have a non-Windows example where it does this?

Nobu’s example summarizes the problem:

require ‘use’

module X
XXX = 1
def xxx
XXX
end
end

class Foo
use X
end

Foo.new.xxx

Regards,

Dan

David A. Black wrote:

I need help!
why.
XXX
your “use” library:
XXX
cloning and constants, but I haven’t tracked it down beyond that.
Thanks for that David. I was wondering what the underlying issue was.

Regards,

Dan

Hi –

On Sun, 28 Sep 2008, Daniel B. wrote:

1.8.6-p111 and Ruby 1.8.6-p287.

Foo.new.xxx

For what it’s worth, here’s an even simpler example, not involving
your “use” library:

class Class
def myuse(mod)
include mod.clone
end
end

module X
XXX = 1
def xxx
XXX
end
end

class Foo
myuse X
end

Foo.new.xxx

This works with p230 but not p287. I guess it has to do with module
cloning and constants, but I haven’t tracked it down beyond that.

David