Skip to content

Commit

Permalink
Add HashLiteral#has_key? and NamedTupleLiteral#has_key? (#14890)
Browse files Browse the repository at this point in the history
  • Loading branch information
kamil-gwozdz committed Aug 19, 2024
1 parent 2fcb168 commit a9e0457
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 10 deletions.
14 changes: 14 additions & 0 deletions spec/compiler/macro/macro_methods_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,12 @@ module Crystal
assert_macro %({{ {'z' => 6, 'a' => 9}.of_value }}), %()
end

it "executes has_key?" do
assert_macro %({{ {'z' => 6, 'a' => 9}.has_key?('z') }}), %(true)
assert_macro %({{ {'z' => 6, 'a' => 9}.has_key?('x') }}), %(false)
assert_macro %({{ {'z' => nil, 'a' => 9}.has_key?('z') }}), %(true)
end

it "executes type" do
assert_macro %({{ x.type }}), %(Headers), {x: HashLiteral.new([] of HashLiteral::Entry, name: Path.new("Headers"))}
end
Expand Down Expand Up @@ -1195,6 +1201,14 @@ module Crystal
assert_macro %({% a = {a: 1}; a["a"] = 2 %}{{a["a"]}}), "2"
end

it "executes has_key?" do
assert_macro %({{{a: 1}.has_key?("a")}}), "true"
assert_macro %({{{a: 1}.has_key?(:a)}}), "true"
assert_macro %({{{a: nil}.has_key?("a")}}), "true"
assert_macro %({{{a: nil}.has_key?("b")}}), "false"
assert_macro_error %({{{a: 1}.has_key?(true)}}), "expected 'NamedTupleLiteral#has_key?' first argument to be a SymbolLiteral, StringLiteral or MacroId, not BoolLiteral"
end

it "creates a named tuple literal with a var" do
assert_macro %({% a = {a: x} %}{{a[:a]}}), "1", {x: 1.int32}
end
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/crystal/macros.cr
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,10 @@ module Crystal::Macros
def []=(key : ASTNode, value : ASTNode) : ASTNode
end

# Similar to `Hash#has_hey?`
def has_key?(key : ASTNode) : BoolLiteral
end

# Returns the type specified at the end of the Hash literal, if any.
#
# This refers to the key type after brackets in `{} of String => Int32`.
Expand Down Expand Up @@ -874,6 +878,10 @@ module Crystal::Macros
# Adds or replaces a key.
def []=(key : SymbolLiteral | StringLiteral | MacroId, value : ASTNode) : ASTNode
end

# Similar to `NamedTuple#has_key?`
def has_key?(key : SymbolLiteral | StringLiteral | MacroId) : ASTNode
end
end

# A range literal.
Expand Down
27 changes: 17 additions & 10 deletions src/compiler/crystal/macros/methods.cr
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,10 @@ module Crystal
interpret_check_args { @of.try(&.key) || Nop.new }
when "of_value"
interpret_check_args { @of.try(&.value) || Nop.new }
when "has_key?"
interpret_check_args do |key|
BoolLiteral.new(entries.any? &.key.==(key))
end
when "type"
interpret_check_args { @name || Nop.new }
when "clear"
Expand Down Expand Up @@ -1042,11 +1046,7 @@ module Crystal
when "[]"
interpret_check_args do |key|
case key
when SymbolLiteral
key = key.value
when MacroId
key = key.value
when StringLiteral
when SymbolLiteral, MacroId, StringLiteral
key = key.value
else
raise "argument to [] must be a symbol or string, not #{key.class_desc}:\n\n#{key}"
Expand All @@ -1058,11 +1058,7 @@ module Crystal
when "[]="
interpret_check_args do |key, value|
case key
when SymbolLiteral
key = key.value
when MacroId
key = key.value
when StringLiteral
when SymbolLiteral, MacroId, StringLiteral
key = key.value
else
raise "expected 'NamedTupleLiteral#[]=' first argument to be a SymbolLiteral or MacroId, not #{key.class_desc}"
Expand All @@ -1077,6 +1073,17 @@ module Crystal

value
end
when "has_key?"
interpret_check_args do |key|
case key
when SymbolLiteral, MacroId, StringLiteral
key = key.value
else
raise "expected 'NamedTupleLiteral#has_key?' first argument to be a SymbolLiteral, StringLiteral or MacroId, not #{key.class_desc}"
end

BoolLiteral.new(entries.any? &.key.==(key))
end
else
super
end
Expand Down

0 comments on commit a9e0457

Please sign in to comment.