mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-04 22:02:52 +01:00
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:
@@ -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.
|
||||
Reference in New Issue
Block a user