mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
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:
parent
362e93fb29
commit
ef7c338452
@ -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)];
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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),
|
||||
|
||||
45
unittests/strong_typedef.chai
Normal file
45
unittests/strong_typedef.chai
Normal 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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user