Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Compatibility] Add Refinement#refined_class #3094

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

/CHANGELOG.md merge=union

/spec/truffleruby.next-specs merge=union

# Rules for GitHub's Linguist language-classification system. We're abusing the
# 'vendored' attribute to exclude files as a lot of this isn't really vendored,
# and a whole lot of actually vendored code isn't listed! What we want to do is
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Compatibility:
* Add `String#byteindex` and `String#byterindex` (#3039, @itarato).
* Add implementations of `rb_proc_call_with_block`, `rb_proc_call_kw`, `rb_proc_call_with_block_kw` and `rb_funcall_with_block_kw` (#3068, @andrykonchin).
* Add optional `timeout` argument to `Thread::Queue#pop` (#3039, @itarato).
* Add `Refinement#refined_class` (#3039, @itarato).

Performance:

Expand Down
2 changes: 2 additions & 0 deletions spec/truffleruby.next-specs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ spec/ruby/core/string/byterindex_spec.rb
spec/ruby/core/queue/deq_spec.rb
spec/ruby/core/queue/pop_spec.rb
spec/ruby/core/queue/shift_spec.rb

spec/ruby/core/refinement/refined_class_spec.rb
5 changes: 5 additions & 0 deletions src/main/java/org/truffleruby/builtins/BuiltinsClasses.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
import org.truffleruby.core.method.UnboundMethodNodesFactory;
import org.truffleruby.core.module.ModuleNodesBuiltins;
import org.truffleruby.core.module.ModuleNodesFactory;
import org.truffleruby.core.refinement.RefinementNodesBuiltins;
import org.truffleruby.core.refinement.RefinementNodesFactory;
import org.truffleruby.core.monitor.TruffleMonitorNodesBuiltins;
import org.truffleruby.core.monitor.TruffleMonitorNodesFactory;
import org.truffleruby.core.mutex.ConditionVariableNodesBuiltins;
Expand Down Expand Up @@ -223,6 +225,7 @@ public static void setupBuiltinsLazy(CoreMethodNodeManager coreManager) {
RangeNodesBuiltins.setup(coreManager);
ReadlineNodesBuiltins.setup(coreManager);
ReadlineHistoryNodesBuiltins.setup(coreManager);
RefinementNodesBuiltins.setup(coreManager);
RegexpNodesBuiltins.setup(coreManager);
SecureRandomizerNodesBuiltins.setup(coreManager);
SizedQueueNodesBuiltins.setup(coreManager);
Expand Down Expand Up @@ -304,6 +307,7 @@ public static void setupBuiltinsLazyPrimitives(PrimitiveManager primitiveManager
RangeNodesBuiltins.setupPrimitives(primitiveManager);
ReadlineNodesBuiltins.setupPrimitives(primitiveManager);
ReadlineHistoryNodesBuiltins.setupPrimitives(primitiveManager);
RefinementNodesBuiltins.setupPrimitives(primitiveManager);
RegexpNodesBuiltins.setupPrimitives(primitiveManager);
SecureRandomizerNodesBuiltins.setupPrimitives(primitiveManager);
SizedQueueNodesBuiltins.setupPrimitives(primitiveManager);
Expand Down Expand Up @@ -386,6 +390,7 @@ public static List<List<? extends NodeFactory<? extends RubyBaseNode>>> getCoreN
RangeNodesFactory.getFactories(),
ReadlineNodesFactory.getFactories(),
ReadlineHistoryNodesFactory.getFactories(),
RefinementNodesFactory.getFactories(),
RegexpNodesFactory.getFactories(),
SecureRandomizerNodesFactory.getFactories(),
SizedQueueNodesFactory.getFactories(),
Expand Down
68 changes: 0 additions & 68 deletions src/main/java/org/truffleruby/core/module/ModuleNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleString.ByteIndexOfStringNode;
import org.truffleruby.RubyContext;
import org.truffleruby.RubyLanguage;
import org.truffleruby.annotations.CoreMethod;
import org.truffleruby.builtins.CoreMethodArrayArgumentsNode;
import org.truffleruby.builtins.CoreMethodNode;
Expand Down Expand Up @@ -2467,73 +2466,6 @@ private RubyModule newRefinementModule(RubyModule namespace, RubyModule moduleTo

}

@Primitive(name = "refinement_import_methods")
public abstract static class ImportMethodsNode extends PrimitiveArrayArgumentsNode {

@TruffleBoundary
@Specialization
protected RubyModule importMethods(RubyModule refinement, RubyModule moduleToImportFrom) {
var firstNonRubyMethod = getFirstNonRubyMethodOrNull(moduleToImportFrom, getLanguage());
if (firstNonRubyMethod != null) {
throw new RaiseException(getContext(),
coreExceptions().argumentError(createErrorMessage(firstNonRubyMethod, moduleToImportFrom),
this));
}

importMethodsFromModuleToRefinement(moduleToImportFrom, refinement);

return refinement;
}

private String createErrorMessage(InternalMethod method, RubyModule module) {
return StringUtils.format("Can't import method which is not defined with Ruby code: %s#%s",
module.getName(), method.getName());
}

private void importMethodsFromModuleToRefinement(RubyModule module, RubyModule refinement) {
var declarationContext = createDeclarationContextWithRefinement(refinement);
for (InternalMethod methodToCopy : module.fields.getMethods()) {
var clonedMethod = cloneMethod(methodToCopy, declarationContext, refinement);
refinement.fields.addMethod(getContext(), this, clonedMethod);
}
}

private InternalMethod getFirstNonRubyMethodOrNull(RubyModule module, RubyLanguage language) {
for (InternalMethod method : module.fields.getMethods()) {
if (!method.isDefinedInRuby(language)) {
return method;
}
}

return null;
}

// Creates a declaration context which contains the refined methods from the given refinement
private DeclarationContext createDeclarationContextWithRefinement(RubyModule refinement) {
final Map<RubyModule, RubyModule[]> refinements = new HashMap<>();
refinements.put(refinement.fields.getRefinedModule(), new RubyModule[]{ refinement });
return new DeclarationContext(
Visibility.PUBLIC,
new FixedDefaultDefinee(refinement),
refinements);
}

private InternalMethod cloneMethod(InternalMethod method, DeclarationContext declarationContext,
RubyModule refinement) {
var clonedCallTarget = cloneCallTarget(method);
return method.withCallTargetAndDeclarationContextAndDeclarationModule(clonedCallTarget, declarationContext,
refinement);
}

private RootCallTarget cloneCallTarget(InternalMethod method) {
var rubyRootNode = (RubyRootNode) method.getCallTarget().getRootNode();
var clonedRootNode = rubyRootNode.cloneUninitialized();

return clonedRootNode.getCallTarget();
}
}


@GenerateUncached
@CoreMethod(names = "using", required = 1, visibility = Visibility.PRIVATE, alwaysInlined = true)
public abstract static class ModuleUsingNode extends UsingNode {
Expand Down
110 changes: 110 additions & 0 deletions src/main/java/org/truffleruby/core/refinement/RefinementNodes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 2.0, or
* GNU General Public License version 2, or
* GNU Lesser General Public License version 2.1.
*/
package org.truffleruby.core.refinement;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.dsl.Specialization;
import org.truffleruby.RubyLanguage;
import org.truffleruby.annotations.CoreMethod;
import org.truffleruby.annotations.CoreModule;
import org.truffleruby.annotations.Primitive;
import org.truffleruby.annotations.Visibility;
import org.truffleruby.builtins.CoreMethodArrayArgumentsNode;
import org.truffleruby.builtins.PrimitiveArrayArgumentsNode;
import org.truffleruby.core.module.RubyModule;
import org.truffleruby.core.string.StringUtils;
import org.truffleruby.language.RubyRootNode;
import org.truffleruby.language.control.RaiseException;
import org.truffleruby.language.methods.DeclarationContext;
import org.truffleruby.language.methods.DeclarationContext.FixedDefaultDefinee;
import org.truffleruby.language.methods.InternalMethod;

import java.util.HashMap;
import java.util.Map;

@CoreModule(value = "Refinement", isClass = true)
public abstract class RefinementNodes {

@Primitive(name = "refinement_import_methods")
public abstract static class ImportMethodsNode extends PrimitiveArrayArgumentsNode {

@TruffleBoundary
@Specialization
protected RubyModule importMethods(RubyModule refinement, RubyModule moduleToImportFrom) {
var firstNonRubyMethod = getFirstNonRubyMethodOrNull(moduleToImportFrom, getLanguage());
if (firstNonRubyMethod != null) {
throw new RaiseException(getContext(),
coreExceptions().argumentError(createErrorMessage(firstNonRubyMethod, moduleToImportFrom),
this));
}

importMethodsFromModuleToRefinement(moduleToImportFrom, refinement);

return refinement;
}

private String createErrorMessage(InternalMethod method, RubyModule module) {
return StringUtils.format("Can't import method which is not defined with Ruby code: %s#%s",
module.getName(), method.getName());
}

private void importMethodsFromModuleToRefinement(RubyModule module, RubyModule refinement) {
var declarationContext = createDeclarationContextWithRefinement(refinement);
for (InternalMethod methodToCopy : module.fields.getMethods()) {
var clonedMethod = cloneMethod(methodToCopy, declarationContext, refinement);
refinement.fields.addMethod(getContext(), this, clonedMethod);
}
}

private InternalMethod getFirstNonRubyMethodOrNull(RubyModule module, RubyLanguage language) {
for (InternalMethod method : module.fields.getMethods()) {
if (!method.isDefinedInRuby(language)) {
return method;
}
}

return null;
}

// Creates a declaration context which contains the refined methods from the given refinement
private DeclarationContext createDeclarationContextWithRefinement(RubyModule refinement) {
final Map<RubyModule, RubyModule[]> refinements = new HashMap<>();
refinements.put(refinement.fields.getRefinedModule(), new RubyModule[]{ refinement });
return new DeclarationContext(
Visibility.PUBLIC,
new FixedDefaultDefinee(refinement),
refinements);
}

private InternalMethod cloneMethod(InternalMethod method, DeclarationContext declarationContext,
RubyModule refinement) {
var clonedCallTarget = cloneCallTarget(method);
return method.withCallTargetAndDeclarationContextAndDeclarationModule(clonedCallTarget, declarationContext,
refinement);
}

private RootCallTarget cloneCallTarget(InternalMethod method) {
var rubyRootNode = (RubyRootNode) method.getCallTarget().getRootNode();
var clonedRootNode = rubyRootNode.cloneUninitialized();

return clonedRootNode.getCallTarget();
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: it makes sense to have moving the class into a separate file committed separately.


@CoreMethod(names = "refined_class")
public abstract static class RefinedClassNode extends CoreMethodArrayArgumentsNode {

@Specialization
protected RubyModule refinedClass(RubyModule refinement) {
return refinement.fields.getRefinedModule();
}
}
}
14 changes: 14 additions & 0 deletions test/mri/tests/ruby/test_refinement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1835,6 +1835,20 @@ def test_used_modules
assert_equal [ref::RefB, ref::RefA], ref::Combined::USED_MODS
end

def test_refined_class
refinements = Module.new {
refine Integer do
int_refinement = self
end

refine String do
str_refinement = self
end
}.refinements
assert_equal(Integer, refinements[0].refined_class)
assert_equal(String, refinements[1].refined_class)
end

andrykonchin marked this conversation as resolved.
Show resolved Hide resolved
def test_warn_setconst_in_refinmenet
bug10103 = '[ruby-core:64143] [Bug #10103]'
warnings = [
Expand Down