Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct class names for KeysView, ValuesView and ItemsView in bind_map #4353

Merged
merged 20 commits into from
Dec 9, 2022

Conversation

aimir
Copy link
Contributor

@aimir aimir commented Nov 23, 2022

Description

Wrap KeysView, ValuesView and ItemsView with correct typing information in mappings wrapped via bind_map.

For example, for a mapping from strings to doubles, its .keys() function should have return type KeysView[str].

Currently, things have been wrapped like KeysView[MappingWrapperName], which is both misleading and inconsistent with the parallel python classes (especially for ItemsView, which usually has two type arguments, but here has only one).

This also means that we get differently-typed KeysView objects for different mappings with the same key type, even though they are functionally identical types.

This is solved by making KeysView etc. an abstract class that's a template on the relevant types only, rather than the entire map, so that maps with similar keys will have KeysView with the same type. The actual implementation is provided later, when wrapping the .keys() function of the mapping class, and similarly for values and items.

Closes #3986.

Suggested changelog entry:

* Fixed typing of ``KeysView``, ``ValuesView`` and ``ItemsView`` in ``bind_map``

@aimir aimir marked this pull request as draft November 23, 2022 16:36
@rwgk
Copy link
Collaborator

rwgk commented Nov 23, 2022

I glanced through: this looks great at first sight but isn't easy to review for me (I don't know much about this code).

@bmerry is there a chance that you could help reviewing this code?

I ran this with ASAN, MSAN, UBSAN using the Google toolchain: everything passes.

I spot-checked the CI failures. It looks like a C++11 and C++14 incompatibility, a static variable at class scope that only works with C++17. I think it's this:

pybind11::detail::type_caster<double, void>::name

With C++11 and C++14 this can only be used as a constexpr, but e.g. printing the name doesn't work. I never understood this fully. I didn't look enough to see what triggers the error, I hope this helps you at least a little bit.

Please tag me here if you need a button click to trigger the CI.

@aimir
Copy link
Contributor Author

aimir commented Nov 23, 2022

Thanks! I see I've still got quite a lot of issues, so I moved this to to be a draft for a while.

My last commits should hopefully resolve most of the issues, except for:

  • incorrect type strings for view of python-wrapped c++ classes - this should hopefully be easy to fix by doing something similar to pybind11.h here:
    const std::type_info *t = types[type_index++];
  • The macos-latest failures are a bit weirder, I don't really understand what the issue is - any insights?

@rwgk
Copy link
Collaborator

rwgk commented Nov 25, 2022

The macos-latest failures are a bit weirder, I don't really understand what the issue is - any insights?

It's gone :-)

The only one CI failure is definitely unrelated. (PGI key signing issue)

include/pybind11/stl_bind.h Outdated Show resolved Hide resolved
include/pybind11/stl_bind.h Outdated Show resolved Hide resolved
include/pybind11/stl_bind.h Outdated Show resolved Hide resolved
include/pybind11/stl_bind.h Outdated Show resolved Hide resolved
tests/test_stl_binders.py Show resolved Hide resolved
@aimir aimir marked this pull request as ready for review November 27, 2022 00:12
@aimir
Copy link
Contributor Author

aimir commented Dec 2, 2022

I'm not sure what's with that failing check on pypy-3.7 windows-2022 x64...

But assuming this is just a random failiure, I think this is ready to be merged, isn't it?

@rwgk
Copy link
Collaborator

rwgk commented Dec 2, 2022

But assuming this is just a random failiure

Yes, that's just one of the annoying flakes we have.

Do you know someone with a good background to review this PR?

Copy link
Collaborator

@rwgk rwgk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After you make this change, and pending completion of updating the smart_holder branch (hopefully very soon), I will run this through Google-internal global testing, which includes testing with a large number of 3rd-party (OSS) projects.

include/pybind11/stl_bind.h Outdated Show resolved Hide resolved
@aimir
Copy link
Contributor Author

aimir commented Dec 3, 2022

@rwgk Did you happen to run the Google-internal tests?

I can't really think of anything else to do, and if those pass it would probably be a pretty strong indication that things work as expected.

@rwgk
Copy link
Collaborator

rwgk commented Dec 3, 2022

@rwgk Did you happen to run the Google-internal tests?

I tried last night but it was too early after the smart_holder update (there is a few hours lag for the batched global testing that I was trying to use).

