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)
@@ -0,0 +1,79 @@
# Clarify support for imported object-like macros
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
[Pull request](https://github.com/carbon-language/carbon-lang/pull/7308)
<!-- toc -->
## Table of contents
- [Abstract](#abstract)
- [Problem](#problem)
- [Proposal](#proposal)
- [Details](#details)
- [Rationale](#rationale)
- [Alternatives considered](#alternatives-considered)
<!-- tocstop -->
## Abstract
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.
## Problem
Proposal [p006676](p006676-carbon-c-interop-importing-c-c-object-like-macros.md)
introduced support for importing object-like macros by evaluating their
definitions as C++ constant expressions. By the time the proposal was adopted,
this feature was intended to support most if not all macros that permit such
evaluation, but due to its drafting history, the proposal text seems to limit
the macro definition to a narrow allow-list of C++ operations (which excludes
function calls, among other things).
In addition, p006676 states that if the constant value refers to a named
constant, it is imported as an alias rather than a value, but it's not entirely
clear what counts as a named constant, or what the alias/value distinction means
(particularly when applied to C++ rvalues like enumerators). Here again, the
proposal text doesn't fully reflect the design intent, which was that the
expression category of the imported constant reflects the C++ value category of
the constant expression.
## Proposal
This proposal rephrases the design of this feature to avoid the impression of an
allow-list. It also replaces the discussion of named constants with a statement
that lvalues are imported as references, and rvalues are imported as values.
## Details
See the changes in `docs/design/interoperability/macros.md` in the PR for this
proposal.
## Rationale
This proposal advances the
[Community and culture](/docs/project/goals.md#community-and-culture) goal, by
ensuring that the design choices we made for this feature are clearly
documented, and that the actual design (as adopted by the evolution process)
matches the design as understood by the leads and the team.
## Alternatives considered
We considered treating this as an ordinary PR editing the design documentation,
without going through the evolution process, under the rationale that these
changes are merely clarifications that reflect what we intended p006676 to mean
when we adopted it. However, that rationale seems questionable in this case,
given the extent of the changes, and the lack of evidence for that intent in the
adopted proposal text.