diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 10ba78d5bdc6..ea0260f8a731 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1785,9 +1785,30 @@ module Crystal end end - it "executes instance_vars" do - assert_macro("{{x.instance_vars.map &.stringify}}", %(["bytesize", "length", "c"])) do |program| - {x: TypeNode.new(program.string)} + describe "#instance_vars" do + it "executes instance_vars" do + assert_macro("{{x.instance_vars.map &.stringify}}", %(["bytesize", "length", "c"])) do |program| + {x: TypeNode.new(program.string)} + end + end + + it "errors when called from top-level scope" do + assert_error <<-CRYSTAL, "`TypeNode#instance_vars` cannot be called in the top-level scope: instance vars are not yet initialized" + class Foo + end + {{ Foo.instance_vars }} + CRYSTAL + end + + it "does not error when called from def scope" do + assert_type <<-CRYSTAL { |program| program.string } + module Moo + end + def moo + {{ Moo.instance_vars.stringify }} + end + moo + CRYSTAL end end @@ -2485,6 +2506,25 @@ module Crystal {x: TypeNode.new(program.proc_of(program.void))} end end + + it "errors when called from top-level scope" do + assert_error <<-CRYSTAL, "`TypeNode#has_inner_pointers?` cannot be called in the top-level scope: instance vars are not yet initialized" + class Foo + end + {{ Foo.has_inner_pointers? }} + CRYSTAL + end + + it "does not error when called from def scope" do + assert_type <<-CRYSTAL { |program| program.bool } + module Moo + end + def moo + {{ Moo.has_inner_pointers? }} + end + moo + CRYSTAL + end end end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index d3ccdf13fc87..4758ddc74253 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -208,6 +208,7 @@ end def prepare_macro_call(macro_body, flags = nil, &) program = new_program program.flags.concat(flags.split) if flags + program.top_level_semantic_complete = true args = yield program macro_params = args.try &.keys.join(", ") diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index ab7b353fec45..2ee24a6b9892 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -1859,7 +1859,7 @@ module Crystal when "type_vars" interpret_check_args { TypeNode.type_vars(type) } when "instance_vars" - interpret_check_args { TypeNode.instance_vars(type) } + interpret_check_args { TypeNode.instance_vars(type, name_loc) } when "class_vars" interpret_check_args { TypeNode.class_vars(type) } when "ancestors" @@ -2021,7 +2021,7 @@ module Crystal end end when "has_inner_pointers?" - interpret_check_args { BoolLiteral.new(type.has_inner_pointers?) } + interpret_check_args { TypeNode.has_inner_pointers?(type, name_loc) } else super end @@ -2077,8 +2077,16 @@ module Crystal end end - def self.instance_vars(type) + def self.instance_vars(type, name_loc) if type.is_a?(InstanceVarContainer) + unless type.program.top_level_semantic_complete? + message = "`TypeNode#instance_vars` cannot be called in the top-level scope: instance vars are not yet initialized" + if name_loc + raise Crystal::TypeException.new(message, name_loc) + else + raise Crystal::TypeException.new(message) + end + end ArrayLiteral.map(type.all_instance_vars) do |name, ivar| meta_var = MetaMacroVar.new(name[1..-1], ivar.type) meta_var.var = ivar @@ -2090,6 +2098,19 @@ module Crystal end end + def self.has_inner_pointers?(type, name_loc) + unless type.program.top_level_semantic_complete? + message = "`TypeNode#has_inner_pointers?` cannot be called in the top-level scope: instance vars are not yet initialized" + if name_loc + raise Crystal::TypeException.new(message, name_loc) + else + raise Crystal::TypeException.new(message) + end + end + + BoolLiteral.new(type.has_inner_pointers?) + end + def self.class_vars(type) if type.is_a?(ClassVarContainer) ArrayLiteral.map(type.all_class_vars) do |name, ivar| diff --git a/src/compiler/crystal/semantic.cr b/src/compiler/crystal/semantic.cr index 46b0482606be..89f48c4bb655 100644 --- a/src/compiler/crystal/semantic.cr +++ b/src/compiler/crystal/semantic.cr @@ -87,6 +87,17 @@ class Crystal::Program end end + self.top_level_semantic_complete = true + {node, processor} end + + # This property indicates that the compiler has finished the top-level semantic + # stage. + # At this point, instance variables are declared and macros `#instance_vars` + # and `#has_internal_pointers?` provide meaningful information. + # + # FIXME: Introduce a more generic method to track progress of compiler stages + # (potential synergy with `ProcessTracker`?). + property? top_level_semantic_complete = false end