Skip to content
This repository was archived by the owner on Oct 12, 2026. It is now read-only.

Commit 633b0c8

Browse files
committed
Add better type system.
1 parent 10abe22 commit 633b0c8

2 files changed

Lines changed: 327 additions & 0 deletions

File tree

‎include/lbd/type/Type.hpp‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#pragma once
2+
3+
#include <memory>
4+
#include <vector>
5+
#include <lbd/type/TypeKind.hpp>
6+
7+
namespace lbd::type
8+
{
9+
class Type;
10+
using TypePtr = std::shared_ptr<Type>;
11+
12+
struct TypePtrHash
13+
{
14+
size_t operator()(const TypePtr& t) const noexcept;
15+
};
16+
17+
struct TypePtrEq
18+
{
19+
bool operator()(const TypePtr& a, const TypePtr& b) const noexcept;
20+
};
21+
22+
// Usage: std::unordered_map<TypePtr, TypePtr, TypePtrHash, TypePtrEq> typeSubstitutionMap;
23+
24+
struct Constraint
25+
{
26+
std::string className; // Eq, Ord, Show.
27+
TypePtr type; // Most likely will be a VARIABLE: a, b.
28+
29+
[[nodiscard]] bool operator==(const Constraint& other) const noexcept;
30+
};
31+
32+
class Type
33+
{
34+
public:
35+
// Factory functions.
36+
[[nodiscard]] static TypePtr named(const std::string& name);
37+
[[nodiscard]] static TypePtr variable(const std::string& name);
38+
[[nodiscard]] static TypePtr applied(const TypePtr& base, std::vector<TypePtr> arguments);
39+
[[nodiscard]] static TypePtr function(const TypePtr& from, const TypePtr& to);
40+
[[nodiscard]] static TypePtr qualified(std::vector<Constraint> constraints, const TypePtr& qualifiedType);
41+
[[nodiscard]] static TypePtr any();
42+
43+
// Observers.
44+
[[nodiscard]] TypeKind getKind() const noexcept;
45+
[[nodiscard]] const std::string& getName() const noexcept;
46+
[[nodiscard]] const TypePtr& getBase() const noexcept;
47+
[[nodiscard]] const std::vector<TypePtr>& getArguments() const noexcept;
48+
[[nodiscard]] const TypePtr& getFrom() const noexcept;
49+
[[nodiscard]] const TypePtr& getTo() const noexcept;
50+
[[nodiscard]] const std::vector<Constraint>& getConstraints() const noexcept;
51+
[[nodiscard]] const TypePtr& getQualifiedType() const noexcept;
52+
53+
// Comparison.
54+
[[nodiscard]] bool equals(const Type& other) const noexcept;
55+
[[nodiscard]] bool accepts(const Type& other) const noexcept;
56+
[[nodiscard]] bool operator==(const Type& other) const noexcept;
57+
[[nodiscard]] size_t hash() const noexcept;
58+
59+
friend std::ostream& operator<<(std::ostream&, const Type&) noexcept;
60+
61+
private:
62+
TypeKind kind;
63+
64+
// NAMED, VARIABLE.
65+
std::string name;
66+
67+
// APPLIED.
68+
TypePtr base;
69+
std::vector<TypePtr> arguments;
70+
71+
// FUNCTION.
72+
TypePtr from;
73+
TypePtr to;
74+
75+
// QUALIFIED.
76+
std::vector<Constraint> constraints;
77+
TypePtr qualifiedType;
78+
};
79+
}

