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

[GR-59866] Implement ObjectSpace::WeakKeyMap #3784

Closed
wants to merge 6 commits into from
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
27 changes: 27 additions & 0 deletions spec/ruby/core/objectspace/weakkeymap/clear_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require_relative '../../../spec_helper'

ruby_version_is '3.3' do
describe "ObjectSpace::WeakKeyMap#clear" do
it "removes all the entries" do
m = ObjectSpace::WeakKeyMap.new

key = Object.new
value = Object.new
m[key] = value

key2 = Object.new
value2 = Object.new
m[key2] = value2

m.clear

m.key?(key).should == false
m.key?(key2).should == false
end

it "returns self" do
m = ObjectSpace::WeakKeyMap.new
m.clear.should.equal?(m)
end
end
end
11 changes: 11 additions & 0 deletions spec/ruby/core/objectspace/weakkeymap/delete_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,16 @@
m = ObjectSpace::WeakMap.new
m.delete(Object.new).should == nil
end

it "returns nil when a key cannot be garbage collected" do
map = ObjectSpace::WeakKeyMap.new

map.delete(1).should == nil
map.delete(1.0).should == nil
map.delete(:a).should == nil
map.delete(true).should == nil
map.delete(false).should == nil
map.delete(nil).should == nil
end
end
end
78 changes: 77 additions & 1 deletion spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require_relative '../../../spec_helper'
require_relative 'fixtures/classes'

ruby_version_is "3.3" do
describe "ObjectSpace::WeakKeyMap#[]" do
Expand All @@ -15,12 +16,87 @@
map[key2].should == ref2
end

it "matches using equality semantics" do
it "compares keys with #eql? semantics" do
map = ObjectSpace::WeakKeyMap.new
map[[1.0]] = "x"
map[[1]].should == nil
map[[1.0]].should == "x"

map = ObjectSpace::WeakKeyMap.new
map[[1]] = "x"
map[[1.0]].should == nil
map[[1]].should == "x"

map = ObjectSpace::WeakKeyMap.new
key1, key2 = %w[a a].map(&:upcase)
ref = "x"
map[key1] = ref
map[key2].should == ref
end

it "compares key via #hash first" do
x = mock('0')
x.should_receive(:hash).and_return(0)

map = ObjectSpace::WeakKeyMap.new
map['foo'] = :bar
map[x].should == nil
end

it "does not compare keys with different #hash values via #eql?" do
x = mock('x')
x.should_not_receive(:eql?)
x.stub!(:hash).and_return(0)

y = mock('y')
y.should_not_receive(:eql?)
y.stub!(:hash).and_return(1)

map = ObjectSpace::WeakKeyMap.new
map[y] = 1
map[x].should == nil
end

it "compares keys with the same #hash value via #eql?" do
x = mock('x')
x.should_receive(:eql?).and_return(true)
x.stub!(:hash).and_return(42)

y = mock('y')
y.should_not_receive(:eql?)
y.stub!(:hash).and_return(42)

map = ObjectSpace::WeakKeyMap.new
map[y] = 1
map[x].should == 1
end

it "finds a value via an identical key even when its #eql? isn't reflexive" do
x = mock('x')
x.should_receive(:hash).at_least(1).and_return(42)
x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI.

map = ObjectSpace::WeakKeyMap.new
map[x] = :x
map[x].should == :x
end

it "supports keys with private #hash method" do
key = WeakKeyMapSpecs::KeyWithPrivateHash.new
map = ObjectSpace::WeakKeyMap.new
map[key] = 42
map[key].should == 42
end

it "returns nil and does not raise error when a key cannot be garbage collected" do
map = ObjectSpace::WeakKeyMap.new

map[1].should == nil
map[1.0].should == nil
map[:a].should == nil
map[true].should == nil
map[false].should == nil
map[nil].should == nil
end
end
end
57 changes: 33 additions & 24 deletions spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ def should_accept(map, key, value)
map[key].should == value
end

def should_not_accept(map, key, value)
-> { map[key] = value }.should raise_error(ArgumentError)
end

it "is correct" do
map = ObjectSpace::WeakKeyMap.new
key1, key2 = %w[a b].map(&:upcase)
Expand Down Expand Up @@ -40,32 +36,45 @@ def should_not_accept(map, key, value)
should_accept(map, y, x)
end

