diff --git a/spec/compiler/codegen/macro_spec.cr b/spec/compiler/codegen/macro_spec.cr index ae5769e88f4cb54d988bbac662ab6c17e5038bc4..34e7d6ae4dcff8878e3dc64f3426a656cb5beb29 100755 --- a/spec/compiler/codegen/macro_spec.cr +++ b/spec/compiler/codegen/macro_spec.cr @@ -288,4 +288,23 @@ describe "Code gen: macro" do foo(1) )).to_i.should eq(3) end + + it "expands def macro with instance var and method call (bug)" do + run(%( + struct Nil + def to_i + 0 + end + end + + class Foo + def foo : Int32 + name = 1 + @name = name + end + end + + Foo.new.foo.to_i + )).to_i.should eq(1) + end end diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr index fcbf276ed1bd61bec3604b2409ddc4d0f64e3d4f..22cfc4f8cfca1937fa56d48577df99162cc39475 100644 --- a/src/compiler/crystal/macros.cr +++ b/src/compiler/crystal/macros.cr @@ -38,7 +38,9 @@ module Crystal target_def.vars = vars arg_names = target_def.args.map(&.name) - generated_nodes = parse_macro_source(generated_source, the_macro, target_def, arg_names.to_set) + generated_nodes = parse_macro_source(generated_source, the_macro, target_def, arg_names.to_set) do |parser| + parser.parse_to_def(target_def) + end type_visitor = TypeVisitor.new(@program, vars, target_def) type_visitor.scope = owner @@ -51,11 +53,15 @@ module Crystal target_def.body = generated_nodes end + def parse_macro_source(generated_source, the_macro, node, vars) + parse_macro_source generated_source, the_macro, node, vars, &.parse + end + def parse_macro_source(generated_source, the_macro, node, vars) begin parser = Parser.new(generated_source, [vars]) parser.filename = VirtualFile.new(the_macro, generated_source, node.location) - normalize parser.parse + normalize(yield parser) rescue ex : Crystal::SyntaxException node.raise "macro didn't expand to a valid program, it expanded to:\n\n#{"=" * 80}\n#{"-" * 80}\n#{generated_source.lines.to_s_with_line_numbers}\n#{"-" * 80}\n#{ex.to_s(generated_source)}\n#{"=" * 80}" end diff --git a/src/compiler/crystal/parser.cr b/src/compiler/crystal/parser.cr index af82c6f99a7c250a9e7499fb351afb28399ca6c3..232bf24e845e2e4281ed90b6cc2b3857536c602e 100644 --- a/src/compiler/crystal/parser.cr +++ b/src/compiler/crystal/parser.cr @@ -20,7 +20,6 @@ module Crystal @def_nest = 0 @block_arg_count = 0 @in_macro_expression = false - @in_def_count = 0 @stop_on_yield = 0 end @@ -1597,11 +1596,24 @@ module Crystal inc end + def parse_to_def(a_def) + instance_vars = prepare_parse_def + @def_nest += 1 + + # Small memory optimization: don't keep the Set in the Def if it's empty + instance_vars = nil if instance_vars.empty? + + result = parse + + a_def.instance_vars = instance_vars + a_def.calls_super = @calls_super + a_def.uses_block_arg = @uses_block_arg + + result + end + def parse_def - instance_vars = @instance_vars = Set(String).new - @calls_super = false - @uses_block_arg = false - @block_arg_name = nil + instance_vars = prepare_parse_def a_def = parse_def_helper # Small memory optimization: don't keep the Set in the Def if it's empty @@ -1617,6 +1629,13 @@ module Crystal a_def end + def prepare_parse_def + @calls_super = false + @uses_block_arg = false + @block_arg_name = nil + @instance_vars = Set(String).new + end + def parse_macro push_def @def_nest += 1 diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index ea401564f5c4935291daab98cd48c027c3ea7ecc..048e2063905ed85b8559af8fd68eb8dc43e6338d 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -1018,6 +1018,9 @@ module Crystal end def transfer_instance_vars(a_def) + # Don't consider macro defs here (only later, when expanded) + return if a_def.return_type + is_initialize = a_def.name == "initialize" if a_def_instance_vars = a_def.instance_vars