I tried again just now and it accepted the job. It will probably take 6+ hours to finish.

I did run the pybind11 unit tests with all sanitizers interactively and that passed.

@Skylion007
Copy link
Collaborator

Do we have any testing for PyBInd11 without RTTI. I know that was supported before this PR, but I am wondering if the virtual methods will break it.

@aimir
Copy link
Contributor Author

aimir commented Dec 3, 2022

I don't really see how any of the current code would work without RTTI. There's already typeid everywhere, applied to both types and variable names. Examples:

type = src ? &typeid(*src) : nullptr;
return detail::clean_type_id(typeid(T).name());

Perhaps I misunderstand? I just tried to compile with -fno-rtti and saw many failures unrelated to this PR.

Edit: see also #526, I think the discussion there implies that pybind11 without RTTI is not supported ever since d7efa4f

@rwgk
Copy link
Collaborator

rwgk commented Dec 4, 2022

Do we have any testing for PyBInd11 without RTTI. I know that was supported before this PR,

@Skylion007

  • What made you think that any part of pybind11 works without RTTI?
  • What would be the motivation for avoiding RTTI?

I systematically looked at all files that mention "rtti" (case-insensitive):

  • include/pybind11/attr.h
  • include/pybind11/detail/type_caster_base.h
  • docs/changelog.rst
  • docs/advanced/classes.rst

I don't see any mention of pybind11 working without RTTI.

Then I did a small quick experiment, creating this file:

$ cat include_stl_bind_h.cpp
#include "pybind11/stl_bind.h"

Then:

$ clang++ -c -std=c++17 -Iinclude -I/usr/include/python3.10 include_stl_bind_h.cpp

Succeeds without error.

clang++ -c -std=c++17 -fno-rtti -Iinclude -I/usr/include/python3.10 include_stl_bind_h.cpp

See output below.

Thinking about it, it occurred to me maybe pytypes.h could work without RTTI, so I tried that, too:

$ clang++ -c -std=c++17 -Iinclude -I/usr/include/python3.10 include_pytypes_h.cpp

Succeeds without error.

$ clang++ -c -std=c++17 -fno-rtti -Iinclude -I/usr/include/python3.10 include_pytypes_h.cpp
In file included from include_pytypes_h.cpp:1:
include/pybind11/pytypes.h:741:44: error: use of typeid requires -frtti
    return detail::isinstance_generic(obj, typeid(T));
                                           ^
1 error generated.

In file included from include_stl_bind_h.cpp:1:
In file included from include/pybind11/stl_bind.h:13:
In file included from include/pybind11/operators.h:12:
In file included from include/pybind11/pybind11.h:13:
In file included from include/pybind11/detail/class.h:12:
In file included from include/pybind11/detail/../attr.h:14:
In file included from include/pybind11/cast.h:14:
include/pybind11/detail/descr.h:40:19: error: use of typeid requires -frtti
        return {{&typeid(Ts)..., nullptr}};
                  ^
In file included from include_stl_bind_h.cpp:1:
In file included from include/pybind11/stl_bind.h:13:
In file included from include/pybind11/operators.h:12:
In file included from include/pybind11/pybind11.h:13:
In file included from include/pybind11/detail/class.h:12:
In file included from include/pybind11/detail/../attr.h:14:
In file included from include/pybind11/cast.h:15:
In file included from include/pybind11/detail/type_caster_base.h:12:
include/pybind11/detail/../pytypes.h:741:44: error: use of typeid requires -frtti
    return detail::isinstance_generic(obj, typeid(T));
                                           ^
