From f2e2da4a3473a70006693651052e407fd0443698 Mon Sep 17 00:00:00 2001
From: Eugen Wissner <belka@caraus.de>
Date: Sun, 30 Mar 2025 17:09:22 +0200
Subject: [PATCH] Detect type aliasing cycles

---
 boot/ast.cc                     | 12 +-----------
 boot/parser.yy                  |  6 +++---
 boot/semantic.cc                | 26 +++++++++++++++++++++++---
 boot/symbol.cc                  | 10 ++++++++++
 gcc/elna-generic.cc             | 29 ++++++++++++++++++++---------
 include/elna/boot/ast.h         | 20 ++++----------------
 include/elna/boot/result.h      | 24 ++++++++++++++++++++++++
 include/elna/boot/semantic.h    |  2 +-
 include/elna/boot/symbol.h      | 14 ++++++++++++++
 include/elna/gcc/elna-generic.h |  3 ++-
 10 files changed, 102 insertions(+), 44 deletions(-)

diff --git a/boot/ast.cc b/boot/ast.cc
index 89ee549..588830c 100644
--- a/boot/ast.cc
+++ b/boot/ast.cc
@@ -264,18 +264,8 @@ namespace elna::boot
         delete m_body;
     }
 
-    return_declaration::return_declaration(std::shared_ptr<type_expression> type)
-        : type(type)
-    {
-    }
-
-    return_declaration::return_declaration(std::monostate)
-        : no_return(true)
-    {
-    }
-
     procedure_type_expression::procedure_type_expression(const struct position position,
-            return_declaration return_type)
+            return_declaration<std::shared_ptr<type_expression>> return_type)
         : type_expression(position), return_type(return_type)
     {
     }
diff --git a/boot/parser.yy b/boot/parser.yy
index 74f2e19..19cb999 100644
--- a/boot/parser.yy
+++ b/boot/parser.yy
@@ -144,7 +144,7 @@ along with GCC; see the file COPYING3.  If not see
 %type <std::vector<elna::boot::statement *>> statements;
 %type <elna::boot::procedure_definition *> procedure_definition;
 %type <std::pair<std::vector<std::string>, std::shared_ptr<elna::boot::procedure_type_expression>>> procedure_heading;
-%type <elna::boot::return_declaration> return_declaration;
+%type <elna::boot::procedure_type_expression::return_t> return_declaration;
 %type <std::vector<elna::boot::procedure_definition *>> procedure_definitions procedure_part;
 %type <elna::boot::type_definition *> type_definition;
 %type <std::vector<elna::boot::type_definition *>> type_definitions type_part;
@@ -197,8 +197,8 @@ identifier_definitions:
     | identifier_definition { $$.emplace_back(std::move($1)); }
 return_declaration:
     /* proper procedure */ {}
-    | "->" "!" { $$ = boot::return_declaration(std::monostate{}); }
-    | "->" type_expression { $$ = boot::return_declaration($2); }
+    | "->" "!" { $$ = boot::procedure_type_expression::return_t(std::monostate{}); }
+    | "->" type_expression { $$ = boot::procedure_type_expression::return_t($2); }
 procedure_heading:
     "(" formal_parameters ")" return_declaration
         {
diff --git a/boot/semantic.cc b/boot/semantic.cc
index f081b4d..8c480f5 100644
--- a/boot/semantic.cc
+++ b/boot/semantic.cc
@@ -142,8 +142,28 @@ namespace elna::boot
         this->current_type = type(result_type);
     }
 
-    void declaration_visitor::visit(procedure_type_expression *)
+    void declaration_visitor::visit(procedure_type_expression *type_expression)
     {
+        std::shared_ptr<procedure_type> result_type;
+
+        if (type_expression->return_type.no_return)
+        {
+            result_type = std::make_shared<procedure_type>(procedure_type::return_t(std::monostate{}));
+        }
+        else if (type_expression->return_type.proper_type != nullptr)
+        {
+            type_expression->return_type.proper_type->accept(this);
+            result_type = std::make_shared<procedure_type>(procedure_type::return_t(this->current_type));
+        }
+        else
+        {
+            result_type = std::make_shared<procedure_type>(procedure_type::return_t());
+        }
+        for (auto& parameter : type_expression->parameters)
+        {
+            parameter->accept(this);
+            result_type->parameters.push_back(this->current_type);
+        }
     }
 
     void declaration_visitor::visit(variable_declaration *declaration)
@@ -161,9 +181,9 @@ namespace elna::boot
         {
             heading_parameter->accept(this);
         }