‎src/type/Type.cpp‎

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
#include <functional>
2+
#include <lbd/type/Type.hpp>
3+
4+
namespace lbd::type
5+
{
6+
static size_t hashCombine(const size_t a, const size_t b)
7+
{
8+
// ReSharper disable once CppRedundantParentheses
9+
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
10+
}
11+
12+
size_t TypePtrHash::operator()(const TypePtr& t) const noexcept { return t->hash(); }
13+
14+
bool TypePtrEq::operator()(const TypePtr& a, const TypePtr& b) const noexcept { return *a == *b; }
15+
16+
bool Constraint::operator==(const Constraint& other) const noexcept
17+
{
18+
return className == other.className && type->equals(*other.type);
19+
}
20+
21+
TypePtr Type::named(const std::string& name)
22+
{
23+
const auto type = std::make_shared<Type>();
24+
type->kind = TypeKind::NAMED;
25+
type->name = name;
26+
return type;
27+
}
28+
29+
TypePtr Type::variable(const std::string& name)
30+
{
31+
const auto type = std::make_shared<Type>();
32+
type->kind = TypeKind::VARIABLE;
33+
type->name = name;
34+
return type;
35+
}
36+
37+
TypePtr Type::applied(const TypePtr& base, std::vector<TypePtr> arguments)
38+
{
39+
auto type = std::make_shared<Type>();
40+
type->kind = TypeKind::APPLIED;
41+
type->base = base; // Most likely would be a NAMED: List, Map.
42+
type->arguments = std::move(arguments);
43+
return type;
44+
}
45+
46+
TypePtr Type::function(const TypePtr& from, const TypePtr& to)
47+
{
48+
auto type = std::make_shared<Type>();
49+
type->kind = TypeKind::FUNCTION;
50+
type->from = from;
51+
type->to = to;
52+
return type;
53+
}
54+
55+
TypePtr Type::qualified(std::vector<Constraint> constraints, const TypePtr& qualifiedType)
56+
{
57+
auto type = std::make_shared<Type>();
58+
type->kind = TypeKind::QUALIFIED;
59+
type->constraints = std::move(constraints);
60+
type->qualifiedType = qualifiedType;
61+
return type;
62+
}
63+
64+
TypePtr Type::any()
65+
{
66+
auto type = std::make_shared<Type>();
67+
type->kind = TypeKind::ANY;
68+
return type;
69+
}
70+
71+
TypeKind Type::getKind() const noexcept { return kind; }
72+
const std::string& Type::getName() const noexcept { return name; }
73+
const TypePtr& Type::getBase() const noexcept { return base; }
74+
const std::vector<TypePtr>& Type::getArguments() const noexcept { return arguments; }
75+
const TypePtr& Type::getFrom() const noexcept { return from; }
76+
const TypePtr& Type::getTo() const noexcept { return to; }
77+
78+
const std::vector<Constraint>& Type::getConstraints() const noexcept { return constraints; }
79+
80+
const TypePtr& Type::getQualifiedType() const noexcept { return qualifiedType; }
81+
82+
bool Type::equals(const Type& other) const noexcept
83+
{
84+
if (kind != other.kind) return false;
85+
86+
switch (kind)
87+
{
88+
case TypeKind::ANY: return true;
89+
90+
case TypeKind::NAMED:
91+
case TypeKind::VARIABLE: return name == other.name;
92+
93+
case TypeKind::APPLIED:
94+
{
95+
if (!base->equals(*other.base) || arguments.size() != other.arguments.size())
96+
return false;
97+
for (size_t i = 0; i < arguments.size(); ++i)
98+
if (!arguments[i]->equals(*other.arguments[i])) return false;
99+
return true;
100+
}
101+
102+
case TypeKind::FUNCTION: return from->equals(*other.from) && to->equals(*other.to);
103+
104+
case TypeKind::QUALIFIED:
105+
if (constraints.size() != other.constraints.size()) return false;
106+
for (size_t i = 0; i < constraints.size(); ++i)
107+
if (constraints[i] != other.constraints[i]) return false;
108+
return qualifiedType->equals(*other.qualifiedType);
109+
}
110+
return false;
111+
}
112+
113+
bool Type::accepts(const Type& other) const noexcept
114+
{
115+
// Any accepts everything
116+
if (kind == TypeKind::ANY) return true;
117+
if (kind != other.kind) return false;
118+
119+
switch (kind)
120+
{
121+
case TypeKind::NAMED: return name == other.name;
122+
123+
case TypeKind::VARIABLE:
124+
// Type variables accept any type (like generics).
125+
return true;
126+
127+
case TypeKind::APPLIED:
128+
{
129+
if (!base->accepts(*other.base) || arguments.size() != other.arguments.size()) return false;
130+
for (size_t i = 0; i < arguments.size(); ++i)
131+
if (!arguments[i]->accepts(*other.arguments[i])) return false;
132+
return true;
133+
}
134+
135+
case TypeKind::FUNCTION:
136+
// Function types are CONTRAVARIANT in arguments and COVARIANT in return type.
137+
return other.from->accepts(*from) && // contra-variant
138+
to->accepts(*other.to); // co-variant
139+
140+
case TypeKind::QUALIFIED:
141+
// Ignore constraints for now, just check underlying type.
142+
return qualifiedType->accepts(*other.qualifiedType);
143+
144+
case TypeKind::ANY: return true;
145+
}
146+
147+
return false;
148+
}
149+
150+
bool Type::operator==(const Type& other) const noexcept { return equals(other); }
151+
152+
size_t Type::hash() const noexcept
153+
{
154+
size_t hash = std::hash<int>{}(static_cast<int>(kind));
155+
156+
switch (kind)
157+
{
158+
case TypeKind::ANY: return hash;
159+
160+
case TypeKind::NAMED:
161+
case TypeKind::VARIABLE: return hashCombine(hash, std::hash<std::string>{}(name));
162+
163+
case TypeKind::APPLIED:
164+
{
165+
hash = hashCombine(hash, base->hash());
166+
for (const auto& argument : arguments)
167+
hash = hashCombine(hash, argument->hash());
168+
return hash;
169+
}
170+
171+
case TypeKind::FUNCTION:
172+
hash = hashCombine(hash, from->hash());
173+
hash = hashCombine(hash, to->hash());
174+
return hash;
175+
176+
case TypeKind::QUALIFIED:
177+
{
178+
for (const auto& [className, type] : constraints)
179+
{
180+
hash = hashCombine(hash, std::hash<std::string>{}(className));
181+
hash = hashCombine(hash, type->hash());
182+
}
183+
hash = hashCombine(hash, qualifiedType->hash());
184+
return hash;
185+
}
186+
}
187+
188+
return hash;
189+
}
190+
191+
std::ostream& operator<<(std::ostream& os, const Type& type) noexcept
192+
{
193+
switch (type.kind)
194+
{
195+
case TypeKind::ANY:
196+
os << "Any";
197+
break;
198+
199+
case TypeKind::NAMED:
200+
case TypeKind::VARIABLE:
201+
os << type.name;
202+
break;
203+
204+
case TypeKind::APPLIED:
205+
{
206+
os << *type.base << "<";
207+
for (size_t i = 0; i < type.arguments.size(); ++i)
208+
{
209+
if (i) os << ", ";
210+
211+
// Parenthesize if the argument is a function.
212+
if (type.from->getKind() == TypeKind::FUNCTION)
213+
os << "(" << *type.arguments[i] << ")";
214+
else
215+
os << *type.arguments[i];
216+
}
217+
os << ">";
218+
break;
219+
}
220+
221+
case TypeKind::FUNCTION:
222+
{
223+
// Parenthesize left side if it's also a function.
224+
if (type.from->getKind() == TypeKind::FUNCTION)
225+
os << "(" << *type.from << ")";
226+
else
227+
os << *type.from;
228+
229+
os << " -> ";
230+
os << *type.to;
231+
break;
232+
}
233+
234+
case TypeKind::QUALIFIED:
235+
{
236+
for (size_t i = 0; i < type.constraints.size(); ++i)
237+
{
238+
if (i) os << ", ";
239+
os << type.constraints[i].className << " " << *type.constraints[i].type;
240+
}
241+
os << " => " << *type.qualifiedType;
242+
break;
243+
}
244+
}
245+
246+
return os;
247+
}
248+
}

0 commit comments

Comments
 (0)