diff --git a/spec/compiler/codegen/const_spec.cr b/spec/compiler/codegen/const_spec.cr index 0c8342261bd0c62dd593213991de357302d3e96f..7aab57a8315af99004c263fc2e0c0b34bb1e8ec4 100644 --- a/spec/compiler/codegen/const_spec.cr +++ b/spec/compiler/codegen/const_spec.cr @@ -87,7 +87,11 @@ describe "Codegen: const" do end it "declare constants in right order" do - run("A = 1 + 1; B = true ? A : 0; B").to_i.should eq(2) + run(%( + A = 1 + 1 + B = true ? A : 0 + B + )).to_i.should eq(2) end it "uses correct types lookup" do @@ -255,4 +259,39 @@ describe "Codegen: const" do Foo::Y.value )).to_i.should eq(1) end + + it "codegens constant that refers to another one later in the file through a method call" do + build(%( + def foo + 1 + end + + class Some + CONST_1 = Some.method + CONST_2 = foo + + def self.method + CONST_2 + end + end + + Some::CONST_1 + )) + end + + it "codegens constant that refers to another one later, twice" do + build(%( + def foo + 1 + end + + class Some + CONST_1 = CONST_2 + CONST_2 = CONST_3 + CONST_3 = foo + end + + Some::CONST_1 + )) + end end diff --git a/spec/compiler/type_inference/const_spec.cr b/spec/compiler/type_inference/const_spec.cr index ad04e2c11523945058bd7d431dc0a3849251aba1..46484dcd92d6be434dc4f8a42517ded519b166af 100755 --- a/spec/compiler/type_inference/const_spec.cr +++ b/spec/compiler/type_inference/const_spec.cr @@ -162,4 +162,14 @@ describe "Type inference: const" do B::CONSTANT )) { bool } end + + it "detects recursive constant definition" do + assert_error %( + A = B + B = A + + A + ), + "recursive constant definition: A -> B -> A" + end end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index c1b7a973bbc312fb21bc1160ba6da848d33e45d0..dca831fcf4b9e328859f1cdad507b8ab29328384 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -130,7 +130,7 @@ end def build(code) node = parse code result = infer_type node - result.program.build result.node, single_module: true + result.program.build result.node, single_module: false end class Crystal::SpecRunOutput diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 1c17c9f545630b089fd3022ff4e5d38c113a676f..301e5a0834e175802585ba9018c01f1b6c6aea7a 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -640,7 +640,7 @@ module Crystal # Initialize constants if they are used if target.is_a?(Path) const = target.target_const.not_nil! - declare_const(const) + declare_const const @last = llvm_nil return false end @@ -874,10 +874,10 @@ module Crystal if const = node.target_const global_name = const.llvm_name - # TODO: the `||` part is to take care of constants that, for their - # initialization, depend on a constant that comes later in the code. - # We should maybe give an error in the type inference phase in this case. - global = @main_mod.globals[global_name]? || declare_const(const).not_nil! + global = @main_mod.globals[global_name]? + unless global + node.raise "Bug: global not found for #{const}" + end if @llvm_mod != @main_mod global = @llvm_mod.globals[global_name]? @@ -902,7 +902,17 @@ module Crystal end def declare_const(const, global_name = const.llvm_name) - return nil unless const.used + return unless const.used + + # First declare constants this constant depends on + const.dependencies.try &.each do |dep| + declare_const dep + end + + # The constant might be already codegened + if global = @main_mod.globals[global_name]? + return global + end global = @main_mod.globals.add(llvm_type(const.value.type), global_name) diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index 0d951f441d58127f2377ccc145a9f63be4335011..95a89f968ca8a98f2b292ebb77ef7132ac0406a3 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -15,6 +15,7 @@ module Crystal getter splat_expansions property vars property literal_expander + getter consts_stack def initialize super(self, self, "main") @@ -109,6 +110,7 @@ module Crystal @macro_expander = MacroExpander.new self @def_macros = [] of Def @splat_expansions = {} of Def => Type + @consts_stack = [] of Const define_primitives end @@ -147,6 +149,18 @@ module Crystal flags.add(flag) end + def push_const(const) + if last_const = @consts_stack.last? + last_const.add_dependency const + end + + @consts_stack.push const + end + + def pop_const + @consts_stack.pop + end + def program self end diff --git a/src/compiler/crystal/semantic/type_inference.cr b/src/compiler/crystal/semantic/type_inference.cr index b227430f87c3e24ea11c63ed85015bf86e3ffed0..105dd27fd8c332dab081894604ea1a2e8278c28e 100644 --- a/src/compiler/crystal/semantic/type_inference.cr +++ b/src/compiler/crystal/semantic/type_inference.cr @@ -1772,12 +1772,22 @@ module Crystal case type when Const unless type.value.type? + if type.visited? + node.raise "recursive constant definition: #{@mod.consts_stack.join " -> "} -> #{type}" + end + + type.visited = true + meta_vars = MetaVars.new const_def = Def.new("const", [] of Arg) type_visitor = TypeVisitor.new(@mod, meta_vars, const_def) type_visitor.types = type.scope_types type_visitor.scope = type.scope + + @mod.push_const type type.value.accept type_visitor + @mod.pop_const + type.vars = const_def.vars end node.target_const = type diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr index 8690cb86d7453346b5c698cad335590ab0c085df..8d2770f8712ec001810afc30e54917485e40ace2 100644 --- a/src/compiler/crystal/types.cr +++ b/src/compiler/crystal/types.cr @@ -2551,10 +2551,18 @@ module Crystal getter scope property! vars property used + property dependencies + property? visited def initialize(program, container, name, @value, @scope_types = [] of Type, @scope = nil) super(program, container, name) @used = false + @visited = false + end + + def add_dependency(const) + dependencies = @dependencies ||= [] of Const + dependencies.push const end def type_desc