#+TITLE: C++ Interoperability and Transpilation
* Intro/Context
C++ interop and transpilation are closely related (I cannot define the exact relationship yet). This
document will probably express most examples in terms of translations between Hylo and C++.
** Interop Goals

*** High priority
1. Interop with C++ adds no new language rules to Hylo code.
  - A Hylo module can be constructed from a C++ module.
  - All language rules related to C++ interop are captured in that construction. Hylo code using that
    module is just interoperating with other Hylo code.
2. No public C++ APIs (except preprocessor macros and those using =volatile=) are inaccessible from
   Hylo.  Why the exception for preprocessor macros? Most of them have no first-class representation
   in Hylo.
3. No public Hylo APIs are inaccessible from C++
4. The way C++ APIs are presented to Hylo is tunable with =[[attributes]]=
*** Stretch
With only reasonable effort expended by the programmer:
- “Modern” C++ APIs are represented in Hylo as idiomatic Hylo APIs (e.g. a C++ =range= can easily
  conform to =Collection= and in Hylo appears as a thing with Collection APIs rather than iterators).
- Hylo APIs are represented to C++ as idiomatic C++ APIs (important, e.g., if you want to use a C++
  algorithm on a Hylo =Collection=).

** Transpilation Goals
*** Hylo abstractions are recognizable in C++ code
- A Hylo type compiles down to a similarly named C++ type
- A Hylo function, subscript, or computed property compiles down to a similarly-named C++ function
  or, in the case of bundles, into a (notional) overload set.  I say notional because it may make
  sense to use distinct names for the “overloads”
- A Hylo binding compiles down to a similarly-named C++ variable
- A Hylo module compiles down to a  similarly-named C++ module
*** Debuggable
Mostly just means generating #line directives(?)
* Known Hurdles
** C++ has class inheritance and Hylo has no direct analogue
Trait conformance is close, but traits do not come with any storage.

We've discussed some kind of [[https://val-qs97696.slack.com/archives/C035NEV54LE/p1657591189742969][storage inheritance/mixins for Hylo]], but that is not currently part of
the language. We'd want to represent a base class as a composition of a mixin and an (existential)
trait.

** Move semantics are different
Hylo moves are destructive.  To present a Hylo =Movable= type as C++ movable, we could add conformance to
an =Emptyable= protocol.  See also rvalue references in the [[Function call interop]] section.
** Overload sets
Hylo doesn't have type-based overloading, but C++ does.  We would like to capture C++ overload sets
as some kind of single entity in Hylo.  This may imply the creation of customization points and
conformances.

** class templates vs. checked generics
In general, a class template may have specializations and may even not have a defined primary
template.  If you want to use such a thing with a constrained Hylo generic, there needs to be a
conformance declaration somewhere.

** C++ has no argument labels
Will need to encode labels into names when exporting Hylo to C++.

** No “universal references” in Hylo
We are not sure how much of a problem this is.  Our effects system may be able to model them, but we
are unsure.

** Hylo has extensions
Methods in Hylo may be defined outside of the primary type declaration, in a type or conformance declaration.
Those cannot be retroactively added to a C++ type declaration.

** Operators are different
Hylo supports the definition of custom operators.
Further, even implementation-provided opetators do not necessarily have the same precedence and associativity as in C++.

* Transpiling Hylo Types
Hylo will deinit storage at different places than C++ would.  The implication is that if we want
=var= bindings to appear as regular stack variables in C++, all Hylo types must have a no-op
destructor and provide a different =deinit= method that is called when Hylo does destruction.  This
goal is in tension with the interop goal that a Hylo type should be directly usable in C++, because a
Hylo type declared on the C++ stack wouldn't get destroyed.  We'd need to use a wrapper like this one
on the C++ side
#+BEGIN_SRC
template <class HyloType> struct InCpp {
  HyloType stored;
  ~InCpp() { this->stored.deinit(); }
}
#+END_SRC
An alternative is to generate a C++ destructor for each Hylo =deinit= with the requirement that
running a Hylo =deinit= twice is harmless.  That would provide a better interop experience at the
(small) cost of needlessly nulling out deallocated pointers when non-interop'ing Hylo code invokes
=deinit=.  The latter looks like a good bet.

