diff --git a/include/chaiscript/dispatchkit/bootstrap_stl.hpp b/include/chaiscript/dispatchkit/bootstrap_stl.hpp index 088a9122..6ac2939c 100644 --- a/include/chaiscript/dispatchkit/bootstrap_stl.hpp +++ b/include/chaiscript/dispatchkit/bootstrap_stl.hpp @@ -542,10 +542,13 @@ namespace chaiscript::bootstrap::standard_library { /// Add a String_View type (e.g. std::string_view), with conversions to and from /// the matching owning String type (e.g. std::string). /// - /// Only registers operations that don't share names with the owning String type's - /// methods that take arithmetic arguments (e.g. substr/find). Those would create - /// dispatch ambiguity once the implicit String -> StringView conversion is in - /// play, because neither overload would exactly match a (String, int, int) call. + /// Mirrors the search/substring surface of string_type so that scripts can + /// traverse a buffer through StringView without allocating. Sharing method + /// names with the owning String type is safe: dispatch deprioritizes any + /// candidate whose first/receiver argument requires a type conversion (see + /// dispatch() in proxy_functions.hpp), so myString.substr(1, 2) still + /// resolves to String::substr while mySV.substr(1, 2) resolves to + /// StringView::substr (returning a StringView). /// /// \note A String_View is a non-owning reference. Constructing one from a /// temporary owning String yields a dangling reference once that temporary @@ -564,6 +567,24 @@ namespace chaiscript::bootstrap::standard_library { m.add(fun([](const StringView *s) { return s->empty(); }), "empty"); m.add(fun([](const StringView *s) { return s->data(); }), "data"); + // Random-access via [] / at, returning const_reference; StringView is read-only. + m.add(fun([](const StringView &sv, int index) -> typename StringView::const_reference { + return sv.at(static_cast(index)); + }), + "[]"); + + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find(f, pos); }), "find"); + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->rfind(f, pos); }), "rfind"); + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_first_of(f, pos); }), "find_first_of"); + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_last_of(f, pos); }), "find_last_of"); + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_last_not_of(f, pos); }), "find_last_not_of"); + m.add(fun([](const StringView *s, const StringView &f, size_t pos) { return s->find_first_not_of(f, pos); }), "find_first_not_of"); + + m.add(fun([](const StringView *s, const StringView &f) { return s->starts_with(f); }), "starts_with"); + m.add(fun([](const StringView *s, const StringView &f) { return s->ends_with(f); }), "ends_with"); + + m.add(fun([](const StringView *s, size_t pos, size_t len) { return s->substr(pos, len); }), "substr"); + // Built-in implicit conversion from owning String to non-owning StringView. m.add(type_conversion()); diff --git a/include/chaiscript/dispatchkit/proxy_functions.hpp b/include/chaiscript/dispatchkit/proxy_functions.hpp index 07cb55fa..e0e10163 100644 --- a/include/chaiscript/dispatchkit/proxy_functions.hpp +++ b/include/chaiscript/dispatchkit/proxy_functions.hpp @@ -737,11 +737,24 @@ namespace chaiscript { if (matching_func == end) { matching_func = begin; } else { - // handle const members vs non-const member, which is not really ambiguous const auto &mat_fun_param_types = matching_func->second->get_param_types(); const auto &next_fun_param_types = begin->second->get_param_types(); - if (plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) { + // Prefer the candidate whose first parameter (receiver) matches the + // actual receiver type exactly over one that needs a type conversion. + // Conversions on the receiver create temporaries, so any mutation + // would be silently lost; this mirrors the deprioritization in + // dispatch() and resolves cases like myString.substr(int, int) when + // both string::substr and string_view::substr are registered. + const bool plist_empty = plist.empty(); + const bool mat_receiver_exact = !plist_empty && mat_fun_param_types[1].bare_equal(plist[0].get_type_info()); + const bool next_receiver_exact = !plist_empty && next_fun_param_types[1].bare_equal(plist[0].get_type_info()); + + if (mat_receiver_exact && !next_receiver_exact) { + // keep the old one, it has the better receiver match + } else if (!mat_receiver_exact && next_receiver_exact) { + matching_func = begin; // keep the new one, it has the better receiver match + } else if (plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) { matching_func = begin; // keep the new one, the const/non-const matchup is correct } else if (!plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) { // keep the old one, it has a better const/non-const matchup diff --git a/unittests/compiled_tests.cpp b/unittests/compiled_tests.cpp index 4738285d..67cbc890 100644 --- a/unittests/compiled_tests.cpp +++ b/unittests/compiled_tests.cpp @@ -2220,3 +2220,57 @@ TEST_CASE("Issue #458: std::string_view interop with ChaiScript strings") { CHECK(chai.eval(R"(string(string_view("round trip")))") == "round trip"); } + +// Issue #458 follow-up: string_view should expose the same search/substring +// surface as string, and overload dispatch must keep myString.substr(...) on +// String::substr while mySV.substr(...) goes to StringView::substr. +TEST_CASE("Issue #458: string_view supports string-like search and substring") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + // Receiver-type wins: string keeps String::substr, string_view picks StringView::substr. + CHECK(chai.eval(R"( + fun(){ + var s = "hello world"; + var sv = string_view(s); + type_name(sv.substr(6, 5)) == "string_view" && type_name(s.substr(6, 5)) == "string" + }() + )")); + + // Round-trip: substr through string_view yields the expected substring. + CHECK(chai.eval(R"( + fun(){ + var s = "hello world"; + var sv = string_view(s); + to_string(sv.substr(6, 5)) + }() + )") == "world"); + + // starts_with / ends_with on string_view. + CHECK(chai.eval(R"( + fun(){ + var s = "hello world"; + var sv = string_view(s); + sv.starts_with(string_view("hello")) && sv.ends_with(string_view("world")) + }() + )")); + + // find / rfind / find_first_of return the expected positions. + CHECK(chai.eval(R"( + fun(){ + var s = "hello world"; + var sv = string_view(s); + sv.find(string_view("world"), 0) == 6 && + sv.rfind(string_view("o"), sv.size()) == 7 && + sv.find_first_of(string_view("aeiou"), 0) == 1 + }() + )")); + + // [] indexed access reads the character at the given position. + CHECK(chai.eval(R"( + fun(){ + var s = "hello"; + var sv = string_view(s); + sv[1] == 'e' + }() + )")); +}