Fix #677: Add strong typedefs via 'using Type = BaseType' syntax

Strong typedefs create distinct types backed by Dynamic_Object, so
'using Meters = int' makes Meters a type that is not interchangeable
with int or other typedefs of int. The constructor Meters(val) wraps
the base value, and function dispatch enforces the type distinction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
leftibot 2026-04-13 18:08:10 -06:00
parent 362e93fb29
commit ef7c338452
4 changed files with 126 additions and 4 deletions

View File

@ -33,7 +33,7 @@ namespace chaiscript {
template<typename T>
static bool is_reserved_word(const T &s) noexcept {
const static std::unordered_set<std::uint32_t>
words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__"), utility::hash("const")};
words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__"), utility::hash("const"), utility::hash("using")};
return words.count(utility::hash(s)) == 1;
}
@ -106,7 +106,8 @@ namespace chaiscript {
Constant,
Compiled,
Const_Var_Decl,
Const_Assign_Decl
Const_Assign_Decl,
Using
};
enum class Operator_Precedence {
@ -127,7 +128,7 @@ namespace chaiscript {
namespace {
/// Helper lookup to get the name of each node type
constexpr const char *ast_node_type_to_string(AST_Node_Type ast_node_type) noexcept {
constexpr const char *const ast_node_types[] = {"Id", "Fun_Call", "Unused_Return_Fun_Call", "Arg_List", "Equation", "Var_Decl", "Assign_Decl", "Array_Call", "Dot_Access", "Lambda", "Block", "Scopeless_Block", "Def", "While", "If", "For", "Ranged_For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Continue", "Map_Pair", "Value_Range", "Inline_Range", "Try", "Catch", "Finally", "Method", "Attr_Decl", "Logical_And", "Logical_Or", "Reference", "Switch", "Case", "Default", "Noop", "Class", "Binary", "Arg", "Global_Decl", "Constant", "Compiled", "Const_Var_Decl", "Const_Assign_Decl"};
constexpr const char *const ast_node_types[] = {"Id", "Fun_Call", "Unused_Return_Fun_Call", "Arg_List", "Equation", "Var_Decl", "Assign_Decl", "Array_Call", "Dot_Access", "Lambda", "Block", "Scopeless_Block", "Def", "While", "If", "For", "Ranged_For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Continue", "Map_Pair", "Value_Range", "Inline_Range", "Try", "Catch", "Finally", "Method", "Attr_Decl", "Logical_And", "Logical_Or", "Reference", "Switch", "Case", "Default", "Noop", "Class", "Binary", "Arg", "Global_Decl", "Constant", "Compiled", "Const_Var_Decl", "Const_Assign_Decl", "Using"};
return ast_node_types[static_cast<int>(ast_node_type)];
}

View File

@ -887,6 +887,45 @@ namespace chaiscript {
}
};
template<typename T>
struct Using_AST_Node final : AST_Node_Impl<T> {
Using_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
: AST_Node_Impl<T>(std::move(t_ast_node_text), AST_Node_Type::Using, std::move(t_loc), std::move(t_children)) {
assert(this->children.size() == 2);
}
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
const auto &new_type_name = this->children[0]->text;
const auto &base_type_name = this->children[1]->text;
const auto base_type = t_ss->get_type(base_type_name, true);
t_ss->add(user_type<dispatch::Dynamic_Object>(), new_type_name);
dispatch::Param_Types param_types(std::vector<std::pair<std::string, Type_Info>>{
{new_type_name, Type_Info()},
{base_type_name, base_type}});
auto ctor_body = dispatch::make_dynamic_proxy_function(
[](const Function_Params &t_params) -> Boxed_Value {
auto *obj = static_cast<dispatch::Dynamic_Object *>(t_params[0].get_ptr());
obj->get_attr("__value") = t_params[1];
return void_var();
},
2,
std::shared_ptr<AST_Node>(),
param_types);
try {
t_ss->add(std::make_shared<dispatch::detail::Dynamic_Object_Constructor>(new_type_name, ctor_body), new_type_name);
} catch (const exception::name_conflict_error &e) {
throw exception::eval_error("Type alias redefined '" + e.name() + "'");
}
return void_var();
}
};
template<typename T>
struct If_AST_Node final : AST_Node_Impl<T> {
If_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)

View File

@ -2031,6 +2031,43 @@ namespace chaiscript {
return retval;
}
bool Using(const bool t_class_allowed) {
Depth_Counter dc{this};
const auto prev_stack_top = m_match_stack.size();
if (Keyword("using")) {
if (!t_class_allowed) {
throw exception::eval_error("Type alias definitions only allowed at top scope",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if (!Id(true)) {
throw exception::eval_error("Missing type name in 'using' declaration",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if (!Symbol("=", true)) {
throw exception::eval_error("Missing '=' in 'using' declaration",
File_Position(m_position.line, m_position.col),
*m_filename);
}
if (!Id(true)) {
throw exception::eval_error("Missing base type name in 'using' declaration",
File_Position(m_position.line, m_position.col),
*m_filename);
}
build_match<eval::Using_AST_Node<Tracer>>(prev_stack_top);
return true;
}
return false;
}
/// Reads a while block from input
bool While() {
Depth_Counter dc{this};
@ -2776,7 +2813,7 @@ namespace chaiscript {
while (has_more) {
const auto start = m_position;
if (Def() || Try() || If() || While() || Class(t_class_allowed) || For() || Switch()) {
if (Def() || Try() || If() || While() || Class(t_class_allowed) || Using(t_class_allowed) || For() || Switch()) {
if (!saw_eol) {
throw exception::eval_error("Two function definitions missing line separator",
File_Position(start.line, start.col),

View File

@ -0,0 +1,45 @@
// Strong typedef: using Type = int creates a distinct type
using Meters = int
def measure(Meters m) {
return m
}
// Constructing a strong typedef value should work
var m = Meters(42)
// Calling with the typedef'd value should succeed
measure(m)
// Calling with a plain int should fail (strong typedef)
try {
measure(42)
assert_equal(true, false)
} catch(e) {
// Expected: type mismatch because int is not Meters
}
// Multiple strong typedefs from the same base type should be distinct
using Seconds = int
def wait(Seconds s) {
return s
}
var s = Seconds(10)
wait(s)
// Meters and Seconds should not be interchangeable
try {
wait(m)
assert_equal(true, false)
} catch(e) {
// Expected: Meters is not Seconds
}
try {
measure(s)
assert_equal(true, false)
} catch(e) {
// Expected: Seconds is not Meters
}