From 64ff3dd3beea23284056fbd4bdfaae224d73dc48 Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Thu, 4 Jun 2026 17:33:51 -0700 Subject: [PATCH] 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. --- docs/design/interoperability/macros.md | 170 ++++++------------ ...support-for-imported-object-like-macros.md | 79 ++++++++ 2 files changed, 137 insertions(+), 112 deletions(-) create mode 100644 proposals/p007308-clarify-support-for-imported-object-like-macros.md diff --git a/docs/design/interoperability/macros.md b/docs/design/interoperability/macros.md index 024e64fbc19e..4cae1359a1e0 100644 --- a/docs/design/interoperability/macros.md +++ b/docs/design/interoperability/macros.md @@ -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 - - #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 + +#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) diff --git a/proposals/p007308-clarify-support-for-imported-object-like-macros.md b/proposals/p007308-clarify-support-for-imported-object-like-macros.md new file mode 100644 index 000000000000..69c804067c88 --- /dev/null +++ b/proposals/p007308-clarify-support-for-imported-object-like-macros.md @@ -0,0 +1,79 @@ +# Clarify support for imported object-like macros + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/7308) + + + +## Table of contents + +- [Abstract](#abstract) +- [Problem](#problem) +- [Proposal](#proposal) +- [Details](#details) +- [Rationale](#rationale) +- [Alternatives considered](#alternatives-considered) + + + +## 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.