diff --git a/spec/compiler/semantic/macro_spec.cr b/spec/compiler/semantic/macro_spec.cr index cc40def9203b..4792270a2b58 100644 --- a/spec/compiler/semantic/macro_spec.cr +++ b/spec/compiler/semantic/macro_spec.cr @@ -1084,4 +1084,42 @@ describe "Semantic: macro" do Foo.new.main )) { tuple_of [int32, char] } end + + it "finds macro in included module at class level (#4639)" do + assert_type(%( + module Moo + macro foo + def self.bar + 2 + end + end + end + + class Foo + include Moo + + foo + end + + Foo.bar + ), inject_primitives: false) { int32 } + end + + it "finds macro in module in Object" do + assert_type(%( + class Object + macro foo + def self.bar + 2 + end + end + end + + module Moo + foo + end + + Moo.bar + ), inject_primitives: false) { int32 } + end end diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr index 3f64159dcf39..531aa9dca677 100644 --- a/src/compiler/crystal/semantic/call.cr +++ b/src/compiler/crystal/semantic/call.cr @@ -664,7 +664,10 @@ class Crystal::Call node_scope = node_scope.base_type if node_scope.is_a?(VirtualType) macros = yield node_scope - if !macros && node_scope.module? + + # If the scope is a module (through its instance type), lookup in Object too + # (so macros like `property` and others, defined in Object, work at the module level) + if !macros && node_scope.instance_type.module? macros = yield program.object end diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 127febb35b73..24154e02ddd5 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -407,7 +407,9 @@ module Crystal return DefInMacroLookup.new end - parents.try &.each do |parent| + # We need to go through the instance type because of module + # inclusion and inheritance. + instance_type.parents.try &.each do |parent| parent_macro = parent.lookup_macro(name, args, named_args) return parent_macro if parent_macro end @@ -431,7 +433,9 @@ module Crystal return DefInMacroLookup.new end - parents.try &.each do |parent| + # We need to go through the instance type because of module + # inclusion and inheritance. + instance_type.parents.try &.each do |parent| parent_macros = parent.lookup_macros(name) return parent_macros if parent_macros end