diff --git a/spec/compiler/codegen/macro_spec.cr b/spec/compiler/codegen/macro_spec.cr index b66010fc82e9d13efbaf2a6a0316e32784ed4451..1bf2af79e90d6a897a4a67fb1b6497a34d58e031 100755 --- a/spec/compiler/codegen/macro_spec.cr +++ b/spec/compiler/codegen/macro_spec.cr @@ -578,4 +578,14 @@ describe "Code gen: macro" do end )).to_i.should eq(2) end + + it "executes with named arguments" do + run(%( + macro foo(x = 1) + {{x}} + 1 + end + + foo x: 2 + )).to_i.should eq(3) + end end diff --git a/spec/compiler/type_inference/macro_spec.cr b/spec/compiler/type_inference/macro_spec.cr index 5913a915f9725b4184741d6d60521ffa55c769e9..77ad591bab1909962bdc6e9015ddf42d10b98780 100755 --- a/spec/compiler/type_inference/macro_spec.cr +++ b/spec/compiler/type_inference/macro_spec.cr @@ -202,4 +202,26 @@ describe "Type inference: macro" do ->(x : Foo) { x.foo; x.ivars_length } )) { fun_of(types["Foo"], no_return) } end + + it "errors if non-existent named arg" do + assert_error %( + macro foo(x = 1) + {{x}} + 1 + end + + foo y: 2 + ), + "no argument named 'y'" + end + + it "errors if named arg already specified" do + assert_error %( + macro foo(x = 1) + {{x}} + 1 + end + + foo 2, x: 2 + ), + "argument 'x' already specified" + end end diff --git a/src/compiler/crystal/macros/macros.cr b/src/compiler/crystal/macros/macros.cr index d0be3d5ffc3122806d1ea6d443a9500a6a9891f9..24ee8fef5d507ceb6ca2cf62a11eb30f3b217042 100644 --- a/src/compiler/crystal/macros/macros.cr +++ b/src/compiler/crystal/macros/macros.cr @@ -165,6 +165,11 @@ module Crystal vars[macro_arg.name] = call_arg.to_macro_var end + # The named arguments + call.named_args.try &.each do |named_arg| + vars[named_arg.name] = named_arg.value + end + # The block arg call_block = call.block macro_block_arg = a_macro.block_arg diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index af586b2d9e37df82cec184f2a33339b6ff59ce2d..1cdd1529026e3d51a99a3195fed172f1f9871fcb 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -492,7 +492,7 @@ module Crystal end def lookup_macro - in_macro_target &.lookup_macro(name, args.length) + in_macro_target &.lookup_macro(name, args.length, named_args) end def in_macro_target @@ -1016,6 +1016,17 @@ module Crystal if macros all_arguments_lengths = Set(Int32).new macros.each do |macro| + named_args.try &.each do |named_arg| + index = macro.args.index { |arg| arg.name == named_arg.name } + if index + if index < args.length + raise "argument '#{named_arg.name}' already specified" + end + else + raise "no argument named '#{named_arg.name}'" + end + end + min_length = macro.args.index(&.default_value) || macro.args.length min_length.upto(macro.args.length) do |args_length| all_arguments_lengths << args_length diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 7c19941a7a62a9205eef36934ca2b4a45c851406..8ac517cdd8a623bbde3a0572669a808b8bcb1821 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -894,7 +894,7 @@ module Crystal name.length end - def matches_args_length?(args_length) + def matches?(args_length, named_args) my_args_length = args.length min_args_length = args.index(&.default_value) || my_args_length max_args_length = my_args_length @@ -902,7 +902,23 @@ module Crystal min_args_length -= 1 max_args_length = Int32::MAX end - min_args_length <= args_length <= max_args_length + + unless min_args_length <= args_length <= max_args_length + return false + end + + named_args.try &.each do |named_arg| + index = args.index { |arg| arg.name == named_arg.name } + if index + if index < args_length + return false + end + else + return false + end + end + + true end def clone_without_location diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 5ac0a6ebac31d050d82045ef4af97aa94d31bc9d..700a462cc801980d9a329577377f314b596436c3 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -289,7 +289,7 @@ module Crystal raise "Bug: #{self} doesn't implement add_macro" end - def lookup_macro(name, args_length) + def lookup_macro(name, args_length, named_args) raise "Bug: #{self} doesn't implement lookup_macro" end @@ -303,7 +303,7 @@ module Crystal def lookup_method_missing # method_missing is actually stored in the metaclass - method_missing = metaclass.lookup_macro("method_missing", 3) + method_missing = metaclass.lookup_macro("method_missing", 3, nil) return method_missing if method_missing parents.try &.each do |parent| @@ -697,14 +697,14 @@ module Crystal nil end - def lookup_macro(name, args_length) + def lookup_macro(name, args_length, named_args) if (macros = self.macros) && (array = macros[name]?) - match = array.find &.matches_args_length?(args_length) + match = array.find &.matches?(args_length, named_args) return match if match end parents.try &.each do |parent| - parent_macro = parent.lookup_macro(name, args_length) + parent_macro = parent.lookup_macro(name, args_length, named_args) return parent_macro if parent_macro end @@ -2890,7 +2890,7 @@ module Crystal true end - def lookup_macro(name, args_length) + def lookup_macro(name, args_length, named_args) nil end