it "rejects symbols as keys" do
it "does not duplicate and freeze String keys (like Hash#[]= does)" do
map = ObjectSpace::WeakKeyMap.new
should_not_accept(map, :foo, true)
should_not_accept(map, rand.to_s.to_sym, true)
end
key = +"a"
map[key] = 1

it "rejects integers as keys" do
map = ObjectSpace::WeakKeyMap.new
should_not_accept(map, 42, true)
should_not_accept(map, 2 ** 68, true)
map.getkey("a").should.equal? key
map.getkey("a").should_not.frozen?
end

it "rejects floats as keys" do
map = ObjectSpace::WeakKeyMap.new
should_not_accept(map, 4.2, true)
end
context "a key cannot be garbage collected" do
it "raises ArgumentError when Integer is used as a key" do
map = ObjectSpace::WeakKeyMap.new
-> { map[1] = "x" }.should raise_error(ArgumentError, "WeakKeyMap must be garbage collectable")
end

it "rejects booleans as keys" do
map = ObjectSpace::WeakKeyMap.new
should_not_accept(map, true, true)
should_not_accept(map, false, true)
end
it "raises ArgumentError when Float is used as a key" do
map = ObjectSpace::WeakKeyMap.new
-> { map[1.0] = "x" }.should raise_error(ArgumentError, "WeakKeyMap must be garbage collectable")
end

it "rejects nil as keys" do
map = ObjectSpace::WeakKeyMap.new
should_not_accept(map, nil, true)
it "raises ArgumentError when Symbol is used as a key" do
map = ObjectSpace::WeakKeyMap.new
-> { map[:a] = "x" }.should raise_error(ArgumentError, "WeakKeyMap must be garbage collectable")
end

it "raises ArgumentError when true is used as a key" do
map = ObjectSpace::WeakKeyMap.new
-> { map[true] = "x" }.should raise_error(ArgumentError, "WeakKeyMap must be garbage collectable")
end

it "raises ArgumentError when false is used as a key" do
map = ObjectSpace::WeakKeyMap.new
-> { map[false] = "x" }.should raise_error(ArgumentError, "WeakKeyMap must be garbage collectable")
end

it "raises ArgumentError when nil is used as a key" do
map = ObjectSpace::WeakKeyMap.new
-> { map[nil] = "x" }.should raise_error(ArgumentError, "WeakKeyMap must be garbage collectable")
end
end
end
end
5 changes: 5 additions & 0 deletions spec/ruby/core/objectspace/weakkeymap/fixtures/classes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module WeakKeyMapSpecs
class KeyWithPrivateHash
private :hash
end
end
11 changes: 11 additions & 0 deletions spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,16 @@
map.getkey(key2).should equal(key1)
map.getkey("X").should == nil
end

it "returns nil when a key cannot be garbage collected" do
map = ObjectSpace::WeakKeyMap.new

map.getkey(1).should == nil
map.getkey(1.0).should == nil
map.getkey(:a).should == nil
map.getkey(true).should == nil
map.getkey(false).should == nil
map.getkey(nil).should == nil
end
end
end
11 changes: 11 additions & 0 deletions spec/ruby/core/objectspace/weakkeymap/key_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,16 @@
map[key] = nil
map.key?(key).should == true
end

it "returns false when a key cannot be garbage collected" do
map = ObjectSpace::WeakKeyMap.new

map.key?(1).should == false
map.key?(1.0).should == false
map.key?(:a).should == false
map.key?(true).should == false
map.key?(false).should == false
map.key?(nil).should == false
end
end
end
4 changes: 0 additions & 4 deletions spec/tags/core/objectspace/weakkeymap/delete_tags.txt

This file was deleted.

This file was deleted.

8 changes: 0 additions & 8 deletions spec/tags/core/objectspace/weakkeymap/element_set_tags.txt

This file was deleted.

1 change: 0 additions & 1 deletion spec/tags/core/objectspace/weakkeymap/getkey_tags.txt

This file was deleted.

1 change: 0 additions & 1 deletion spec/tags/core/objectspace/weakkeymap/inspect_tags.txt

This file was deleted.

3 changes: 0 additions & 3 deletions spec/tags/core/objectspace/weakkeymap/key_tags.txt

