From 4441bbfa045208c46f0488a3d29e39742bf150e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 18 Dec 2024 17:46:22 +0100 Subject: [PATCH 1/3] Error on `TypeNode#instance_vars` macro in top-level scope --- spec/compiler/macro/macro_methods_spec.cr | 46 +++++++++++++++++++++-- spec/spec_helper.cr | 1 + src/compiler/crystal/macros/methods.cr | 25 ++++++++++-- src/compiler/crystal/semantic.cr | 11 ++++++ 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index 10ba78d5bdc6..e460d87b915f 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..06cf6165eab8 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,15 @@ 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? + if name_loc + raise Crystal::TypeException.new("`TypeNode#instance_vars` cannot be called in the top-level scope: instance vars are not yet initialized", name_loc) + else + raise Crystal::TypeException.new("`TypeNode#instance_vars` cannot be called in the top-level scope: instance vars are not yet initialized") + 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 +2097,18 @@ module Crystal end end + def self.has_inner_pointers?(type, name_loc) + unless type.program.top_level_semantic_complete? + if name_loc + raise Crystal::TypeException.new("`TypeNode#has_inner_pointers?` cannot be called in the top-level scope: instance vars are not yet initialized", name_loc) + else + raise Crystal::TypeException.new("`TypeNode#has_inner_pointers?` cannot be called in the top-level scope: instance vars are not yet initialized") + 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 From 9a229c78162bbaab71d7f59c574d8faa8889bca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 20 Dec 2024 12:40:20 +0100 Subject: [PATCH 2/3] De-duplicate error messages --- src/compiler/crystal/macros/methods.cr | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr index 06cf6165eab8..2ee24a6b9892 100644 --- a/src/compiler/crystal/macros/methods.cr +++ b/src/compiler/crystal/macros/methods.cr @@ -2080,10 +2080,11 @@ module Crystal 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("`TypeNode#instance_vars` cannot be called in the top-level scope: instance vars are not yet initialized", name_loc) + raise Crystal::TypeException.new(message, name_loc) else - raise Crystal::TypeException.new("`TypeNode#instance_vars` cannot be called in the top-level scope: instance vars are not yet initialized") + raise Crystal::TypeException.new(message) end end ArrayLiteral.map(type.all_instance_vars) do |name, ivar| @@ -2099,10 +2100,11 @@ module Crystal 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("`TypeNode#has_inner_pointers?` cannot be called in the top-level scope: instance vars are not yet initialized", name_loc) + raise Crystal::TypeException.new(message, name_loc) else - raise Crystal::TypeException.new("`TypeNode#has_inner_pointers?` cannot be called in the top-level scope: instance vars are not yet initialized") + raise Crystal::TypeException.new(message) end end From 3c16494c4af490be03138922b90eef6b513c8bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 20 Dec 2024 12:41:16 +0100 Subject: [PATCH 3/3] format --- spec/compiler/macro/macro_methods_spec.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr index e460d87b915f..ea0260f8a731 100644 --- a/spec/compiler/macro/macro_methods_spec.cr +++ b/spec/compiler/macro/macro_methods_spec.cr @@ -1801,7 +1801,7 @@ module Crystal end it "does not error when called from def scope" do - assert_type <<-CRYSTAL {|program| program.string } + assert_type <<-CRYSTAL { |program| program.string } module Moo end def moo @@ -2516,7 +2516,7 @@ module Crystal end it "does not error when called from def scope" do - assert_type <<-CRYSTAL {|program| program.bool } + assert_type <<-CRYSTAL { |program| program.bool } module Moo end def moo