From 255ff87f3744e99937947cfa01cc45ee0957fdbd Mon Sep 17 00:00:00 2001 From: leftibot Date: Sat, 11 Apr 2026 15:45:22 -0600 Subject: [PATCH] Fix #405: push_back() on script-created vector has no effect if vector_conversion is in effect (#663) * Fix #405: push_back() on script-created vector has no effect with vector_conversion When both vector_type and vector_conversion are registered, the dispatch scoring treats all parameter mismatches equally. This causes the C++ push_back (for the converted type) to be selected over the built-in one when both have the same number of type differences. The converted function operates on a temporary copy of the vector, silently discarding the mutation. The fix deprioritizes overloads that require type conversion on the first parameter (the object/receiver), ensuring functions matching the receiver type exactly are always tried first. Co-Authored-By: Claude Opus 4.6 (1M context) * Address review: remove issue references from comments, add round-trip conversion tests Requested by @lefticus in PR #663 review. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: leftibot Co-authored-by: Claude Opus 4.6 (1M context) --- .../dispatchkit/proxy_functions.hpp | 8 +++ unittests/compiled_tests.cpp | 62 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/include/chaiscript/dispatchkit/proxy_functions.hpp b/include/chaiscript/dispatchkit/proxy_functions.hpp index fe65ee5a..002e473b 100644 --- a/include/chaiscript/dispatchkit/proxy_functions.hpp +++ b/include/chaiscript/dispatchkit/proxy_functions.hpp @@ -798,6 +798,14 @@ namespace chaiscript { ++numdiffs; } } + + // Deprioritize functions whose first parameter (object/receiver) requires + // type conversion: conversions create temporaries, so mutations on the + // converted object are silently lost. + if (plist.size() > 1 && !func->get_param_types()[1].bare_equal(plist[0].get_type_info())) { + numdiffs = plist.size(); + } + ordered_funcs.emplace_back(numdiffs, func.get()); } } diff --git a/unittests/compiled_tests.cpp b/unittests/compiled_tests.cpp index 4c7d96fa..5a3f09d1 100644 --- a/unittests/compiled_tests.cpp +++ b/unittests/compiled_tests.cpp @@ -1437,6 +1437,68 @@ TEST_CASE("Issue #421 - Switch with type_conversion does not compare destroyed o })") == 0); } +// Regression test: push_back() on script-created vector has no effect when +// vector_conversion is in effect. The bug occurs because dispatch selects +// the C++ push_back for the converted type over the built-in one, operating +// on a temporary copy of the vector. +TEST_CASE("push_back on script vector with vector_conversion") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + auto m = std::make_shared(); + chaiscript::bootstrap::standard_library::vector_type>("VectorString", *m); + m->add(chaiscript::vector_conversion>()); + chai.add(m); + + // Register a C++ function that accepts the converted type, so we can + // verify that vector_conversion actually works for passing vectors + chai.add(chaiscript::fun([](const std::vector &v) -> std::string { + std::string result; + for (const auto &s : v) { + if (!result.empty()) { result += ","; } + result += s; + } + return result; + }), "join_strings"); + + // push_back on an empty script-created vector must be visible + CHECK(chai.eval( + "auto x = [];" + "x.push_back(\"Hello\");" + "x.size() == 1" + )); + + // push_back on a vector with initial elements must grow correctly + CHECK(chai.eval( + "auto y = [\"a\", \"b\"];" + "y.push_back(\"c\");" + "y.push_back(\"d\");" + "y.size() == 4" + )); + + // Verify the actual content is preserved after push_back + CHECK(chai.eval( + "auto z = [];" + "z.push_back(\"World\");" + "z[0]" + ) == "World"); + + // Round-trip: build a vector in script, push_back elements, then pass it + // to a C++ function via vector_conversion and verify the contents + CHECK(chai.eval( + "auto v = [\"one\", \"two\"];" + "v.push_back(\"three\");" + "join_strings(v)" + ) == "one,two,three"); + + // Verify conversion works on a freshly created vector too + CHECK(chai.eval( + "auto w = [];" + "w.push_back(\"hello\");" + "w.push_back(\"world\");" + "join_strings(w)" + ) == "hello,world"); +} + // Regression test for issue #607: AST_Node_Trace must be a complete type // when used in eval_error's std::vector call_stack member. // This failed to compile with C++20 on clang/libc++ when AST_Node_Trace