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

Add Enum#inspect #13004

Merged
Merged
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
31 changes: 30 additions & 1 deletion spec/std/enum_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe Enum do

it "for flags enum" do
assert_prints SpecEnumFlags::None.to_s, "None"
assert_prints SpecEnumFlags::All.to_s, "One | Two | Three"
assert_prints SpecEnumFlags::All.to_s, "All"
assert_prints (SpecEnumFlags::One | SpecEnumFlags::Two).to_s, "One | Two"
assert_prints SpecEnumFlags.new(128).to_s, "128"
assert_prints (SpecEnumFlags::One | SpecEnumFlags.new(128)).to_s, "One | 128"
Expand All @@ -78,6 +78,35 @@ describe Enum do
end
end

describe "#inspect" do
it "for simple enum" do
assert_prints SpecEnum::One.inspect, "SpecEnum::One"
assert_prints SpecEnum::Two.inspect, "SpecEnum::Two"
assert_prints SpecEnum::Three.inspect, "SpecEnum::Three"
assert_prints SpecEnum.new(127).inspect, "SpecEnum[127]"
end

it "for flags enum" do
assert_prints SpecEnumFlags::None.inspect, "SpecEnumFlags::None"
assert_prints SpecEnumFlags::All.inspect, "SpecEnumFlags::All"
assert_prints (SpecEnumFlags::One).inspect, "SpecEnumFlags::One"
assert_prints (SpecEnumFlags::One | SpecEnumFlags::Two).inspect, "SpecEnumFlags[One, Two]"
assert_prints SpecEnumFlags.new(128).inspect, "SpecEnumFlags[128]"
assert_prints (SpecEnumFlags::One | SpecEnumFlags.new(128)).inspect, "SpecEnumFlags[One, 128]"
assert_prints (SpecEnumFlags::One | SpecEnumFlags.new(8) | SpecEnumFlags.new(16)).inspect, "SpecEnumFlags[One, 24]"
assert_prints (SpecEnumFlags::One | SpecEnumFlags::Two | SpecEnumFlags.new(16)).inspect, "SpecEnumFlags[One, Two, 16]"
end

it "for private enum" do
assert_prints PrivateEnum::FOO.inspect, "PrivateEnum::FOO"
assert_prints PrivateFlagsEnum::FOO.inspect, "PrivateFlagsEnum::FOO"
assert_prints PrivateEnum::QUX.inspect, "PrivateEnum::FOO"
assert_prints (PrivateFlagsEnum::FOO | PrivateFlagsEnum::BAZ).inspect, "PrivateFlagsEnum[FOO, BAZ]"
assert_prints PrivateFlagsEnum.new(128).inspect, "PrivateFlagsEnum[128]"
assert_prints (PrivateFlagsEnum::FOO | PrivateFlagsEnum.new(128)).inspect, "PrivateFlagsEnum[FOO, 128]"
end
end

it "creates an enum instance from an auto-casted symbol (#8573)" do
enum_value = SpecEnum.new(:two)
enum_value.should eq SpecEnum::Two
Expand Down
4 changes: 2 additions & 2 deletions spec/std/file_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1478,8 +1478,8 @@ describe "File" do
it "does to_s" do
perm = File::Permissions.flags(OwnerAll, GroupRead, GroupWrite, OtherRead)
perm.to_s.should eq("rwxrw-r-- (0o764)")
perm.inspect.should eq("rwxrw-r-- (0o764)")
perm.pretty_inspect.should eq("rwxrw-r-- (0o764)")
perm.inspect.should eq("File::Permissions[OtherRead, GroupWrite, GroupRead, OwnerExecute, OwnerWrite, OwnerRead]")
perm.pretty_inspect.should eq("File::Permissions[OtherRead, GroupWrite, GroupRead, OwnerExecute, OwnerWrite, OwnerRead]")
end
end
end
94 changes: 72 additions & 22 deletions src/enum.cr
Original file line number Diff line number Diff line change
Expand Up @@ -132,26 +132,14 @@ struct Enum
{% if @type.annotation(Flags) %}
if value == 0
io << "None"
elsif name = member_name
io << name
else
remaining_value = self.value
{% for member in @type.constants %}
{% if member.stringify != "All" %}
if {{@type.constant(member)}} != 0 && value.bits_set? {{@type.constant(member)}}
io << " | " unless remaining_value == self.value
io << {{member.stringify}}
remaining_value &= ~{{@type.constant(member)}}
end
{% end %}
{% end %}
unless remaining_value.zero?
io << " | " unless remaining_value == self.value
io << remaining_value
end
stringify_names(io, " | ")
end
{% else %}
io << to_s
{% end %}
nil
end

# Returns a `String` representation of this enum member.
Expand All @@ -173,15 +161,77 @@ struct Enum
{% if @type.annotation(Flags) %}
String.build { |io| to_s(io) }
{% else %}
# Can't use `case` here because case with duplicate values do
# not compile, but enums can have duplicates (such as `enum Foo; FOO = 1; BAR = 1; end`).
{% for member, i in @type.constants %}
if value == {{@type.constant(member)}}
return {{member.stringify}}
member_name || value.to_s
{% end %}
end

# Returns an unambiguous `String` representation of this enum member.
# In the case of a single member value, this is the fully qualified name of
# the member (equvalent to `#to_s` with the enum name as prefix).
# In the case of multiple members (for a flags enum), it's a call to `Enum.[]`
# for recreating the same value.
#
# If the value can't be represented fully by named members, the remainig value
# is appended.
#
# ```
# Color::Red # => Color:Red
# IOMode::None # => IOMode::None
# (IOMode::Read | IOMode::Write) # => IOMode[Read, Write]
#
# Color.new(10) # => Color[10]
# ```
def inspect(io : IO) : Nil
{% if @type.annotation(Flags) %}
if value == 0
io << {{ "#{@type}::None" }}
elsif name = member_name
io << {{ "#{@type}::" }} << name
else
io << {{ "#{@type}[" }}
stringify_names(io, ", ")
io << "]"
end
{% else %}
inspect_single(io)
{% end %}
end

private def stringify_names(io, separator) : Nil
remaining_value = self.value
{% for member in @type.constants %}
{% if member.stringify != "All" %}
if {{@type.constant(member)}} != 0 && remaining_value.bits_set? {{@type.constant(member)}}
unless remaining_value == self.value
io << separator
end
io << {{member.stringify}}
remaining_value &= ~{{@type.constant(member)}}
end
{% end %}
{% end %}

value.to_s
unless remaining_value.zero?
io << separator unless remaining_value == self.value
io << remaining_value
end
end

private def inspect_single(io) : Nil
if name = member_name
io << {{ "#{@type}::" }} << name
else
io << {{ "#{@type}[" }} << value << "]"
end
end

private def member_name
# Can't use `case` here because case with duplicate values do
# not compile, but enums can have duplicates (such as `enum Foo; FOO = 1; BAR = 1; end`).
{% for member in @type.constants %}
if value == {{@type.constant(member)}}
return {{member.stringify}}
end
{% end %}
end

Expand Down Expand Up @@ -495,7 +545,7 @@ struct Enum
# Convenience macro to create a combined enum (combines given members using `|` (or) logical operator)
#
# ```
# IOMode.flags(Read, Write) # => IOMode::Read | IOMode::Write
# IOMode.flags(Read, Write) # => IOMode[Read, Write]
# ```
#
# * `Enum.[]` is a more advanced alternative which also allows int and symbol parameters.
Expand Down