For C++ interop, we want users to have an absolutely amazing expirence no matter the API they’re importing. In order to achieve this, we’re going to have to do a lot of design work to import base classes correctly. This means native casting, calling base class methods, handling virtual and overridden members, and so much more.
I think this will be one of those common cases where the design work is actually the most difficult part. C++ interop in general has a problem: we don’t know, specifically, how it’s going to be used. And this makes the design work even more difficult. But, luckily, we are constantly able to import more and more APIs, and use them (even if it might not be pretty). Soon we will hopefully add a few source compatibility projects, and maybe even start migrating libSwift to use C++ interop. These projects will allow us to iterate on our designs. They will show us what users want and make the best models and designs clear.
Given this, I propose that we add “support” for base classes in two phases. First, we add support for the most basic functionality: the ability to call base class methods, use base class type aliases and sub-types, and read from/write to base class stored properties. This will give users the ability to use most of the functionality they rely on and that will inform us of how best we can properly support base classes in the future. It will show us the most common use cases, and the biggest problems users have. With this information we can re-design how we import base classes and make the experience truly fantastic.
I’ve already started prototyping an implementation of this proposal and I hope it won’t take more than a few days to get a full patch up on GitHub. With lazy member loading, the basic functionality is actually quite simple. Given an API
struct Base { int fn() const; };
struct Derived : Base {};
Today (with lazy member loading) we import Derived
using these steps:
- Create a Swift struct named
Derived
. - Issue a member lookup request for all the named members in
Derived
. - There are no members so we get
struct Derived {}
.
I propose we modify this to do the following:
- Create a Swift struct named
Derived
. - Issue a member lookup request for all the named members in
Derived
. - Issue a member lookup request for all the named members in all of
Derived
’s base classes. - For each method found in a base class:
a. "Clone" it into the derived class.
b. Synthesize a body that castsself
to the base class type, then calls that method on the casted self. - So we get
struct Derived { func fn() -> CInt { static_cast<Base>(self).fn() } }
.
You can imagine something similar for VarDecl
s: we can just synthesize accessors that cast self
and lookup the field on the casted self base class.
For now, I think we shouldn’t touch casting, virtual base classes, etc. This basic behavior will allow us to start using even more APIs and will therefore allow us to get lots of information about not only how base classes are going to be used, but many C++ libraries. This will help us iterate and improve how we import C++ APIs in general.
Here’s a related post discussion how we might import base classes more generally.