-        if (definition->heading().return_type.type != nullptr)
+        if (definition->heading().return_type.proper_type != nullptr)
         {
-            definition->heading().return_type.type->accept(this);
+            definition->heading().return_type.proper_type->accept(this);
         }
         if (definition->body != nullptr)
         {
diff --git a/boot/symbol.cc b/boot/symbol.cc
index 0e2de25..80a9d77 100644
--- a/boot/symbol.cc
+++ b/boot/symbol.cc
@@ -135,6 +135,11 @@ namespace elna::boot
         return *this;
     }
 
+    bool type::operator==(const std::nullptr_t&)
+    {
+        return empty();
+    }
+
     type::~type()
     {
         switch (tag)
@@ -265,6 +270,11 @@ namespace elna::boot
     {
     }
 
+    procedure_type::procedure_type(return_t return_type)
+        : return_type(return_type)
+    {
+    }
+
     info::~info()
     {
     }
diff --git a/gcc/elna-generic.cc b/gcc/elna-generic.cc
index e5ddc54..e195121 100644
--- a/gcc/elna-generic.cc
+++ b/gcc/elna-generic.cc
@@ -22,6 +22,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "elna/gcc/elna-diagnostic.h"
 #include "elna/gcc/elna1.h"
 
+#include <algorithm>
+
 #include "ggc.h"
 #include "function.h"
 #include "cgraph.h"
@@ -37,7 +39,7 @@ along with GCC; see the file COPYING3.  If not see
 namespace elna::gcc
 {
     tree get_inner_alias(const boot::type& type, std::shared_ptr<symbol_table> symbols,
-            std::unordered_map<std::string, tree>& unresolved)
+            std::unordered_map<std::string, tree>& unresolved, std::vector<std::string>& path)
     {
         if (auto reference = type.get<boot::primitive_type>())
         {
@@ -57,7 +59,7 @@ namespace elna::gcc
         }
         else if (auto reference = type.get<boot::pointer_type>())
         {
-            return build_global_pointer_type(get_inner_alias(reference->base, symbols, unresolved));
+            return build_global_pointer_type(get_inner_alias(reference->base, symbols, unresolved, path));
         }
         else if (auto reference = type.get<boot::array_type>())
         {
@@ -65,19 +67,27 @@ namespace elna::gcc
             tree upper_bound = build_int_cst_type(integer_type_node, reference->size);
             tree range_type = build_range_type(integer_type_node, lower_bound, upper_bound);
 
-            return build_array_type(get_inner_alias(reference->base, symbols, unresolved), range_type);
+            return build_array_type(get_inner_alias(reference->base, symbols, unresolved, path), range_type);
         }
         else if (auto reference = type.get<boot::alias_type>())
         {
-            return handle_symbol(reference->name, reference, symbols, unresolved);
+            return handle_symbol(reference->name, reference, symbols, unresolved, path);
         }
         return error_mark_node;
     }
 
     tree handle_symbol(const std::string& symbol_name, std::shared_ptr<boot::alias_type> reference,
-            std::shared_ptr<symbol_table> symbols, std::unordered_map<std::string, tree>& unresolved)
+            std::shared_ptr<symbol_table> symbols, std::unordered_map<std::string, tree>& unresolved,
+            std::vector<std::string>& path)
     {
-        auto looked_up = get_inner_alias(reference->reference, symbols, unresolved);
+        path.push_back(symbol_name);
+        std::vector<std::string>::const_iterator head_peace = std::cbegin(path);
+
+        if (std::find(std::next(head_peace), std::cend(path), *head_peace) != std::cend(path))
+        {
+            return error_mark_node;
+        }
+        auto looked_up = get_inner_alias(reference->reference, symbols, unresolved, path);
 
         unresolved.insert({ symbol_name, looked_up });
 
@@ -97,7 +107,8 @@ namespace elna::gcc
         {
             for (auto& [symbol_name, symbol_info] : declaration_visitor.unresolved)
             {
-                handle_symbol(symbol_name, symbol_info, symbols, unresolved);
+                std::vector<std::string> type_path;
+                handle_symbol(symbol_name, symbol_info, symbols, unresolved, type_path);
             }
         }
         return std::move(declaration_visitor.errors());
@@ -803,9 +814,9 @@ namespace elna::gcc
         }
         tree return_type = void_type_node;
 
-        if (type.return_type.type != nullptr)
+        if (type.return_type.proper_type != nullptr)
         {
-            type.return_type.type->accept(this);
+            type.return_type.proper_type->accept(this);
             return_type = this->current_expression;
         }
         this->current_expression = NULL_TREE;
diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h
index a3ef2ca..b06f863 100644
--- a/include/elna/boot/ast.h
+++ b/include/elna/boot/ast.h
@@ -21,7 +21,6 @@ along with GCC; see the file COPYING3.  If not see
 #include <memory>
 #include <string>
 #include <vector>
