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

Commit f4650af

Browse files
committed
feat: finish support for runtime type checking
1 parent a1247ef commit f4650af

4 files changed

Lines changed: 104 additions & 25 deletions

File tree

‎include/lbd/runtime/interpreter.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ namespace runtime {
126126

127127
Value evalExpression(const frontend::Expression &expression, std::shared_ptr<Environment> environment);
128128

129+
std::vector<Value> forceArguments(const std::vector<std::shared_ptr<Thunk> > &arguments);
130+
129131
Value applyFunctionApplication(Value functionName, const std::vector<std::shared_ptr<Thunk> > &arguments,
130132
const std::shared_ptr<Environment> &callSiteEnvironment,
131133
const std::optional<frontend::Location> &callLocation = std::nullopt);

‎include/lbd/runtime/type.h‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ namespace runtime::type {
2323
Any
2424
};
2525

26-
TypeTag typeTagFromValue(const Value &value);
26+
[[nodiscard]] TypeTag typeTagFromValue(const Value &value);
2727

28-
std::string typeTagToString(TypeTag tag);
28+
[[nodiscard]] std::string typeTagToString(TypeTag tag);
2929

3030
// TODO: Implement move-constructor and possibly move-assignment-operator.
31+
// TODO: Overload operator<< for printing.
3132
struct Type {
3233
virtual ~Type() = default;
3334

@@ -57,6 +58,7 @@ namespace runtime::type {
5758
TypeTag tag;
5859
};
5960

61+
// TODO: List should not have element-type, as we can add any type to it.
6062
struct ListType final : Type {
6163
explicit ListType(const std::shared_ptr<Type> &elementType);
6264

@@ -81,8 +83,14 @@ namespace runtime::type {
8183

8284
~FunctionType() override = default;
8385

86+
[[nodiscard]] const Type &getReturnType() const;
87+
8488
[[nodiscard]] bool matches(const Value &value) const override;
8589

90+
[[nodiscard]] bool matchesArgumentTypes(const std::vector<Value> &values) const;
91+
92+
[[nodiscard]] bool matchesReturnType(const Value &value) const;
93+
8694
[[nodiscard]] bool equals(const Type &otherType) const override;
8795

8896
[[nodiscard]] std::string toString() const override;
@@ -97,4 +105,6 @@ namespace runtime::type {
97105
bool isVariadic;
98106
std::shared_ptr<Type> variadicType;
99107
};
108+
109+
[[nodiscard]] std::shared_ptr<Type> typeFromValue(const Value &value);
100110
}

‎src/runtime/interpreter.cpp‎

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,15 @@ namespace runtime {
241241
return thunk;
242242
}
243243

244+
std::vector<Value> forceArguments(const std::vector<std::shared_ptr<Thunk> > &arguments) {
245+
std::vector<Value> values;
246+
values.reserve(arguments.size());
247+
for (const auto &argument: arguments) {
248+
values.push_back(argument->force());
249+
}
250+
return values;
251+
}
252+
244253
Value applyFunctionApplication(Value functionName, const std::vector<std::shared_ptr<Thunk> > &arguments,
245254
const std::shared_ptr<Environment> &callSiteEnvironment,
246255
const std::optional<frontend::Location> &callLocation) {
@@ -295,19 +304,38 @@ namespace runtime {
295304
if (const auto &nativeFunction = *std::get<std::shared_ptr<NativeFunction> >(currentFunction);
296305
nativeFunction.getArity() != -1) {
297306
const int arity = nativeFunction.getArity();
298-
307+
const type::FunctionType &signature = *nativeFunction.getSignature();
299308
if (workArguments.size() - index < arity) {
300-
optionsValue.logger.error(callLocation, "runtime error: native function ",
309+
optionsValue.logger.error(callLocation, "runtime error: native-function ",
301310
nativeFunction.getName(), " expects ",
302-
arity, " argument(s), found ", workArguments.size() - index);
311+
arity, " argument(s), found ", workArguments.size() - index, "\n",
312+
nativeFunction.getName(), " signature: ", signature.toString());
303313
}
304314
std::vector<std::shared_ptr<Thunk> > slice;
305315
slice.reserve(arity);
306316
for (size_t i = 0; i < arity; ++i) {
307317
slice.push_back(workArguments[index + i]);
308318
}
319+
// // ReSharper disable once CppTooWideScopeInitStatement
320+
// std::vector<Value> releasedValues = forceArguments(slice);
321+
// // Type-Check the function-arguments.
322+
// if (!signature.matchesArgumentTypes(releasedValues)) {
323+
// optionsValue.logger.error(callLocation,
324+
// "runtime error: wrong arguments provided to native-function ",
325+
// nativeFunction.getName(),
326+
// "\n", nativeFunction.getName(), " signature: ", signature.toString());
327+
// // TODO: Also print exactly which argument caused the error.
328+
// // Print the signature got, with the incorrect argument color differently.
329+
// }
309330
auto [resultantValue_, resultOptions] = nativeFunction.getImplementation()(
310331
slice, callSiteEnvironment);
332+
// if (!signature.matchesReturnType(resultantValue_)) {
333+
// optionsValue.logger.error(callLocation,
334+
// "internal error: expected native-function to return ",
335+
// signature.getReturnType().toString(), ", but got ",
336+
// type::typeFromValue(resultantValue_)->toString());
337+
// // TODO: possibly not create an additional type-object just for printing.
338+
// }
311339
resultantValue = resultantValue_;
312340
globalResultOptions.interpolate(resultOptions);
313341
index += arity;

‎src/runtime/type.cpp‎

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -107,36 +107,55 @@ namespace runtime::type {
107107
}
108108
}
109109

110+
const Type &FunctionType::getReturnType() const {
111+
return *returnType;
112+
}
113+
114+
110115
bool FunctionType::matches(const Value &value) const {
111116
// Only type-check function-applications, cannot type-check
112117
// closures/lambda-expressions due to lazy-evaluation.
113-
// TODO: Complete
114-
return false;
118+
if (!std::holds_alternative<std::shared_ptr<NativeFunction> >(value)) {
119+
return false;
120+
}
121+
return equals(*std::get<std::shared_ptr<NativeFunction> >(value)->getSignature());
115122
}
116123

117-
bool FunctionType::equals(const Type &otherType) const {
118-
if (const auto *otherFunctionType = dynamic_cast<const FunctionType *>(&otherType)) {
119-
// TODO: Check for variadic.
120-
if (argumentTypes.size() != otherFunctionType->argumentTypes.size()) {
121-
return false;
122-
}
123-
for (size_t i = 0; i < argumentTypes.size(); i++) {
124-
if (!(argumentTypes[i] && otherFunctionType->argumentTypes[i])) {
125-
return false;
126-
}
127-
if (!argumentTypes[i]->equals(*otherFunctionType->argumentTypes[i])) {
128-
return false;
129-
}
130-
}
131-
if (!(returnType && otherFunctionType->returnType)) {
124+
bool FunctionType::matchesArgumentTypes(const std::vector<Value> &values) const {
125+
// Perform size-check on non-variadic-functions.
126+
if (!isVariadic && argumentTypes.size() != values.size()) {
127+
return false;
128+
}
129+
for (size_t index = 0; index < argumentTypes.size(); index++) {
130+
if (!argumentTypes[index] || !argumentTypes[index]->matches(values[index])) {
132131
return false;
133132
}
134-
if (!returnType->equals(*otherFunctionType->returnType)) {
133+
}
134+
return true;
135+
}
136+
137+
bool FunctionType::matchesReturnType(const Value &value) const {
138+
return returnType && returnType->matches(value);
139+
}
140+
141+
bool FunctionType::equals(const Type &otherType) const {
142+
const auto *otherFunctionType = dynamic_cast<const FunctionType *>(&otherType);
143+
if (!otherFunctionType) {
144+
return false;
145+
}
146+
// Perform size-check on non-variadic-functions.
147+
if (!isVariadic && argumentTypes.size() != otherFunctionType->argumentTypes.size()) {
148+
return false;
149+
}
150+
for (size_t index = 0; index < argumentTypes.size(); index++) {
151+
const auto &leftType = isVariadic ? variadicType : argumentTypes[index];
152+
// ReSharper disable once CppTooWideScopeInitStatement
153+
const auto &rightType = otherFunctionType->argumentTypes[index];
154+
if (!leftType || !rightType || !leftType->equals(*rightType)) {
135155
return false;
136156
}
137-
return true;
138157
}
139-
return false;
158+
return returnType && otherFunctionType->returnType && returnType->equals(*otherFunctionType->returnType);
140159
}
141160

142161
std::string FunctionType::toString() const {
@@ -166,4 +185,24 @@ namespace runtime::type {
166185
}
167186
return static_cast<int>(argumentTypes.size());
168187
}
188+
189+
std::shared_ptr<Type> typeFromValue(const Value &value) {
190+
return std::visit([&]<typename T0>(T0 &&) -> std::shared_ptr<Type> {
191+
using T = std::decay_t<T0>;
192+
if constexpr (std::is_same_v<T, double>) {
193+
return std::make_shared<SimpleType>(TypeTag::Float);
194+
} else if constexpr (std::is_same_v<T, std::string>) {
195+
return std::make_shared<SimpleType>(TypeTag::String);
196+
} else if constexpr (std::is_same_v<T, Closure>) {
197+
return std::make_shared<SimpleType>(TypeTag::Closure);
198+
} else if constexpr (std::is_same_v<T, std::shared_ptr<NativeFunction> >) {
199+
const auto &nativeFunction = *std::get<std::shared_ptr<NativeFunction> >(value);
200+
return std::make_shared<FunctionType>(*nativeFunction.getSignature());
201+
} else if constexpr (std::is_same_v<T, std::shared_ptr<NativeFunction> >) {
202+
return std::make_shared<ListType>(std::make_shared<SimpleType>(TypeTag::Any));
203+
} else {
204+
return std::make_shared<SimpleType>(TypeTag::Any);
205+
}
206+
}, value);
207+
}
169208
}

0 commit comments

Comments
 (0)