From 47c8f99b6ef812dbc22ca56a39ba21bccad47796 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Tue, 8 Sep 2026 20:43:39 +0200 Subject: Implement generic pointers --- include/elna/boot/ast.h | 40 +++++++++++++ include/elna/boot/name_analysis.h | 6 ++ include/elna/boot/symbol.h | 116 +++++++++++++++++++++++++++++++++++++- include/elna/boot/type_check.h | 58 ++++++++++++++++++- 4 files changed, 218 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h index 4919510..f697b44 100644 --- a/include/elna/boot/ast.h +++ b/include/elna/boot/ast.h @@ -445,11 +445,20 @@ namespace elna::boot public: const std::vector fields; const std::optional base; + /** + * Type arguments of a generic base. Empty when the base is plain or + * absent. + */ + std::vector base_arguments; record_type_expression(const source_position position, std::vector&& fields); record_type_expression(const source_position position, std::vector&& fields, identifier&& base); + record_type_expression(const source_position position, + std::vector&& fields, identifier&& base, + std::vector&& base_arguments); + ~record_type_expression() override; void accept(parser_visitor *visitor) override; record_type_expression *is_record() override; @@ -481,10 +490,19 @@ namespace elna::boot public: const identifier type_name; const std::vector field_initializers; + /** + * Type arguments naming the instantiation being constructed. Empty + * when no argument list was written. + */ + const std::vector arguments; record_constructor_expression(const source_position position, identifier&& type_name, std::vector&& field_initializers); + record_constructor_expression(const source_position position, + identifier&& type_name, std::vector&& arguments, + std::vector&& field_initializers); + ~record_constructor_expression() override; void accept(parser_visitor *visitor) override; record_constructor_expression *is_record_constructor() override; }; @@ -609,6 +627,12 @@ namespace elna::boot public: std::optional body; + /** + * Type parameters bound by this declaration. Empty for a plain + * procedure. + */ + std::vector parameters; + procedure_declaration(const source_position position, identifier_definition identifier, procedure_type_expression *heading, procedure_body&& body); procedure_declaration(const source_position position, identifier_definition identifier, @@ -628,6 +652,11 @@ namespace elna::boot type_expression *m_underlying_type; public: + /** + * Type parameters bound by this declaration. Empty for a plain type. + */ + std::vector parameters; + type_declaration(const source_position position, identifier_definition identifier, type_expression *underlying_type); ~type_declaration() override; @@ -752,8 +781,19 @@ namespace elna::boot { public: const std::string name; + /** + * Type arguments of a generic instantiation. Empty when no argument + * list was written, the lists being non-empty where they appear. + */ + std::vector arguments; + /// Resolved form of \c arguments, filled by name analysis. + std::vector argument_types; named_expression(const source_position position, const std::string& name); + named_expression(const source_position position, const std::string& name, + std::vector&& arguments); + ~named_expression() override; + void accept(parser_visitor *visitor) override; named_expression *is_named() override; diff --git a/include/elna/boot/name_analysis.h b/include/elna/boot/name_analysis.h index 4a22df7..46a3e2d 100644 --- a/include/elna/boot/name_analysis.h +++ b/include/elna/boot/name_analysis.h @@ -186,6 +186,12 @@ namespace elna::boot * other than a type. */ type resolve_type(type_expression& expression); + /* + * Opens a scope holding the given type parameters, which the caller + * closes once the declaration they belong to is resolved. + */ + std::vector enter_parameters(const std::vector& parameters); + std::vector resolve_arguments(const std::vector& arguments); type lookup_primitive_type(const std::string& name); std::filesystem::path redefinition_file(const std::shared_ptr& original) const; std::shared_ptr register_variable(const std::string& name, diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h index 40cbbc6..8646f48 100644 --- a/include/elna/boot/symbol.h +++ b/include/elna/boot/symbol.h @@ -42,6 +42,9 @@ namespace elna::boot struct procedure_type; struct enumeration_type; struct extern_type; + struct parameter_type; + struct generic_type; + struct instantiated_type; /** * Represents a type stored in the symbol table. @@ -62,7 +65,10 @@ namespace elna::boot std::shared_ptr, std::shared_ptr, std::shared_ptr, - std::shared_ptr + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr >; Payload payload; @@ -200,6 +206,50 @@ namespace elna::boot { }; + /** + * Type parameter of a generic declaration. + * + * It denotes an unknown pointer type chosen at instantiation, so it is + * usable wherever a pointer is, and erases to a pointer in the backend. + * Each declaration gets fresh parameters; two parameters spelled alike in + * different declarations never compare equal. + */ + struct parameter_type + { + const std::string name; + + explicit parameter_type(const std::string& name); + }; + + /** + * Generic declaration. + * + * Not usable as a type by itself; it carries the parameter names so that a + * missing or miscounted argument list can be reported against them. + */ + struct generic_type + { + /// Parameters of this declaration, each holding a \c parameter_type. + const std::vector parameters; + type referent; + + explicit generic_type(std::vector&& parameters, type referent = type()); + }; + + /** + * Generic applied to type arguments. + * + * \c generic holds the alias the arguments were applied to, so that two + * mentions of the same instantiation share the generic's identity. + */ + struct instantiated_type + { + const type generic; + const std::vector arguments; + + instantiated_type(type generic, std::vector&& arguments); + }; + class type_info; class procedure_info; class variable_info; @@ -378,6 +428,10 @@ namespace elna::boot /// Local definitions. std::shared_ptr scope; + /// Type parameters, each holding a \c parameter_type. Empty for a + /// plain procedure. + std::vector parameters; + /** * Constructs procedure symbol information. * @@ -567,6 +621,66 @@ namespace elna::boot */ type resolve_aliases(const type& checked); + /** + * Resolves an alias chain without seeing through instantiations, which + * \c resolve_aliases does. + * + * \param checked The type to resolve. + * \return The first non-alias type in the chain. + */ + type unwrap_aliases(const type& checked); + + /** + * \param checked A type naming a generic, possibly through aliases. + * \return The generic, or \c nullptr if \p checked does not name one. + */ + std::shared_ptr unwrap_generic(const type& checked); + + /** + * Replaces type parameters with the arguments standing at their position. + * + * The substitution is structural and stops at record boundaries, whose + * fields are substituted when they are looked up instead. + * + * \param subject Type to substitute in. + * \param parameters Parameters of the generic being substituted. + * \param arguments Arguments to replace them with. + * \return The substituted type. + */ + type substitute(const type& subject, const std::vector& parameters, + const std::vector& arguments); + + /** + * Resolves a type to the view layout and codegen see, in which an + * instantiation is its generic's referent with the arguments dropped. + * + * \param checked The type to resolve. + * \return The erased type, empty if a generic is unresolved. + */ + type erase_generic(const type& checked); + + /** + * \param derived A record type or an instantiation of a generic record. + * \return The type it extends, with the arguments of \p derived + * substituted into it. Empty if it extends nothing. + */ + type base_of(const type& derived); + + /** + * \param base Candidate base type. + * \param derived Candidate derived type. + * \return Whether \p base occurs in \p derived's base chain. + */ + bool is_base_of(const type& base, const type& derived); + + /** + * Applies an instantiation's arguments to its generic's referent. + * + * \param instance The instantiation to apply. + * \return The substituted referent, empty if the generic is unresolved. + */ + type instantiate(const std::shared_ptr& instance); + /** * Checks whether the given type is the built-in type \a name. * diff --git a/include/elna/boot/type_check.h b/include/elna/boot/type_check.h index 50b9225..e948ada 100644 --- a/include/elna/boot/type_check.h +++ b/include/elna/boot/type_check.h @@ -94,6 +94,8 @@ namespace elna::boot opaque_element, opaque_cast, opaque_arithmetic, + dereference_of_parameter, + parameter_arithmetic, zero_sized, not_addressable }; @@ -119,7 +121,8 @@ namespace elna::boot { trait, call, - array + array, + generic }; private: kind m_kind; @@ -160,6 +163,51 @@ namespace elna::boot payload_type payload; }; + /** + * Error applying a generic or declaring its parameters. + */ + class generic_error final : public diagnostic + { + public: + struct not_generic + { + }; + struct unused_parameter + { + std::string parameter; + }; + struct argument_not_pointer + { + type actual; + }; + struct constant_argument + { + type actual; + }; + struct uninferrable_parameter + { + std::string parameter; + }; + struct shape_mismatch + { + type declared; + type actual; + }; + + using payload_type = std::variant; + + generic_error(const source_position position, const std::string& generic_name, + payload_type payload); + + std::string what() const override; + + private: + std::string generic_name; + payload_type payload; + }; + /** * Chain of responsibility for type compatibility checks. * @@ -205,6 +253,8 @@ namespace elna::boot symbol_bag bag; std::shared_ptr current_procedure; const target_info& target; + /// Whether the name being visited stands in a call's callable position. + bool in_callable_position{ false }; /* * Whether an expression of type assignment can be assigned to a variable @@ -214,6 +264,11 @@ namespace elna::boot static bool is_equality_compatible(const type& left, const type& right); void visit_and_validate_condition(expression& condition); + void check_arguments(const named_expression& reference, const std::vector& arguments); + void check_parameters_used(const source_position position, const std::string& name, + const std::vector& parameters, const type& heading); + type infer_call(procedure_call& call, const named_expression& reference, + const procedure_info& procedure); void check_return(const procedure_body& body, const source_position position, const std::string& name); @@ -240,6 +295,7 @@ namespace elna::boot void visit(slicing_expression *expression) override; void visit(array_access_expression *expression) override; void visit(dereference_expression *expression) override; + void visit(named_expression *expression) override; void visit(cast_expression *expression) override; void visit(unary_expression *expression) override; void visit(binary_expression *expression) override; -- cgit v1.2.3