Yume
type.cpp
Go to the documentation of this file.
1#include "type.hpp"
2#include "ast/ast.hpp"
3#include "compiler/vals.hpp"
4#include "qualifier.hpp"
6#include "ty/substitution.hpp"
7#include "ty/type_base.hpp"
8#include "util.hpp"
9#include <cstddef>
10#include <limits>
11#include <llvm/Support/Casting.h>
12#include <llvm/Support/ErrorHandling.h>
13#include <llvm/Support/raw_ostream.h>
14#include <map>
15#include <sstream>
16#include <stdexcept>
17#include <type_traits>
18#include <utility>
19#include <variant>
20
21namespace yume::ty {
22static auto qual_suffix(Qualifier qual) -> string {
23 switch (qual) {
24 case Qualifier::Mut: return " mut";
25 case Qualifier::Ptr: return " ptr";
26 case Qualifier::Ref: return " ref";
27 default: return "";
28 }
29}
30
31auto Type::known_qual(Qualifier qual) const -> Type {
32 switch (qual) {
33 case Qualifier::Mut: return {m_base, true, false};
34 case Qualifier::Ref: return {m_base, false, true};
35 case Qualifier::Ptr:
36 if (m_base->m_known_ptr == nullptr)
37 m_base->m_known_ptr = std::make_unique<Ptr>(m_base->base_name(), *this, qual);
38
39 return {m_base->m_known_ptr.get()};
40 case Qualifier::Type:
41 if (m_base->m_known_meta == nullptr)
42 m_base->m_known_meta = std::make_unique<Meta>(this->base());
43
44 return {m_base->m_known_meta.get()};
46 if (m_base->m_known_opaque_self == nullptr)
47 m_base->m_known_opaque_self = std::make_unique<OpaqueSelf>(this->base());
48
49 return {m_base->m_known_opaque_self.get()};
50 }
51}
52
53static void visit_subs(Type a, Type b, std::unordered_map<string, ty::Type>& sub) {
54 YUME_ASSERT(b.is_generic(), "Cannot substitute generics in a non-generic type");
55
56 // `Foo ptr` -> `T ptr`, with `T = Foo`.
57 if (auto a_ptr_base = a.ptr_base(), b_ptr_base = b.ptr_base();
58 a_ptr_base && b_ptr_base && a.base_cast<Ptr>()->qualifier() == b.base_cast<Ptr>()->qualifier()) {
59 return visit_subs(*a_ptr_base, *b_ptr_base, sub);
60 }
61 // `Foo mut` -> `T mut`, with `T = Foo`.
62 if (a.is_mut() && b.is_mut())
63 return visit_subs(a.ensure_mut_base(), b.ensure_mut_base(), sub);
64 // `Foo type` -> `T type`, with `T = Foo`.
65 if (a.is_meta() && b.is_meta())
66 return visit_subs(a.without_meta(), b.without_meta(), sub);
67
68 // `Foo ptr mut` -> `T ptr`, with `T = Foo`.
69 if (a.is_mut() && !b.is_mut())
70 return visit_subs(a.ensure_mut_base(), b, sub);
71
72 // `Foo{Bar}` -> `Foo{T}`, with `T = Foo`.
73 if (auto a_st_ty = a.base_dyn_cast<Struct>(), b_st_ty = b.base_dyn_cast<Struct>();
74 a_st_ty != nullptr && b_st_ty != nullptr) {
75 if (a_st_ty->base_name() == b_st_ty->base_name()) {
76 // TODO(rymiel): Currently only handling type parameters
77 auto a_ty_mapping = a_st_ty->subs()->type_mappings();
78 const auto* b_subs = b_st_ty->subs();
79 for (auto [a_key, a_sub] : a_ty_mapping) {
80 const auto* b_mapping = b_subs->mapping_ref_or_null({a_key});
81 if (b_mapping != nullptr && b_mapping->unassigned())
82 sub.try_emplace(a_key, a_sub);
83 }
84 }
85 }
86
87 // Substitution impossible! For example, `Foo` -> `T ptr`.
88 if (!b.base_isa<Generic>())
89 return;
90
91 // Any other generic that didn't match above.
92 // `Foo ptr` -> `T`, with `T = Foo ptr`.
93 sub.try_emplace(b.base_cast<Generic>()->name(), a);
94}
95
96auto Type::determine_generic_subs(Type generic, const Substitutions& subs) const -> optional<Substitutions> {
97 YUME_ASSERT(generic.is_generic(), "Cannot substitute generics in a non-generic type");
98
99 auto clean_subs = subs;
100 std::unordered_map<string, ty::Type> replacements{};
101
102 visit_subs(*this, generic, replacements);
103 for (auto [k, v] : subs.mapping()) {
104 if (!k->holds_type())
105 continue; // Only determining type arguments
106
107 auto iter = replacements.find(k->name);
108 if (iter == replacements.end()) {
109 // No new value was found for this key, so leave it as it was
110 continue;
111 }
112
113 auto new_v = iter->second;
114
115 if (v->unassigned()) {
116 // No value existed anyway, so we can put the new value in directly
117 clean_subs.associate(*k, new_v);
118 continue;
119 }
120
121 auto existing_v = v->as_type();
122 auto intersection = new_v.intersect(existing_v);
123
124 if (!intersection.has_value()) {
125 // The types cannot coexist; this substitution cannot proceed;
126 return {};
127 }
128
129 clean_subs.associate(*k, *intersection);
130 }
131
132 return clean_subs;
133}
134
135auto Type::compatibility(Type other, Compat compat) const -> Compat {
136 if (*this == other) {
137 compat.valid = true;
138 return compat;
139 }
140
141 // `Foo mut` -> `Foo`.
142 // Note that the base types are also compared, so `I32 mut` -> `I64`.
143 if (this->is_mut() && other.is_unqualified()) {
144 compat.conv.dereference = true;
145 compat = ensure_mut_base().compatibility(other, compat);
146 return compat;
147 }
148
149 // `I32` -> `I64`. `U8` -> `I16`. An implicit integer cast with no loss of information.
150 if (const auto this_int = base_dyn_cast<Int>(), other_int = other.base_dyn_cast<Int>();
151 (this_int != nullptr) && (other_int != nullptr)) {
152 if (this_int->is_signed() == other_int->is_signed() && this_int->size() == other_int->size()) {
153 // The two integer types are perfect matches, but werent caught by the pointer equality check above. This is the
154 // case for conversions such as `USize` -> `U32` (on a 32-bit platform).
155 compat.valid = true;
156 return compat;
157 }
158
159 if ((this_int->is_signed() == other_int->is_signed() && this_int->size() <= other_int->size()) ||
160 (!this_int->is_signed() && other_int->is_signed() && this_int->size() * 2 <= other_int->size())) {
161 compat.valid = true;
162 compat.conv.kind = Conv::Int;
163 return compat;
164 }
165 }
166
167 // A function type with captures can be converted to a matching function type without captures.
168 // A function type without captures can be converted to a matching function *pointer* type.
169 if (const auto this_fn = base_dyn_cast<Function>(), other_fn = other.base_dyn_cast<Function>();
170 (this_fn != nullptr) && (other_fn != nullptr)) {
171 if (this_fn->m_args == other_fn->m_args && this_fn->m_ret == other_fn->m_ret && other_fn->m_closure.empty() &&
172 this_fn->m_fn_ptr == other_fn->m_fn_ptr) {
173 compat.valid = true;
174 return compat;
175 }
176 if (this_fn->m_args == other_fn->m_args && this_fn->m_ret == other_fn->m_ret && this_fn->m_closure.empty() &&
177 !this_fn->m_fn_ptr && other_fn->m_fn_ptr) {
178 compat.valid = true;
179 compat.conv.kind = Conv::FnPtr;
180 return compat;
181 }
182 }
183
184 const auto* this_st = base_dyn_cast<Struct>();
185 const auto* other_st = other.base_dyn_cast<Struct>();
186 // A struct type which implements an interface can be casted to said interface.
187 if ((this_st != nullptr) && (other_st != nullptr) && (other_st->is_interface()) &&
188 (this_st->implements().has_value()) && (this_st->implements()->ensure_ty() == other.m_base) &&
189 (this->is_mut() == other.is_mut()) && (this->is_ref() == other.is_ref())) {
190 compat.valid = true;
191 compat.conv.kind = Conv::Virtual;
192 return compat;
193 }
194
195 // An interface is essentially always opaque, and thus can be implicitly converted to be "opaque"
196 if (const auto* other_opaque = other.base_dyn_cast<OpaqueSelf>();
197 (this_st != nullptr) && (other_opaque != nullptr) && (this_st->is_interface()) && (!this->is_opaque_self()) &&
198 (this_st == other_opaque->indirect()) && (this->is_mut() == other.is_mut()) &&
199 (this->is_ref() == other.is_ref())) {
200 // TODO(rymiel): should this recurse?
201 compat.valid = true;
202 return compat;
203 }
204
205 // An opaque struct type converted to a regular struct type is basically just a dereference.
206 // TODO(rymiel): This is sorta abusing the notion of "dereference", come up with a separate type? Or merge with
207 // Virtual somehow?
208 // TODO(rymiel): This doesn't support anything related to mutable types, should it?
209 if (const auto* this_opaque = this->base_dyn_cast<OpaqueSelf>();
210 (this_opaque != nullptr) && (other_st != nullptr) && (!other_st->is_interface()) && (!other.is_opaque_self()) &&
211 (other_st == this_opaque->indirect()) && (this->is_unqualified()) && (other.is_unqualified())) {
212 // TODO(rymiel): should this recurse?
213 compat.conv.dereference = true;
214 compat.valid = true;
215 return compat;
216 }
217
218 return compat;
219}
220
221auto Type::is_generic() const noexcept -> bool {
222 if (base_isa<Generic>())
223 return true;
224
225 if (base_isa<Ptr>())
226 return ensure_ptr_base().is_generic();
227
228 if (base_isa<Meta>())
229 return without_meta().is_generic(); // TODO(rymiel): Needs a `ensure_meta_indirect` or something idk
230
231 if (const auto* fn_base = base_dyn_cast<Function>(); fn_base != nullptr) {
232 auto ret = fn_base->ret();
233 return std::ranges::any_of(fn_base->args(), &Type::is_generic) || (ret.has_value() && ret->is_generic());
234 }
235
236 if (const auto* struct_ty = base_dyn_cast<Struct>())
237 return !struct_ty->subs()->fully_substituted();
238
239 return false;
240}
241
242auto Type::is_slice() const noexcept -> bool {
243 if (const auto* base = base_dyn_cast<Struct>())
244 return base->m_decl->has_annotation(ast::StructDecl::BUILTIN_TYPE_SLICE);
245
246 return false;
247};
248
249auto Type::is_opaque_self() const noexcept -> bool { return base_isa<OpaqueSelf>(); };
250
251auto Type::is_meta() const noexcept -> bool { return base_isa<Meta>(); };
252
254 if (base_isa<ty::Int>() || base_isa<ty::Ptr>() || base_isa<ty::Function>() || base_isa<ty::Nil>())
255 return true;
256
257 if (is_slice())
258 return false;
259
260 if (const auto* struct_type = base_dyn_cast<ty::Struct>()) {
261 return std::ranges::all_of(
262 struct_type->fields(), [](const auto& i) { return i.is_trivially_destructible(); }, &ast::TypeName::ensure_ty);
263 }
264
265 // A generic or something, shouldn't occur
266 throw std::logic_error("Cannot check if "s + name() + " is trivially destructible");
267}
268
269auto Type::has_qualifier(Qualifier qual) const -> bool {
270 if (m_mut)
271 return (qual == Qualifier::Mut);
272 if (m_ref)
273 return (qual == Qualifier::Ref);
274 if (const auto* ptr_base = base_dyn_cast<Ptr>(); ptr_base)
275 return ptr_base->has_qualifier(qual);
276 return false;
277}
278
280 if (!is_generic())
281 return *this; // Nothing to do!
282
283 if (const auto* generic_this = base_dyn_cast<Generic>()) {
284 if (auto mapped = sub.find_type(generic_this->name()); mapped.has_value())
285 return Type{mapped->base(), mapped->is_mut() || m_mut, mapped->is_ref() || m_ref};
286 }
287
288 if (const auto* ptr_this = base_dyn_cast<Ptr>())
289 return ensure_ptr_base().apply_generic_substitution(sub).known_qual(ptr_this->qualifier());
290
291 if (is_meta())
292 return without_meta().apply_generic_substitution(sub).known_meta();
293
294 if (const auto* st_this = base_dyn_cast<Struct>())
295 return Type{&st_this->get_or_create_instantiation(sub), m_mut, m_ref};
296
297 if (const auto* fn_this = base_dyn_cast<Function>())
298 return Type{&fn_this->get_or_create_instantiation(sub), m_mut, m_ref};
299
300 std::string dump;
301 llvm::raw_string_ostream os{dump};
302 sub.dump(os);
303 throw std::logic_error("Cannot apply generic substitution (" + dump + ") to type `" + name() + "'");
304}
305
307 // auto detailed_dump = [](const Struct& st) -> auto& {
308 // errs() << "[`" << st.name() << "'@" << &st << ", parent@" << st.m_parent << ", decl@" << st.m_decl << "]";
309 // return errs();
310 // };
311
312 // errs() << "Struct::goci for ", detailed_dump(*this) << " with `";
313 // sub.dump(errs());
314 // errs() << "'\n";
315
316 if (m_parent != nullptr)
317 return m_parent->get_or_create_instantiation(move(sub));
318
319 // TODO(rymiel): What does this accomplish? If the subs are equivalent, it means the current object is already
320 // substituted, and this function shouldn't really be called at all. This should *probably* be replaced with a
321 // guard checking that we're not fully substituted.
322 // Also, I think a class invariant is that m_parent and m_subs are mutually exclusive, and we already checked for
323 // m_parent above.
324 if (m_subs != nullptr && sub == *m_subs)
325 return *this;
326
327 auto existing = m_instantiations.find(sub);
328 if (existing == m_instantiations.end()) {
329 // errs() << "Struct::goci for ", detailed_dump(*this) << ": Creating new type: `";
330 auto [iter, ok] = m_instantiations.emplace(move(sub), make_unique<Struct>(base_name(), m_fields, m_decl, nullptr));
331 iter->second->m_subs = &iter->first;
332 iter->second->m_parent = this;
333 // detailed_dump(*iter->second) << "'\n";
334 return *iter->second;
335 }
336 return *existing->second;
337}
338
340 // auto detailed_dump = [](const Function& st) -> auto& {
341 // errs() << "[`" << st.name() << "'@" << &st << ", parent@" << st.m_parent << "]";
342 // return errs();
343 // };
344
345 // errs() << "Function::goci for ", detailed_dump(*this) << " with `";
346 // sub.dump(errs());
347 // errs() << "'\n";
348
349 if (m_parent != nullptr)
350 return m_parent->get_or_create_instantiation(move(sub));
351
352 auto existing = m_instantiations.find(sub);
353 if (existing == m_instantiations.end()) {
354 // errs() << "Function::goci for ", detailed_dump(*this) << ": Creating new type: ";
355
356 auto args = vector<ty::Type>{};
357 auto ret = optional<ty::Type>{};
358
359 for (const auto& arg : m_args)
360 args.push_back(arg.apply_generic_substitution(sub));
361
362 if (m_ret.has_value())
363 ret = m_ret->apply_generic_substitution(sub);
364
365 // TODO(rymiel): Is this necessary?
366 YUME_ASSERT(m_closure.empty(), "Cannot substitute function type with closure.");
367
368 auto [iter, ok] = m_instantiations.emplace(
369 move(sub), make_unique<Function>(base_name(), move(args), ret, m_closure, m_fn_ptr, m_c_varargs));
370 iter->second->m_parent = this;
371 // detailed_dump(*iter->second) << "'\n";
372 return *iter->second;
373 }
374 return *existing->second;
375}
376
377auto Struct::is_interface() const -> bool { return m_decl->st_ast.is_interface; }
378
379auto Struct::implements() const -> const ast::OptionalType& { return m_decl->st_ast.implements; }
380
381auto Type::mut_base() const noexcept -> optional<Type> {
382 if (is_mut())
383 return Type(m_base);
384 return std::nullopt;
385}
386
388 if (is_mut())
389 return {m_base};
390 throw std::logic_error("Tried calling ensure_mut_base on a type that isn't a mutable reference");
391}
392
393auto Type::ptr_base() const noexcept -> optional<Type> {
394 if (const auto* ptr = base_dyn_cast<Ptr>())
395 return ptr->pointee();
396 return {};
397}
398
400 if (const auto* ptr = base_dyn_cast<Ptr>())
401 return ptr->pointee();
402 throw std::logic_error("Tried calling ensure_ptr_base on a type that isn't a pointer-like type");
403}
404
405auto Type::coalesce(Type other) const noexcept -> optional<Type> {
406 if (*this == other)
407 return *this;
408 if (m_mut && !other.m_mut && m_base == other.m_base)
409 return *this;
410 if (other.m_mut && !m_mut && other.m_base == m_base)
411 return other;
412
413 return std::nullopt;
414}
415
416auto Type::intersect(Type other) const noexcept -> optional<Type> {
417 if (*this == other)
418 return *this;
419 if (m_mut && !other.m_mut && m_base == other.m_base)
420 return other;
421 if (other.m_mut && !m_mut && other.m_base == m_base)
422 return *this;
423
424 return std::nullopt;
425}
426
427auto Type::without_mut() const noexcept -> Type { return {m_base}; }
428
429auto Type::without_opaque() const noexcept -> Type {
430 if (const auto* opaque_self = base_dyn_cast<OpaqueSelf>(); opaque_self != nullptr)
431 return {opaque_self->indirect()};
432 return *this;
433}
434
435auto Type::without_meta() const noexcept -> Type {
436 if (const auto* meta = base_dyn_cast<Meta>(); meta != nullptr)
437 return {meta->indirect()};
438 return *this;
439}
440
441auto Type::generic_base() const noexcept -> Type {
442 if (const auto* st = base_dyn_cast<Struct>(); st != nullptr) {
443 auto primary_generic = st->decl()->get_self_ty();
444 YUME_ASSERT(primary_generic.has_value(), "Generic type doesn't have a known primary unsubstituted type");
445 return {*primary_generic};
446 }
447 return *this;
448}
449
450auto Type::name() const -> string {
451 auto name = m_base->name();
452 if (m_mut)
454 if (m_ref)
456 return name;
457}
458auto Type::base_name() const -> string { return m_base->name(); }
459
460auto Ptr::name() const -> string { return m_base.name() + qual_suffix(m_qual); }
461
462auto Struct::name() const -> string {
463 if (m_subs == nullptr || m_subs->empty())
464 return base_name();
465
466 auto ss = stringstream{};
467 ss << base_name() << "{";
468 for (const auto& i : llvm::enumerate(m_subs->mapping())) {
469 auto [key, mapping] = i.value();
470 if (i.index() > 0)
471 ss << ",";
472 ss << (mapping->unassigned() ? key->name : mapping->name());
473 }
474 ss << "}";
475
476 return ss.str();
477}
478
479auto Function::name() const -> string {
480 auto ss = stringstream{};
481 ss << "(" << base_name();
482 if (!m_closure.empty()) {
483 ss << "[";
484 for (const auto& i : llvm::enumerate(m_closure)) {
485 if (i.index() > 0)
486 ss << ",";
487 ss << i.value().name();
488 }
489 ss << "] ";
490 }
491 for (const auto& i : llvm::enumerate(m_args)) {
492 if (i.index() > 0)
493 ss << ",";
494 ss << i.value().name();
495 }
496 ss << "->";
497 if (m_fn_ptr)
498 ss << "ptr";
499 if (m_ret.has_value())
500 ss << m_ret->name();
501 ss << ")";
502
503 return ss.str();
504}
505
506auto Type::opaque_equal(const Type& other) const noexcept -> bool {
507 return *this == other || (this->is_opaque_self() && other.is_opaque_self());
508};
509
510namespace detail {
511struct MinMax {
512 uint64_t u_min;
513 uint64_t u_max;
514 int64_t s_min;
515 int64_t s_max;
516};
517
518template <typename UIntType> consteval auto minmax_for_bits() -> MinMax {
519 using SIntType = typename std::make_signed<UIntType>::type;
520
521 return {std::numeric_limits<UIntType>::min(), std::numeric_limits<UIntType>::max(),
522 std::numeric_limits<SIntType>::min(), std::numeric_limits<SIntType>::max()};
523}
524
525constexpr auto minmax_for_bits(size_t bits) -> MinMax {
526 switch (bits) {
527 case 8: return minmax_for_bits<uint8_t>();
528 case 16: return minmax_for_bits<uint16_t>();
529 case 32: return minmax_for_bits<uint32_t>();
530 case 64: return minmax_for_bits<uint64_t>();
531 default: throw std::logic_error("Integer type must be 8, 16, 32, or 64 bits, not "s + std::to_string(bits));
532 };
533}
534} // namespace detail
535
536auto Int::in_range(int64_t num) const -> bool {
537 if (num < 0 && !m_signed)
538 return false;
539 auto min_max = detail::minmax_for_bits(m_size);
540 if (m_signed)
541 return num >= min_max.s_min && num <= min_max.s_max;
542 return static_cast<uint64_t>(num) >= min_max.u_min && static_cast<uint64_t>(num) <= min_max.u_max;
543}
544} // namespace yume::ty
auto ensure_ty() const -> ty::Type
Definition: ast.hpp:254
auto kind() const -> Kind
Definition: type_base.hpp:57
auto base_name() const -> string
Definition: type_base.hpp:58
A function pointer type.
Definition: type.hpp:129
auto name() const -> string override
Definition: type.cpp:479
auto get_or_create_instantiation(Substitutions sub) const -> const Function &
Definition: type.cpp:339
An unsubstituted generic type variable, usually something like T.
Definition: type.hpp:178
auto name() const -> string override
Definition: type.hpp:181
A built-in integral type, such as I32 or Bool.
Definition: type.hpp:35
auto in_range(int64_t num) const -> bool
Definition: type.cpp:536
The "self" type of abstract or overriding functions. An extra layer of indirection is introduced for ...
Definition: type.hpp:186
A "qualified" type, with a stackable qualifier, i.e. ptr.
Definition: type.hpp:59
auto qualifier() const -> Qualifier
Definition: type.hpp:71
auto name() const -> string override
Definition: type.cpp:460
An user-defined struct type with associated fields.
Definition: type.hpp:78
auto name() const -> string override
Definition: type.cpp:462
auto implements() const -> const ast::OptionalType &
Definition: type.cpp:379
auto get_or_create_instantiation(Substitutions sub) const -> const Struct &
Definition: type.cpp:306
auto is_interface() const -> bool
Definition: type.cpp:377
A "qualified" type, with a non-stackable qualifier, i.e. mut.
Definition: type_base.hpp:66
auto apply_generic_substitution(const Substitutions &sub) const -> Type
Definition: type.cpp:279
auto is_slice() const noexcept -> bool
Definition: type.cpp:242
auto ensure_mut_base() const noexcept(false) -> Type
If this type is a mutable reference, return the base of it (T mut -> T)
Definition: type.cpp:387
auto is_mut() const noexcept -> bool
Definition: type_base.hpp:114
auto mut_base() const noexcept -> optional< Type >
If this type is a mutable reference, return the base of it (T mut -> T)
Definition: type.cpp:381
auto intersect(Type other) const noexcept -> optional< Type >
Return the intersection of this and other. For example, the intersection of T and T mut is T.
Definition: type.cpp:416
auto is_opaque_self() const noexcept -> bool
Definition: type.cpp:249
auto is_trivially_destructible() const -> bool
Definition: type.cpp:253
auto coalesce(Type other) const noexcept -> optional< Type >
The union of this and other. For example, the union of T and T mut is T mut.
Definition: type.cpp:405
auto generic_base() const noexcept -> Type
Definition: type.cpp:441
auto determine_generic_subs(Type generic, const Substitutions &subs) const -> optional< Substitutions >
Definition: type.cpp:96
auto ensure_ptr_base() const noexcept(false) -> Type
If this type is a pointer type, return the base of it (T ptr -> T)
Definition: type.cpp:399
auto ptr_base() const noexcept -> optional< Type >
If this type is a pointer type, return the base of it (T ptr -> T)
Definition: type.cpp:393
auto has_qualifier(Qualifier qual) const -> bool
Definition: type.cpp:269
auto is_meta() const noexcept -> bool
Definition: type.cpp:251
auto base_isa() const noexcept -> bool
Definition: type_base.hpp:90
auto without_meta() const noexcept -> Type
Definition: type.cpp:435
auto base_name() const -> string
Definition: type.cpp:458
auto base_cast() const noexcept -> nonnull< const T * >
Definition: type_base.hpp:80
auto is_generic() const noexcept -> bool
Definition: type.cpp:221
auto base_dyn_cast() const noexcept -> nullable< const T * >
Definition: type_base.hpp:81
auto name() const -> string
Definition: type.cpp:450
auto without_mut() const noexcept -> Type
If this type is a mutable reference, return the base of it, otherwise return itself.
Definition: type.cpp:427
auto opaque_equal(const Type &other) const noexcept -> bool
Definition: type.cpp:506
auto base() const noexcept -> nonnull< const BaseType * >
Definition: type_base.hpp:79
auto known_qual(Qualifier qual) const -> Type
Get this type with a given qualifier applied.
Definition: type.cpp:31
auto compatibility(Type other, Compat compat=Compat()) const -> Compat
Definition: type.cpp:135
auto without_opaque() const noexcept -> Type
If this type is a opaque wrapper type, return the wrapper type, otherwise return itself.
Definition: type.cpp:429
Type(nonnull< const BaseType * > base, bool mut, bool ref) noexcept
Definition: type_base.hpp:72
OptionalAnyBase< Type > OptionalType
Definition: ast.hpp:325
consteval auto minmax_for_bits() -> MinMax
Definition: type.cpp:518
static void visit_subs(Type a, Type b, std::unordered_map< string, ty::Type > &sub)
Definition: type.cpp:53
static auto qual_suffix(Qualifier qual) -> string
Definition: type.cpp:22
Qualifier
Definition: qualifier.hpp:4
@ Opaque
Opaque self types.
static constexpr auto BUILTIN_TYPE_SLICE
Definition: ast.hpp:857
The compatibility between two types, for overload selection.
#define YUME_ASSERT(assertion, message)
Definition: util.hpp:81