diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 0ec94c6abbd2..af0e1788a638 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1195,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)}}), "argument to has_key? must be a symbol or string, 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 diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index 1cf733c56318..41fd3fe183fa 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -878,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. diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 9855dd72cc0c..9d66d7a518ac 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1082,6 +1082,22 @@ module Crystal value end + when "has_key?" + interpret_check_args do |key| + case key + when SymbolLiteral + key = key.value + when MacroId + key = key.value + when StringLiteral + key = key.value + else + raise "argument to has_key? must be a symbol or string, not #{key.class_desc}:\n\n#{key}" + end + + entry = entries.find &.key.==(key) + BoolLiteral.new(entry != nil) + end else super end