Skip to content

Commit

Permalink
Merge branch 'p2996' into p2996_requires_clause
Browse files Browse the repository at this point in the history
# Conflicts:
#	P2996.md
  • Loading branch information
delimbetov committed Aug 8, 2024
2 parents 22b31b6 + b28f899 commit 4d4ded2
Show file tree
Hide file tree
Showing 31 changed files with 2,196 additions and 2,357 deletions.
40 changes: 13 additions & 27 deletions P2996.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,16 @@ Any implemented language or library extensions that are _not_ candidates for inc
One sharp edge worth mentioning is that our current implementation of P2996 metafunctions is regrettably resistant to proper AST serialization. As such, this compiler cannot, in its current state, be safely used to build precompiled headers or C++20 modules that contain reflection features. This is a difficult problem that is discussed in greater detail below. We are very interested in identifying a design that can elegantly address this issue.

### Incomplete features
A few features and behavior from P2996 are not yet supported (e.g., template splicers). The [issue tracker](https://github.com/bloomberg/clang-p2996/issues) will be kept updated with all known bugs, noncomformant behaviors, and missing features.
Nearly all of P2996 is supported. We make an effort to keep the [issue tracker](https://github.com/bloomberg/clang-p2996/issues) updated with all known bugs, noncomformant behaviors, and missing features.

Our goal has been to provide a working compiler capable of building executables. It is currently a non-goal to support the full family of features and tools offered by `clang` (e.g., AST output, `clang-tidy`, etc). We have therefore taken several shortcuts when it comes to defining things like the formatting of a reflection in a text dump. We are open to pull requests implementing such things, but are otherwise content to wait until P2996 is further along in the WG21 process.
It has been our goal to provide a working compiler capable of building executables, but it has not been a non-goal to support the full family of features and tools offered by `clang` (e.g., AST output, `clang-tidy`, etc). Several shortcuts were therefore taken when it comes to defining things like the formatting of a reflection in a text dump. We are open to pull requests implementing such things, but are otherwise content to wait until P2996 is further along in the WG21 process.

### P2996 test cases
A significant number of tests have been written for this project, covering both the reflection and splicing operators proposed for the core language and the various metafunctions proposed for the Standard Library (found [here](/clang/test/Reflection/) and [here](/libcxx/test/std/experimental/reflection/) respectively). Among these tests are many of the examples from the P2996 paper (e.g., "_Parsing Command-Line Options II_", "_Compile-Time Ticket Counter_", and "_Emulating Typeful Reflection_"). We expect for this body of tests to continue to grow, and hope that it may furthermore assist validation of future implementations of P2996.

At this time, our test cases only verify correct uses of P2996 facilities; that is, we have not yet written tests to validate expected diagnostics for ill-formed programs. As such, this is probably an area having a nontrivial number of bugs and crashes waiting to be discovered.


## License
Following the example of the upstream LLVM project, new code developed for Clang/P2996 is licensed under Apache 2.0 with LLVM extensions ([LICENSE.TXT](/LICENSE.TXT)).

Expand All @@ -80,42 +81,26 @@ The Clang/P2996 project has adopted a [Code of Conduct](https://github.com/bloom
Below are some high level notes about our implementation of P2996, along with some thoughts regarding certain "sharp edges" of the proposal as it relates to `clang`.

## Representing reflections
A reflection is represented as a `ReflectionValue`: effectively a `void *` tagged with an enum value identifying the kind of entity reflected (i.e., very similar to `TemplateArgument`, `APValue`, and several other AST vocabulary types found in clang). A `ReflectionValue` is one of:
P2996 proposes a _value-based_ reflection model, whereby reflections are encoded as opaque scalar values usable during translation. The evaluation of an expression can compute a reflection, so it becomes important for an `APValue` to be capable of representing a reflection. A new `APValue::Reflection` kind is introduced for this purpose.

Most reflections are internally represented as a `void *` tagged with an enum value identifying the kind of entity reflected (similar to e.g., `TemplateArgument`). The pointer identifies one of:

* A type (represented by a `QualType`)
* A constant value (represented by an `ConstantExpr *`)
* A declaration of an evaluatable entity (represented by a `ValueDecl *`)
* A template (represented by a `TemplateName`)
* A namespace (represented by a `Decl *`)
* A base class specifier (represented by a `CXXBaseSpecifier *`)
* A description of a hypothetical data member, for representing the result of a call to `std::meta::data_member_spec` (represented by a `TagDataMemberSpec *`).

This set of possible "kinds" is intended to be extensible.

Since a constant expression can evaluate to a `ReflectionValue` scalar prvalue, `ReflectionValue` becomes a new possible kind of `APValue`.

Since a reflection can only be spliced if it is a constant expression, the passing of reflections as template arguments is an important use case. However, the semantics of equality for reflection values do differ from those of, for example, integers. In part to implement this bespoke definition of equality, `ReflectionValue` also becomes a distinct kind of `TemplateArgument`.

Representing all of these kinds of reflections is straightforward. Less straightforward is the representation of _values_ and _objects_, the natural representations of which are _also_ `APValue`s. The puzzling situation emerges in which an `APValue` must sometimes be able to hold a reflection, but a reflection must sometimes also be able to hold an `APValue`.

### Retrospective
This foundational design has worked well, but a few shortcomings are worth noting.
Our initial implementation of P2996 used a separate `ReflectionValue` class to model a reflection, and made `ReflectionValue` one of the several different "kinds" of structs that might be held by an `APValue`. When a reflection of a value or object was required, we stored the reflected result in a separate dynamically allocated `APValue`.

The use of a `ConstantExpr *` to represent a constant value was chosen as a convenient container for an `APValue`, but a smarter implementation might either use `APValue` directly. This does leave a somewhat odd cycle in which an `APValue` might hold a `ReflectionValue`, which itself might hold an `APValue`, but the use of a `ConstantExpr *`-node as a layer of indirection does nothing to make this any less weird. In fact, it necessitated several otherwise unnecessary changes to allow `ConstantExpr` to hold an `APValue` without an associated `SubExpr`.

It's also a bit unfortunate that the "most specific" type that could be used to represent a namespace is a `Decl *`. This was chosen because a reflection of a namespace could be a `NamespaceDecl`, a `NamespaceAlias`, or a `TranslationUnitDecl` (representing the global namespace). Regrettably, the nearest common ancestor of these is `Decl` itself.
We eventually settled on a more efficient, if more invasive, change: We merged the `ReflectionValue` class into the `APValue` class, and added a `ReflectionDepth` "counter" to the representation of every `APValue`. This reduces the task of "taking a reflection of a value" to little more than incrementing its "reflection depth". The details are slightly more involved, but this model requires no dynamic allocations to represent any reflections.


## Reflect expressions
We generally found parsing reflect expressions (e.g., `^int`) to be an easier problem than parsing splices. An expression of the form `^E`, where `E` is a named entity or a constant expression, is parsed as a `CXXReflectExpr` with the operand represented by a `ReflectionValue` data member.

Several different interpretations of `E` are tentatively parsed:
1. As a template
2. As a namespace
3. As a type
4. As an expression

The first interpretation of these to succeed is selected; if none succeed, the reflect-expression is ill-formed.

We generally found parsing reflect expressions (e.g., `^int`) to be an easier problem than parsing splices. An expression of the form `^E`, where `E` is a named entity or a constant expression, is parsed as a `CXXReflectExpr` with the operand represented by a `APValue` data member.

### Reflection contexts
The operand of a reflect expression is unevaluated, but we found that parsing reflections follows slightly different rules from other unevaluated contexts. Examples here include allowing direct reference to class members without taking their address (e.g., `^S::fn<int>`) and resolving a namespace to a `NamespaceAliasDecl` without dealiasing.
Expand All @@ -128,18 +113,19 @@ While the act of reflecting over an entity always produces an expression, the ac
* A type (i.e., `QualType`)
* A reference to an entity or constant expression (e.g., `ConstantExpr *`)
* A namespace
* A template name

It thus becomes clear that a _splice_, without further context, can only be given a coherent definition at the grammatical level; its semantics are dependent on both the context and the evaluation of the spliced expression.

If the operand of the splice can be evaluated at parse time, then this presents little difficulty. However, if the splice is value-dependent on a template parameter (e.g., the common use case of splicing a reflection received as a template argument), then it may not be possible to determine what the splice "is" until template substitution. P2996 addresses this in a familiar way, requiring that all such splices be preceded with e.g., a leading `typename` to disambiguate the class of entity; an expression is assumed in the absence of such a disambiguator.

To help with such cases, we decompose a splice `[:R:]` into two objects:
1. A `CXXSpliceSpecifierExpr` representing the expression whose evaluation will yield the `ReflectionValue` of the entity being spliced, and
1. A `CXXSpliceSpecifierExpr` representing the expression whose evaluation will yield the `APValue` of the entity being spliced, and
2. An AST object holding a pointer to the `CXXSpliceSpecifierExpr` (e.g., `ReflectionSpliceType` for types, `CXXExprSpliceExpr` for expressions).


### Parsing splices of expressions
Splices of expressions usually appear in the context of a _cast-expression_ (e.g., `[:R:] + 13`), but they can also appear on the right-hand side of a _member access expression_. The existing machinery in `SemaMemberExpr.cpp` assumes that the member being accessed has not yet been resolved and performs name lookup to find a `Decl`. However, in the case of an expression like `a.[:R:]`, we already have the `Decl` from a `ReflectionValue` obtained through evaluation of `R`. Therefore, we introduce overloads of functions like `Sema::ActOnMemberAccessExpr` to support the case where the right-hand of the member access is a splice. A special `CXXDependentMemberSpliceExpr` is introduced to cover cases when the reflection is dependent on a template parameter, in order to "hold" the underlying `CXXSpliceSpecifierExpr` until template substitution.
Splices of expressions usually appear in the context of a _cast-expression_ (e.g., `[:R:] + 13`), but they can also appear on the right-hand side of a _member access expression_. The existing machinery in `SemaMemberExpr.cpp` assumes that the member being accessed has not yet been resolved and performs name lookup to find a `Decl`. However, in the case of an expression like `a.[:R:]`, we already have the `Decl` from a `APValue` obtained through evaluation of `R`. Therefore, we introduce overloads of functions like `Sema::ActOnMemberAccessExpr` to support the case where the right-hand of the member access is a splice. A special `CXXDependentMemberSpliceExpr` is introduced to cover cases when the reflection is dependent on a template parameter, in order to "hold" the underlying `CXXSpliceSpecifierExpr` until template substitution.


### Nested-name-specifiers containing splices
Expand Down
Loading

0 comments on commit 4d4ded2

Please sign in to comment.