Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Compatibility:
* Fixed `Integer#digits` implementation to handle more bases (#2224, #2225).
* Support the `inherit` parameter for `Module#{private, protected, public}_method_defined?`.
* Implement `Thread.pending_interrupt?` and `Thread#pending_interrupt?` (#2219).
* Implemented `Module#const_source_location` (#2212, @tomstuart and @wildmaples).

Performance:

Expand Down
30 changes: 0 additions & 30 deletions spec/tags/core/module/const_source_location_tags.txt
Original file line number Diff line number Diff line change
@@ -1,32 +1,2 @@
fails:Module#const_source_location return empty path if constant defined in C code
fails:Module#const_source_location accepts a String or Symbol name
fails:Module#const_source_location returns nil if no constant is defined in the search path
fails:Module#const_source_location raises a NameError if the name contains non-alphabetic characters except '_'
fails:Module#const_source_location calls #to_str to convert the given name to a String
fails:Module#const_source_location raises a TypeError if conversion to a String by calling #to_str fails
fails:Module#const_source_location does not search the singleton class of a Class or Module
fails:Module#const_source_location does not search the containing scope
fails:Module#const_source_location returns nil if the constant is defined in the receiver's superclass and the inherit flag is false
fails:Module#const_source_location searches into the receiver superclasses if the inherit flag is true
fails:Module#const_source_location returns nil when the receiver is a Module, the constant is defined at toplevel and the inherit flag is false
fails:Module#const_source_location returns nil when the receiver is a Class, the constant is defined at toplevel and the inherit flag is false
fails:Module#const_source_location accepts a toplevel scope qualifier
fails:Module#const_source_location accepts a scoped constant name
fails:Module#const_source_location does search private constants path
fails:Module#const_source_location with dynamically assigned constants searches a path in the immediate class or module first
fails:Module#const_source_location with dynamically assigned constants searches a path in a module included in the immediate class before the superclass
fails:Module#const_source_location with dynamically assigned constants searches a path in the superclass before a module included in the superclass
fails:Module#const_source_location with dynamically assigned constants searches a path in a module included in the superclass
fails:Module#const_source_location with dynamically assigned constants searches a path in the superclass chain
fails:Module#const_source_location with dynamically assigned constants returns path to a toplevel constant when the receiver is a Class
fails:Module#const_source_location with dynamically assigned constants returns path to a toplevel constant when the receiver is a Module
fails:Module#const_source_location with dynamically assigned constants returns path to the updated value of a constant
fails:Module#const_source_location with statically assigned constants searches location path the immediate class or module first
fails:Module#const_source_location with statically assigned constants searches location path a module included in the immediate class before the superclass
fails:Module#const_source_location with statically assigned constants searches location path the superclass before a module included in the superclass
fails:Module#const_source_location with statically assigned constants searches location path a module included in the superclass
fails:Module#const_source_location with statically assigned constants searches location path the superclass chain
fails:Module#const_source_location with statically assigned constants returns location path a toplevel constant when the receiver is a Class
fails:Module#const_source_location with statically assigned constants returns location path a toplevel constant when the receiver is a Module
fails:Module#const_source_location autoload returns the autoload location while not resolved
fails:Module#const_source_location autoload returns where the constant was resolved when resolved
56 changes: 56 additions & 0 deletions src/main/java/org/truffleruby/core/module/ModuleNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,62 @@ protected Object constMissing(RubyModule module, String name) {

}

@CoreMethod(names = "const_source_location", required = 1, optional = 1)
@NodeChild(value = "module", type = RubyNode.class)
@NodeChild(value = "name", type = RubyNode.class)
@NodeChild(value = "inherit", type = RubyNode.class)
public abstract static class ConstSourceLocationNode extends CoreMethodNode {

@Child private MakeStringNode makeStringNode = MakeStringNode.create();

@CreateCast("name")
protected RubyNode coerceToStringOrSymbol(RubyNode name) {
return ToStringOrSymbolNodeGen.create(name);
}

@CreateCast("inherit")
protected RubyNode coerceToBoolean(RubyNode inherit) {
return BooleanCastWithDefaultNodeGen.create(true, inherit);
}

@Specialization(guards = { "strings.isRubyString(name)" })
@TruffleBoundary
protected Object constSourceLocation(RubyModule module, Object name, boolean inherit,
@CachedLibrary(limit = "2") RubyStringLibrary strings) {
final ConstantLookupResult lookupResult = ModuleOperations
.lookupScopedConstant(getContext(), module, strings.getJavaString(name), inherit, this, true);

return getLocation(lookupResult);
}

@Specialization
@TruffleBoundary
protected Object constSourceLocation(RubyModule module, RubySymbol name, boolean inherit) {
final ConstantLookupResult lookupResult = ModuleOperations
.lookupConstantWithInherit(getContext(), module, name.getString(), inherit, this, true);

return getLocation(lookupResult);
}

private Object getLocation(ConstantLookupResult lookupResult) {
if (!lookupResult.isFound()) {
return nil;
}

final SourceSection sourceSection = lookupResult.getConstant().getSourceSection();
if (sourceSection == null || !sourceSection.isAvailable()) {
return createEmptyArray();
} else {
final RubyString file = makeStringNode.executeMake(
getContext().getSourcePath(sourceSection.getSource()),
UTF8Encoding.INSTANCE,
CodeRange.CR_UNKNOWN);
return createArray(new Object[]{ file, sourceSection.getStartLine() });
}
}

}

@CoreMethod(names = "const_set", required = 2)
@NodeChild(value = "module", type = RubyNode.class)
@NodeChild(value = "name", type = RubyNode.class)
Expand Down