Skip to content

Commit

Permalink
Add a SetLike module that contains the duck-typed comparisons.
Browse files Browse the repository at this point in the history
This fixes #5227 for the methods mentioned there.
  • Loading branch information
headius committed Jun 25, 2018
1 parent 459b812 commit 9f86e0e
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 14 deletions.
37 changes: 23 additions & 14 deletions core/src/main/java/org/jruby/ext/set/RubySet.java
Expand Up @@ -424,13 +424,8 @@ private void flattenMerge(final ThreadContext context, final IRubyObject set, fi
}
}
else {
set.callMethod(context, "each", IRubyObject.NULL_ARRAY, new Block(
new EachBody(context.runtime) {
IRubyObject yieldImpl(ThreadContext context, IRubyObject e) {
addFlattened(context, seen, e); return context.nil;
}
})
);
// call on superclasses to get SetLike impl
getType().getSuperClass().finvoke(context, this, "flatten_merge", set);
}
}

Expand Down Expand Up @@ -495,7 +490,9 @@ public IRubyObject superset_p(final ThreadContext context, IRubyObject set) {
size() >= ((RubySet) set).size() && allElementsIncluded((RubySet) set)
);
}
throw context.runtime.newArgumentError("value must be a set");

// call on superclasses to get SetLike impl
return getType().getSuperClass().finvoke(context, this, "superset?", set);
}

// Returns true if the set is a proper superset of the given set.
Expand All @@ -510,7 +507,9 @@ public IRubyObject proper_superset_p(final ThreadContext context, IRubyObject se
size() > ((RubySet) set).size() && allElementsIncluded((RubySet) set)
);
}
throw context.runtime.newArgumentError("value must be a set");

// call on superclasses to get SetLike impl
return getType().getSuperClass().finvoke(context, this, "proper_superset?", set);
}

@JRubyMethod(name = "subset?", alias = { "<=" })
Expand All @@ -524,7 +523,9 @@ public IRubyObject subset_p(final ThreadContext context, IRubyObject set) {
size() <= ((RubySet) set).size() && allElementsIncluded((RubySet) set)
);
}
throw context.runtime.newArgumentError("value must be a set");

// call on superclasses to get SetLike impl
return getType().getSuperClass().finvoke(context, this, "subset?", set);
}

@JRubyMethod(name = "proper_subset?", alias = { "<" })
Expand All @@ -538,7 +539,9 @@ public IRubyObject proper_subset_p(final ThreadContext context, IRubyObject set)
size() < ((RubySet) set).size() && allElementsIncluded((RubySet) set)
);
}
throw context.runtime.newArgumentError("value must be a set");

// call on superclasses to get SetLike impl
return getType().getSuperClass().finvoke(context, this, "proper_subset?", set);
}

/**
Expand All @@ -549,7 +552,9 @@ public IRubyObject intersect_p(final ThreadContext context, IRubyObject set) {
if ( set instanceof RubySet ) {
return context.runtime.newBoolean( intersect((RubySet) set) );
}
throw context.runtime.newArgumentError("value must be a set");

// call on superclasses to get SetLike impl
return getType().getSuperClass().finvoke(context, this, "intersect?", set);
}

public boolean intersect(final RubySet set) {
Expand Down Expand Up @@ -577,7 +582,9 @@ public IRubyObject disjoint_p(final ThreadContext context, IRubyObject set) {
if ( set instanceof RubySet ) {
return context.runtime.newBoolean( ! intersect((RubySet) set) );
}
throw context.runtime.newArgumentError("value must be a set");

// call on superclasses to get SetLike impl
return context.runtime.newBoolean(!getType().getSuperClass().finvoke(context, this, "intersect?", set).isTrue());
}

@JRubyMethod
Expand Down Expand Up @@ -873,7 +880,9 @@ public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
return context.tru;
}
}
return context.fals;

// call on superclasses to get SetLike impl
return getType().getSuperClass().finvoke(context, this, "==", other);
}

@JRubyMethod(name = "reset")
Expand Down
107 changes: 107 additions & 0 deletions core/src/main/ruby/jruby/set.rb
Expand Up @@ -2,7 +2,114 @@
#
# .rb part for JRuby's native Set impl (taken from set.rb)

module SetLike

This comment has been minimized.

Copy link
@kares

kares Jun 25, 2018

Member

maybe these can be directly on set.rb as in MRI - to not add "custom" constants into the global name-space.
since it leaks on other places ... or make it a private_constant

def flatten_merge(set, seen = Set.new) # :nodoc:
set.each { |e|
if e.is_a?(Set)
if seen.include?(e_id = e.object_id)
raise ArgumentError, "tried to flatten recursive Set"
end

seen.add(e_id)
flatten_merge(e, seen)
seen.delete(e_id)
else
add(e)
end
}

self
end
protected :flatten_merge

# Returns true if the set is a superset of the given set.
def superset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:>=)
@hash >= set.instance_variable_get(:@hash)
when set.is_a?(Set)
size >= set.size && set.all? { |o| include?(o) }
else
raise ArgumentError, "value must be a set"
end
end
alias >= superset?

# Returns true if the set is a proper superset of the given set.
def proper_superset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:>)
@hash > set.instance_variable_get(:@hash)
when set.is_a?(Set)
size > set.size && set.all? { |o| include?(o) }
else
raise ArgumentError, "value must be a set"
end
end
alias > proper_superset?

# Returns true if the set is a subset of the given set.
def subset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:<=)
@hash <= set.instance_variable_get(:@hash)
when set.is_a?(Set)
size <= set.size && all? { |o| set.include?(o) }
else
raise ArgumentError, "value must be a set"
end
end
alias <= subset?

# Returns true if the set is a proper subset of the given set.
def proper_subset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:<)
@hash < set.instance_variable_get(:@hash)
when set.is_a?(Set)
size < set.size && all? { |o| set.include?(o) }
else
raise ArgumentError, "value must be a set"
end
end
alias < proper_subset?

# Returns true if the set and the given set have at least one
# element in common.
#
# Set[1, 2, 3].intersect? Set[4, 5] #=> false
# Set[1, 2, 3].intersect? Set[3, 4] #=> true
def intersect?(set)
set.is_a?(Set) or raise ArgumentError, "value must be a set"
if size < set.size
any? { |o| set.include?(o) }
else
set.any? { |o| include?(o) }
end
end

# Returns true if two sets are equal. The equality of each couple
# of elements is defined according to Object#eql?.
#
# Set[1, 2] == Set[2, 1] #=> true
# Set[1, 3, 5] == Set[1, 5] #=> false
# Set['a', 'b', 'c'] == Set['a', 'c', 'b'] #=> true
# Set['a', 'b', 'c'] == ['a', 'c', 'b'] #=> false
def ==(other)
if self.equal?(other)
true
elsif other.instance_of?(self.class)
@hash == other.instance_variable_get(:@hash)
elsif other.is_a?(Set) && self.size == other.size
other.all? { |o| @hash.include?(o) }
else
false
end
end
end

class Set
include SetLike

def pretty_print(pp) # :nodoc:
pp.text sprintf('#<%s: {', self.class.name)
Expand Down

0 comments on commit 9f86e0e

Please sign in to comment.