diff --git a/core/src/main/java/org/jruby/IncludedModuleWrapper.java b/core/src/main/java/org/jruby/IncludedModuleWrapper.java index b048e6d113c..b27513bffdb 100644 --- a/core/src/main/java/org/jruby/IncludedModuleWrapper.java +++ b/core/src/main/java/org/jruby/IncludedModuleWrapper.java @@ -186,6 +186,16 @@ protected IRubyObject constantTableRemove(String name) { return origin.constantTableRemove(name); } + @Override + protected Map getAutoloadMap() { + return origin.getAutoloadMap(); + } + + @Override + protected synchronized Map getAutoloadMapForWrite() { + return origin.getAutoloadMapForWrite(); + } + @Override @Deprecated public List getStoredConstantNameList() { diff --git a/core/src/main/java/org/jruby/RubyKernel.java b/core/src/main/java/org/jruby/RubyKernel.java index cf351519b8a..00d5a307306 100644 --- a/core/src/main/java/org/jruby/RubyKernel.java +++ b/core/src/main/java/org/jruby/RubyKernel.java @@ -62,9 +62,11 @@ import org.jruby.exceptions.CatchThrow; import org.jruby.exceptions.MainExitException; import org.jruby.exceptions.RaiseException; +import org.jruby.exceptions.TypeError; import org.jruby.internal.runtime.methods.DynamicMethod; import org.jruby.internal.runtime.methods.JavaMethod.JavaMethodNBlock; import org.jruby.ir.interpreter.Interpreter; +import org.jruby.ir.runtime.IRRuntimeHelpers; import org.jruby.java.proxies.ConcreteJavaProxy; import org.jruby.platform.Platform; import org.jruby.runtime.Arity; @@ -165,52 +167,31 @@ static void recacheBuiltinMethods(Ruby runtime) { runtime.setRespondToMissingMethod(module.searchMethod("respond_to_missing?")); } - @JRubyMethod(module = true, visibility = PRIVATE) + @JRubyMethod(module = true, visibility = PRIVATE, reads = {CLASS}) public static IRubyObject at_exit(ThreadContext context, IRubyObject recv, Block block) { return context.runtime.pushExitBlock(context.runtime.newProc(Block.Type.PROC, block)); } - @JRubyMethod(name = "autoload?", required = 1, module = true, visibility = PRIVATE) + @JRubyMethod(name = "autoload?", required = 1, module = true, visibility = PRIVATE, reads = {CLASS}) public static IRubyObject autoload_p(ThreadContext context, final IRubyObject recv, IRubyObject symbol) { - final Ruby runtime = context.runtime; - final RubyModule module = getModuleForAutoload(runtime, recv); - final RubyString file = module.getAutoloadFile(symbol.asJavaString()); - return file == null ? context.nil : file; - } - - @JRubyMethod(required = 2, module = true, visibility = PRIVATE) - public static IRubyObject autoload(ThreadContext context, final IRubyObject recv, IRubyObject symbol, IRubyObject file) { - final Ruby runtime = context.runtime; - final String nonInternedName = symbol.asJavaString(); + final RubyModule module = getModuleForAutoload(context, recv); - if (!IdUtil.isValidConstantName(nonInternedName)) { - throw runtime.newNameError("autoload must be constant name", nonInternedName); + if (module == null) { + return context.nil; } - final RubyString fileString = - StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, file)); - - if (fileString.isEmpty()) throw runtime.newArgumentError("empty file name"); - - final String baseName = nonInternedName.intern(); // interned, OK for "fast" methods - final RubyModule module = getModuleForAutoload(runtime, recv); - - IRubyObject existingValue = module.fetchConstant(baseName); - if (existingValue != null && existingValue != RubyObject.UNDEF) return context.nil; + return module.autoload_p(context, symbol); + } - module.defineAutoload(baseName, new RubyModule.AutoloadMethod() { + @JRubyMethod(required = 2, module = true, visibility = PRIVATE, reads = {CLASS}) + public static IRubyObject autoload(ThreadContext context, final IRubyObject recv, IRubyObject symbol, IRubyObject file) { + final RubyModule module = getModuleForAutoload(context, recv); - public RubyString getFile() { return fileString; } + if (module == null) { + throw context.runtime.newTypeError("Can not set autoload on singleton class"); + } - public void load(final Ruby runtime) { - final String file = getFile().asJavaString(); - if (runtime.getLoadService().autoloadRequire(file)) { - // Do not finish autoloading by cyclic autoload - module.finishAutoload(baseName); - } - } - }); - return context.nil; + return module.autoload(context, symbol, file); } @Deprecated @@ -218,13 +199,14 @@ public static IRubyObject autoload(final IRubyObject recv, IRubyObject symbol, I return autoload(recv.getRuntime().getCurrentContext(), recv, symbol, file); } - static RubyModule getModuleForAutoload(Ruby runtime, IRubyObject recv) { - RubyModule module = recv instanceof RubyModule ? (RubyModule) recv : recv.getMetaClass().getRealClass(); - if (module == runtime.getKernel()) { - // special behavior if calling Kernel.autoload directly - module = runtime.getObject().getSingletonClass(); + static RubyModule getModuleForAutoload(ThreadContext context, IRubyObject recv) { + RubyModule target = IRRuntimeHelpers.findInstanceMethodContainer(context, context.getCurrentScope(), recv); + + while (target != null && (target.isSingleton() || target.isIncluded())) { + target = target.getSuperClass(); } - return module; + + return target; } public static IRubyObject method_missing(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { diff --git a/core/src/main/java/org/jruby/RubyModule.java b/core/src/main/java/org/jruby/RubyModule.java index 35db4365ece..2340a51869e 100644 --- a/core/src/main/java/org/jruby/RubyModule.java +++ b/core/src/main/java/org/jruby/RubyModule.java @@ -112,6 +112,7 @@ import org.jruby.util.ClassProvider; import org.jruby.util.CommonByteLists; import org.jruby.util.IdUtil; +import org.jruby.util.StringSupport; import org.jruby.util.TypeConverter; import org.jruby.util.cli.Options; import org.jruby.util.collections.WeakHashSet; @@ -167,7 +168,6 @@ public static RubyClass createModuleClass(Ruby runtime, RubyClass moduleClass) { moduleClass.kindOf = new RubyModule.JavaClassKindOf(RubyModule.class); moduleClass.defineAnnotatedMethods(RubyModule.class); - moduleClass.defineAnnotatedMethods(ModuleKernelMethods.class); return moduleClass; } @@ -202,42 +202,6 @@ void setClassIndex(ClassIndex classIndex) { this.index = classIndex.ordinal(); } - public static class ModuleKernelMethods { - @JRubyMethod - public static IRubyObject autoload(ThreadContext context, IRubyObject self, IRubyObject symbol, IRubyObject file) { - return RubyKernel.autoload(context, self, symbol, file); - } - - @JRubyMethod(name = "autoload?") - public static IRubyObject autoload_p(ThreadContext context, IRubyObject self, IRubyObject symbol) { - final Ruby runtime = context.runtime; - final String name = TypeConverter.checkID(symbol).idString(); - - RubyModule mod = RubyKernel.getModuleForAutoload(runtime, self); - for (/* RubyModule mod = (RubyModule) self */; mod != null; mod = mod.getSuperClass()) { - final IRubyObject loadedValue = mod.fetchConstant(name); - if ( loadedValue != null && loadedValue != UNDEF ) return context.nil; - - final RubyString file; - if ( mod.isIncluded() ) { - file = mod.getNonIncludedClass().getAutoloadFile(name); - } - else { - file = mod.getAutoloadFile(name); - } - - if ( file != null ) { // due explicit requires still need to : - if ( runtime.getLoadService().featureAlreadyLoaded(file.asJavaString()) ) { - // TODO in which case the auto-load never finish-es ?! - return context.nil; - } - return file; - } - } - return context.nil; - } - } - @Override public ClassIndex getNativeClassIndex() { return ClassIndex.MODULE; @@ -294,11 +258,11 @@ public synchronized Map getConstantMapForWrite() { * For looking up constant, check constantMap first then try to get an Autoload object from autoloadMap. * For setting constant, update constantMap first and remove an Autoload object from autoloadMap. */ - private Map getAutoloadMap() { + protected Map getAutoloadMap() { return autoloads; } - private synchronized Map getAutoloadMapForWrite() { + protected synchronized Map getAutoloadMapForWrite() { return autoloads == Collections.EMPTY_MAP ? autoloads = new ConcurrentHashMap(4, 0.9f, 1) : autoloads; } @@ -3384,27 +3348,15 @@ private Collection classVariablesCommon(boolean inherit) { ////////////////// CONSTANT RUBY METHODS //////////////// // - /** rb_mod_const_defined - * + /** + * MRI: rb_mod_const_defined */ - public RubyBoolean const_defined_p(ThreadContext context, IRubyObject symbol) { - return const_defined_p19(context, new IRubyObject[]{symbol}); - } - - private RubyBoolean constantDefined(Ruby runtime, RubySymbol symbol, boolean inherit) { - if (symbol.validConstantName()) { - return runtime.newBoolean(getConstantSkipAutoload(symbol.idString(), inherit, inherit) != null); - } - - throw runtime.newNameError(str(runtime, "wrong constant name", ids(runtime, symbol)), symbol.idString()); - } - @JRubyMethod(name = "const_defined?", required = 1, optional = 1) - public RubyBoolean const_defined_p19(ThreadContext context, IRubyObject[] args) { + public RubyBoolean const_defined_p(ThreadContext context, IRubyObject[] args) { Ruby runtime = context.runtime; - boolean inherit = args.length == 1 || (!args[1].isNil() && args[1].isTrue()); + boolean recur = args.length == 1 ? true : args[1].isTrue(); - if (args[0] instanceof RubySymbol) return constantDefined(runtime, ((RubySymbol) args[0]), inherit); + if (args[0] instanceof RubySymbol) return constantDefined(runtime, ((RubySymbol) args[0]), recur); RubyString fullName = args[0].convertToString(); @@ -3414,23 +3366,50 @@ public RubyBoolean const_defined_p19(ThreadContext context, IRubyObject[] args) module = this; } - String id = RubySymbol.newConstantSymbol(runtime, fullName, segment).idString(); - IRubyObject obj = ((RubyModule) module).getConstantNoConstMissing(id, inherit, inherit); + RubyModule rubyModule = (RubyModule) module; + + RubySymbol symbol = RubySymbol.newConstantSymbol(runtime, fullName, segment); + String id = symbol.idString(); + + IRubyObject obj; + if (recur && index == 0) { // RTEST(recur) in MRI rb_mod_const_defined + obj = rubyModule.getConstantNoConstMissing(id); + } else { + obj = rubyModule.getConstantAt(id); + } if (obj == null) return null; if (!(obj instanceof RubyModule)) throw runtime.newTypeError(segment + " does not refer to class/module"); return obj; }, (index, segment, module) -> { - if (module == null) module = this; // Bare 'Foo' + if (module == null) { + module = this; // Bare 'Foo' + } - String id = RubySymbol.newConstantSymbol(runtime, fullName, segment).idString(); - return ((RubyModule) module).getConstantSkipAutoload(id, inherit, inherit); + RubyModule rubyModule = (RubyModule) module; + + RubySymbol symbol = RubySymbol.newConstantSymbol(runtime, fullName, segment); + String id = symbol.idString(); + + if (recur && index == 0) { // RTEST(recur) in MRI rb_mod_const_defined + return rubyModule.getConstantNoConstMissing(id); + } else { + return rubyModule.getConstantAt(id); + } }); return runtime.newBoolean(value != null); } + private RubyBoolean constantDefined(Ruby runtime, RubySymbol symbol, boolean inherit) { + if (symbol.validConstantName()) { + return runtime.newBoolean(getConstantSkipAutoload(symbol.idString(), inherit, inherit) != null); + } + + throw runtime.newNameError(str(runtime, "wrong constant name", ids(runtime, symbol)), symbol.idString()); + } + /** rb_mod_const_get * */ @@ -3696,6 +3675,60 @@ public IRubyObject prepended(ThreadContext context, IRubyObject other) { return context.nil; } + @JRubyMethod(name = "autoload?", required = 1) + public IRubyObject autoload_p(ThreadContext context, IRubyObject symbol) { + String name = symbol.asJavaString(); + + RubyModule mod = this; + while (!mod.autoloadDefined(context, name)) { + mod = mod.superClass; + + if (mod == null) return context.nil; + } + + // TODO: Missing logic from MRI +// boolean load = check_autoload_required(mod, id, 0); +// if (!load) return context.nil; + + RubyString file = mod.getAutoloadFile(name); + + return file == null ? context.nil : file; + } + + @JRubyMethod(required = 2) + public IRubyObject autoload(ThreadContext context, IRubyObject symbol, IRubyObject file) { + final Ruby runtime = context.runtime; + final String nonInternedName = symbol.asJavaString(); + + if (!IdUtil.isValidConstantName(nonInternedName)) { + throw runtime.newNameError("autoload must be constant name", nonInternedName); + } + + final RubyString fileString = + StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, file)); + + if (fileString.isEmpty()) throw runtime.newArgumentError("empty file name"); + + final String baseName = nonInternedName.intern(); // interned, OK for "fast" methods + + IRubyObject existingValue = fetchConstant(baseName); + if (existingValue != null && existingValue != RubyObject.UNDEF) return context.nil; + + defineAutoload(baseName, new RubyModule.AutoloadMethod() { + + public RubyString getFile() { return fileString; } + + public void load(final Ruby runtime) { + final String file = getFile().asJavaString(); + if (runtime.getLoadService().autoloadRequire(file)) { + // Do not finish autoloading by cyclic autoload + finishAutoload(baseName); + } + } + }); + return context.nil; + } + // NOTE: internal API public final void setConstantVisibility(Ruby runtime, String name, boolean hidden) { ConstantEntry entry = getConstantMap().get(name); @@ -4620,6 +4653,28 @@ protected IRubyObject getAutoloadConstant(String name, boolean loadConstant) { return autoload.getConstant( getRuntime().getCurrentContext() ); } + // MRI: autoload_defined + protected boolean autoloadDefined(ThreadContext context, String name) { + IRubyObject value = fetchConstant(name); + + if (value == null || value != UNDEF) { + return false; + } + + return !autoloadingValue(context, name); + } + + // MRI: rb_autoloading_value + protected boolean autoloadingValue(ThreadContext context, String name) { + Autoload autoload = getAutoloadMap().get(name); + + if (autoload != null + && autoload.isSelf(context) + && autoload.getValue() != UNDEF) return true; + + return false; + } + /** * Set an Object as a defined constant in autoloading. */ @@ -4781,7 +4836,7 @@ public interface AutoloadMethod { * 'Module#autoload' creates this object and stores it in autoloadMap. * This object can be shared with multiple threads so take care to change volatile and synchronized definitions. */ - private static final class Autoload { + protected static final class Autoload { // A ThreadContext which is executing autoload. private volatile ThreadContext ctx; // The lock for test-and-set the ctx. @@ -4962,6 +5017,24 @@ public void setRefinements(Map refinements) { "local_variables" )); + @Deprecated + public static class ModuleKernelMethods { + @JRubyMethod + public static IRubyObject autoload(ThreadContext context, IRubyObject self, IRubyObject symbol, IRubyObject file) { + return RubyKernel.autoload(context, self, symbol, file); + } + + @JRubyMethod(name = "autoload?") + public static IRubyObject autoload_p(ThreadContext context, IRubyObject self, IRubyObject symbol) { + return RubyKernel.autoload_p(context, self, symbol); + } + } + + @Deprecated + public RubyBoolean const_defined_p19(ThreadContext context, IRubyObject symbol) { + return const_defined_p(context, new IRubyObject[]{symbol}); + } + protected ClassIndex classIndex = ClassIndex.NO_INDEX; private volatile Map classVariables = Collections.EMPTY_MAP; diff --git a/spec/tags/ruby/core/kernel/autoload_tags.txt b/spec/tags/ruby/core/kernel/autoload_tags.txt index a92459953e9..7798bdb9be5 100644 --- a/spec/tags/ruby/core/kernel/autoload_tags.txt +++ b/spec/tags/ruby/core/kernel/autoload_tags.txt @@ -1,9 +1 @@ -fails:Kernel.autoload sets the autoload constant in Object's constant table -fails:Kernel#autoload registers a file to load the first time the named constant is accessed -fails:Kernel#autoload? returns the name of the file that will be autoloaded -fails:Kernel#autoload when called from included module's method setups the autoload on the included module -fails:Kernel#autoload when called from included module's method the autoload relative to the included module works -fails:Kernel.autoload when called from included module's method setups the autoload on the included module -fails:Kernel.autoload when called from included module's method the autoload is reacheable from the class too -fails:Kernel.autoload when called from included module's method the autoload relative to the included module works fails:Kernel#autoload calls main.require(path) to load the file diff --git a/spec/tags/ruby/core/module/autoload_tags.txt b/spec/tags/ruby/core/module/autoload_tags.txt index 02fce7e9fa3..435675d6472 100644 --- a/spec/tags/ruby/core/module/autoload_tags.txt +++ b/spec/tags/ruby/core/module/autoload_tags.txt @@ -1,18 +1,15 @@ fails:Module#autoload does not load the file when referring to the constant in defined? fails:Module#autoload shares the autoload request across dup'ed copies of modules -fails:Module#autoload returns 'constant' on referring the constant with defined?() fails:Module#autoload does not load the file when referring to the constant in defined? -fails:Module#autoload returns 'constant' on referring the constant with defined?() -fails(hangs):Module#autoload during the autoload before the constant is assigned returns nil in autoload thread and 'constant' otherwise for defined? +critical(hangs):Module#autoload during the autoload before the constant is assigned returns nil in autoload thread and 'constant' otherwise for defined? fails:Module#autoload calls main.require(path) to load the file fails:Module#autoload does not remove the constant from Module#constants if load fails and keeps it as an autoload fails:Module#autoload does not remove the constant from Module#constants if load raises a RuntimeError and keeps it as an autoload fails:Module#autoload interacting with defined? does not load the file when referring to the constant in defined? fails:Module#autoload during the autoload before the constant is assigned returns false in autoload thread and true otherwise for Module#const_defined? -fails:Module#autoload during the autoload before the constant is assigned returns nil in autoload thread and returns the path in other threads for Module#autoload? fails:Module#autoload after autoloading searches for the constant like the original lookup in the included modules fails:Module#autoload after autoloading searches for the constant like the original lookup in the included modules of the superclass fails:Module#autoload after autoloading searches for the constant like the original lookup in the prepended modules fails:Module#autoload the autoload is removed when the same file is required directly without autoload with a full path fails:Module#autoload the autoload is removed when the same file is required directly without autoload with a relative path -fails(hangs):Module#autoload the autoload is removed when the same file is required directly without autoload in a nested require +critical(hangs):Module#autoload the autoload is removed when the same file is required directly without autoload in a nested require diff --git a/test/jruby/autoloaded.rb b/test/jruby/autoloaded.rb index b4237590a32..0ee3f06a15b 100644 --- a/test/jruby/autoloaded.rb +++ b/test/jruby/autoloaded.rb @@ -1,2 +1,3 @@ +puts :here class Autoloaded end diff --git a/test/jruby/test_autoload.rb b/test/jruby/test_autoload.rb index 7167b081abe..a9c6583756f 100644 --- a/test/jruby/test_autoload.rb +++ b/test/jruby/test_autoload.rb @@ -6,7 +6,7 @@ def test_basic_autoload assert_nil Object.autoload("Autoloaded", "#{File.dirname(__FILE__)}/autoloaded.rb") assert_equal true, Object.const_defined?("Autoloaded") assert_nil Object.autoload?("Object::Autoloaded") - assert_equal "#{File.dirname(__FILE__)}/autoloaded.rb", Object.autoload?(:Autoloaded) + assert_equal nil, Object.autoload?(:Autoloaded) assert_equal(Class, Object::Autoloaded.class) # This should not really autoload since it is set for real Object.autoload("Autoloaded", "#{File.dirname(__FILE__)}/autoloaded2.rb")