* Function call interop
These are intended to describe defaults. =[[attributes]]= can be used to override.
** Hylo signatures
*** parameters
- =let T=: Presented to C++ as =T const&=
- =inout=: Presented to C++ as =T&=.
  We could leave it up to the C++ code to ensure `inout` parameters don't overlap any others.
  Is there a way to add dynamic checking?
  We could create a C++ wrapper that moves-in/moves-out, which would ensure that the LoE is upheld
  for some efficient code.
- =sink=: Presented to C++ as =T&&=.  Hylo will destroy the referent so we need to reconstitute it
  after the call or move out of it before the call.  The former is more efficient.  If =T= is a C++
  type, either one could be illegal, so a smart wrapper will detect which one is needed and choose.

  A possible interop scheme:
  #+BEGIN_SRC: Hylo
  // Pure Hylo function
  fun g(_ x: sink T) { ... }
  #+END_SRC

  #+BEGIN_SRC: c++
  // C++ declaration corresponding to the symbol represented by `g` in Hylo.
  extern void g_hylo(hylo_sink<T> x);

  // Wrapper function used by C++ code
  inline void g(T&& x) {
    g_(&x);           // Callee destroys x
    new ((void*)&x)(T)() // Construct a replacement
  }
  #+END_SRC
- =set=: the best translation is unclear.  A safe API passes an empty =optional<T>&=.  However, if
  someone wants to initialize a =T= in place, there may be no =optional= wrapper available.
*** function return
Return values from Hylo are fully owned by C++ code, just as in Hylo.
*** projections
For interop purposes, projections could be translated into functions that accept a C++ lambda
parameter.  That would make interop somewhat ugly but what else can we do?

We may be able to translate stored part projections as corresponding stored parts, i.e. public Hylo
stored properties could be translated into public data members in C++.

Well, we could alternatively present projections as functions returning proxy objects.  The C++
caller would have to obey certain disciplines to use the proxy correctly, and use of the proxies
would also be ugly.

** C++ signatures
*** parameters
- =T const= (non-reference): Presented to Hylo as =let=.  In a non-transpilation world, we can pass a
  pointer to the =T= into the raw C++ function for this parameter, as C++ ABIs appear to implement
  pass-by-value by passing a pointer.
- =T= (non-reference): Presented to Hylo as =sink=.  This is in keeping with the idea that copies
  should be visible in Hylo code.  The Hylo code will move into the parameter.
- =T&= (non-=const=): Presented to Hylo as =inout=.  Hylo's LoE will prevent certain rare valid
  usages if the C++ happens to have been written to be resilient to LoE violations.  tant pis.
- =T const&=: Presented to Hylo as =let=.
- =T const&&=: Presented to Hylo as =let=.
- =T&&=: Presented to Hylo as =sink=.  The thin wrapper generated for consumption by Hylo will destroy
  the argument after the call.
*** function return
- =T const= (non-reference): Presented to Hylo as =T=.
- =T= (non-reference): Presented to Hylo as =T=
- =T&= (non-=const=): Present the function as an =inout= projection
- =T const&=: Present the function as a =let= projection
- =T const&&=: Present the function as a =let= projection
- =T&&=: Presented to Hylo as a =T= return value.  The thin wrapper generated for consumption by Hylo
  will C++-move out of the returned rvalue reference into the result.
* Projections
- =let=:
- =inout=:
- =sink=: Because the result is independent, a sink projection should be seen as producing a new
  value just as with the return value of a function.
* Upholding Hylo's expectations when called from C++
The programmer will need to ensure the independence of parameters to Hylo calls.  The choice of
whether to inject dynamic independence checks can be made by the programmer at whatever granularity
we choose to support.
* Consuming truly non-independent data structures and “referency” types
A complete interop story requires a strategy that lets Hylo code interact with C++ that's built on
=shared_ptr= or that otherwise exposes reference semantics.  In addition to describing the strategy,
this section should contain a survey of important cases and rationales for our treatment of them.