-#include <variant>
 #include "elna/boot/result.h"
 
 namespace elna::boot
@@ -316,30 +315,19 @@ namespace elna::boot
         virtual ~constant_definition() override;
     };
 
-    /**
-     * Tags a procedure type as never returning.
-     */
-    struct return_declaration
-    {
-        return_declaration() = default;
-        explicit return_declaration(std::shared_ptr<type_expression> type);
-        explicit return_declaration(std::monostate);
-
-        std::shared_ptr<type_expression> type{ nullptr };
-        bool no_return{ false };
-    };
-
     /**
      * Procedure type.
      */
     class procedure_type_expression : public type_expression
     {
     public:
-        const return_declaration return_type;
+        using return_t = return_declaration<std::shared_ptr<type_expression>>;
+
+        const return_t return_type;
         std::vector<std::shared_ptr<type_expression>> parameters;
 
         procedure_type_expression(const struct position position,
-                return_declaration return_type = return_declaration());
+                return_t return_type = return_t());
 
         void accept(parser_visitor *visitor) override;
         std::shared_ptr<procedure_type_expression> is_procedure() override;
diff --git a/include/elna/boot/result.h b/include/elna/boot/result.h
index f053ece..b4ac13b 100644
--- a/include/elna/boot/result.h
+++ b/include/elna/boot/result.h
@@ -21,6 +21,7 @@ along with GCC; see the file COPYING3.  If not see
 #include <string>
 #include <deque>
 #include <memory>
+#include <variant>
 
 namespace elna::boot
 {
@@ -79,4 +80,27 @@ namespace elna::boot
             m_errors.emplace_back(std::move(new_error));
         }
     };
+
+    /**
+     * Tags a procedure type as never returning.
+     */
+    template<typename T>
+    struct return_declaration
+    {
+        return_declaration() = default;
+
+        explicit return_declaration(T type)
+            : proper_type(type)
+        {
+        }
+
+        explicit return_declaration(std::monostate)
+            : no_return(true)
+        {
+        }
+
+        T proper_type{};
+        bool no_return{ false };
+    };
+
 }
diff --git a/include/elna/boot/semantic.h b/include/elna/boot/semantic.h
index 761933f..295ec2c 100644
--- a/include/elna/boot/semantic.h
+++ b/include/elna/boot/semantic.h
@@ -63,7 +63,7 @@ namespace elna::boot
         void visit(type_definition *definition) override;
         void visit(record_type_expression *type_expression) override;
         void visit(union_type_expression *type_expression) override;
-        void visit(procedure_type_expression *) override;
+        void visit(procedure_type_expression *type_expression) override;
 
         void visit(variable_declaration *declaration) override;
         void visit(constant_definition *) override;
diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h
index 17533ba..20439b0 100644
--- a/include/elna/boot/symbol.h
+++ b/include/elna/boot/symbol.h
@@ -23,6 +23,8 @@ along with GCC; see the file COPYING3.  If not see
 #include <memory>
 #include <vector>
 
+#include "elna/boot/result.h"
+
 namespace elna::boot
 {
     class alias_type;
@@ -73,6 +75,8 @@ namespace elna::boot
         type(type&& other);
         type& operator=(type&& other);
 
+        bool operator==(const std::nullptr_t&);
+
         ~type();
 
         template<typename T>
@@ -123,6 +127,16 @@ namespace elna::boot
         std::vector<type_field> fields;
     };
 
+    struct procedure_type
+    {
+        using return_t = return_declaration<type>;
+
+        std::vector<type> parameters;
+        const return_t return_type;
+
+        procedure_type(return_t return_type = return_t());
+    };
+
     class type_info;
 
     class info : public std::enable_shared_from_this<info>
diff --git a/include/elna/gcc/elna-generic.h b/include/elna/gcc/elna-generic.h
index 14b9b0f..31e53b0 100644
--- a/include/elna/gcc/elna-generic.h
+++ b/include/elna/gcc/elna-generic.h
@@ -36,7 +36,8 @@ namespace elna::gcc
             std::unique_ptr<boot::program>& ast, std::shared_ptr<boot::symbol_table> info_table,
             std::shared_ptr<symbol_table> symbols, std::unordered_map<std::string, tree>& unresolved);
     tree handle_symbol(const std::string& symbol_name, std::shared_ptr<boot::alias_type> reference,
-            std::shared_ptr<symbol_table> symbols, std::unordered_map<std::string, tree>& unresolved);
+            std::shared_ptr<symbol_table> symbols, std::unordered_map<std::string, tree>& unresolved,
+            std::vector<std::string>& path);
 
 
     class generic_visitor final : public boot::parser_visitor