In file included from include_stl_bind_h.cpp:1:
In file included from include/pybind11/stl_bind.h:13:
In file included from include/pybind11/operators.h:12:
In file included from include/pybind11/pybind11.h:13:
In file included from include/pybind11/detail/class.h:12:
In file included from include/pybind11/detail/../attr.h:14:
In file included from include/pybind11/cast.h:15:
In file included from include/pybind11/detail/type_caster_base.h:15:
include/pybind11/detail/internals.h:361:31: error: use of dynamic_cast requires -frtti
        if (const auto *nep = dynamic_cast<const std::nested_exception *>(std::addressof(e))) {
                              ^
In file included from include_stl_bind_h.cpp:1:
In file included from include/pybind11/stl_bind.h:13:
In file included from include/pybind11/operators.h:12:
In file included from include/pybind11/pybind11.h:13:
In file included from include/pybind11/detail/class.h:12:
In file included from include/pybind11/detail/../attr.h:14:
In file included from include/pybind11/cast.h:15:
In file included from include/pybind11/detail/type_caster_base.h:16:
include/pybind11/detail/typeid.h:62:34: error: use of typeid requires -frtti
    return detail::clean_type_id(typeid(T).name());
                                 ^
In file included from include_stl_bind_h.cpp:1:
In file included from include/pybind11/stl_bind.h:13:
In file included from include/pybind11/operators.h:12:
In file included from include/pybind11/pybind11.h:13:
In file included from include/pybind11/detail/class.h:12:
In file included from include/pybind11/detail/../attr.h:14:
In file included from include/pybind11/cast.h:15:
include/pybind11/detail/type_caster_base.h:894:23: error: use of typeid requires -frtti
        type = src ? &typeid(*src) : nullptr;
                      ^
include/pybind11/detail/type_caster_base.h:911:43: error: use of typeid requires -frtti
    type_caster_base() : type_caster_base(typeid(type)) {}
                                          ^
include/pybind11/detail/type_caster_base.h:930:33: error: use of typeid requires -frtti
        const auto &cast_type = typeid(itype);
                                ^
In file included from include_stl_bind_h.cpp:1:
In file included from include/pybind11/stl_bind.h:13:
In file included from include/pybind11/operators.h:12:
In file included from include/pybind11/pybind11.h:13:
In file included from include/pybind11/detail/class.h:12:
In file included from include/pybind11/detail/../attr.h:14:
include/pybind11/cast.h:1650:36: error: use of typeid requires -frtti
    return detail::get_type_handle(typeid(T), true);
                                   ^
In file included from include_stl_bind_h.cpp:1:
In file included from include/pybind11/stl_bind.h:13:
In file included from include/pybind11/operators.h:12:
In file included from include/pybind11/pybind11.h:13:
In file included from include/pybind11/detail/class.h:12:
include/pybind11/detail/../attr.h:551:69: error: use of typeid requires -frtti
    static void init(const base<T> &, type_record *r) { r->add_base(typeid(T), nullptr); }
                                                                    ^
In file included from include_stl_bind_h.cpp:1:
In file included from include/pybind11/stl_bind.h:13:
In file included from include/pybind11/operators.h:12:
include/pybind11/pybind11.h:305:70: error: use of typeid requires -frtti
                = const_cast<void *>(reinterpret_cast<const void *>(&typeid(FunctionType)));
                                                                     ^
include/pybind11/pybind11.h:1530:24: error: use of typeid requires -frtti
        record.type = &typeid(type);
                       ^
include/pybind11/pybind11.h:1551:39: error: use of typeid requires -frtti
            instances[std::type_index(typeid(type_alias))]
                                      ^
include/pybind11/pybind11.h:1552:45: error: use of typeid requires -frtti
                = instances[std::type_index(typeid(type))];
                                            ^
include/pybind11/pybind11.h:1558:22: error: use of typeid requires -frtti
        rec.add_base(typeid(Base), [](void *src) -> void * {
                     ^
include/pybind11/pybind11.h:1854:69: error: use of typeid requires -frtti
        auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type)));
                                                                    ^
