-
Notifications
You must be signed in to change notification settings - Fork 251
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
fix(cpp1): emit concept
in Phase 1 "Cpp2 type declarations"
#578
Conversation
Aaah... |
It's currently more convenient for me to have |
Emitting What I have found is that a library that defines concepts and uses them In contrast, if emitted in Phase 1, |
The same would be true for a concept that needs to use something emitted in Phase 2 "Cpp2 type definitions and function declarations" |
… type function This commit includes "just enough" to make this first meta function work, which can be used like this... ``` Human: @interface type = { speak: (this); } ``` ... where the implementation of `interface` is just about line-for-line from my paper P0707, and now (just barely!) compiles and runs in cppfront (and I did test the `.require` failure cases and it's quite lovely to see them merge with the compiler's own built-in diagnostics): ``` //----------------------------------------------------------------------- // interface: an abstract base class having only pure virtual functions auto interface( meta::type_declaration& t ) -> void { bool has_dtor = false; for (auto m : t.get_members()) { m.require( !m.is_object(), "interfaces may not contain data objects"); if (m.is_function()) { auto mf = m.as_function(); mf.require( !mf.is_copy_or_move(), "interfaces may not copy or move; consider a virtual clone() instead"); mf.require( !mf.has_initializer(), "interface functions must not have a function body; remove the '=' initializer"); mf.require( mf.make_public(), "interface functions must be public"); mf.make_function_virtual(); has_dtor |= mf.is_destructor(); } } if (!has_dtor) { t.require( t.add_member( "operator=: (virtual move this) = { }"), "could not add pure virtual destructor"); } } ``` That's the only example that works so far. To make this example work, so far I've added: - The beginnings of a reflection API. - The beginnings of generation from source code: The above `t.add_member` call now takes the source code fragment string, lexes it, parses it, and adds it to the `meta::type_declaration` object `t`. - The first compile-time meta function that participates in interpreting the meaning of a type definition immediately after the type grammar is initially parsed (we'll never modify a type after it's defined, that would be ODR-bad). I have NOT yet added the following, and won't get to them in the short term (thanks in advance for understanding): - There is not yet a general reflection operator/expression. - There is not yet a general Cpp2 interpreter that runs inside the cppfront compiler and lets users write meta functions like `interface` as external code outside the compiler. For now I've added `interface`, and I plan to add a few more from P0707, as meta functions provided within the compiler. But with this commit, `interface` is legitimately doing everything except being run through an interpreter -- it's using the `meta::` API and exercising it so I can learn how that API should expand and become richer, it's spinning up a new lexer and parser to handle code generation to add a member, it's stitching the generated result into the parse tree as if it had been written by the user explicitly... it's doing everything I envisioned for it in P0707 except for being run through an interpreter. This commit is just one step. That said, it is a pretty big step, and I'm quite pleased to finally have reached this point. --- This example is now part of the updated `pure2-types-inheritance.cpp2` test case: // Before this commit it was this Human: type = { speak: (virtual this); } // Now it's this... and this fixed a subtle bug (can you spot it?) Human: @interface type = { speak: (this); } That's a small change, but it actually also silently fixed a bug that I had written in the original code but hadn't noticed: Before this commit, the `Human` interface did not have a virtual destructor (oops). But now it does, because part of `interface`'s implementation is to generate a virtual destructor if the user didn't write one, and so by letting the user (today, that was me) express their intent, we get to do more on their behalf. I didn't even notice the omission until I saw the diff for the test case's generated `.cpp` had added a `virtual ~Human()`... sweet. Granted, if `Human` were a class I was writing for real use, I would have later discovered that I forgot to write a virtual destructor when I did more testing or tried to do a polymorphic destruction, or maybe a lint/checker tool might have told me. But by declaratively expressing my intent, I got to not only catch the problem earlier, but even prevent it. I think it's a promising data point that my own first attempt to use a metaclass in such a simple way already fixed a latent simple bug in my own code that I hadn't noticed. Cool beans. --- Re syntax: I considered several options to request a meta function `m` be applied to the type being defined, including variations of `is(m)` and `as(m)` and `type(m)` and `$m`. I'm going with `@m` for now, and not because of Python envy... there are two main reasons: - I think "generation of new code is happening here" is such a fundamental and important new concept that it should be very visible, and actually warrants taking a precious new symbol. The idea of "generation" is likely to be more widely used, so being able to have a symbol reserved for that meaning everywhere is useful. The list of unused symbols is quite short (Cpp2 already took `$` for capture), and the `@` swirl maybe even visually connotes generation (like the swirl in a stirred pot -- we're stirring/cooking something up here -- or maybe it's just me). - I want the syntax to not close the door on applying meta functions to declarations other than types. So putting the decoration up front right after `:` is important, because putting it at the end of the type would likely much harder to read for variables and especially functions.
This is generally more useful.
Mentioned at #406 (comment).
Testing summary:
Acknowledgements: