Clarify support for imported object-like macros (#7308)

This proposal clarifies some unclear aspects of the interop support for
object-like macros. In particular:
- Carbon supports importing an object-like macro if its definition can
be evaluated as a constant expression, without further restrictions on
that definition.
- When importing the result of that evaluation, C++ lvalues are imported
as references, and rvalues are imported as values.
This commit is contained in:
Geoff Romer
2026-06-05 00:33:51 +00:00
committed by GitHub
parent 2d87bb02d9
commit 64ff3dd3be
2 changed files with 137 additions and 112 deletions
+58 -112
View File
@@ -12,11 +12,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Overview](#overview)
- [Details](#details)
- [Namespace](#namespace)
- [Constant type](#constant-type)
- [Constant value](#constant-value)
- [Supported constant expressions](#supported-constant-expressions)
- [Empty macros](#empty-macros)
- [Implementation](#implementation)
- [Future work](#future-work)
- [Alternatives considered](#alternatives-considered)
@@ -48,119 +43,68 @@ let a: i32 = Cpp.BUFFER_SIZE;
## Details
### Namespace
When importing an object-like macro, the tokens of the macro's replacement list
are evaluated as a C++ constant expression in the global C++ namespace, and the
resulting constant value is imported into the `Cpp` Carbon namespace. Its type
is mapped to a Carbon type following the
[Carbon <-> C++ type mapping rules](/proposals/p005448-carbon-c-interop-primitive-types.md),
and its expression category is determined by the C++ value category: lvalues are
imported as references, and rvalues are imported as values.
Imported C++ macros are evaluated in the global `Cpp` namespace and are
accessible under that prefix (for example, `Cpp.BUFFER_SIZE`).
For example:
### Constant type
The type of the imported constant is deduced by Clang by evaluating the constant
expression, and then mapped to a Carbon type following the
[Carbon <-> C++ type mapping rules](/proposals/p005448-carbon-c-interop-primitive-types.md).
### Constant value
The value of the constant is deduced by evaluating the tokens of the macro's
replacement list as a C++ constant expression.
### Supported constant expressions
The replacement list in the object-like macro expanding to a constant expression
can contain:
- **Operators**: arithmetic: `+`, `-`, `*`, `/`; bitwise: `|`, `&`, `^`, `<<`,
`>>` ; logical: `||`, `&&`; comparison: `<`, `>`, `<=`, `>=`, `==`; casts
etc, with arbitrary number of operands.
For example:
```cpp
#define ADDITION 1+2+3
```
However, note that this macro behaves differently in Carbon when used inside
an expression. [The following C++ program](https://godbolt.org/z/6ndzv764n)
prints `7`, since the macro is expanded before the multiplication operation;
`2 * 1 + 2 + 3` is evaluated as `(2 * 1) + 2 + 3`:
```cpp
#include <iostream>
#define ADDITION 1+2+3
int main() {
std::cout << (2 * ADDITION) << '\n';
}
```
While [the following Carbon program](https://godbolt.org/z/WxvrjYGn6) prints
`12`, since `Cpp.ADDITION` is treated as a constant with value `6`:
```carbon
import Core library "io";
import Cpp inline "#define ADDITION 1+2+3";
fn Run() {
Core.Print(2 * Cpp.ADDITION);
}
```
- **Chained macros**: macros that expand to other macros which evaluate to
constants.
For example:
```cpp
#define VALUE 123
#define MY_VALUE VALUE
```
- **Enum constants and `constexpr` variables**: if a macro's replacement list
refers to a named constant, such as an enum constant or a `constexpr`
variable, it is imported as an alias rather than as a literal value. This
allows Carbon to preserve the specific type of the constant (such as `Color`
in the example below). In the case of `constexpr` variables, importing as an
alias also preserves addressability (that the constant is an lvalue), which
would be lost if only the value were imported.
For example:
**C++**:
```cpp
enum class Color { Red = 1, Green = 2 };
#define GREEN_COLOR Color::Green
constexpr int kValue = 123;
#define VALUE kValue
```
**Carbon**:
```carbon
// Cpp.GREEN_COLOR is an alias to Cpp.Color.Green which has a type Cpp.Color.
let b: Cpp.Color = Cpp.GREEN_COLOR;
// Cpp.VALUE is an alias to kValue.
let a: i32 = Cpp.VALUE;
```
Macros are evaluated in the global namespace (for example `Cpp.VALUE`).
> **Future work**: Evaluating in a child namespace (`Cpp.SomeNamespace.VALUE`)
> may also be possible.
### Empty macros
Macros without a replacement list are not imported into Carbon. They do not have
a Carbon equivalent.
**C++**:
```cpp
#define EMPTY
enum class Color { Red = 1, Green = 2 };
#define GREEN_COLOR Color::Green
constexpr int kValue = 123;
#define VALUE kValue
```
**Carbon**:
```carbon
// Cpp.GREEN_COLOR is equal to Cpp.Color.Green, and has type Cpp.Color.
let b: Cpp.Color = Cpp.GREEN_COLOR;
// Cpp.VALUE is an alias to kValue.
let a: i32 = Cpp.VALUE;
```
Note that this means that an imported macro can behave differently in Carbon
when used inside an expression.
[The following C++ program](https://godbolt.org/z/6ndzv764n) prints `7`, since
the macro is expanded before the multiplication operation; `2 * 1 + 2 + 3` is
evaluated as `(2 * 1) + 2 + 3`:
```cpp
#include <iostream>
#define ADDITION 1+2+3
int main() {
std::cout << (2 * ADDITION) << '\n';
}
```
While [the following Carbon program](https://godbolt.org/z/WxvrjYGn6) prints
`12`, since `Cpp.ADDITION` is treated as a constant with value `6`:
```carbon
import Core library "io";
import Cpp inline "#define ADDITION 1+2+3";
fn Run() {
Core.Print(2 * Cpp.ADDITION);
}
```
> **Future work**: It may be possible to evaluate the macro definition in a
> child namespace, rather than the global C++ namespace.
### Implementation
1. **Name lookup**: When a C++ macro name is encountered in Carbon, it is
@@ -198,3 +142,5 @@ Whether Carbon will support other macro forms is still to be determined:
- Proposal
[#6676: Carbon/C++ Interop: Importing C/C++ object-like macros](https://github.com/carbon-language/carbon-lang/pull/6676)
- Proposal
[#7308: Clarify support for imported object-like macros](https://github.com/carbon-language/carbon-lang/pull/7308)