diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 7bcda4ce2..dcec1158c 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -216,7 +216,8 @@ constexpr descr concat(const descr &descr) { template constexpr auto concat(const descr &d, const Args &...args) -> decltype(std::declval>() + concat(args...)) { - return d + const_name(", ") + concat(args...); + // Ensure that src_loc of existing descr is used. + return d + const_name(", ", src_loc{nullptr, 0}) + concat(args...); } template diff --git a/tests/test_descr_src_loc.cpp b/tests/test_descr_src_loc.cpp new file mode 100644 index 000000000..2ee065071 --- /dev/null +++ b/tests/test_descr_src_loc.cpp @@ -0,0 +1,141 @@ +// Copyright (c) 2022 The Pybind Development Team. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#include "pybind11_tests.h" + +#if !defined(PYBIND11_CPP17) + +// C++17 is required for the static constexpr inline definitions. Adapting +// this unit test to older compilers is more trouble than it is worth. + +TEST_SUBMODULE(descr_src_loc, m) { m.attr("block_descr_offset") = py::none(); } + +#else + +namespace pybind11_tests { +namespace descr_src_loc { + +using py::detail::const_name; +using py::detail::src_loc; + +struct block_descr { + static constexpr unsigned offset = __LINE__; + static constexpr auto c0 = py::detail::descr<0>(src_loc::here()); + static constexpr auto c1 = py::detail::descr<3>("Abc"); + static constexpr auto c2 = py::detail::descr<1>(src_loc::here(), 'D'); + static constexpr auto c3 = py::detail::descr<2>(src_loc::here(), 'E', 'f'); +}; + +struct block_const_name { + static constexpr unsigned offset = __LINE__; + static constexpr auto c0 = const_name("G"); + static constexpr auto c1 = const_name("Hi"); + static constexpr auto c2 = const_name<0>(); + static constexpr auto c3 = const_name<1>(); + static constexpr auto c4 = const_name<23>(); + static constexpr auto c5 = const_name(); + static constexpr auto c6 = const_name("J", "K"); + static constexpr auto c7 = const_name("L", "M"); +}; + +# ifdef PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY +struct block_underscore { + static constexpr unsigned offset = __LINE__; + // Using a macro to avoid copying the block_const_name code garbles the src_loc.line numbers. + static constexpr auto c0 = const_name("G"); + static constexpr auto c1 = const_name("Hi"); + static constexpr auto c2 = const_name<0>(); + static constexpr auto c3 = const_name<1>(); + static constexpr auto c4 = const_name<23>(); + static constexpr auto c5 = const_name(); + static constexpr auto c6 = const_name("J", "K"); + static constexpr auto c7 = const_name("L", "M"); +}; +# endif + +struct block_plus { + static constexpr unsigned offset = __LINE__; + static constexpr auto c0 = const_name("N") + // critical line break + const_name("O"); + static constexpr auto c1 = const_name("P", src_loc(nullptr, 0)) + // critical line break + const_name("Q"); +}; + +struct block_concat { + static constexpr unsigned offset = __LINE__; + static constexpr auto c0 = py::detail::concat(const_name("R")); + static constexpr auto c1 = py::detail::concat(const_name("S"), // critical line break + const_name("T")); + static constexpr auto c2 + = py::detail::concat(const_name("U", src_loc(nullptr, 0)), // critical line break + const_name("V")); +}; + +struct block_type_descr { + static constexpr unsigned offset = __LINE__; + static constexpr auto c0 = py::detail::type_descr(const_name("W")); +}; + +struct block_int_to_str { + static constexpr unsigned offset = __LINE__; + static constexpr auto c0 = py::detail::int_to_str<0>::digits; + static constexpr auto c1 = py::detail::int_to_str<4>::digits; + static constexpr auto c2 = py::detail::int_to_str<56>::digits; +}; + +} // namespace descr_src_loc +} // namespace pybind11_tests + +TEST_SUBMODULE(descr_src_loc, m) { + using namespace pybind11_tests::descr_src_loc; + +# define ATTR_OFFS(B) m.attr(# B "_offset") = B::offset; +# define ATTR_BLKC(B, C) \ + m.attr(#B "_" #C) = py::make_tuple(B::C.text, B::C.sloc.file, B::C.sloc.line); + + ATTR_OFFS(block_descr) + ATTR_BLKC(block_descr, c0) + ATTR_BLKC(block_descr, c1) + ATTR_BLKC(block_descr, c2) + ATTR_BLKC(block_descr, c3) + + ATTR_OFFS(block_const_name) + ATTR_BLKC(block_const_name, c0) + ATTR_BLKC(block_const_name, c1) + ATTR_BLKC(block_const_name, c2) + ATTR_BLKC(block_const_name, c3) + ATTR_BLKC(block_const_name, c4) + ATTR_BLKC(block_const_name, c5) + ATTR_BLKC(block_const_name, c6) + ATTR_BLKC(block_const_name, c7) + + ATTR_OFFS(block_underscore) + ATTR_BLKC(block_underscore, c0) + ATTR_BLKC(block_underscore, c1) + ATTR_BLKC(block_underscore, c2) + ATTR_BLKC(block_underscore, c3) + ATTR_BLKC(block_underscore, c4) + ATTR_BLKC(block_underscore, c5) + ATTR_BLKC(block_underscore, c6) + ATTR_BLKC(block_underscore, c7) + + ATTR_OFFS(block_plus) + ATTR_BLKC(block_plus, c0) + ATTR_BLKC(block_plus, c1) + + ATTR_OFFS(block_concat) + ATTR_BLKC(block_concat, c0) + ATTR_BLKC(block_concat, c1) + ATTR_BLKC(block_concat, c2) + + ATTR_OFFS(block_type_descr) + ATTR_BLKC(block_type_descr, c0) + + ATTR_OFFS(block_int_to_str) + ATTR_BLKC(block_int_to_str, c0) + ATTR_BLKC(block_int_to_str, c1) + ATTR_BLKC(block_int_to_str, c2) +} + +#endif // PYBIND11_CPP17 diff --git a/tests/test_descr_src_loc.py b/tests/test_descr_src_loc.py new file mode 100644 index 000000000..e477cb811 --- /dev/null +++ b/tests/test_descr_src_loc.py @@ -0,0 +1,58 @@ +import pytest + +from pybind11_tests import descr_src_loc as m + +if m.block_descr_offset is None: + block_parametrize = (("all_blocks", None),) +else: + block_parametrize = ( + ("block_descr", (("", 1), ("Abc", 2), ("D", 3), ("Ef", 4))), + ( + "block_const_name", + ( + ("G", 1), + ("Hi", 2), + ("0", 0), + ("1", 0), + ("23", 0), + ("%", 6), + ("J", 7), + ("M", 8), + ), + ), + ( + "block_underscore", + ( + ("G", 2), + ("Hi", 3), + ("0", 0), + ("1", 0), + ("23", 0), + ("%", 7), + ("J", 8), + ("M", 9), + ), + ), + ("block_plus", (("NO", 1), ("PQ", 4))), + ("block_concat", (("R", 1), ("S, T", 2), ("U, V", 6))), + ("block_type_descr", (("{W}", 1),)), + ("block_int_to_str", (("", 0), ("4", 0), ("56", 0))), + ) + + +@pytest.mark.skipif( + m.block_descr_offset is None, reason="C++17 is required for this unit test." +) +@pytest.mark.parametrize("block_name, expected_text_line", block_parametrize) +def test_block(block_name, expected_text_line): + offset = getattr(m, f"{block_name}_offset") + for ix, (expected_text, expected_line) in enumerate(expected_text_line): + text, file, line = getattr(m, f"{block_name}_c{ix}") + assert text == expected_text + if expected_line: + assert file is not None, expected_text_line + assert file.endswith("test_descr_src_loc.cpp") + assert line == offset + expected_line + else: + assert file is None + assert line == 0