1/*
2    tests/test_tagbased_polymorphic.cpp -- test of polymorphic_type_hook
3
4    Copyright (c) 2018 Hudson River Trading LLC <opensource@hudson-trading.com>
5
6    All rights reserved. Use of this source code is governed by a
7    BSD-style license that can be found in the LICENSE file.
8*/
9
10#include "pybind11_tests.h"
11#include <pybind11/stl.h>
12
13struct Animal
14{
15    enum class Kind {
16        Unknown = 0,
17        Dog = 100, Labrador, Chihuahua, LastDog = 199,
18        Cat = 200, Panther, LastCat = 299
19    };
20    static const std::type_info* type_of_kind(Kind kind);
21    static std::string name_of_kind(Kind kind);
22
23    const Kind kind;
24    const std::string name;
25
26  protected:
27    Animal(const std::string& _name, Kind _kind)
28        : kind(_kind), name(_name)
29    {}
30};
31
32struct Dog : Animal
33{
34    Dog(const std::string& _name, Kind _kind = Kind::Dog) : Animal(_name, _kind) {}
35    std::string bark() const { return name_of_kind(kind) + " " + name + " goes " + sound; }
36    std::string sound = "WOOF!";
37};
38
39struct Labrador : Dog
40{
41    Labrador(const std::string& _name, int _excitement = 9001)
42        : Dog(_name, Kind::Labrador), excitement(_excitement) {}
43    int excitement;
44};
45
46struct Chihuahua : Dog
47{
48    Chihuahua(const std::string& _name) : Dog(_name, Kind::Chihuahua) { sound = "iyiyiyiyiyi"; }
49    std::string bark() const { return Dog::bark() + " and runs in circles"; }
50};
51
52struct Cat : Animal
53{
54    Cat(const std::string& _name, Kind _kind = Kind::Cat) : Animal(_name, _kind) {}
55    std::string purr() const { return "mrowr"; }
56};
57
58struct Panther : Cat
59{
60    Panther(const std::string& _name) : Cat(_name, Kind::Panther) {}
61    std::string purr() const { return "mrrrRRRRRR"; }
62};
63
64std::vector<std::unique_ptr<Animal>> create_zoo()
65{
66    std::vector<std::unique_ptr<Animal>> ret;
67    ret.emplace_back(new Labrador("Fido", 15000));
68
69    // simulate some new type of Dog that the Python bindings
70    // haven't been updated for; it should still be considered
71    // a Dog, not just an Animal.
72    ret.emplace_back(new Dog("Ginger", Dog::Kind(150)));
73
74    ret.emplace_back(new Chihuahua("Hertzl"));
75    ret.emplace_back(new Cat("Tiger", Cat::Kind::Cat));
76    ret.emplace_back(new Panther("Leo"));
77    return ret;
78}
79
80const std::type_info* Animal::type_of_kind(Kind kind)
81{
82    switch (kind) {
83        case Kind::Unknown: break;
84
85        case Kind::Dog: break;
86        case Kind::Labrador: return &typeid(Labrador);
87        case Kind::Chihuahua: return &typeid(Chihuahua);
88        case Kind::LastDog: break;
89
90        case Kind::Cat: break;
91        case Kind::Panther: return &typeid(Panther);
92        case Kind::LastCat: break;
93    }
94
95    if (kind >= Kind::Dog && kind <= Kind::LastDog) return &typeid(Dog);
96    if (kind >= Kind::Cat && kind <= Kind::LastCat) return &typeid(Cat);
97    return nullptr;
98}
99
100std::string Animal::name_of_kind(Kind kind)
101{
102    std::string raw_name = type_of_kind(kind)->name();
103    py::detail::clean_type_id(raw_name);
104    return raw_name;
105}
106
107namespace pybind11 {
108    template <typename itype>
109    struct polymorphic_type_hook<itype, detail::enable_if_t<std::is_base_of<Animal, itype>::value>>
110    {
111        static const void *get(const itype *src, const std::type_info*& type)
112        { type = src ? Animal::type_of_kind(src->kind) : nullptr; return src; }
113    };
114}
115
116TEST_SUBMODULE(tagbased_polymorphic, m) {
117    py::class_<Animal>(m, "Animal")
118        .def_readonly("name", &Animal::name);
119    py::class_<Dog, Animal>(m, "Dog")
120        .def(py::init<std::string>())
121        .def_readwrite("sound", &Dog::sound)
122        .def("bark", &Dog::bark);
123    py::class_<Labrador, Dog>(m, "Labrador")
124        .def(py::init<std::string, int>(), "name"_a, "excitement"_a = 9001)
125        .def_readwrite("excitement", &Labrador::excitement);
126    py::class_<Chihuahua, Dog>(m, "Chihuahua")
127        .def(py::init<std::string>())
128        .def("bark", &Chihuahua::bark);
129    py::class_<Cat, Animal>(m, "Cat")
130        .def(py::init<std::string>())
131        .def("purr", &Cat::purr);
132    py::class_<Panther, Cat>(m, "Panther")
133        .def(py::init<std::string>())
134        .def("purr", &Panther::purr);
135    m.def("create_zoo", &create_zoo);
136};
137