Yume
substitution.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "ast/ast.hpp"
4#include "ty/type_base.hpp"
5#include "util.hpp"
6#include <concepts>
7#include <llvm/Support/raw_ostream.h>
8#include <map>
9#include <stdexcept>
10#include <variant>
11
12namespace yume {
13namespace ty {
14class Generic;
15} // namespace ty
16
17struct GenericKey {
18 string name{};
20 // std::vector<unique_ptr<ast::Expr>> exprs{};
21
22 /* implicit */ GenericKey(string_view name) : name{name} {}
23 GenericKey(string_view name, nonnull<ast::Type*> type) : name{name}, expr_type{type} {}
24
25 [[nodiscard]] auto holds_type() const -> bool { return expr_type == nullptr; }
26 [[nodiscard]] auto holds_expr() const -> bool { return expr_type != nullptr; }
27
28 auto operator==(const GenericKey& other) const noexcept -> bool = default;
29 auto operator<=>(const GenericKey& other) const noexcept = default;
30};
31
33 optional<ty::Type> type{};
35
36 GenericValue() = default;
39
40 [[nodiscard]] auto unassigned() const -> bool { return expr == nullptr && !type.has_value(); }
41 [[nodiscard]] auto holds_type() const -> bool { return type.has_value(); }
42 [[nodiscard]] auto holds_expr() const -> bool { return expr != nullptr; }
43
44 [[nodiscard]] auto as_type() const& -> const ty::Type& {
45 YUME_ASSERT(type.has_value(), "Generic value does not hold a type");
46 return *type;
47 }
48 [[nodiscard]] auto as_expr() const& -> const ast::Expr* { return expr; }
49
50 [[nodiscard]] auto unsubstituted_primary() const -> bool { return type.has_value() && type->is_generic(); }
51
52 [[nodiscard]] auto unassigned_or_unsubstituted() const -> bool { return unassigned() || unsubstituted_primary(); }
53
54 [[nodiscard]] auto name() const -> string {
55 if (unassigned())
56 return "?"s;
57 if (type.has_value())
58 return type->name();
59 return expr->describe();
60 }
61
62 auto operator==(const GenericValue& other) const noexcept -> bool {
63 return this->type == other.type && static_cast<bool>(this->expr) == static_cast<bool>(other.expr) &&
64 ((this->expr == nullptr) || this->expr->equals_by_hash(*other.expr));
65 };
66};
67
69private:
70 vector<GenericKey> m_keys;
71 vector<GenericValue> m_values;
72 vector<ty::Generic*> m_generic_type_fallbacks;
73
74public:
75 Substitutions() = delete;
76 Substitutions(vector<GenericKey> keys, const vector<unique_ptr<ty::Generic>>& generic_type_fallbacks,
77 nullable<Substitutions*> parent = nullptr)
78 : m_keys{move(keys)} {
79 for (const auto& i : generic_type_fallbacks)
80 m_generic_type_fallbacks.emplace_back(i.get());
81 for ([[maybe_unused]] const auto& i : m_keys)
82 m_values.emplace_back();
83
84 if (parent != nullptr) {
85 for (auto [k, v] : llvm::zip(parent->m_keys, parent->m_values)) {
86 m_keys.push_back(k);
87 m_values.push_back(v);
88 }
89 for (auto* f : parent->m_generic_type_fallbacks)
90 m_generic_type_fallbacks.push_back(f);
91 }
92 }
93 Substitutions(vector<GenericKey> keys, vector<ty::Generic*> generic_type_fallbacks,
94 nullable<Substitutions*> parent = nullptr)
95 : m_keys{move(keys)}, m_generic_type_fallbacks{move(generic_type_fallbacks)} {
96 for ([[maybe_unused]] const auto& i : m_keys)
97 m_values.emplace_back();
98
99 if (parent != nullptr) {
100 for (auto [k, v] : llvm::zip(parent->m_keys, parent->m_values)) {
101 m_keys.push_back(k);
102 m_values.push_back(v);
103 }
104 for (auto* f : parent->m_generic_type_fallbacks)
105 m_generic_type_fallbacks.push_back(f);
106 }
107 }
108
109 [[nodiscard]] auto empty() const -> bool { return m_values.empty(); }
110 [[nodiscard]] auto size() const -> size_t { return m_values.size(); }
111 [[nodiscard]] auto fully_substituted() const -> bool {
112 // We consider types with no generic arguments at all to be fully substituted
113 return (empty() || std::ranges::none_of(m_values, &GenericValue::unassigned_or_unsubstituted));
114 }
115
116 [[nodiscard]] auto mapping_ref_or_null(const GenericKey& generic) -> nullable<GenericValue*>;
117 [[nodiscard]] auto mapping_ref_or_null(const GenericKey& generic) const -> nullable<const GenericValue*>;
118 [[nodiscard]] auto mapping_ref(const GenericKey& generic) -> GenericValue& {
119 auto* ptr = mapping_ref_or_null(generic);
120 YUME_ASSERT(ptr != nullptr, "Mapped value must not be null");
121 return *ptr;
122 };
123 [[nodiscard]] auto mapping_ref(const GenericKey& generic) const -> const GenericValue& {
124 const auto* ptr = mapping_ref_or_null(generic);
125 YUME_ASSERT(ptr != nullptr, "Mapped value must not be null");
126 return *ptr;
127 };
128
129 [[nodiscard]] auto find_type(string_view generic_name) const -> optional<ty::Type> {
130 const auto* mapping = mapping_ref_or_null(generic_name);
131
132 if (mapping == nullptr)
133 return std::nullopt;
134
135 if (mapping->unassigned())
136 return std::nullopt;
137
138 YUME_ASSERT(mapping->holds_type(), "A generic type key must correspond to a generic type mapping");
139
140 return mapping->as_type();
141 }
142
143 [[nodiscard]] auto type_mappings() const -> std::map<string, ty::Type>;
144
145 void associate(const GenericKey& key, GenericValue value) { mapping_ref(key) = value; }
146
148 m_keys.emplace_back(move(key));
149 return m_values.emplace_back();
150 }
151
152 void dump(llvm::raw_ostream& os) const {
153 int i = 0;
154 for (const auto& [k, v] : llvm::zip(m_keys, m_values)) {
155 if (i++ > 0)
156 os << ", ";
157 os << k.name << "=" << v.name();
158 }
159 }
160
161 [[nodiscard]] auto all_keys() const -> vector<const GenericKey*> {
162 vector<const GenericKey*> keys{};
163 keys.reserve(m_keys.size());
164 for (const auto& k : m_keys)
165 keys.push_back(&k);
166
167 return keys;
168 }
169 [[nodiscard]] auto all_values() const -> vector<const GenericValue*> {
170 vector<const GenericValue*> values{};
171 values.reserve(m_values.size());
172 for (const auto& m : m_values)
173 values.push_back(&m);
174
175 return values;
176 }
177 [[nodiscard]] auto all_values() -> vector<GenericValue*> {
178 vector<GenericValue*> values{};
179 values.reserve(m_values.size());
180 for (auto& m : m_values)
181 values.push_back(&m);
182
183 return values;
184 }
185
186 [[nodiscard]] auto mapping() { return llvm::zip(all_keys(), all_values()); }
187 [[nodiscard]] auto mapping() const { return llvm::zip(all_keys(), all_values()); }
188
189 [[nodiscard]] auto get_generic_fallback(string_view generic_name) const -> ty::Generic*;
190
191 auto operator==(const Substitutions& other) const noexcept -> bool {
192 auto this_keys = this->all_keys();
193 auto other_keys = other.all_keys();
194 auto this_vals = this->all_values();
195 auto other_vals = other.all_values();
196
197 return std::ranges::equal(this_keys, other_keys, [](auto* a, auto* b) { return *a == *b; }) &&
198 std::ranges::equal(this_vals, other_vals, [](auto* a, auto* b) { return *a == *b; });
199 }
200};
201} // namespace yume
202
203template <> struct std::hash<yume::Substitutions> {
204 auto operator()(const yume::Substitutions& s) const noexcept -> std::size_t {
205 uint64_t seed = 0;
206 for (const auto* k : s.all_keys()) {
207 yume::hash_combine(seed, k->name);
208 yume::hash_combine(seed, k->expr_type);
209 }
210 for (const auto* v : s.all_values()) {
211 yume::hash_combine(seed, v->type.has_value());
212 if (v->type.has_value()) {
213 yume::hash_combine(seed, v->type->base());
214 yume::hash_combine(seed, v->type->is_mut());
215 yume::hash_combine(seed, v->type->is_ref());
216 }
217 yume::hash_combine(seed, v->expr != nullptr);
218 if (v->expr != nullptr)
219 yume::hash_combine(seed, std::hash<yume::ast::AST>{}(*v->expr));
220 }
221
222 return seed;
223 }
224};
An unsubstituted generic type variable, usually something like T.
Definition: type.hpp:178
A "qualified" type, with a non-stackable qualifier, i.e. mut.
Definition: type_base.hpp:66
Definition: ast.cpp:8
T nullable
Definition: util.hpp:72
T nonnull
Definition: util.hpp:73
static void hash_combine(uint64_t &seed, const T &v)
Definition: util.hpp:112
auto holds_expr() const -> bool
auto holds_type() const -> bool
auto operator<=>(const GenericKey &other) const noexcept=default
GenericKey(string_view name, nonnull< ast::Type * > type)
GenericKey(string_view name)
nullable< ast::Type * > expr_type
auto operator==(const GenericKey &other) const noexcept -> bool=default
GenericValue(nonnull< ast::Expr * > expr)
auto holds_expr() const -> bool
auto as_expr() const &-> const ast::Expr *
optional< ty::Type > type
auto as_type() const &-> const ty::Type &
auto unsubstituted_primary() const -> bool
GenericValue()=default
GenericValue(ty::Type type)
auto operator==(const GenericValue &other) const noexcept -> bool
auto name() const -> string
auto unassigned() const -> bool
auto unassigned_or_unsubstituted() const -> bool
auto holds_type() const -> bool
nullable< ast::Expr * > expr
void associate(const GenericKey &key, GenericValue value)
auto all_values() const -> vector< const GenericValue * >
auto mapping() const
Substitutions(vector< GenericKey > keys, const vector< unique_ptr< ty::Generic > > &generic_type_fallbacks, nullable< Substitutions * > parent=nullptr)
Substitutions(vector< GenericKey > keys, vector< ty::Generic * > generic_type_fallbacks, nullable< Substitutions * > parent=nullptr)
auto mapping_ref(const GenericKey &generic) -> GenericValue &
auto get_generic_fallback(string_view generic_name) const -> ty::Generic *
void dump(llvm::raw_ostream &os) const
auto fully_substituted() const -> bool
auto find_type(string_view generic_name) const -> optional< ty::Type >
auto size() const -> size_t
auto all_values() -> vector< GenericValue * >
auto append_new_association(GenericKey key) -> GenericValue &
auto operator==(const Substitutions &other) const noexcept -> bool
auto mapping_ref_or_null(const GenericKey &generic) -> nullable< GenericValue * >
Definition: substitution.cpp:7
auto mapping_ref(const GenericKey &generic) const -> const GenericValue &
auto type_mappings() const -> std::map< string, ty::Type >
auto all_keys() const -> vector< const GenericKey * >
auto empty() const -> bool
#define YUME_ASSERT(assertion, message)
Definition: util.hpp:81