include/pybind11/pybind11.h:2362:32: error: use of typeid requires -frtti
    if (!detail::get_type_info(typeid(state), false)) {
                               ^
include/pybind11/pybind11.h:2491:45: error: use of typeid requires -frtti
    if (auto *tinfo = detail::get_type_info(typeid(OutputType))) {
                                            ^
include/pybind11/pybind11.h:2761:41: error: use of typeid requires -frtti
    auto *tinfo = detail::get_type_info(typeid(T));
                                        ^
In file included from include_stl_bind_h.cpp:1:
include/pybind11/stl_bind.h:489:46: error: use of typeid requires -frtti
    auto *vtype_info = detail::get_type_info(typeid(vtype));
                                             ^
fatal error: too many errors emitted, stopping now [-ferror-limit=]
20 errors generated.

@rwgk
Copy link
Collaborator

rwgk commented Dec 4, 2022

The global testing passed.

But I need to find a little bit of time to better understand what this PR is doing.

Copy link
Collaborator

@rwgk rwgk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach with the virtual functions looks really smart to me, and economical.

include/pybind11/stl_bind.h Show resolved Hide resolved
tests/test_stl_binders.py Show resolved Hide resolved
Copy link
Collaborator

@rwgk rwgk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!

I'm working on getting our CI healthy again under PR #4382. I already confirmed that the sledge-hammer method of globally changing ubuntu-latest to ubuntu-20.04 works. I'm still working on more targeted (limited) changes. My thinking: let's get that settled & merged first (hopefully today), then rebase this PR to confirm that we're all green again here, too.

include/pybind11/stl_bind.h Show resolved Hide resolved
@rwgk
Copy link
Collaborator

rwgk commented Dec 5, 2022

let's get that settled & merged first (hopefully today), then rebase this PR to confirm that we're all green again here, too.

All done & green now!

@aimir
Copy link
Contributor Author

aimir commented Dec 6, 2022

Amazing! So is it ready to merge? 😄

@rwgk
Copy link
Collaborator

rwgk commented Dec 6, 2022

@Skylion007 could you please comment or approve?

Copy link
Collaborator

@Skylion007 Skylion007 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

Comment on lines 672 to 675
if (it == map.end()) {
return false;
}
return true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (it == map.end()) {
return false;
}
return true;
return it != map.end();

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied with commit 4525c5d

After that it was staring me in the face that map.find(k) != map.end() is even better.

And then clang-format turned the entire function into a one-liner.

Amir and others added 7 commits December 8, 2022 22:36
… and implement them on-the-fly when wrapping any specific map type
…and const double, for example, as both wrappers will be named ValuesView[float]
…ing the 'override' keyword when overriding functions
…he struct being defined inside a module, which was also needlessly ugly anyway
@rwgk
Copy link
Collaborator

rwgk commented Dec 9, 2022

Similar to #2768, I went ahead with a rebase & applying the review suggestions, to get this into the next smart_holder update.

Waiting for the CI.

@rwgk rwgk merged commit 9db9880 into pybind:master Dec 9, 2022
@github-actions github-actions bot added the needs changelog Possibly needs a changelog entry label Dec 9, 2022
@henryiii henryiii removed the needs changelog Possibly needs a changelog entry label Dec 20, 2022
cielavenir pushed a commit to cielavenir/pybind11 that referenced this pull request Jun 12, 2023
pybind#4353)

* Create templated abstract classes KeysView, ValuesView and ItemsView, and implement them on-the-fly when wrapping any specific map type

* We don't want to wrap different ValuesView objects for double values and const double, for example, as both wrappers will be named ValuesView[float]

* Fallback to C++ names if key or values types are not wrapped

* Added a test for .keys(), .values() and .items() returning the same types for similarly-typed maps

* Fixed wrong use of auto in a declarator list: the two descriptions might have different types

* Fixes for clang-tidy issues: explicit single-argument constructor, using the 'override' keyword when overriding functions

* Bugfix for old versions of clang++, which seem to have trouble with the struct being defined inside a module, which was also needlessly ugly anyway

* Bugfix for clang++, which doesn't have some of the names in runtime uness they are specified to be static

* A fix for clang-tidy performance-inefficient-string-concatenation issues - I personally think this looks uglier, but it's probably worth it for clang-tidy to be happy

* Possible fix for clang++ linking issues - make the descriptions static constexpr to make sure they are known before linking

* Correct names for previously-wrapped types as keys/values of maps

* Bugfix - typo in type info names which caused things to segfault

* Apply suggestions from code review

Co-authored-by: Aaron Gokaslan <[email protected]>

* Use detail::remove_cvref_t instead of doing remove_cv and remove_reference separately

* Avoid names with double underscore, as they are reserved

* Improved testing for KeysView, ValuesView and ItemsView: check type names + stricter asserts

* Moved description logic to helper function in type_caster_base.h

* style: pre-commit fixes

* Fix a clang-tidy issue: do not use 'else' after 'return'

* Apply suggestion by @Skylion007, with additional trivial simplification.

Co-authored-by: Amir <aimir@local>
Co-authored-by: aimir <aimir@localhost>
Co-authored-by: Aaron Gokaslan <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ralf W. Grosse-Kunstleve <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG]: stl_bind generates invalid type signature for Sphinx/Pylance
4 participants