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 HashLiteral#has_key? and NamedTupleLiteral#has_key? #14890

Merged
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 @@ -1084,6 +1084,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 @@ -1189,6 +1195,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
29 changes: 19 additions & 10 deletions src/compiler/crystal/macros/methods.cr
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,11 @@ 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|
entry = entries.find &.key.==(key)
BoolLiteral.new(!entry.nil?)
kamil-gwozdz marked this conversation as resolved.
Show resolved Hide resolved
end
when "type"
interpret_check_args { @name || Nop.new }
when "clear"
Expand Down Expand Up @@ -1042,11 +1047,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 +1059,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 +1074,18 @@ 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

entry = entries.find &.key.==(key)
BoolLiteral.new(!entry.nil?)
kamil-gwozdz marked this conversation as resolved.
Show resolved Hide resolved
end
else
super
end
Expand Down
Loading