This file was deleted.

7 changes: 7 additions & 0 deletions spec/truffle/methods/ObjectSpace::WeakKeyMap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[]
[]=
clear
delete
getkey
inspect
key?
15 changes: 15 additions & 0 deletions spec/truffle/methods/ObjectSpace::WeakMap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[]
[]=
delete
each
each_key
each_pair
each_value
include?
inspect
key?
keys
length
member?
size
values
3 changes: 2 additions & 1 deletion spec/truffle/methods_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
Array BasicObject Binding Class Complex Complex Data Dir ENV.singleton_class
Encoding Enumerable Enumerator Enumerator::Lazy Exception FalseClass Fiber
File FileTest Float GC GC.singleton_class Hash IO Integer Kernel Marshal MatchData Math Method
Module Mutex NilClass Numeric Object ObjectSpace Proc Process Process.singleton_class Queue Random
Module Mutex NilClass Numeric Object ObjectSpace ObjectSpace::WeakKeyMap ObjectSpace::WeakMap
Proc Process Process.singleton_class Queue Random
Random::Formatter Random.singleton_class Range Rational Regexp Signal
SizedQueue String Struct Symbol SystemExit Thread TracePoint TrueClass
UnboundMethod Warning
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/truffleruby/RubyLanguage.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import org.truffleruby.core.mutex.RubyConditionVariable;
import org.truffleruby.core.mutex.RubyMutex;
import org.truffleruby.core.objectspace.ObjectSpaceManager;
import org.truffleruby.core.objectspace.RubyWeakKeyMap;
import org.truffleruby.core.objectspace.RubyWeakMap;
import org.truffleruby.core.proc.RubyProc;
import org.truffleruby.core.queue.RubyQueue;
Expand Down Expand Up @@ -332,6 +333,7 @@ private RubyThread getOrCreateForeignThread(RubyContext context, Thread thread)
public final Shape truffleFFIPointerShape = createShape(RubyPointer.class);
public final Shape unboundMethodShape = createShape(RubyUnboundMethod.class);
public final Shape weakMapShape = createShape(RubyWeakMap.class);
public final Shape weakKeyMapShape = createShape(RubyWeakKeyMap.class);

public final Shape classVariableShape = Shape
.newBuilder()
Expand Down
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.objectspace.WeakKeyMapNodesBuiltins;
import org.truffleruby.core.objectspace.WeakKeyMapNodesFactory;
import org.truffleruby.core.refinement.RefinementNodesBuiltins;
import org.truffleruby.core.refinement.RefinementNodesFactory;
import org.truffleruby.core.monitor.TruffleMonitorNodesBuiltins;
Expand Down Expand Up @@ -254,6 +256,7 @@ public static void setupBuiltinsLazy(CoreMethodNodeManager coreManager) {
TypeNodesBuiltins.setup(coreManager);
UnboundMethodNodesBuiltins.setup(coreManager);
VMPrimitiveNodesBuiltins.setup(coreManager);
WeakKeyMapNodesBuiltins.setup(coreManager);
WeakMapNodesBuiltins.setup(coreManager);
WeakRefNodesBuiltins.setup(coreManager);
}
Expand Down Expand Up @@ -336,6 +339,7 @@ public static void setupBuiltinsLazyPrimitives(PrimitiveManager primitiveManager
TypeNodesBuiltins.setupPrimitives(primitiveManager);
UnboundMethodNodesBuiltins.setupPrimitives(primitiveManager);
VMPrimitiveNodesBuiltins.setupPrimitives(primitiveManager);
WeakKeyMapNodesBuiltins.setupPrimitives(primitiveManager);
WeakMapNodesBuiltins.setupPrimitives(primitiveManager);
WeakRefNodesBuiltins.setupPrimitives(primitiveManager);
}
Expand Down Expand Up @@ -419,6 +423,7 @@ public static List<List<? extends NodeFactory<? extends RubyBaseNode>>> getCoreN
TypeNodesFactory.getFactories(),
UnboundMethodNodesFactory.getFactories(),
VMPrimitiveNodesFactory.getFactories(),
WeakKeyMapNodesFactory.getFactories(),
WeakMapNodesFactory.getFactories(),
WeakRefNodesFactory.getFactories());
}
Expand Down
Loading
Loading