Compare commits

...
Author SHA1 Message Date
Chandler Carruth 08a11ff3ef Avoid passing -unwindlib on macOS where it isn't needed (#7825)
Removes some warnings from our links on macs when the flag is ignored.
2026-09-24 13:51:02 +00:00
Christopher Di Bella f7cd39428e Add Destroy.SubobjectDestroy as a temporary replacement for Destroy.Op (#7773)
This change partially implements [PR #7362], which revises how objects
are destroyed. It is a partial implementation for two reasons:

1. This change moves `Destroy.Op`'s current behaviour into
`Destroy.SubobjectDestroy`, but it doesn't add support for objects with
non-trivial destruction.
2. `Destroy.SubobjectDestroy` is a workaround for `require impls
SubobjectDestroy`. We aren't able to use the latter until the dependents
add their requirements' implementations to their own witness tables.

[PR #7362]: https://github.com/carbon-language/carbon-lang/pulls/7362
2026-09-24 00:06:28 +00:00
Richard Smith 03939a9223 Add language server support for SemIR in testdata. (#7803)
Add hover cards and jump to declaration / definition / reference for the
formatted SemIR that appears in check tests. This is done by adding a
heuristic "parser" for SemIR to the language server. The
cross-references are strictly best-effort, since this is just a tool for
Carbon developers, not a user-facing facility.

A couple of other changes made along the way:

* file_test tests with an AUTOUPDATE-SPLIT no longer look for CHECK:
lines outside that split. This was motivated by the tests for this new
facility including CHECK: lines as part of the test input.
* An agent skill for working on the language server, tracking some
things that cost Claude time when working on this.

Assisted-by: Claude via Antigravity
2026-09-23 23:44:32 +00:00
Chandler Carruth 652e0ce7d0 Fix the jj_push.sh script to work on macOS (#7820)
It had a few subtle GNU extensions in it that didn't work on macOS.
There are simple portable alternatives so it was easy to adapt.

Assisted-by: Antigravity with Gemini
2026-09-23 22:47:02 +00:00
Dana Jansens c832d7c11c Skill file for reviewing filetest output changes (#7815)
When an LLM tool is used to prototype a change, one of the major tasks
it must undertake is validating filetest output changes. This skill
helps to ground that validation in some best practices and explain what
sorts of changes should or should not be expected, and how to judge
STDOUT vs STDERR changes.

Assisted-by: Opus 5
2026-09-23 19:18:59 +00:00
Dana Jansens 15e3eeaca9 Only look in facet types for name scopes when they have constraints (#7819)
This falls back to diagnosing that values of type `type` can not be used
for name lookup more consistently.
2026-09-23 17:50:34 +00:00
Richard Smith efbe1d2489 Add default fns and final fns to the eval block for a generic impl (#7817)
When a generic impl uses a default or final fn, it picks the specific
function value out of the interface to put in the witness table.
However, because this is done by modifying an existing instruction
block, the generics machinery has no hook to convert the function
constant into an attached constant, and because it was found in a
specific for a different generic, the constant inst will be unattached.
Fix this by manually mapping to an attached constant inst in the current
generic when building the witness table.
2026-09-23 01:19:28 +00:00
Lucile Rose Nihlen 53b7cfbaba Add basic caller-side support for default values in check (#7800)
Modifies the arity check to include a lower-bound for arguments.
Adds logic to pattern matching to supply default arguments for
missing parameters.
2026-09-22 22:07:35 +00:00
Dana Jansens 795729bb4a Fix git path globs in summarize testdata changes SKILL (#7818)
When there's a wildcard in a path, git treats the path as matching
exactly, unless the path also ends in a wildcard. So
`toolchain/*/testdata` only matches the testdata directory names,
whereas `toolchain/*/testdata/*` matches all the files under them.
2026-09-22 17:23:11 +00:00
Dana Jansens fcae9610bd Make SemIR::TypeType be an empty FacetType instruction (#7813)
The type `type` is now a `FacetType` inst with no constraints. This
brings the model implemented in the toolchain into better alignment with
the language design. The `SemIR::TypeType` struct remains as a scope for
holding the `TypeInstId`, `ConstantId`, and `TypeId` constants, but is
not an `InstKind` anymore.

The `TypeType` inst looks a lot like singletons, but there are many
`FacetType` insts so it doesn't quite fit that model. So we put it
alongside singletons with a fixed inst id but refer to it as a more
general "builtin" inst that is not a singleton.
`Namespace::PackageInstId` is similar, and we group it with `TypeType`
conceptually as another builtin instruction with a fixed id.

No conversion is needed anymore to use a `type` as a facet, since types
also have a `FacetType` type. This simplifies and removes a number of
helpers and branches throughout the code.

The `TypeType` inst is now part of the constant store, so we end up
printing it in the constants block in every test. But it's also named
`type` rather than `%type` to preserve the majority of existing
formatting behaviour, though this does look different from other
constants.

Assisted-by: Opus 5 was used to generate a first draft and validate the
refactoring. Though nearly everything non-trivial the tool wrote has
been modified or rewritten.
2026-09-22 15:04:40 +00:00
Lucile Rose Nihlen 83ca5b71e3 Move default value inst ids to a dedicated value store. (#7810)
Per #7737 we move the default value `InstId` storage from
a block in the `SemIR::Function` data structure to a
`SemIR::File` scoped `ValueStore`.

Moves the default value consistency checking to the general
merge argument pattern matching logic, which changes the
error message issued to the generic one.
2026-09-21 18:27:01 +00:00
Dana Jansens fa12d9ded2 Skill file docs about how to write prose, names, and to prioritize the data model (#7814)
Largely about writing less, and defining what is worth talking about and
what is not.
2026-09-21 17:48:35 +00:00
Richard Smith 76e7fc5900 Preserve the type-as-written for bindings and use it in diagnostics. (#7804)
In the `EntityName` for a binding, preserve the `TypeInstId` describing
how the type was written. When a diagnostic refers to that type via
`TypeOfInstId`, use the type-as-written in the diagnostic rather than
the canonical type.

Assisted-by: Claude Opus via Antigravity
2026-09-19 00:02:49 +00:00
Chandler Carruth d037848a96 Replace hashtable ForEach callback with range-based iteration (#7806)
Replaces the callback-based `ForEach` methods on `RawHashtable`, `Map`,
and
`Set` with a range object supporting range-for loops, structured
bindings, and
the standard range concepts.

- Adds `.entries()` on `Map`, `Set`, and `RawHashtable`, returning a
range that
  models `std::ranges::forward_range` and `std::ranges::common_range`.
  Obtaining one is an explicit call rather than `begin()`/`end()` on the
container, as scanning a whole table is costly and shouldn't be hidden.
- Iterating a `Map` yields a `std::pair` of key and value references,
which
fits in two registers and is returned without being materialized in
memory.
- `Map::Range` and `Set::Range` are aliases of the raw hashtable's range
rather
than wrappers around it. The raw iterator produces the user-facing
reference
itself -- a `KeyT&` for a set, a pair of references for a map -- picked
by
`StorageEntry`, which is already specialized on whether there is a value
  type. That leaves one iterator to reason about instead of three.
- Deletes the rvalue `.entries()` overloads on the owning containers, as
a
  range built from a temporary table would dangle. Views don't own their
  storage, so the operation remains available on them.
- In release builds, the walk over the groups is a single induction
variable: a
  negative byte offset counting up to zero, anchored at the ends of the
metadata and entry arrays. Both arrays are then reached by indexed
addressing
off a base that stays put, and the entry pointer is formed only once a
group
  with a present entry has been found.
- In debug builds, the range hashes the table's metadata when it is
built and
re-checks that hash when it is destroyed, catching mutation of the table
while a range is live. It also picks a random starting group and a
random odd
group stride, which varies the traversal order between ranges while
still
visiting every group exactly once. That entropy is drawn when the range
is
built rather than in `begin()`, so `begin()` stays a pure function of
the
  range and the multi-pass guarantee holds.
- Removes `ForEachEntry` and all of its callers.

Measured against the iteration benchmark added in its own commit, a
traversal is at or ahead of what the callback compiled to across nearly
the
whole size range. The largest tables spend 3-5% fewer cycles, small
`Set`s as
much as 24% fewer, and instruction counts stay within about 1%. What
remains
behind is a handful of mid-sized `Map`s by up to 1%, and `Set` at 65536,
which
sits at exactly half its load factor, by 2%.

Both revisions were built with `-c opt --copt=-march=x86-64-v3` and
compared
with:

```
./scripts/bench_runner.py --exp_benchmark=... --base_benchmark=... \
    --benchmark_args=--benchmark_perf_counters=INSTRUCTIONS,CYCLES \
    --benchmark_args='--benchmark_filter=(Set|Map)Iterate<(Set|Map)<' \
    --extra_metrics_filter='(INSTRUCTIONS|CYCLES)'
```

Trimmed below to the primary integer configurations and to the two
counters;
the pointer- and string-keyed configurations follow the same pattern.

```
 Benchmark                             ┃           CYCLES            ┃        INSTRUCTIONS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 BM_MapIterate<Map<int, int>>/1....... │ 👍  -6.032%      p=1.14e-05 │      ??          p=0.752
                             baseline: │     12.06      ±   1.520%   │     64         ±   3.125%
                           experiment: │     11.33      ±   2.765%   │     65.5       ±   3.817%
                                       │                             │
 BM_MapIterate<Map<int, int>>/2....... │      ??          p=0.155    │      ??          p=0.343
                             baseline: │      7.587     ±   1.285%   │     41         ±   0.000%
                           experiment: │      7.652     ±   0.865%   │     41         ±   2.439%
                                       │                             │
 BM_MapIterate<Map<int, int>>/3....... │      ??          p=0.343    │ 👍  -1.020%      p=0.0039
                             baseline: │      6.663     ±   4.260%   │     32.67      ±   2.041%
                           experiment: │      6.368     ±  12.224%   │     32.33      ±   2.062%
                                       │                             │
 BM_MapIterate<Map<int, int>>/4....... │      ??          p=0.343    │ 👍  -1.786%      p=0.0297
                             baseline: │      6.091     ±  15.470%   │     28         ±   3.571%
                           experiment: │      5.957     ±   8.932%   │     27.5       ±   3.636%
                                       │                             │
 BM_MapIterate<Map<int, int>>/8....... │      ??          p=0.323    │ 👍  -1.220%      p=0.000148
                             baseline: │      4.845     ±   0.800%   │     20.5       ±   0.000%
                           experiment: │      4.814     ±   3.585%   │     20.25      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/16...... │ 👍  -2.195%      p=0.00908  │ 👍   0.769%      p=6.58e-06
                             baseline: │      4.312     ±   0.187%   │     16.25      ±   0.000%
                           experiment: │      4.218     ±   2.368%   │     16.13      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/32...... │      ??          p=0.236    │ 👍   0.442%      p=9.53e-06
                             baseline: │      4.051     ±   1.084%   │     14.13      ±   0.000%
                           experiment: │      4.063     ±   0.737%   │     14.06      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/64...... │      ??          p=0.693    │ 👎   0.227%      p=4.52e-06
                             baseline: │      4.021     ±   0.239%   │     13.75      ±   0.000%
                           experiment: │      4.019     ±   0.417%   │     13.78      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/256..... │ 👍   0.360%      p=0.00119  │ 👎   0.754%      p=1.37e-05
                             baseline: │      3.996     ±   0.173%   │     13.47      ±   0.000%
                           experiment: │      3.982     ±   0.272%   │     13.57      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/4096.... │ 👍   0.581%      p=1.96e-05 │ 👎   0.923%      p=1.96e-05
                             baseline: │      4.005     ±   0.816%   │     13.38      ±   0.000%
                           experiment: │      3.981     ±   0.192%   │     13.5       ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/65536... │ 👍  -4.957%      p=1.14e-05 │ 👎   0.934%      p=1.14e-05
                             baseline: │      5.307     ±   0.501%   │     13.38      ±   0.000%
                           experiment: │      5.044     ±   1.746%   │     13.5       ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/1048576. │ 👍  -3.947%      p=9.09e-05 │ 👎   0.935%      p=3.3e-05
                             baseline: │      6.074     ±   0.807%   │     13.38      ±   0.000%
                           experiment: │      5.834     ±   2.159%   │     13.5       ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/16777216 │      ??          p=0.155    │ 👎   0.935%      p=2.11e-05
                             baseline: │      5.082     ±   3.650%   │     13.38      ±   0.000%
                           experiment: │      5.012     ±   1.316%   │     13.5       ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/56...... │ 👎   0.825%      p=0.0268   │ 👍   0.270%      p=1.14e-05
                             baseline: │      3.918     ±   0.501%   │     13.21      ±   0.000%
                           experiment: │      3.951     ±   0.342%   │     13.18      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/224..... │ 👎   0.788%      p=0.000504 │ 👎   0.346%      p=1.64e-05
                             baseline: │      3.895     ±   0.111%   │     12.89      ±   0.000%
                           experiment: │      3.926     ±   0.285%   │     12.94      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/3584.... │ 👎   1.028%      p=0.000148 │ 👎   0.545%      p=1.14e-05
                             baseline: │      3.913     ±   0.427%   │     12.79      ±   0.000%
                           experiment: │      3.954     ±   0.325%   │     12.86      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/57344... │      ??          p=0.236    │ 👎   0.558%      p=2.55e-06
                             baseline: │      4.574     ±   0.721%   │     12.79      ±   0.000%
                           experiment: │      4.51      ±   3.709%   │     12.86      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/917504.. │ 👍  -3.826%      p=6.58e-06 │ 👎   0.559%      p=2.33e-05
                             baseline: │      5.221     ±   0.507%   │     12.79      ±   0.000%
                           experiment: │      5.021     ±   0.556%   │     12.86      ±   0.000%
                                       │                             │
 BM_MapIterate<Map<int, int>>/14680064 │ 👍  -3.839%      p=1.37e-05 │ 👎   0.559%      p=3.31e-05
                             baseline: │      5.129     ±   1.194%   │     12.79      ±   0.000%
                           experiment: │      4.932     ±   1.475%   │     12.86      ±   0.000%
                                       │                             │

 Benchmark                        ┃           CYCLES            ┃        INSTRUCTIONS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 BM_SetIterate<Set<int>>/1....... │ 👍  -3.104%      p=0.000583 │      ??          p=0.206
                        baseline: │     11.2       ±   6.323%   │     60         ±   3.333%
                      experiment: │     10.85      ±   5.820%   │     61         ±   3.279%
                                  │                             │
 BM_SetIterate<Set<int>>/2....... │ 👍  -7.037%      p=0.0362   │      ??          p=0.155
                        baseline: │      7.086     ±  16.857%   │     37         ±   0.000%
                      experiment: │      6.587     ±   0.479%   │     36         ±   4.167%
                                  │                             │
 BM_SetIterate<Set<int>>/3....... │ 👎   1.400%      p=2.34e-05 │ 👍  -1.163%      p=0.00136
                        baseline: │      5.363     ±   0.463%   │     28.67      ±   2.326%
                      experiment: │      5.438     ±  32.763%   │     28.33      ±   1.176%
                                  │                             │
 BM_SetIterate<Set<int>>/4....... │      ??          p=0.968    │      ??          p=0.286
                        baseline: │      4.642     ±  32.751%   │     23.5       ±   2.128%
                      experiment: │      4.658     ±  38.416%   │     23.63      ±   3.704%
                                  │                             │
 BM_SetIterate<Set<int>>/8....... │ 👍 -23.823%      p=3.74e-06 │ 👍  -1.515%      p=5.52e-05
                        baseline: │      4.701     ±   6.589%   │     16.5       ±   0.000%
                      experiment: │      3.581     ±   7.790%   │     16.25      ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/16...... │ 👍  -4.502%      p=1.37e-05 │ 👍  -1.020%      p=3.31e-05
                        baseline: │      3.124     ±   0.585%   │     12.25      ±   0.000%
                      experiment: │      2.983     ±   0.625%   │     12.13      ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/32...... │ 👍  -4.032%      p=5.46e-06 │ 👍   0.617%      p=1.96e-05
                        baseline: │      2.957     ±   0.260%   │     10.13      ±   0.000%
                      experiment: │      2.838     ±   0.434%   │     10.06      ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/64...... │ 👍  -5.054%      p=4.52e-06 │ 👎   0.321%      p=1.37e-05
                        baseline: │      2.937     ±   0.301%   │      9.75      ±   0.000%
                      experiment: │      2.788     ±   1.143%   │      9.781     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/256..... │ 👍  -5.325%      p=1.14e-05 │ 👎   1.073%      p=6.58e-06
                        baseline: │      2.916     ±   0.220%   │      9.469     ±   0.000%
                      experiment: │      2.761     ±   0.142%   │      9.57      ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/4096.... │ 👍  -4.865%      p=4.52e-06 │ 👎   1.317%      p=2.34e-05
                        baseline: │      2.921     ±   0.194%   │      9.381     ±   0.000%
                      experiment: │      2.779     ±   0.224%   │      9.504     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/65536... │ 👎   1.961%      p=3.93e-05 │ 👎   1.332%      p=1.49e-05
                        baseline: │      4.015     ±   0.482%   │      9.375     ±   0.000%
                      experiment: │      4.094     ±   0.613%   │      9.5       ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/1048576. │ 👍  -4.843%      p=1.14e-05 │ 👎   1.333%      p=5.38e-06
                        baseline: │      5.239     ±   0.144%   │      9.375     ±   0.000%
                      experiment: │      4.986     ±   0.139%   │      9.5       ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/16777216 │ 👍   0.840%      p=0.0362   │ 👎   1.333%      p=2.52e-06
                        baseline: │      3.719     ±   1.420%   │      9.375     ±   0.000%
                      experiment: │      3.688     ±   1.308%   │      9.5       ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/56...... │ 👍  -2.857%      p=9.53e-06 │ 👍   0.388%      p=3.31e-05
                        baseline: │      2.942     ±   0.439%   │      9.214     ±   0.000%
                      experiment: │      2.858     ±   0.619%   │      9.179     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/224..... │ 👍  -2.161%      p=2.34e-05 │ 👎   0.502%      p=4.52e-06
                        baseline: │      2.888     ±   0.347%   │      8.893     ±   0.000%
                      experiment: │      2.826     ±   0.450%   │      8.938     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/3584.... │ 👍  -1.750%      p=6.58e-06 │ 👎   0.793%      p=2.34e-05
                        baseline: │      2.89      ±   0.261%   │      8.792     ±   0.000%
                      experiment: │      2.84      ±   0.411%   │      8.862     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/57344... │      ??          p=0.502    │ 👎   0.812%      p=2.78e-05
                        baseline: │      3.684     ±   4.246%   │      8.786     ±   0.000%
                      experiment: │      3.644     ±   4.431%   │      8.857     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/917504.. │ 👍  -2.629%      p=0.000148 │ 👎   0.813%      p=3.08e-06
                        baseline: │      4.372     ±   0.693%   │      8.786     ±   0.000%
                      experiment: │      4.257     ±   0.210%   │      8.857     ±   0.000%
                                  │                             │
 BM_SetIterate<Set<int>>/14680064 │ 👍  -2.927%      p=0.0219   │ 👎   0.813%      p=3.03e-06
                        baseline: │      4.154     ±   3.286%   │      8.786     ±   0.000%
                      experiment: │      4.032     ±   3.198%   │      8.857     ±   0.000%
                                  │                             │
```

Assisted-by: Antigravity with Opus
2026-09-18 20:19:46 +00:00
Chandler Carruth 681b3b10c4 Add a jj push wrapper that runs prek before pushing. (#7805)
`scripts/jj_push.sh` takes the same arguments as `jj git push`, runs
prek over the commits that push would send, and pushes only if they
pass. It learns what is being sent by running `jj git push --dry-run`
and reading back the plan, so `--bookmark`, `--change`, `--all` and the
rest work without reimplementing how they select commits.

Hooks that rewrite files need a commit to write into, so the checks run
with the working copy on top of the commit being pushed. When the
working copy is already an empty commit there, which is the common case,
it is used directly; otherwise one is created, and named in the error so
the fixes can be squashed.

`scripts/jj_prek.sh` gets two changes. It forwards its arguments to
`prek run`, so `jj_push.sh` can ask for a specific range, and it now
changes to the workspace root before running. It exports `GIT_DIR`,
which makes git treat the current directory as the work tree, so prek
could not find its configuration from a subdirectory.

`jj` does not expand aliases when completing arguments, so `jj push`
completed file names. `scripts/completions` has Bash, Zsh, and Fish
completions that give it the same completions as `jj git push`.

`docs/project/contribution_tools.md` documents the `push` alias, and a
`prek` alias for `jj_prek.sh`, with the other per-repository `jj`
configuration. Both are opt-in.

Assisted-by: Claude Code
2026-09-18 19:40:25 +00:00
Nicholas Bishop dbf79d5229 Fix accessing protected members from templates derived classes (#7780)
When evaluating a deferred member access action, the scope stack cannot
be relied on, so `LookupUnqualifiedName` cannot be used in
`GetHighestAllowedAccess` to get the `Self` type.

Instead, store the `Self` type in the `Context` when evaluating a
method, and use that in `GetHighestAllowedAccess`.
2026-09-18 19:12:05 +00:00
Dana Jansens 413ac55d4f Add rules for working with jj history and prek (#7808)
Add rules to not overwrite git/jj history without asking, since this
destroys the reviewer's view of things. And some information on dealing
with stacks of commits within a single bookmark/PR.

Prek can make fixes for whatever caused a failure, and then pass when
you run it again, even though the user didn't change anything, and that
is now explained.

Assisted-by: Opus 5
2026-09-18 18:36:58 +00:00
ATHARVAandDana Jansens 804359dce0 Fix crash when lowering Carbon derived class with C++ virtual base class (#7745)
If a Carbon class overrides virtual functions from a C++ base class but
is never referenced from C++, it is never exported to Clang. During
lowering, `BuildVtable` then fails to find a `CXXRecordDecl` and crashes
when attempting to get the vtable from Clang's code generator.

Ensure dynamic classes with foreign vtables are exported to Clang when
completing the class definition in `CheckCompleteClassType`, and look up
`first_decl_id()` in `BuildVtable`.

Fixes #7721

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-09-18 18:08:53 +00:00
Dana Jansens d84387f8b8 Add --build-mode to autoupdate script (#7809)
Allow the user to specify a build mode instead of detecting the last
used one.
2026-09-18 15:15:26 +00:00
David Blaikie 094742740a Handle instantiating an imported class/vtable (#7792)
Usual LoadImportRef, plus some generalization of importing entities.
2026-09-17 23:50:28 +00:00
Lucile Rose Nihlen 994bad5143 Use non-canonical instructions in default values. (#7737)
Per feedback on #7665, this PR switches the default value
table storage from canonical constant inst_ids to
non-canonical.

Furthermore, this PR simplifies the default value support in
check by requiring that the first owned declaration of a
function completely specify all of its default values.
Updates the diagnostic code and tests to reflect this new
stricter requirement.
2026-09-17 22:04:43 +00:00
Chandler Carruth ae4be1be14 Use inline small storage for small SemIR ID sets in toolchain (#7796)
Apply SmallSize = 16 to frequent identifier and instruction/function
sets in ScopeStack, Class, FacetType, and SpecificCoalescer to avoid
dynamic heap allocations on small scopes. Also defer dest_field_names
set allocation in struct conversion and provide default KeyContext for
SetBase. This was found by inspection, but does seem to be a clear 1.5%
win on overall compile time.

```
Ran baseline and experiment 10 times on 128 x 2450 MHz CPUs
CPU caches:
  L1 Data 32Ki
  L1 Instruction 32Ki
  L2 Unified 512Ki
  L3 Unified 32Mi
Load avg: 1.2041 1.16895 2.86133
Computing statistically significant deltas only wherethe P-value < 𝛂 of 0.05
Metric key:
   BenchmarkName... 👍 <delta>    p=<U-test P-value>
          baseline:    <median> ± <% at 95th conf>
        experiment:    <median> ± <% at 95th conf>

 Benchmark                                                      ┃          CPU Time          ┃           CYCLES           ┃       INSTRUCTIONS        ┃          Lines
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/256... │ 👍  -1.768%      p=0.0346  │ 👍  -1.683%      p=0.0346  │ 👍   0.160%    p=0.000428 │ ~
                                                      baseline: │     49.11  ms  ±   1.363%  │    155.6   M   ±   1.875%  │    293.9   M ±   0.015%   │     4.093 k ±   1.360%
                                                    experiment: │     48.24  ms  ±   2.213%  │    153     M   ±   2.465%  │    293.4   M ±   0.081%   │     4.166 k ±   2.165%
                                                                │                            │                            │                           │
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/1024.. │      ??          p=0.159   │      ??          p=0.067   │ 👍   0.153%    p=0.000249 │ ~
                                                      baseline: │     50.61  ms  ±   2.379%  │    160.7   M   ±   2.777%  │    309.7   M ±   0.015%   │    19.96  k ±   2.326%
                                                    experiment: │     50.32  ms  ±   0.971%  │    159.6   M   ±   0.787%  │    309.2   M ±   0.086%   │    20.07  k ±   0.980%
                                                                │                            │                            │                           │
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/4096.. │ 👍  -1.593%      p=0.0112  │ 👍  -1.632%      p=0.00743 │ 👍   0.138%    p=0.000328 │ ~
                                                      baseline: │     58.58  ms  ±   1.673%  │    186.5   M   ±   1.487%  │    371.2   M ±   0.102%   │    70.96  k ±   1.645%
                                                    experiment: │     57.65  ms  ±   1.032%  │    183.5   M   ±   1.079%  │    370.7   M ±   0.091%   │    72.11  k ±   1.043%
                                                                │                            │                            │                           │
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/16384. │ 👍  -1.695%      p=0.029   │ 👍  -1.823%      p=0.0411  │ 👍   0.221%    p=0.000428 │ ~
                                                      baseline: │     90.07  ms  ±   3.686%  │    287.1   M   ±   3.694%  │    618.9   M ±   0.106%   │   186.9   k ±   3.555%
                                                    experiment: │     88.55  ms  ±   1.891%  │    281.8   M   ±   2.142%  │    617.6   M ±   0.158%   │   190.1   k ±   1.856%
                                                                │                            │                            │                           │
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/65536. │ 👍  -1.797%      p=0.0201  │ 👍  -1.762%      p=0.0201  │ 👍   0.222%    p=0.000328 │ ~
                                                      baseline: │    222.1   ms  ±   2.560%  │    711     M   ±   2.417%  │      1.613 G ±   0.158%   │   304.2   k ±   2.496%
                                                    experiment: │    218.1   ms  ±   1.950%  │    698.5   M   ±   2.010%  │      1.61  G ±   0.075%   │   309.7   k ±   1.989%
                                                                │                            │                            │                           │
 BM_CompileApiFileDenseDecls<Lang::Carbon, Phase::Check>/262144 │      ??          p=0.398   │      ??          p=0.36    │ 👍   0.166%    p=0.000931 │ ~
                                                      baseline: │    782.2   ms  ±   2.666%  │      2.502 G   ±   2.546%  │      5.596 G ±   0.038%   │   345.8   k ±   2.597%
                                                    experiment: │    780.6   ms  ±   4.249%  │      2.483 G   ±   4.691%  │      5.587 G ±   0.024%   │   346.5   k ±   4.075%
                                                                │                            │                            │                           │
```

Assisted-by: Antigravity with Gemini
2026-09-17 15:49:43 +00:00
Chandler Carruth e65694db57 Narrow the benchmark test runs for set and benchmark (#7795)
Hopefully this reduces the occurances of timeouts on GitHub, and it
shouldn't reduce coverage meaningfully.
2026-09-17 12:42:57 +00:00
Chandler Carruth 5c601f8224 Make the filesystem benchmark test less expensive (#7794)
While some of the slowness here is unrelated, there isn't really any
reason to test even as much of the benchmark as it is.
2026-09-17 12:41:59 +00:00
Richard Smith ccb5ba1b92 Turn off -Wunneeded-internal-declaration in clangd. (#7793)
Like the other unused warnings, it misfires when the only uses are in
template instantiations.

This is misfiring in #7771's CI checks.
2026-09-17 01:23:25 +00:00
Richard Smith ebf1675356 Add linux perftools output files to gitignore. (#7791) 2026-09-16 23:34:46 +00:00
Richard Smith 843c7ce498 Improve compilation database for agents. (#7790)
Remove claim from script that it takes minutes; this caused Claude to
decide to not run it. It only takes a few seconds these days. Add note
in toolchain development skill that lints won't be accurate if the
compilation database is outdated.

Assisted-by: Claude Opus 5 via Antigravity
2026-09-16 20:12:32 +00:00
Richard Smith 59c1d6ff39 Remove refine_inst_action and its splices. (#7769)
This action was created to wrap any `MetaInstId` operand of an action
instruction. This served two purposes:

1) It had a special hook in `OperandIsDependent` to allow it to be
   performed while it had a dependent operand (the reference to the
   instruction in the generic).
2) It created a `specific_inst` so that the downstream action saw an
   instruction in the specific instead of one in the generic.

These are both replaced: the special case in `OperandIsDependent` for
`RefineInstAction` is replaced by a special case for `MetaInstId`s in
general, and the `SpecificInst` is now created as part of performing the
downstream action, rather than as a separate step carried out
beforehand.

This simplifies the produced SemIR and reduces the number of splices
significantly. It also prepares us to handle actions like
initialization, where we don't actually want to create `SpecificInst`s
immediately in the location where the action is performed, because they
actually belong somewhere else in the IR.

Assisted-by: Claude Opus 5 and Gemini via Antigravity
2026-09-16 01:21:00 +00:00
Richard Smith eb887c367a Add benchmark for missing bracket fixing and optimize various parts of the algorithm. (#7655)
Replaces several quadratic-time steps in the algorithm with linear or n
log n implementations. Worst-case runtime before hitting the complexity
bailout drops from 25ms to about 12ms, and typical runtime is
single-digit ms on my development machine.

Assisted-by: Claude Code
2026-09-15 23:22:29 +00:00
Richard Smith f3d67de480 Start to preserve type sugar in diagnostics. (#7768)
Instead of always printing types as canonical, attempt to find a sugared
type where possible, and include that type in the diagnostic. We can
only do this when given the instruction whose type is being printed
(`TypeOfInstId`) rather than the canonical type ID.

Initial support here is intentionally minimal: just looking through
calls to the callee's declared return type, and looking through pointer
dereferences and corresponding pointer types, to build out the initial
infrastructure. More cases can be added later; this degrades gracefully
to using the canonical type if a better type can't be found.

Assisted-by: Claude Opus 5 via Antigravity
2026-09-15 23:11:58 +00:00
Richard Smith d8c4fc51cd Fix SemIR for derived-to-base conversion and lowering crash. (#7783)
We use the same conversion codepath to handle both qualification
conversions and derived-to-base conversions, because we allow both to be
performed at once. However, we were previously modeling the
qualification conversion as happening *first*, and producing a result
whose type is the target type of the overall conversion (that is, the
base class type). That led to bogus SemIR, where a `Derived` -> `const
Base` conversion would first have a "compatible" conversion from
`Derived` to `const Base`, *then* an access of the base subobject (of
type `const Base`, within an object of type `const Base`).

We now reverse the order: first we do a derived-to-base conversion,
which already has logic to preserve qualifiers, and then we do any
necessary qualification conversions on the result to reach the overall
target type.

In passing, we now skip forming the `as_compatible` instruction at all
for a pure derived-to-base conversion that has no qualification
conversion, simplifying the SemIR by one instruction in the common case.
2026-09-15 23:11:51 +00:00
simontran7 64ce58dd64 fix malformed parse tree for invalid let struct pattern (#7782)
Fixes the malformed parse tree produced for an invalid let struct
pattern containing a single identifier (e.g., `let {s};`).

As pointed out by @DavidLoftus, the parser should produce a parse tree
similar to that of `let {ref s};`, since both are missing a binding
power operator `:`, and both do not have a `.` preceding the identifier
(i.e., `state.in_field_shorthand_pattern == true`).

This means that the parser can produce an the `InvalidParse` node just
as it does for `let {ref s};`.

Closes #7674
2026-09-15 23:10:17 +00:00
Chandler Carruth 437be71f1f Overhaul the TextMate grammar (#7746)
Four regions had `end` patterns that could fail to match, so `return
var;`, a `fn` with no parameter list, and an unterminated `"` each
swallowed the rest of the file; 77 of 1696 testdata files lost their
highlighting partway through. Operators were wrapped in `\b`, which only
holds next to a word character, so `a + b` highlighted nothing. And the
keywords had drifted about two years behind the lexer.

Identifiers are now classified by naming convention plus a call-site
lookahead, the way the Rust grammar does it, so nothing carries between
lines. Regions survive only for strings and embedded C++, where a
terminator reliably turns up, and raw strings spell out hash levels 0
through 2, so `\n` is an escape in `"..."` and plain text in `#"..."#`.
Trailing comments, character literals, raw identifiers, `$0`, `0o`
octal, arbitrary integer widths and six missing keywords are covered
now, `destructor` is gone, and `i32` reads as a type rather than as a
keyword.

Highlighting stays forgiving rather than diagnostic: anything after `//`
is a comment and odd numeric spellings still read as numbers. Pointing
out mistakes is the toolchain's job, and lenient rules hold steady while
you are still typing.

Every keyword and symbol in `token_kind.def` is covered, and unscoped
tokens across examples and the toolchain drop from 39% to 21%.

Note that I haven't tried to read and reason about every minute change
here as there are just too many. But I'm working on a follow-up PR that
adds testing that should be significantly easier te review.

Assisted-by: Claude Code
2026-09-15 22:03:22 +00:00
Dana Jansens bfebb7cb42 Pass SpecificInterface through custom and C++ witness generation (#7784)
We were passing a SpecificInterfaceId which just makes code have to do a
lookup to get the actual SpecificInterface. The caller already has the
SpecificInterface, so plumb that around.

SpecificInterfaceId really only exists when we need to stick a
SpecificInterface into an instruction as an operand.
2026-09-15 21:23:28 +00:00
David Blaikie 9e68ee0806 Support indirect C++ dependency by sharing domains more broadly (#7763)
Fixes #7731, at least under `--share-cpp-ast` which is expected to be
the future direction.

When --share-cpp-ast is enabled and any compilation unit has C++
imports, include all compilation units in the shared CppDomain inputs
and assign the domain to every unit. In ImportCpp, when a unit has no
direct C++ imports but is covered by a shared CppDomain, initialize
its C++ AST context and import namespace. This ensures units without
direct C++ imports have access to the C++ AST and code generator when
instantiating generics or referencing declarations from units that do.

Assisted-by: Antigravity with Gemini
2026-09-15 20:45:18 +00:00
Chandler CarruthandDana Jansens 37949a2066 Generate the TextMate sample renderings (#7762)
The images beside the samples were screenshots taken by hand, so they
drifted: they still showed `package Carbon api;`, syntax the language
dropped in 2024.

`render_sample.py` renders a sample the way the grammar in this
repository actually highlights it, using `tmlanguage.py`, a small
TextMate tokenizer. VS Code runs grammars under Oniguruma, which we
cannot depend on here, but this grammar uses no Oniguruma-only syntax,
so `re` runs its regexes unchanged and the two agree on every character
of every Carbon file in the repository. The output is SVG, so
regenerating needs nothing but Python, and a later grammar change gets a
same-path image diff showing what it did to real code.

The samples change where a construct left the language (`api`, `Carbon`
as the package name, `StringView`, `destructor`), refresh the
`keywords.carbon` inventory against `token_kind.def`, and add sections
for octal, raw and block literals, character literals, lambdas, and raw
identifiers. `interop.carbon` is new, covering inline C++. These are
highlighting fixtures rather than programs, so the deliberately invalid
lines stay.

Assisted-by: Claude Code

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-09-15 20:29:22 +00:00
Lucile Rose Nihlen e962b12e53 Fix typo in ImplDeclInInvalidScope diagnostic (#7785)
And update the associated file test.
2026-09-15 16:43:22 +00:00
Dana Jansens 4416f3525b Canonicalize generated functions for Core witnesses (#7729)
Use a single `SemIR::Function` per `Core` interface method, whether it's
generated locally or imported. This prevents generating duplicate
functions, which lead to different types when the witness appears in a
`FacetValue` as part of a specific for a class.

We use a `CanonicalValueStore` of `GeneratedFunction` objects that allow
finding an existing FunctionId for a `Generated` special function before
(re-)generating it. Mangling for `Generated` functions is also moved to
use the values from the `GeneratedFunction`'s canonicalization key, so
that we have a consistent source of truth for the unique ID of a
`Generated` function across all files.

New tests are in
`toolchain/check/testdata/impl/custom_witness/destroy.carbon`.
2026-09-15 16:02:13 +00:00
db2e26ba86 Updates to member access (#7697)
Update the rules for member access:
-   Simple member access `a.b`
- If `a` names a scope, performs name lookup and optionally `impl`
lookup.
- Otherwise, `a.b` is shorthand for `a.(typeof(a).b)` and always
performs instance binding.
- Compound member access `a.(m)` does optional `impl` lookup and always
performs instance binding.
- This is a change from only performing instance binding if `m` is an
instance member.
- New operation `a.impl(m)` is introduced. It always performs `impl`
lookup, and nothing else.
- The `BindToType` interface is removed. Only instance binding may be
customized (using the `BindToValue` and `BindToRef` interfaces).

As a result, member access doesn't use whether the right operand is an
instance member anymore. Instead, instance binding is performed whenever
it would be plausible, and a new syntax is used to opt out.

Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-09-15 00:03:41 +00:00
Nicholas Bishop cf66fb8aeb roll llvm to 7024b9e1b423b3c3c6ac76ab6a73cb2c9e4ef842 (#7781)
Updated patch 0006 to include new files added in
https://github.com/llvm/llvm-project/pull/207543.

Dropped patch 0009 which was upstreamed in:
https://github.com/llvm/llvm-project/pull/190088

Minor updates to patch 0011 for changes upstream.

Minor updates in export.cpp to use `llvm::FoldingSetInsertToken` instead
of a void pointer.
2026-09-14 21:54:09 +00:00
Dana Jansens 4081848d65 Disable new clang-tidy rules with google- prefixed aliases (#7778)
Some `google-` prefixed rules have been renamed to rules without the
prefix. The `google-` prefix still remains as an alias to these new
rules. Since we turn on the `google-` prefix rules and use those in
NOLINT expressions, disable the new aliased names. Otherwise we have to
put both names in NOLINT expressions.
2026-09-12 09:35:03 +00:00
Dana Jansens 648ccc5f9b NOLINT a cycle in semir formatting (#7774)
Add a TODO that we should address this cycle.
2026-09-11 22:29:17 +00:00
Dana Jansens 0d9560c049 Disable readability-redundant-nested-if in clang tidy (#7776)
This is a style choice we often agree with, and call out in code review.
But there are many cases where we do want to split apart nested ifs,
such as when working with LLVM apis like `dyn_cast`:
```
if (auto* thing = dyn_cast<Thing>(other)) {
  if (thing->foo()) {
    ...
  }
}
```

Or we may have TODOs or other comments in the scope of the outer if,
which the tidy check ignores.

Since this doesn't lead to bugs, we disable the check and leave this to
reviews and authors for their discretion.
2026-09-11 19:51:41 +00:00
Dana Jansens 09feee7534 Remove empty lambda parameters in handle_function (#7777)
These are marked as redundant by clang-tidy 23
2026-09-11 18:22:33 +00:00
Dana Jansens 111ec69b65 NOLINT an assignment in a complex boolean statement (#7775)
We use a complex fold statement over `operator=`, with an assignment to
a variable earlier in the folded-over expression, which is intentional.
2026-09-11 17:04:40 +00:00
Dana Jansens c45efd625f Move to clang 21 as mininum version and use it in CI (#7779)
Clang 21 is now the latest version available in Ubuntu LTS, so we can
move to it.
2026-09-11 16:58:59 +00:00
Richard Smith 7d70f72bec Remove incorrect CHECK that would fail for out-of-line template declarations. (#7772)
Add a test, which fails for now, but will eventually demonstrate why
this CHECK was incorrect.
2026-09-11 16:58:05 +00:00
Chandler CarruthandRichard Smith b1e9ced0c9 Add a design for rendering diagnostics on a terminal (#7668)
The diagnostics documentation covers how a diagnostic is produced and
worded, not the form it takes on a terminal. This adds
`toolchain/docs/diagnostics_rendering.md`: what a diagnostic is made
of, its layout, color, character set, and width, and how each degrades
when the terminal can't render it.

A diagnostic is one message plus labels read against the code they
mark, `Primary` or `Info`; a context and a path are set out apart. It
renders as one frame, anchored per file, with no headline: the message
hangs off the range that is wrong, led by the level word on a heavy
mark, with `->` in the margin on the reported lines. Labels read in
the order of their ranges, connectors that cross earlier rows break
around them, and a path draws only for the message's own location.
Ten rows on the shared example, against Clang's six and rustc's eleven.

Nothing a diagnostic says is dropped for width: source is windowed,
words wrap past the level word, connectors slide, and a label that
still can't hang is out-dented. Under sixty columns the compact form
takes over, one located line per part. Color is named per element with
a ramp per level and a light-background palette; ASCII keeps every
distinction with `^`, `.`, and the level word. The `file:line:column:`
header is the road not taken: structure comes from the language
server, and the compact form covers a grep-able line.

Assisted-by: Claude Code

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-09-11 07:15:40 +00:00
Richard Smith 455f1af2b2 jj_prek: snapshot the working copy (#7770)
Instead of running on the "current" `@` (the state at which the previous
`jj` command happened to be run), intentionally trigger a working copy
snapshot from `jj_prek`.

Assisted-by: Claude Opus 5 via Antigravity
2026-09-11 06:14:28 +00:00
Dana Jansens 49345352d6 Remove redundant use of typename (#7751)
clang-tidy 24 warns about these
2026-09-11 00:22:27 +00:00
Dana Jansens f819fafa12 NOLINT the use of sizeof() on a pointer if a pointer is hashed (#7765)
The hashing code is generic over the type of the value being given to
sizeof() so ideally this warning would not happen at all, but it does.
Possibly because the value is the return of an overload set, so it's not
obvious that it's the templated type. One of those overloads returns
`const void*` but change that to an integer does not remove the warning
still.
2026-09-10 23:54:11 +00:00
Dana Jansens dd43b50310 NOLINT an initializer list construction that clang-tidy warns on (#7759)
Clang is synthesizing a cast when using an enum value from a template
parameter, and then clang-tidy is finding and reporting that cast.
Upstream bug: https://github.com/llvm/llvm-project/issues/222793
2026-09-10 23:49:07 +00:00
Dana Jansens 981a0e9445 NOLINT the cycle through HandleAction and pattern matching (#7767)
Leave TODOs on all the places that needed to be silenced
2026-09-10 22:33:50 +00:00
Dana Jansens 656026f630 Move diagnostic emit functions to protected to match their base class (#7764)
The emit functions are inherited from a base class as protected, and
clang-tidy warns if we then expose them as public.
2026-09-10 22:29:52 +00:00
Dana Jansens 03bd40c398 Disable clang-tidy forbidding forward decls of classes with the same name in another namespace (#7766) 2026-09-10 22:14:25 +00:00
Dana Jansens e87831373f NOLINT and document use of StringLiteral::data() which subclasses StringRef (#7757)
StringRef::data() is problematic to call, but StringLiteral is always
NUL-terminated, so data() gives a valid C string.
2026-09-10 22:00:36 +00:00
Dana Jansens 8732bd9de1 Replace C-style variadic with a concept (#7761)
clang-tidy 24 warns on C-style variadics, and we don't need to use one
here anymore. Instead of a function call with an argument list, use a
concept to determine if a type can be list initialized.
2026-09-10 21:26:37 +00:00
Dana Jansens eca38a90e1 Remove empty lambda parameter lists (#7760)
clang-tidy 24 warns about these being redundant
2026-09-10 20:10:36 +00:00
Dana Jansens 52e28c02c3 Disable readability-identifier-naming in clang-tidy 24 (#7752)
This check allows styles that we don't use so it's not really useful for
enforcing our style guide. And prevents the use of `_1` or similar in
destructuring declarations where want to use `_` for multiple variables,
such as `auto [_1, _2, foo] = bar()`.
2026-09-10 20:03:26 +00:00
Dana Jansens 3942eca83f Disable the bugprone-crtp-constructor-accessibility warning in clang-tidy 24 (#7758)
The warning wants all classes inherited as CRTP base classes to hide all
their constrcuctors and friend all uses of them. We use CRTP quite a lot
and across different components of the toolchain, which would make
maintaining friend lists frustrating.
2026-09-10 19:53:15 +00:00
ATHARVA d98784b972 Fix crash when initializing class with omitted base class (#7740)
### Description
When looking up default initializers for class elements in
`ConvertStructToClass`, the compiler previously assumed that every
member looked up from the class scope was a `FieldDecl` and called
`GetAs<SemIR::FieldDecl>` directly.

For a derived class with a base class, looking up `base` returns a
`BaseDecl`, which caused a `CHECK` assertion failure when casting to
`FieldDecl`. Use `TryGetAs<SemIR::FieldDecl>` instead so non-`FieldDecl`
entries like `BaseDecl` are recognized as having no default initializer,
cleanly diagnosing that the `base` field is missing.

Fixes #7722

Assisted-by: Google Deepmind Antigravity
2026-09-10 19:29:24 +00:00
Dana Jansens 6f1ae86ce4 Disable -Wunused-template in clangd-tidy (#7750)
This is firing on some of our _used_ templates in eval.cpp in clang-tidy
24

It was coming to `-Wall` for clang as well
(https://github.com/llvm/llvm-project/issues/202945) but was reverted
due to issues like false positives
(https://github.com/llvm/llvm-project/pull/218638). Some fixes were
applied to try enable in `-Wall` in clang 23
(https://github.com/llvm/llvm-project/pull/222336) but it still remains
disabled by default for clang.
2026-09-10 19:28:25 +00:00
Dana Jansens dbcae83784 Passthrough the StringRef to mapRequired instead of just the data() pointer (#7756)
The StringRef represents a bounded region of a string, but using data()
drops the end bound, and makes LLVM construct a new StringRef starting
in the same position and going until a nul terminator. If this worked
correctly before, it was because the StringRef was always pointing to a
full `std::string` or the tail of one.
2026-09-10 19:19:56 +00:00
Dana Jansens 804dc3baaf Disable readability-use-concise-preprocessor-directives in clang-tidy 24 (#7754)
While we do adhere to its expectations most of the time, we don't
always. This is a low value check, and it's a stylistic choice to use
`#if defined(...)` when paired with `#elif defined(...)`.
2026-09-10 19:12:54 +00:00
Dana Jansens 76f52e0abb Move reserveExtraSpace to public (#7755)
The method it overrides in the base class is public, so it's already
accessible publicly. This is warned against in clang-tidy 24.
2026-09-10 19:12:00 +00:00
Dana Jansens bf8c997581 Disable bugprone-return-const-ref-from-parameter in clang tidy 24 (#7753)
We intentionally return const references from stable containers like
value stores.
2026-09-10 19:01:47 +00:00
Dana Jansens 4985e35695 Exclude .clang-tidy from rumdl checks (#7749)
Basically the whole file is an error and the formatting is not how we
want to write the file, so just exclude it.
2026-09-10 18:20:45 +00:00
Dana Jansens dd50e88168 Disable bugprone-derived-method-shadowing-base-method in clang-tidy 24 (#7748)
This fires on methods that we shadow, such as Print for a Printable<T>
subclass.
2026-09-10 17:04:10 +00:00
Dana Jansens c6c40cc444 Avoid using string operator += for a single character (#7747)
This is flagged as a mistake by clang-tidy 24. String's operator `+=` is
pretty bad in general, this moves a few uses to push_back.
2026-09-10 16:39:08 +00:00
Nicholas Bishop c24adea6fe Allow assignment to be called on a template-dependent lhs (#7741) 2026-09-10 15:07:31 +00:00
Dana Jansens eabc7f78b2 Show the errors that occurred, if any, when executing a dump command in lldb (#7743) 2026-09-10 12:47:28 +00:00
Dana Jansens 2aeecef17a Gracefully handle member access on a runtime type value (#7744)
Avoid CHECK failure when performing member access on a runtime type
value. We will just fail to find the CanonicalFacetOrTypeValue and then
fail lookup.
2026-09-09 23:05:48 +00:00
Christopher Di Bella 431e349757 Allow witness tables to support aliases and other types of StructValue (#7738)
Changing the check from "is `FunctionDecl`" to "has `FunctionType`"
provides us with more flexibilty to use aliases and other types of
`StructValue`.
2026-09-09 19:35:43 +00:00
Richard Smith e090a0ef65 autoupdate: add missing verbose check. (#7742)
A check for `args.verbose` was missing for one line of autoupdate's
verbose output. Add a wrapper function to print verbose output to fix
this and to make it easier to get verbose output right in future.
2026-09-09 19:34:28 +00:00
Richard Smith 49c6f488b6 Include spliced inst value in fingerprint while lowering. (#7739)
This prevents different template instantiations from getting
over-eagerly merged. Unfortunately we don't have a good middle-ground
yet, and this effectively disables all merging for templates. We may be
able to find some smart way to fingerprint spliced instructions so that
we can still merge template instantiations, but for now this change is
just fixing the wrong-code bug.
2026-09-09 17:54:10 +00:00
Richard Smith a9fee27bbb Add test for symbolic arguments to templates. (#7736)
Test passing a template argument or a symbolic argument to a template.
Fix a bug in the template case where we'd crash when instantiating a
dependent discarded expression, because conversion produced an
`InstId::None` which the actions machinery did not expect and crashed
on.
2026-09-09 01:39:54 +00:00
Richard Smith d92a981083 Add explicit testing for use of non-constant template arguments. (#7735)
Also move another template test into the template/ subdirectory.
2026-09-08 23:36:36 +00:00
Özgür T. Önsoy c6d8d29172 Diagnose redundant redeclarations in impl files (#7695)
While forward declarations in impl files are allowed, having one in the
impl file is redundant when we also have one in the API file.
2026-09-08 23:15:42 +00:00
Lucile Rose Nihlen ca9e985fa8 Reconcile function default values between decl and def (#7665)
Updates the pattern matching code to support unspecified default values.
Adds logic to decl and def merge code to diagnose mismatches in defaults
if specified in both places, or if let entirely unspecified.

Per https://github.com/carbon-language/carbon-lang/pull/7521.
2026-09-08 20:41:58 +00:00
Richard Smith 5a07a14fe9 Support for lowering templates (#7727)
Add basic support for lowering templates: we can now lower `SpliceInst`
in the case where the generic and specific are from the same file (and
we don't support importing templates from other files yet in general).
In order for this to work, lowering needs to be able to query the
expression category, and to handle instructions that appear to be
(template) constants in the generic but turn out to be non-constant in
the specific, so support for that is added.

Switch `type_of_inst` from being added as an action inst to being added
as a normal inst, since it's not an action and the old approach led to a
crash in lowering.
2026-09-08 18:39:57 +00:00
David Blaikie 3339dd85de Lower FacetType to TypeType to match FacetValue lowering (#7734)
SemIR::FacetValue lowers to context.GetTypeAsValue(), which produces a
constant of LLVM type %type (context.GetTypeType()). However,
SemIR::FacetType previously fell back to an anonymous empty struct {},
causing an argument type mismatch assertion failure when passing a
FacetValue to a function expecting a FacetType parameter.

Identified by @danakj in #7731

Assisted-by: Antigravity with Gemini
2026-09-08 18:32:34 +00:00
ATHARVA e5f30d1738 Cache SemIR::Mangler in FileContext (#7730)
### Description
When lowering symbols (functions, global variables, and vtables), a new
`SemIR::Mangler` was being created on each call. Each `Mangler` creates
its own `InstFingerprinter` with a fresh store, preventing any
fingerprint computations from being reused across manglings.

This PR caches a single `SemIR::Mangler` instance in `FileContext` so
that the underlying fingerprint cache is preserved and reused across all
symbol manglings within the file.

Assisted-by: Google Deepmind Antigravity
2026-09-08 17:53:10 +00:00
DavidLoftusandDavid Blaikie 301172f589 Update Carbon::Format to produce semi-reasonable output. (#7687)
Makes following changes to Carbon::Format()

- TokenKind::Period (i.e. `.`) should never have a space before or after
it.
- TokenKind::CloseSquareParen (i.e. `]`) should be treated as packed
content (no space preceeding it)
  - Only exception I can think of is `impl forall [...]`
- Remove preceeding space from `[` and `(` if previous token was an
identifier (or identifier-ish token)
- Remove seperator following `++` / `--` unary operators.
- Explicit gaps in source code should be retained, up to 2 new lines.

Multiple test files were added to test formatting. 

I imagine eventually this will need to be updated to read parse tree to
gather more context but this atleast lets us get a decent-ish format for
many of our current sample files (e.g. sieve.carbon)

Assisted-With: Gemini / Antigravity

---------

Co-authored-by: David Blaikie <dblaikie@gmail.com>
2026-09-08 16:20:47 +00:00
Richard Smith 812cc1e032 Add SpecificInst to represent a specific version of a template-dependent instruction (#7726)
Replace `refine_type_action` with `refine_inst_action`, and generate a
`specific_inst` instead of an `as_compatible` to represent the specific
version of an instruction that's used as an input to a template action.
This gives us a place to handle other properties of the instruction that
might vary from generic to specific beyond its type, such as its
constant value and its expression category.

For now, we provide a non-template-dependent constant value to the
`specific_inst` in addition to the non-template-dependent type we have
traditionally provided. This doesn't seem to matter for any current
actions, but sets us up to better handle future actions. The
`specific_inst` representation also allows downstream consumers of the
instruction to track which specific they should be requesting
information from. Providing a correct expression category for
`specific_inst` will be handled in a future PR.
2026-09-08 16:01:09 +00:00
Dana Jansens 386327ed4c Disable clang-tidy misc-multiple-inheritance for clang 24 (#7724)
We use multiple inheritance extensively, such as with our
EntityWithParamsBase subclasses. But we don't do this for
vtables/virtual, we do it for composing fields.
2026-09-05 06:25:30 +00:00
Dana Jansens efb7ca9b90 Include ASTContext where it's used in mangler.cpp (#7723) 2026-09-04 16:52:16 +00:00
Dana Jansens 8626d6653d Disable readability-inconsistent-ifelse-braces (#7719)
This produces a warning on every use of CARBON_KIND() with clang 24.

I tried putting NOLINT comments into the macro on the else to no avail.
It seems that comments are stripped from the macro output when it's
performing the check.

We already require {} on every if/else (outside of these weird macro
cases) so this doesn't seem like a problem to disable.
2026-09-04 01:35:46 +00:00
Lucile Rose Nihlen 2952ec9c10 Fix a crash when checking a nested tuple-pattern (#7716)
Adds a virtual node to `DefaultValuePattern` to end the
`ExprRegionForPattern` before checking the expression
for the default value.

When checking the default value expression, the context
was still configured to interpret expressions as patterns,
which caused some corruption of state with tuple-pattern
subpatterns.

Corrects an assertion failure I found while working on
feedback from #7665.
2026-09-04 01:22:16 +00:00
Dana Jansens 1b969c292b Remove a comment that looks to be left behind from a refactoring (#7718)
The GetFacetTypeForQuerySpecificInterface function has two comments on
top of it. The second one actually refers to what the function does.
2026-09-03 19:56:34 +00:00
Özgür T. Önsoy 5474347d7d Support lexing and parsing positional params (#7651)
This implements lexing and parsing positional parameters such as `$0`.
2026-09-03 19:06:20 +00:00
Nicholas Bishop 27849b385c Add TemplateInst and drop CallCppTemplateAction (#7689)
`TemplateInst` wraps another inst. If that inst is symbolic, it is
treated as a template by `OperandDependence`.

Use this to replace `CallCppTemplateAction` with the more general
`CallAction`.
2026-09-03 17:45:47 +00:00
Richard Smith 7c966c2d59 Initialize specifics in-place. (#7717)
Don't wait until we reach the end of the eval block to set the value
block on the specific. This is a prerequisite for allowing template
actions to read from the specific.
2026-09-03 17:06:22 +00:00
Richard Smith b8814f6c80 Add named constraints for Eq and Ordered. (#7714)
Also add a default for `EqWith.NotEqual`.

Switch advent examples to use these named constraints, and also go
through all the other TODOs in the advent examples and fix the ones that
are trivially fixable now.
2026-09-03 16:20:13 +00:00
Dana Jansens 48671bffe2 Use match_first and remove some workarounds in prelude float.carbon (#7713)
The float.carbon conversions for int->float, uint->float and
float->float were using various workarounds through extra indirections
in order to avoid the impls overlapping. Now we can write them all as
`impl From as ImplicitAs(Float(To))`, which makes them all appear to
overlap, though any given type will only match at most one of them. We
use `match_first` to give them an ordering regardless so that they are
allowed to overlap in type structure.
2026-09-03 14:58:27 +00:00
Dana Jansens 2299b94b20 Use match_first to make the same_self_and_interface.carbon test pass again (#7711) 2026-09-03 14:58:13 +00:00
Dana Jansens 1740b24879 Use a consistent SDK version on MacOS (#7704)
The SDK returned by `xcrun --show-sdk-path` does not always match the
SDK
that is used by clang under homebrew, because homebrew has its own
configurations per target that specify an SDK path to `-isysroot`. And
on
Darwin, the `-isysroot` flag supercedes the `--sysroot` flag entirely
when
present.

To override homebrew, and ensure we use the SDK we expect to be using
from
`xcrun`, specify `-isysroot` ourselves on the command line, both when
finding
the include paths and when building.

The compiler ends up taking a dependency on a JSON file at the root of
the
SDK as well, so add that to our allowlist of non-hermetic files,
along-side
the SDK include paths.
2026-09-03 14:58:01 +00:00
Richard Smith 1a9181edf6 Remove repository overview from AGENTS.md (#7705)
Per https://arxiv.org/pdf/2602.11988 (section 4.3), repository overviews
have no effect on the time it takes agents to find files, but do
increase the cost of operations and number of required steps to complete
tasks.
2026-09-02 23:09:03 +00:00
Dana Jansens b6ba4ecdbb Use match_first to make the impl_recurse_with_simpler_type_in_generic_param_bidirectional_no_cycle.carbon test pass again (#7712) 2026-09-02 21:56:12 +00:00
Dana Jansens 95897201ee Avoid disk cache on MacOS by default since it breaks debugging (#7702)
There's no way for the user to override and disable the disk cache once
a path is specified in our current version of bazel. Later versions
would allow the user to specify `--nodisk-cache`. If the user really
wants a disk cache anyway, they can specify as such in their
`user.bazelrc` file.
2026-09-02 21:12:48 +00:00
Richard Smith 01815b6c47 Fix refinement of call action operands. (#7710)
Two somewhat related fixes. The first is call-specific for now (because
it's the first action to take a `MetaInstId`), and the second is general
across all actions, but it seems like calls are the easiest place to hit
it.

1) Add support for refining inst blocks as action operands. Refine all
   the insts in the block, using the appropriate InstId-derived type.
2) When an action operand is a `MetaInstId` referring to an unattached
   constant, form a corresponding attached constant. This comes up when
   forming (for example) an implicit `AddWith(%T)` call, where the `%T`
   operand is an unattached constant.

This causes us to form correct specifics in more cases, where previously
we formed specifics that contained values that were still
template-dependent.

This unfortunately causes some existing template tests to produce more
errors, but those errors reflect cases where we were previously silently
doing the wrong thing.
2026-09-02 20:36:18 +00:00
Dana Jansens f2ca6f6d4a Document installing SSL certificates for Python on MacOS (#7707) 2026-09-02 20:33:54 +00:00
Nicholas Bishop d8bb181db8 Fix SemIR not showing insts used to compute FieldDecl type (#7706)
Add an `ExprRegionId` to `FieldDecl`. This required moving the `NameId`
into `Field`.
2026-09-02 20:28:05 +00:00
Dana Jansens ed074e85ab Include StringSet where it is used (#7708) 2026-09-02 19:38:23 +00:00
Dana Jansens a460ce931e Document that match_first may contain a fourth declaration of an impl. (#7709)
The declarations in a `match_first` must always be in the same file as
the first owning declaration, in order to maintain a consistent view of
impl lookup across all files.
2026-09-02 19:38:12 +00:00
Dana Jansens b8eca6a6da Include the ASTContext header where it's used (#7701) 2026-09-02 17:35:31 +00:00
Richard Smith 2209a3477e Step through splices when checking for a bound method. (#7700)
`GetCallee` is sometimes called on a spliced instruction, and is
checking its exact inst operand to see if it's a `BoundMethod`. This
fails if the `BoundMethod` is wrapped in another instruction, such as a
splice. Normally our approach for such a situation would be to
constant-evaluate the operand, but that doesn't work here because the
`BoundMethod` will be non-constant if its bound `self` is. So instead we
now step through splice instructions manually when looking for the
`BoundMethod`.
2026-09-02 15:19:59 +00:00
Richard Smith 6724d506f3 Add cycle detection to instruction fingerprinting. (#7691)
Use a version of Brent's algorithm, suitably adapted to work for our
worklist-based graph traversal, to very cheaply detect if instruction
fingerprinting fell into a cycle and terminate cleanly with a dump of
the cycle.

The algorithm does not immediately catch when we enter a cycle, but is
guaranteed to catch it eventually (generally after running through the
cycle no more than twice).
2026-09-02 00:33:32 +00:00
josh11bandJosh L fe14c83731 Use jj_prek.sh to run prek in new_proposal.py with jj (#7699)
Recently noticed this issue when creating a proposal using
`new_proposal.py` in a `jj` workspace.

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
2026-09-02 00:22:38 +00:00
Lucile Rose Nihlen 3e64670122 Transform pattern default values to SemIR (#7649)
Adds check functionality to transform the parse node to SemIR. Only
supported for single declarations of functions, re-declaration and
imports to come in a subsequent PR.

Per #7521.
2026-09-01 16:12:34 +00:00
Lucile Rose Nihlen 91c1e1049d Add parsing of default value exprs in pattern lists (#7631)
Adds parsing support only for default value expressions in pattern
lists, including leaving the default value unspecified with an
underscore `_`.

Per #7521.
2026-09-01 01:19:13 +00:00
Richard Smith 742684635a Add --verbose / -v flag to autoupdate. (#7692)
With this set, autoupdate will print messages indicating what it's
doing, such as the command that it's invoking to execute bazel. For
example:

```console
$ ./toolchain/autoupdate_testdata.py -v toolchain/check/testdata/basics/empty.carbon 
Detected --compilation_mode: fastbuild
/home/zygoloid/carbon-lang/scripts/run_bazel.py run -c fastbuild --experimental_convenience_symlinks=ignore --ui_event_filters=-info,-stdout,-stderr,-finish //toolchain/testing:file_test -- --autoupdate --print_slowest_tests 0 --file_tests=toolchain/check/testdata/basics/empty.carbon
[... normal output ...]
```
2026-08-31 20:59:40 +00:00
DavidLoftus dc2ee5bd5e Implement lsp/formatting for Carbon::LanguageServer (#7688)
Implements formatting support within LSP. Current implementation
performs while file formatting, we just replace file with output of
Carbon::Format()

https://github.com/carbon-language/carbon-lang/pull/7687 independently
improves formatting so that this output is somewhat decent.

Assisted-With: Gemini / Antigravity
2026-08-31 16:38:38 +00:00
Nicholas Bishop f519cccaf2 Add CallAction to allow deferring calls (#7682)
Calls with template callee or args can now be deferred via an
InstAction. This allows code like this to check:

```carbon
import Cpp inline '''
template<typename T>
struct C {};
''';

fn F(generic T: type) {
  let unused c: Cpp.C(T) = Cpp.C(T).C();
}
```
2026-08-28 18:00:58 +00:00
Richard Smith 0ea8fb2e74 Add documentation for our constrained overload set idiom. (#7681)
As used in #7679.
2026-08-27 18:51:30 +00:00
Chandler Carruth ba0011bca8 Enable rumdl markdown line-length enforcement and reflowing (#7667)
This should handle over-long lines. I had tried to make the normalize
method work, but it doesn't seem promising and so let's at least enable
this version.

Assisted-by: Antigravity with Gemini
2026-08-27 00:13:39 +00:00
Richard Smith 197cae22f1 Use consistent pattern to generate constrained overload sets. (#7679)
Follow the pattern used by eval_inst.h's `EvalConstantInst` to generate
declarations of an overload set that handles some but not all typed inst
classes. The pattern is:

* A template computes the signature to use for a particular overload,
producing a fallback `() -> void` signature for overloads that should
not exist.
* The `.def` file is used to generate a declaration per instruction
kind, whose signature is generated by the template.
* The `() -> void` signature that all the "should not exist" cases
generate is explicitly deleted.

This avoids the redundancy of manually declaring all the overloads, as
we did for `PerformAction`, and is less error-prone as it both catches
signature errors and definitions of overloads that are dead code and
should not exist, as it did for the `FacetAccessType` overload of
`LowerInst`.
2026-08-26 19:34:37 +00:00
Chandler Carruth 95fc6fae22 common: Decouple LLVM hashing dependencies from common/hashing.h (#7646)
- Move formatted printing to `hashing.cpp` instead of `hashing.h`
- Separate APInt and APFloat hashing specializations into a new
`hashing_llvm.h`
- Update toolchain/base dependencies and include sites that hash LLVM
data types to include `hashing_llvm.h`

Combined, this reduces the transitive includes caused by `hashing.h`.

Assisted-by: Antigravity with Gemini
2026-08-26 00:41:51 +00:00
Richard SmithandGeoff Romer c7dcc50768 language-server: Remove an unnecessary vector copy on each source change. (#7669)
We track a "next" index into each bucket when we insert instructions.
The insert loop effectively shifts each element in the "next" vector
left by one place, so if we instead start the bucket counts shifted one
place to the right, we can use the same vector for "next" and for the
bucket start indexes.

Assisted-by: Claude Code

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-08-25 21:26:46 +00:00
Özgür T. Önsoy 2bdcb1fa62 Unify MergeRedecl functions (#7632)
As discussed in #7620.
2026-08-25 18:43:52 +00:00
Geoff Romer 1410795bf7 Support arbitrary parameter forms in MakeGeneratedFunctionDecl (#7654)
This allows generated functions to have different forms for different
parameters, and by-ref or by-value return forms. As a byproduct, this
allows generated functions to supply a return type _inst_ ID when they
have one, which preserves things like location information.
2026-08-24 21:15:50 +00:00
Nicholas Bishop 0745bda899 Support using Carbon generic types as C++ template parameters (#7673)
Example:
```carbon
import Cpp library "<vector>";

class C(T: type) {
  var v: Cpp.std.vector(T);
}

inline Cpp '''
void F() {
  Carbon::C<int> c;
  c.v.push_back(123);
  std::cout << c.v.back() << std::endl;
}
''';
```

A new `CallCppTemplateAction` is used to delay performing the C++
template call until non-symbolic arguments are known.
2026-08-24 18:53:24 +00:00
Richard Smith 6515090557 Don't provide the "start of file" token to the bracket fixer. (#7675)
We previously passed in the start of file token, classified as
BracketTokenKind::Other, which allowed the bracket fixer to consider
corrections where it inserted tokens (such as a `{`) *before* the
start-of-file token.

Fixes #7672.
2026-08-24 18:14:10 +00:00
Nicholas Bishop 7f3584a123 Support constant InstActions (#7671)
Treat `InstConstantKind::InstAction` the same as
`InstConstantKind::ConstInstAction`. Drop `ConstInstAction`, since the
two now behave the same.

Fix eval for specifics in a couple places to handle `InstId::None`.
2026-08-24 18:05:15 +00:00
Chandler Carruth 369b8fd06f Fix top-of-tree Clang build warnings (#7670)
`-Wunused-template` was added to `-Wunused`, so clean up the things it
found. One of them was a bug in the Clang warning that I've worked
around and reported upstream:

https://github.com/llvm/llvm-project/issues/218429
2026-08-24 15:54:21 +00:00
Chandler CarruthandGeoff Romer 2b9fdd6e42 Fix the check that every registered diagnostic kind is declared (#7660)
`load_diagnostic_kind` matched `^\s+CARBON_DIAGNOSTIC_KIND` without
`re.MULTILINE` against a file whose entries start at column zero, so it
found nothing: `check_unused` was comparing an empty set of declarations
against every use and reporting nothing at all. The check has never run.

With it running, three kinds turn out to be registered and never
declared, and go: `BuildFailureRunningClangToLink`,
`BuildOutputFileOpenError`, and `BuildPreludeManifestError`.

`load_diagnostic_uses_in` looked only for `CARBON_DIAGNOSTIC`, so a
diagnostic declared with `CARBON_DIAGNOSTIC_ON_SCOPE` counted as unused,
and it read the two macros' own definitions in `diagnostic.h` as uses.
Both are why the kinds above could not simply be deleted before.

Assisted-by: Claude Code

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-08-21 22:21:54 +00:00
Chandler Carruth 40aa4419c0 Update the terminal library for rendering diagnostics (#7659)
Everything drawn into a buffer was checked against `columns()`. That is
right for wrapping and for line drawing, both of which have somewhere
else to put what doesn't fit, but wrong for `DrawText`, which exists for
text that must not be broken and sometimes has to run past the width
with no other answer available. It and `DrawCodePoint` now check only
that the column is non-negative and the row is one a grid can index, and
widen the buffer as far as the text needs; `DrawWrappedText`,
`DrawHorizontalLine`, `DrawVerticalLine`, and `DrawBox` are unchanged.
That also settles what a caller does after a drawing overhangs, since
`DrawEnd` exists so that a run can continue where the last one ended,
and that continuation was itself a checked error whenever the previous
run overhung. A column computed to be negative, such as a gutter
narrower than the line number it holds, still fails.

A color picked to read against black is hard to read against white, and
nothing in `Capabilities` said which a stream was going into.
`ChooseBackground` reads `COLORFGBG`, which `rxvt` and its derivatives
set to the foreground and background palette indices, and takes anything
it doesn't answer to be dark: guessing dark costs contrast, while
guessing light puts pale text on a pale background. Asking the terminal
itself with an `OSC 11` query is the accurate answer, and needs raw
mode, a timeout, and somewhere to put the reply, so there is a TODO for
it rather than an implementation.

Every corner, tee, and crossing came out of `Charset::Ascii` as `+`,
which left six of the shapes a diagnostic draws indistinguishable: the
rule closing a frame read as the one separating two snippets, and the
anchor opening a diagnostic as the one carrying it on. Each stand-in now
keeps the axis its line runs through, which leaves `+` meaning a
crossing and nothing else. A tee keeps its through-stroke and leaves the
branch to what is drawn beside it, and a corner is `.` where its line
leaves downward and `'` where it arrives from above, which is where
those characters sit in their cells. A box is a box again:

```
    +--+        .--.
    |  |   ->   |  |
    +--+        '--'
```

Assisted-by: Claude Code
2026-08-21 22:19:12 +00:00
Richard Smith c41033c315 Make template actions refer to values from the specific. (#7663)
When a template action is created, any (non-meta) instruction operand
will refer to instructions in the corresponding generic, or possibly to
a constant. This means that when the action is eventually executed when
forming a specific, it would see the generic value for that operand
rather than the intended specific value.

Fix this by refining `InstId` operands to refer to a corresponding value
in the specific, much like we would when rebuilding a constant in the
eval block.
2026-08-21 18:34:21 +00:00
Richard Smith 186a756b72 Handle more kinds of templated conversion. (#7662)
Generalize ConvertToValue template action to handle other kinds of
conversion target that don't perform initialization. Initializing
conversions will need more work since they also need to use a splice to
form the storage block.
2026-08-21 14:39:19 +00:00
4172f4d3f2 Report an uniterable for range once (#7661)
Building a `for` loop looks `Core.Iterate` up twice: once for
`NewCursor` to make the cursor, and again for `Next` to advance it. A
range that implements neither failed both lookups and reported both, so
a loop over something that isn't iterable produced two errors saying the
same thing about the same expression.

The second lookup is skipped when the first already failed, which is
what `BuildBinaryOperator`'s `diagnose` parameter is for. The
`ErrorInst` it returns instead does not reach the produced SemIR: the
loop is abandoned on the error either way.

Assisted-by: Claude Code

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-08-21 04:09:16 +00:00
Richard SmithandChandler Carruth c588eadb57 Rename and rearrange entities in tests to avoid name reuse (#7656)
Fix a bunch of cases where we use the same external name to mean
multiple different things in the same test. We've historically gotten
away with this, but under `--share-cpp-ast`, it becomes an error, at
least if the entity is either defined in, or used from, C++ code.

Assisted-by: Gemini via Antigravity (original change) and Claude Code
(suggested edits in review)

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
2026-08-21 01:36:40 +00:00
Richard Smith 6eb900dff5 Add template action for compound member access. (#7657)
This allows various templated constructs to get further through
checking, but typically we hit another unsupported action such as a
conversion or call, so it's not enough to make much work.
2026-08-20 15:45:02 +00:00
Chandler Carruth 631f8fb6d2 Modularize driver subcommands and prune unused dependencies (#7658)
- Subdivide toolchain/driver:driver into granular subcommand targets
(compile_subcommand, format_subcommand, lld_subcommand, etc.) to enable
independent compilation and linking.
- Decouple Target and TargetMachine options from driver headers.
- Prune unused direct dependencies across toolchain BUILD files.

Assisted-by: Antigravity with Gemini
2026-08-20 13:32:46 +00:00
Chandler CarruthandRichard Smith 9c486de2de Implement a terminal rendering library in common/terminal (#7597)
Rich diagnostic rendering needs a layer underneath it that knows what
the attached terminal can do and can position styled text in two
dimensions. This adds that layer, both as the foundation the diagnostics
rendering work will build on and as something usable directly for
ordinary CLI output. Nothing depends on it yet, so it lands and is
reviewed on its own.

Four libraries, each with its own tests:

- `color`: a color, either one of the 16 named ANSI colors or a 24-bit
RGB value, and the escape sequences that select it at a given color
depth.
- `style`: colors plus text attributes, and the escapes that move a
terminal from one style to another.
- `capabilities`: what the terminal behind a stream supports, detected
from the environment.
- `buffer`: a grid of styled cells that layout code draws into and that
renders itself once.

Rationale for the design decisions lives in the headers, next to what it
explains. Four things are worth review attention in particular:

- The color detection precedence documented on `ChooseColorMode`. It
settles how a `--color` flag, `NO_COLOR`, `CLICOLOR`, `FORCE_COLOR`, and
the terminal itself interact. The policy is a pure function of those
inputs, so the whole table is tested without touching the process
environment.

- `Charset`, which decides whether any UTF-8 processing happens at all.
Column counts only follow from code points if the terminal agrees about
the encoding, so anything short of a locale naming UTF-8 is treated as
bytes.

- `Buffer` owning column accounting instead of its callers, which is
what keeps double-width characters, combining marks, and stray bytes
from misaligning everything after them.

- The API surface, which is held to operations that nothing else covers.
Junctions in line art come only from lines overlapping, and turning a
style on or off is spelled as a transition to or from the default style.

`terminal_benchmark` covers style transitions, full-screen rendering,
and text drawing. On an M-series laptop, rendering an 80x24 screen in
which every cell changes style costs about 14us with color off and
76-95us with it, and drawing a 40-column line of source costs about
140ns without UTF-8 processing and 394ns with it.

Assisted-by: Gemini and Claude

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-08-19 21:54:46 +00:00
Richard Smith 3bbc03f527 Add better algorithm for repairing mismatched brackets (#7574)
Adds an algorithm to compute where to insert brackets to repair
bracketing mismatches during lexing. This takes indentation, as well as
a number of other cues, into account to predict where the brackets
should have gone. Detects when there is ambiguity between solutions and
makes no suggestion in that case. Reduces the problem by splitting on
properly bracketed top-level constructs, then uses a beam search to find
good candidate solutions quickly.

This includes both a fuzzer and an eval tool that can be used to
determine how well the algorithm fares against a given corpus of valid
Carbon code, by damaging it in various ways and seeing whether the
algorithm can correctly fix it. On all the eval modes, this algorithm
can correctly infer the positions for over 80% of lost brackets (and can
correctly restore 95+% of brackets in some modes), with low rates of
incorrect suggestions.

See added documentation for full details.

Assisted-by: Gemini via Antigravity, Claude via Claude Code
2026-08-19 18:55:32 +00:00
Richard Smith c06165d3e0 Require exported field types to be complete in the clang AST. (#7653)
It's not enough for field types of Carbon classes to be complete in
SemIR. If the field is exported to Clang, we also need the type to be
complete in Clang's AST, since Clang assumes it has a definition
available for the types of all fields of a complete class.
2026-08-19 17:45:34 +00:00
Richard Smith c78751338b language-server: Support simple semantic queries. (#7639)
Add support for "jump to declaration", "find references", type
information on hover. This support is strictly single-file for now; only
references and declarations within the same file are found. We could go
a bit beyond that, but to properly handle cross-file references we'll
need to build an index and a compilation database, which is beyond the
scope of this change.

On hover, we provide the type information for the instruction under the
cursor as-is. This is frequently not very useful, as the type of a
function F is simply "<type of F>", but is a starting point for richer
information.

Assisted-by: Claude Code
2026-08-19 14:35:05 +00:00
Chandler CarruthandRichard Smith a872123a73 Let Printable children default their comparison operators (#7642)
A defaulted `operator==` or `operator<=>` compares every base class
subobject, so children of `Printable` couldn't default their
comparisons: `Printable` had no comparison operators of its own, which
made the defaulted operator implicitly deleted. Children that want
member-wise comparison had to write it out by hand instead.

`Printable` is empty, so it now provides comparisons that always compare
equal. Its operands are constrained template parameter rather than
`const Printable&` so that they're only viable for comparing the base
class subobjects themselves. An overload taking `const Printable&` would
also be viable when comparing two `DerivedT` objects by converting them
to the base class, and would then both make children that provide no
comparison silently compare equal and displace the comparisons of
children that provide them through a conversion of their own, as
`EnumBase` does.

Some hand-written comparisons stay, for reasons unrelated to
`Printable`: using `= default` would change their meaning.

Adds `common/ostream_test.cpp`, which covers both the member and friend
forms of defaulting, the resulting comparison categories, and both of
the hazards above.

Assisted-by: Claude Code

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-08-19 01:00:13 +00:00
Richard Smith 047555bb1c Don't crash if a macro expands to an erroneous expression. (#7652)
For certain kinds of error, clang's parser will succeed but produce an
expression marked as "contains error". Clang's constant evaluator
asserts if given one of those, so return early if we encounter one.
2026-08-19 00:47:03 +00:00
Geoff Romer d21cc3197f Fix the textual IR name of WrapperBindingPattern (#7650)
`at_binding_pattern` is a relic from an earlier revision of #6930.
2026-08-18 22:31:27 +00:00
DavidLoftus 70b6abd6f1 Implement FloatLiteral addition and subtraction (#7621)
Addresses part of issue raised in
https://github.com/carbon-language/carbon-lang/issues/7159 by
implementing float.add & float.sub builtin for FloatLiteralValues

The following code now compiles:

```
let a: f64 = 1.0 + 1.0;
```

Code handles case where operands are both decadic (base 10) and dyadic
(base 2) real literals, with the result being whichever format results
in smaller mantisssa.

File tests assert equality by converting to f128, this can possibly be
improved once CompareWith is implemented for FloatLiteral too.

Assisted-By: Gemini
2026-08-18 17:47:58 +00:00
David Blaikie 0c186053ec Fix invalid parse tree with explicit runtime on invalid var (#7635)
Found by fuzz testing, root caused to #7479 - fixing this issue for Var
in a similar way to how it was fixed for Let in that patch (by adding
`RuntimeBindingName` to the possible things in the name binding.

Not sure if this would be better fixed by adding `RuntimeBindingName` to
`AnyRuntimeBindingPatternName` or something else?

Assisted-By: Gemini with Antigravity
2026-08-17 21:30:15 +00:00
Richard Smith 9a33fc1673 language-server: attempt to negotiate UTF-8 positions. (#7636)
We currently provide UTF-8 positions, since our source representation is
UTF-8 and we use byte offsets as column numbers, but the LSP protocol
default is UTF-16 column positions. Negotiate UTF-8 positions where
possible; this is supported by essentially every LSP client other than
VS Code.

In the case where we select a UTF-16 position, we continue to not do any
actual conversions, so our column offsets in such cases will continue to
be wrong on non-ASCII source files in VS Code.

Assisted by: Claude Code
2026-08-17 20:56:37 +00:00
Christopher Di BellaandRichard Smith 0f06fe65fa Add interface modifiers (#7625)
This adds most support for default and final methods. Missing components
include rejecting definitions for non-default/final methods, and
permitting out-of-line definitions.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-08-17 20:49:23 +00:00
Chandler Carruth 1e6d554cb6 Avoid pseudo-list markers in proposals/p000162-basic-syntax.md (#7641)
Change '1)' and '2)' to '(1)' and '(2)' to prevent the reflow engine
from treating them as ordered list markers when the paragraph is
wrapped.

Assisted-by: Antigravity with Gemini
2026-08-17 20:31:30 +00:00
Geoff Romer b13f0ddaf5 Document why patterns are in separate blocks (#7469) 2026-08-17 20:05:07 +00:00
Özgür T. Önsoy 77c1ad745e Remove redundant todo_access_modifiers.carbon test and dump-sem-ir-ranges flag (#7643)
We already test this in `access_modifiers.carbon`.

This also fixes a typo in the name of `access_modifiers.carbon` and
removes the `--dump-sem-ir-ranges` flag.
2026-08-17 19:58:41 +00:00
Chandler Carruth f58de06c0d parse, lex: Decompose AST extraction and forward-declare dump types (#7645)
- Enhance toolchain/parse/node_kind.def with category x-macros
(CARBON_PARSE_NODE_KIND_DECLARATION, CARBON_PARSE_NODE_KIND_EXPRESSION,
CARBON_PARSE_NODE_KIND_PATTERN, CARBON_PARSE_NODE_KIND_STATEMENT) that
default to CARBON_PARSE_NODE_KIND, keeping node_kind.def as the single
source of truth without manual expansion divergence.
- Decompose the monolithic toolchain/parse/extract.cpp translation unit
into separate compilation units for declarations, expressions, patterns,
and statements by expanding respective category x-macros.
- Forward-declare Tree and TokenizedBuffer in parse and lex dump headers
to reduce header inclusion depth.

Assisted-by: Antigravity with Gemini
2026-08-17 17:39:42 +00:00
DavidLoftus b2900105d7 Add "file://" document filter to vscode extension (#7647)
Currently VSCode extension will try share diff views / scratch files
with Carbon language server. This results in following error:

> Request textDocument/semanticTokens/full failed.
Message: in call to `textDocument/semanticTokens/full`, JSON parse
failed: clangd only supports 'file' URI scheme for workspace files at
(root).textDocument.uri
  Code: -32602 

The change here updates vscode extension to filter for
`file://*/**.carbon` URIs, this matches how clangd extension does it:
https://github.com/clangd/vscode-clangd/blob/893dd905c2a6a7844fe32592796ef593e6f3bca6/src/clangd-context.ts#L18

Also updates tsconfig.json to node16 module resolution, this is done
since [automatic dependabot
PR](https://github.com/carbon-language/carbon-lang/pull/7575) seems to
have broken the build.

```
src/extension.ts:28:8 - error TS2307: Cannot find module 'vscode-languageclient/node' or its corresponding type declarations.
  There are types at '/usr/local/google/home/davidloftus/carbon-lang/utils/vscode/node_modules/vscode-languageclient/lib/node/main.d.ts', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'.

28 } from 'vscode-languageclient/node';
          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Found 1 error in src/extension.ts:28
```


[vscode-languageserver-node](https://github.com/microsoft/vscode-languageserver-node/blame/93d5fe6f443dd6c8fe37f422c271d618edd121af/README.md#L159)
recomends updating to node16.
2026-08-17 16:48:03 +00:00
Chandler Carruth 864845c0c0 lex: Optimize token_kind_test compilation by consolidating test helpers (#7644)
- Consolidate token kind test assertions into helper functions using
EXPECT_THAT(spelling.str(), MatchesRegex(...)) under a single test case
rather than generating ~130 separate TEST classes and redundant matcher
instantiations.
- Slashes individual translation unit compilation time from 59.1s to ~3s
while keeping EXPECT_THAT for clear diagnostic error output and full
token coverage.

Assisted-by: Antigravity with Gemini
2026-08-16 18:21:16 +00:00
Richard Smith 277815fd94 language-server: Stub out support for some messages we don't handle yet. (#7637)
This suppresses warnings for unhandled messages where we currently have
nothing to do. No functionality change, except for less spam in the VS
Code output tab.

Assisted-by: Claude Code
2026-08-14 20:15:42 +00:00
Richard Smith db762cca85 language-server: Fix malformed symbol output on incomplete declarations. (#7638)
When computing the token range of a declaration, we were using the first
and last parse nodes to determine the first and last tokens. That's not
correct -- the parse tree nodes can be in a different order from the
tokens, so the first parse node need not be at the start of the
declaration. For a malformed declaration such as `fn f` (with no
terminator), the first and last parse nodes were both associated with
the `fn` token for error recovery, meaning that the function name wasn't
even within the symbol we handed back to the LSP client, in violation of
the LSP requirements. This led to VS Code producing an error and
discarding all symbols in the file.

Assisted-by: Claude Code
2026-08-14 19:56:07 +00:00
Nicholas Bishop 3bb245364f Support exporting class specifics to C++ (#7634)
This is similar to the previously-added support for accessing generic
carbon classes from C++, but with the specific defined by Carbon, rather
than being derived from template args supplied by clang in
`LoadExternalSpecializations`.

Example:
```carbon
class C(T: type) {
  var t: T;
}
alias A = C(i32);

inline Cpp '''
void F() {
  Carbon::A a;
  a.t = 123;
}
'''
```
2026-08-13 23:30:24 +00:00
DavidLoftus 54b7f1345a Implement Core.Negate for float literals (#7616)
Addresses part of issue raised in #7159 by implementing float.negate
builtin for FloatLiteralValues

The following code now compiles:

```
let a: f64 = -1.0;
```

To achieve this I switch the mantissa from being unsigned to signed.
Lexed literals within source file will still always be unsigned, however
it is now possible to create negative FloatLiteralValue constants. Main
non-local changes this causes is:

- All llvm::APInt parsing / printing calls flipped isSigned param
- Zero extension replaced with sign extension
- getActiveBits() replaced with getSignificantBits() for min bit width
calculation
2026-08-13 20:28:42 +00:00
Nicholas Bishop 2784f33221 Fix clang_decls InstId for generic class export (#7633)
The exported class was being inserted with a type inst ID as the key
(and looked up that way elsewhere), but when checking if the generic
class was already exported, the `first_decl_id` was being used. Make it
consistent, and opt for `first_decl_id` everywhere since it provides a
better location for diagnostics.
2026-08-13 17:30:20 +00:00
Christopher Di BellaandRichard Smith 4ea5ef45dd Skip ImplWitnessTable::elements_id when generating fingerprints (#7629)
Implementing interface modifiers causes an infinite loop when generating
fingerprints because the witness value generates a fingerprint that's
dependent on something dependent on the witness value. We've debugged
this to the witness table's `elements_id` field.

This hack is a workaround for creating a new block type whose value is
not codependent with its identity.

Co-authored-by: Richard Smith <richard@metafoo.co.uk>

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-08-11 22:39:30 +00:00
Özgür T. Önsoy cb5e9b6555 Diagnose missing interface definition in impl files (#7622)
We can now implement this since #4071 is resolved.
2026-08-11 21:06:17 +00:00
Özgür T. ÖnsoyandRichard Smith 1f22ba91bb Handle import refs in TryGetExistingDecl (#7620)
This solves the problem where `interface` imports are incorrectly
diagnosed as duplicate names in impl files.
Follows the implementation logic used in `handle_class.cpp`.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-08-11 19:29:46 +00:00
Richard Smith 0f93cbd370 Support ranges with overloaded const/non-const begin and end. (#7627)
If we find an overload set containing mulitple methods, discard any
non-const methods and try again. This allows libc++'s `std::vector` to
be iterated with range-based for.
2026-08-11 19:14:37 +00:00
Christopher Di Bella e10827ed84 Diagnose when an impl is added for something that has a custom witness (#7623)
The toolchain crash is easily diagnosed, but looking at the log doesn't
offer immediate insight into why the program crashes. This provides us
with a graceful exit.
2026-08-11 19:13:47 +00:00
Özgür T. ÖnsoyandChristopher Di Bella c63bbf3598 Update observe design doc to reflect proposal #7545 (#7617)
This adds the new restrictions introduced for `observe` declarations
inside `interface` definitions to the docs.

---------

Co-authored-by: Christopher Di Bella <cjdb.ns@gmail.com>
2026-08-11 19:09:38 +00:00
Lucile Rose Nihlen b64d863a8f roll llvm to a6b0af7536ef (#7624)
This updates LLVM by a month.

Removes patch 10 which landed upstream.

Adds a new patch to remove more libc-dependent files
from compiler-rt builtins.
2026-08-10 19:37:40 +00:00
Nicholas Bishop de1cd701cf Add initial support for exporting generic classes (#7595)
Currently only fields of generic classes are exported; methods of
generic classes are not supported yet.

Simple example:

```carbon
class C(T: type) {
  var t: T;
}

inline Cpp '''
void F() {
  Carbon::C<int> c;
  c.t = 123;

  Carbon::C<float> c2;
  c2.t = 124.5;
}
''';
```
2026-08-10 17:47:45 +00:00
Nicholas Bishop 700e42a195 Simplify extra_name in ExportFunctionSpecializationToCpp (#7587)
Rather than appending to the thunk name for each template arg, use the
mangled form of the specific ID.
2026-08-10 15:09:58 +00:00
e7050af1c9 Fix bitwise OR markdown table pipe escapes (#7602)
https://docs.carbon-lang.dev/docs/design/expressions/#operators

Escaping `|` is needed to prevent markdown table interpreting it as a
cell edge, however using `\|` in backticks shows the backslash too.
Solution: use `<code>` instead. This overflows the `|` table cell but it
seems to render correctly anyway.

Co-authored-by: David Blaikie <dblaikie@gmail.com>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-08-07 18:21:03 +00:00
Christopher Di Bella 8e90e2a855 Change String.size from u64 to i64 (#7614)
`String.size` is likely to be a signed word-sized integer in the future,
(per a Discord conversation). Changing to `i64` now allows us to iterate
over a string's contents using `IntRange`.
2026-08-07 18:14:50 +00:00
Richard Smith 06a056d795 Fix crash exporting a Carbon function to C++ without a code generator. (#7619)
We can't rely on a `clang::CodeGenerator` existing when compiling C++
code; we don't build one unless we're actually emitting code for the
current file any more.
2026-08-07 18:06:36 +00:00
Richard Smith 7e861cd3c0 Split up failure tests into separate splits. (#7603)
As requested in review of #7596.
2026-08-07 17:52:06 +00:00
Richard Smith c278bea3c5 Support LP64 platforms such as Darwin where int64_t is long long. (#7596)
Add `Core.CppCompat.[U]Long64` to represent a 64-bit long that is not
`i64`. Treat it as being "just slightly smaller than" `i64`, like we
treat `Core.CppCompat.LongLong64` as being "just slightly larger than"
`i64`, so that we get implicit conversions `Cpp.long` -> `i64` ->
`Cpp.long_long` on all targets.

This follows the direction of proposal #5448, and seems like the obvious
extension of the `[U]Long32` and `[U]LongLong64` types added in #6275
for targets of this "shape".

Assisted-by: Gemini via Antigravity
2026-08-07 16:10:01 +00:00
Richard Smith ee2b3888ef Split up tests for bad imports. (#7612)
Clang treats "file not found" as a fatal error and stops emitting more
diagnostics after reaching it, so these tests don't work in
`--share-cpp-ast` mode if they are all in the same file. So split them
into distinct test files.
2026-08-06 22:55:25 +00:00
Richard Smith a1e843718e Create modules for header imports. (#7613)
Make multiple imports of the same header only parse it once per C++
domain. Reuse of the same header in `--share-cpp-ast` mode now reuses
the representation.

Importing a Carbon file with C++ dependencies now makes those transitive
C++ dependencies in the same C++ domain visible too.

Assisted-by: Gemini via Antigravity
2026-08-06 13:32:37 +00:00
Özgür T. Önsoy b9692a0e77 Restrict observe declarations to names that are part of the enclosing interface (#7545)
This proposal restricts `observe` declarations in an `interface` to only
reference names dependent on `.Self`, generic parameters, and associated
constants that are part of the enclosing `interface`, with the following
exceptions:

-   Allow at most one unrelated value in an equivalence (`==`) chain.
- Allow unrelated values that satisfy the `impls` constraint immediately
in an
    `observe .. == .. impls`.
2026-08-05 21:23:21 +00:00
Richard Smith 08605122f4 Improve diagnostic when test split unexpectedly succeeds or fails. (#7608)
When a split file in a test unexpectedly succeeds or fails, include the
test filename in the error as well as the name of the split. This should
make it a bit easier to figure out which test failed from a failing test
log.
2026-08-05 17:34:52 +00:00
Richard Smith dea290db81 Only lower files we are going to emit. (#7611)
Move `--output-last-file-only` and output filename synthesis logic out
of the general-purpose compile driver and into the `carbon compile`
subcommand, which is the only thing that should be using them. Track on
CompilationUnit whether it is being lowered, or whether it exists only
to be imported into other units.

`carbon compile` now never lowers inputs that it discovered for itself,
only inputs that were specified on the command line. In particular, it
doesn't lower (and throw away the result of lowering) the prelude any
more. This makes the toolchain tests about 10% faster in my crude
measurements.

Also, we now do not create a clang `CodeGenerator` for input files that
we are not lowering, similarly saving compilation time for units that
exist only to be imported, not lowered.

One minor change: we use the same mechanism to determine whether an
input is being lowered and to determine what the output filename is.
This means that `--phase=lower` and `--phase=optimize`, which lower but
don't produce an output file, still need an output filename to be
specified now in some cases. Given those are just debugging tools, I
think that's fine.

Assisted-by: Gemini via Antigravity
2026-08-05 12:52:24 +00:00
Richard Smith f776cf0744 Improve readability of test failures. (#7609)
Reduce use of gmock matcher infrastructure for diagnosing mismatches,
and instead manually stream an explanation of the difference. The
gmock-style "EXPECT_THAT" approach adds an unsuppressable "Actual: ..."
line into the output that only contains unreadable and redundant noise.
We're getting zero value from using a matcher diagnostic here, so don't.

Before:
```
Value of: SplitOutput(test_file.actual_stdout)
Expected: matches elements with unified diff
  Actual: { "--- else.carbon", "", "constants {", "  %F.type: type = fn_type @F [concrete]", "  %empty_tuple.type: type = tuple_type () [concrete]", "  %F: %F.type = struct_value () [concrete]", "  %H.type: type = fn_type @H [concrete]", "  %H: %H.type = struct_value () [concrete]", "  %pattern_type: type = pattern_type bool [concrete]", "  %b.param_patt: %pattern_type = value_param_pattern [concrete]", "  %b.patt: %pattern_type = at_binding_pattern b, %b.param_patt [concrete]", "  %If.type: type = fn_type @If [concrete]", "  %If: %If.type = struct_value () [concrete]", "}", "", "file {", "  %If.decl: %If.type = fn_decl @If [concrete = constants.%If] {", "    %b.param_patt: %pattern_type = value_param_pattern [concrete = constants.%b.param_patt]", "    %b.patt: %pattern_type = at_binding_pattern b, %b.param_patt [concrete = constants.%b.patt]", "  } {", "    %b.param: bool = value_param call_param0", "    %.loc8: type = type_literal bool [concrete = bool]", "    %b: bool = wrapper_binding b, %b.param", "  }", "}", "", "fn @If(%b.param: bool) {", "!entry:", "  %b.ref: bool = name_ref b, %b", "  if %b.ref br !if.then else br !if.else", "", "!if.then:", ... }, unified diff (- expected, + actual):
=== diff in expected elements 4 to 11 (1-based index):
    %F.type: type = fn_type @F [concrete]
    %empty_tuple.type: type = tuple_type () [concrete]
    %F: %F.type = struct_value () [concrete]
- is equal to "  %G.type: type = fn_type @G [concrete]"
- is equal to "  %G: %G.type = struct_value () [concrete]"
    %H.type: type = fn_type @H [concrete]
    %H: %H.type = struct_value () [concrete]
    %pattern_type: type = pattern_type bool [concrete]
=== diff in expected elements 37 to 44 (1-based index):
    br !if.done
  
  !if.else:
- is equal to "  %G.ref: %G.type = name_ref G, file.%G.decl [concrete = constants.%G]"
- is equal to "  %G.call: init %empty_tuple.type = call %G.ref()"
    br !if.done
  
  !if.done:
=== diff end
```

After:
```
Value of: testing::Value(SplitOutput(test_file.actual_stdout), testing::ElementsAreArray(test_file.expected_stdout))
  Actual: false
Expected: true
unified diff (- expected, + actual):
=== diff in expected elements 4 to 11 (1-based index):
   %F.type: type = fn_type @F [concrete]
   %empty_tuple.type: type = tuple_type () [concrete]
   %F: %F.type = struct_value () [concrete]
-  %G.type: type = fn_type @G [concrete]
-  %G: %G.type = struct_value () [concrete]
   %H.type: type = fn_type @H [concrete]
   %H: %H.type = struct_value () [concrete]
   %pattern_type: type = pattern_type bool [concrete]
=== diff in expected elements 37 to 44 (1-based index):
   br !if.done
 
 !if.else:
-  %G.ref: %G.type = name_ref G, file.%G.decl [concrete = constants.%G]
-  %G.call: init %empty_tuple.type = call %G.ref()
   br !if.done
 
 !if.done:
=== diff end
``` 

Assisted-by: Gemini via Antigravity
2026-08-04 21:23:36 +00:00
Richard Smith 34a2e270f4 Fix C++ code generation in --share-cpp-ast mode (#7605)
Instead of creating a CodeGenerator per CppDomain, and then crashing in
lowering when we try to consume the same llvm Module multiple times,
create a CodeGenerator for each CppFile within the domain.

For now, we mulitplex all of Clang's ASTConsumer output to all code
generators, which means that any strong external definitions within a
Carbon file (for example, in an inline `Cpp` fragment) will be emitted
to all output files in the same `CppDomain`, resulting in link errors
due to symbol redefinitions. This will be addressed later. But this
should be sufficient for Carbon compilations in which such symbols are
not defined.

We also don't yet attempt to classify which compilations will need C++
code generation, and instead create a clang `CodeGenerator` for every
Carbon file that has C++ imports. For `carbom compile`, only one Carbon
file will need code generation, and yet we still build multiple
`CodeGenerator` objects in general. Fixing this requires more plumbing
from the driver, and this will also be handled in a follow-up.

Assisted-by: Gemini via Antigravity
2026-08-04 17:59:06 +00:00
a9cc7bf490 File concatenation principle (#6031)
We propose a principle that it's always possible to inline the import of
a library from within the same package without changing the meaning of
or diagnostics applied to the code. This is similar to the textual
inlining of an `#include` statement in C++, but is slightly less
general. Cross-package imports place the imported names inside the name
scope of the package, so inlining those necessarily changes the paths
for name lookup.

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
2026-08-03 17:20:59 +00:00
Richard Smith 46b5482bb4 Create a Clang module per Carbon file. (#7594)
This isolates the C++ imports in different Carbon files from each other
in `--share-cpp-ast` mode, so that a Carbon file can only see the
portions of the shared Clang `ASTContext` that it actually imported.

Assisted-by: Gemini via Antigravity
2026-07-31 22:43:56 +00:00
Geoff Romer a5f3d45717 Fix uses of "dependent" to mean "dependency" (#7599)
For consistency with existing code I've chosen to use "dep" as an
abbreviation for "dependency", even though it's somewhat ambiguous with
"dependent".
2026-07-31 22:07:32 +00:00
Dana Jansens 2f58185c44 Find the current impl from accesses in a named constraint being implemented (#7592)
When implementing a named constraint, like `impl as N`, any accesses
through `Self` in the named constraint need to get the value of the
associated constant from the impl's witness table. This isn't possible
immediately, since the impl does not even exist until the declaration is
complete. We use the same model as for accesses found directly in the
impl declaration, but applied to the point where accesses in the named
constraint are substituted during identify to point at the impl's self
type. To get there, we need LookupImplWitness instructions in the named
constraint, when re-evaluated during construction of their enclosing
specific, to evaluate to ImplSelfWitness when they are a reference to
the type and interface being implemented.
2026-07-31 20:17:06 +00:00
Dana Jansens db88edfa1e Disable RTTI in the toolchain (#7552)
This reduces object sizes which is desirable for linking speed.

zygoloid did some analysis to determine if any of our code requires RTTI
for `dynamic_cast` here:
https://github.com/carbon-language/carbon-lang/pull/7532#discussion_r3611196721:
> The only thing I found is that libc++ requires dynamic_cast in order
for std::print to correctly write Unicode to terminals on Windows

We use `llvm::print` functionality, not `std::print`, so this doesn't
affect our toolchain.

Note that libc++ and libc++abi are built with RTTI enabled. It is
explicitly allowed to use different compiler flags when building these
libraries even though they share some headers with users of the
libraries, so this does not cause ODR violations.
2026-07-31 20:09:48 +00:00
Richard Smith 6e9e871b74 Fix handling of recursive macros. (#7593)
Inject the name of a macro rather than its contents when computing its
expansion. If the macro refers to itself, it will not expand within its
own body, rather than expanding once.

Switching from `EnterTokenStream` to `EnterToken` exposed that our Clang
preprocessing environment was a little broken -- we reached the end of
the primary source file and starting tearing stuff down before we
actually finished parsing, which we were mostly getting away with before
but aren't any more. Enabled Clang's incremental processing mode to fix
this. This causes Clang to remain in the main source file when it
reaches EOF instead of popping it. This also causes the diagnostics for
invalid `module;` declarations to change, but in a way that seems not
really any worse than before.

Also slightly changes the diagnostics produced from macro expansion
failures. The new diagnostics are a bit more precise -- they now capture
the outermost level of macro expansion -- but we don't do a good job of
rendering the Clang snippet attached to the "in macro expansion" context
note yet, so the context looks a bit weird: we get two different
snippets attached to the same diagnostic.
2026-07-31 20:09:03 +00:00
Richard Smith 87d2234d5c Fix create_compdb.py. (#7598)
pathlib's suffix includes a `.`, so make sure we include one when
validating the suffix. Otherwise, most files are missing from the
database!
2026-07-31 19:30:25 +00:00
Richard SmithandChristopher Di Bella 9bcee64b32 Add some encapsulation to CppDomain (#7579)
Start tracking the domain within `CppContext`s instead of having them
duplicate its fields. This allows us to remove the shared ownership of
the clang parser.

---------

Co-authored-by: Christopher Di Bella <cjdb.ns@gmail.com>
2026-07-31 18:08:13 +00:00
Dana Jansens 9b8a2124b9 Don't require non-class types to be complete to convert from them (#7590)
In Convert, we require the source value's type to be compete so that we
can look for `base` classes and `adapt` relationships. However these can
only be present in a `ClassType`, so we only need `ClassType`s to be
complete.

Reduce the requirement in Convert to not complete types that are not a
`ClassType`, and which can not contain a `ClassType` as part of their
class.

Ideally we would only _only_ require the `ClassType` itself to be
complete, and only if we're looking for a base or adapt. However lower
depends on us completing all Convert source types that contain a class.
This seems to suggest we're lacking checks for complete types somewhere
else and Convert is making up for it. A TODO has been added. The
`toolchain/driver/testdata/compile/optimize/optimize_debug.carbon` test
is an example that CHECKs due to failing to verify the LLVM module if we
do not compute the complete type of all class-containing types in
Convert.

The critical step this PR is doing is to stop trying to complete a
`FacetType` when converting from a facet. This avoids trying to complete
a named constraint when converting `Self` inside that named constraint.
Doing so causes a cycle when the conversion of `Self` is performed in
eval of an `extend require` decl, since requiring the named constraint
to be complete re-evaluates the `extend require` decl again. A test is
added that crashed in an infinite loop before this change.

It also depends on #7584, which was intended to be an optimization but
is now load bearing. Because converting `Self` leaves an impl lookup
inst behind, and if that inst is re-evaluated inside impl lookup (by
forming a specific of a `require` decl through identify) then we have a
similar cycle.
2026-07-31 17:35:21 +00:00
Geoff Romer 6429c1655c Make the tree structure more explicit in parse dumps (#7591)
It's hard to track 2-space indent levels across large vertical gaps, and
it can be hard to suppress the instinct to read the dump as if it were
preorder. This change introduces a new dump mode that addresses both
problems by using box-drawing characters to explicitly represent the
parent-child edges of the tree. This new mode is the default, but the
old behavior remains available with
`--parse-dump-format=yaml-postorder`.
2026-07-31 16:44:18 +00:00
Nicholas Bishop 2c91e5f5de Take specific into account in CalculateCppFieldOffsets (#7588)
Add a `SpecificID` arg to `Class::GetStructTypeFields`, and use that in
`CalculateCppFieldOffsets`. Also include the specific in the
`clang_decls` lookup. This has no immediate effect since no specific
class fields are being exported yet, but will be useful for exporting
generic classes.
2026-07-30 21:50:13 +00:00
Richard SmithandGeoff Romer 6f68a51286 Filter binaries out of compilation database. (#7589)
Since we started bootstrapping, our stage-1 toolchain binaries have been
inputs to stage-2 compilations, and to the compilation database logic,
they're indistinguishable from generated sources. This caused us to get
compilation command lines for them, which clangd would try to index by
parsing the binary, and would either run incredibly slowly or crash.

Filter out non-source files from the compilation database.

In passing, also emit the JSON dump without whitespace, which makes the
database a bit smaller and faster for clangd to parse.

Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-07-30 21:39:40 +00:00
Richard Smith f473ca994a Fix a collection of small language-server bugs in document_symbol. (#7581)
Don't CHECK-fail if the file contains mismatched braces and generates an
unbalanced parse tree.

Properly balance the start and end of symbols. This previously caused
errors to be reported in clients such as VS Code.

Compute a correct range for definitions. Don't assume we can find the
matching `}` for an opening symbol, since some of our definitions are
delimited by a `;` instead.

Assisted-by: Gemini via Antigravity
2026-07-30 21:35:08 +00:00
Dana Jansens 239ad9fe8b Disable C++ exceptions in the toolchain (#7532)
Pass `-fno-exceptions` when building the toolchain. We do not use
exceptions, so we do not have any try/catch in main, so uncaught
exceptions just unwind and exit. They do not hit our signal handler and
we do not print the stack trace.

Instead of adding a try/catch in main, and redirecting that, we can
build with `-fno-exceptions`. This turns any throw into an `abort()`.
And indeed with that flag, the following code crashes and prints a stack
trace:
```cpp
  std::variant<int, bool> a = {1};
  std::get<bool>(a);
```

Note that libc++ and libc++abi need to be built with exceptions enabled.
It is explicitly allowed to use different compiler flags when building
these libraries even though they share some headers with users of the
libraries, so this does not cause ODR violations.

Fixes #5225
2026-07-30 20:15:03 +00:00
Dana Jansens a5ba0a0f45 Use GetConstantValueInSpecific to get the impl's specific interface after deduction (#7584)
During impl lookup, for each (generic) impl candidate, we form a
specific for that impl by deducing its generic arguments. Then we
compare the query interface against the impl's specific interface. That
comparison needs the deduced arguments applied to the impl's specific
interface. Previously we were doing this by getting the impl's
constraint facet type with the impl's specific applied (via
`GetConstantValueInSpecific()`) and then identifying that facet type
with the impl's deduced self.

Identify is a fairly expensive operation. It runs subst, trying to
replace `.Self` references. It walks named constraints. It collects
require declarations. We're looking at making it do _more_ in the future
too, including rewrite constraint resolution and collecting rewrite and
same-type constraints. For this reason we have a cache to make it cheap
on the second run, but it's still a very heavyweight operation to
involve in impl lookup, when all we want is to apply the impl's specific
to its target interface.

We almost have all the information we need to avoid the identification
step. We have the impl's specific after deduction. And we have the
SpecificInterface that the impl is targeting in the `Impl` struct. When
we form the specific for the impl itself, we resolve the declaration
block and form new constant values for all instructions in there, but
that does not cover the SpecificInterface that we're storing in the
`Impl` struct. So we add a new instruction to the impl's eval block,
which will be symbolic when the impl is generic and the target interface
depends on a generic parameter. And we store the `InstId` in the `Impl`
struct. This allows us to gets its constant value later with the impl's
specific applied. From that constant value we can then pull out the
SpecificInterface that the impl is targeting.
2026-07-30 19:59:10 +00:00
oli-ej a683fb574b Add initial support for parsing struct patterns (#7446)
Implements parsing of struct patterns as per
https://github.com/carbon-language/carbon-lang/issues/6680.

This handles the full and short syntax given in the [design
doc](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/pattern_matching.md#struct-patterns),
but the handling of the shorthand syntax may need to change as I move on
to implementing Check support (currently the shorthand is represented as
one of `LetBindingPattern`, `VarBindingPattern`, or `VariablePattern`).

This implementation assumes that the answer to
https://github.com/carbon-language/carbon-lang/issues/7404 is that
trailing commas are allowed in the struct pattern, except for the case
where an `_` is present, in which case the next token must be the
closing brace `}`.
2026-07-30 16:37:46 +00:00
arhwx 8f224222a1 Export abstract methods as pure virtual (#7578)
`abstract fn` was exported to C++ as a plain virtual function rather
than a pure virtual one, so the class wasn't abstract and could be
instantiated from C++.

Abstract functions no longer get a thunk since there is no definition to
call. The tests are prefixed with `fail_` since an abstract class still
errors on `Core.Destroy` regardless.
2026-07-30 00:29:53 +00:00
Nicholas Bishop 198e447e4a Add specific_id and pattern_inst_id to ClangDecl (#7583)
Exporting class fields in class specifics will require looking up
`ClangDecl`s by the field's `InstId` and the class's `SpecificId`. Add
the `specific_id` to ClangeDecl, and rework the reverse lookup to use a
`Set` with a `KeyContext` rather than a `Map`. The `Lookup` method now
takes an optional `SpecificId` argument, although currently it is always
`None`.

For `VarStorage`, reverse lookup is performed by the pattern `InstId`
rather than the `InstId` of the `VarStorage` itself, so also add
`pattern_inst_id` to `ClangDecl`, and provide a separate
`LookupByPatternInstId` method for reverse lookups. For this lookup, the
`inst_id` part of the key is set to `None`, so only the pattern's
`InstId` is used for lookup.
2026-07-29 19:53:12 +00:00
Nicholas Bishop f4e7a84df2 Remove Class::fields_exported (#7586)
In preparation for exporting specific classes, remove `fields_exported`.
A class's fields will be exported for each specific, so a single boolean
won't work.

Instead, check in `ExportAllFieldsToCpp` if `clang_decls` already has an
export for this field, and skip if so. Once specifics are supported,
this lookup will use both the field's `InstId` and `SpecificId`.

The above changes aren't quite sufficient, because if a field fails to
be exported, it will keep being attempted on each call to
`ExportAllFieldsToCpp` resulting in multiple errors. Fix this by storing
an invalid `FieldDecl` in `ClangDeclStore` if an error occurs. This lets
`ExportAllFieldsToCpp` know not to reattempt export, and
`ExportFieldToCpp` returns `nullptr` for invalid fields same as before.
2026-07-29 19:27:05 +00:00
Lucile Rose Nihlen 3a37b89da2 Change carbon_library Bazel parameters back to srcs and hdrs (#7585)
It's come up that `carbon_library` better represents a linkage unit
instead of having to closely mirror the Carbon language library concept.
Given that we can have more than one API file in a linkage unit, and
that `srcs` and `hdrs` gives better compatibility with other C++
tooling, this PR switches `impls` and `api` back to `srcs` and `hdrs`,
and fixes up the broken code.
2026-07-29 18:49:54 +00:00
arhwx 6ea2087f0a Export methods taking self as const member functions (#7577)
A method declared with `self` does not modify the object, but it was
exported to C++ as a non-const member function, so calling it on a const
reference would fail.

```carbon
class C {
  fn Get(self);
}

inline Cpp '''
void F(const Carbon::C& c) {
  c.Get();
}
''';
```
```
error: 'this' argument to member function 'Get' has type 'const Carbon::C', but function is not marked const
```

Import already maps `f() const` to `fn f(self)`, and this PR implements
the same behavior for exporting. No ref-qualifier is added, since that
maps to `ref self`, so that is unchanged.

`GetThisArg()` now builds `this` from the method instead of the parent
record, so that it picks up the method's const-qualifier.
2026-07-29 17:28:54 +00:00
Dana Jansens f51c075f8b Avoid symbolic witnesses for .Self in an impl decl (#7564)
Point symbolic witnesses into `.Self` written inside an impl decl at the
impl that is being declared. This is tricky because the impl does not
yet exist. So we use a new instruction `ImplSelfWitness` which _will_ be
replaced by the `ImplWitness` once it becomes available. The
`ImplSelfWitness` acts like a symbolic witness, except it does not
perform lookup, since we know which impl we will get a witness from.

This prevents us from finding other impls when performing lookups into
`.Self` in an impl decl, which produces incorrect/incoherent results.
2026-07-29 16:25:23 +00:00
Geoff Romer 2ac425e591 Test calling std::make_unique on a Carbon type from Carbon. (#7563)
As a byproduct, this introduces a new top-level directory
`integration_tests` for tests like this.

This also fixes a latent bug with name mangling on Mac.
2026-07-29 16:07:10 +00:00
Dana Jansens 8ac0edb280 Add a failing test where frozen .Self is passed in generic argument and never thawed (#7582) 2026-07-29 15:18:43 +00:00
Richard Smith dbd24035f8 Fix libunwind advice. (#7580)
Debian packages github.com/libunwind/libunwind as `libunwind-dev`, and
packages LLVM libunwind as `libunwind-N-dev`, with no meta-package to
install the latest version of LLVM libunwind. The libunwind-dev is not
built as PIC, so doesn't work in our build setup. So a specific version
of LLVM libunwind must be installed.
2026-07-29 13:37:46 +00:00
Richard Smith 7d89ac98c7 Add a flag to build a single ASTContext shared across all compilations (#7567)
Instead of building one Clang `ASTContext` per compilation, the
`--share-cpp-ast` flag causes us to build a single `ASTContext` and
share it across all contexts. One new abstraction is added: `CppDomain`
represents the Carbon-side view of a Clang AST that might be shared
across multiple `SemIR::File`s. This object owns the Clang instance and
the AST.

For now, we have no isolation between the C++ state exposed to different
Carbon compilations, and we have no multiplexing of generated LLVM IR
from C++ into different Carbon compilations, so the mode is not usable
yet. The plan is to keep it behind a flag until it's ready.

Assisted-by: Gemini via Antigravity
2026-07-28 00:19:40 +00:00
josh11bandJosh L 4e77f9b6c8 Add slide and video links for NDC 2026 talks (#7572)
Co-authored-by: Josh L <josh11b@users.noreply.github.com>
2026-07-27 22:50:58 +00:00
josh11bandJosh L 1cef214e6a Move adapters from generics to class design docs (#7561)
Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
2026-07-27 22:33:37 +00:00
Richard Smith f38085cbca Add /external to .gitignore (#7568)
Fixes #7430
2026-07-27 19:13:36 +00:00
Richard Smith 13f335befa Add instructions for Carbon development on Windows. (#7565)
In passing, switch `uv` instructions from curl pipe to installation with
`cargo`, and add missing libunwind dependency.
2026-07-27 17:45:02 +00:00
dependabot[bot] a1efff4bcc Bump brace-expansion from 5.0.7 to 5.0.8 in /utils/vscode in the npm_and_yarn group across 1 directory (#7575)
Bumps the npm_and_yarn group with 1 update in the /utils/vscode
directory:
[brace-expansion](https://github.com/juliangruber/brace-expansion).

Updates `brace-expansion` from 5.0.7 to 5.0.8
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/96a63c0011c0288846ad41773c73e3fbd0906b59"><code>96a63c0</code></a>
5.0.8</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/a1bd33999ea75262c4749fff3bbb0d1372bd07b5"><code>a1bd339</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/592a36fd18455c37f81e0848a642d84c63147fa7"><code>592a36f</code></a>
Bump tar from 7.5.16 to 7.5.20 (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/127">#127</a>)</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/bd146909cd6c7bedde61a5d6428ba252860a0159"><code>bd14690</code></a>
Bump brace-expansion from 2.0.2 to 2.1.2 (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/126">#126</a>)</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/e729ba647887042f16b531fdb3d8ac3d7762ccad"><code>e729ba6</code></a>
Bump ws from 8.19.0 to 8.21.1 (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/124">#124</a>)</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/5bf809e2c6dbde64dd9ff7acec7335ea7d5f6d9e"><code>5bf809e</code></a>
Remove Node.js 18 support (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/110">#110</a>)</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/8cb5716bae73e98b63f18aae8e48b6f12522e759"><code>8cb5716</code></a>
Bump markdown-it from 14.1.0 to 14.2.0 (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/113">#113</a>)</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/43122df5955511b8ec82718fa28903b6dbad0d26"><code>43122df</code></a>
Bump sigstore from 4.1.0 to 4.1.1 (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/119">#119</a>)</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/69bd65a46e9361c992bb43b898c9df0d9720cc5c"><code>69bd65a</code></a>
Bump <code>@​sigstore/verify</code> from 3.1.0 to 3.1.1 (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/118">#118</a>)</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/b3bfbf06746fe92645ffd17d1f44e95f480cfe59"><code>b3bfbf0</code></a>
Bump <code>@​sigstore/core</code> from 3.1.0 to 3.2.1 (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/117">#117</a>)</li>
<li>See full diff in <a
href="https://github.com/juliangruber/brace-expansion/compare/v5.0.7...v5.0.8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=brace-expansion&package-manager=npm_and_yarn&previous-version=5.0.7&new-version=5.0.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 15:01:22 +00:00
arhwx 07111398d8 Anchor the orphan rule on the first owning declaration (#7573)
`DiagnoseOrphanImpl` used `definition_id`, but #7140 defines the anchor
as the first owning declaration, so a class declared but not defined was
rejected, which is why three cases in `orphan.carbon` were marked
`fail_todo`, and they now pass.

`fail_use_extern_class` no longer errors, `handle_class.cpp` never
passes the `extern library` name into the class entity, so `C` is
treated as locally owned and counts as an anchor. The expected error is
replaced with a TODO in the test, but it should come back once `extern
library` is implemented for classes.
2026-07-27 14:49:06 +00:00
dependabot[bot] 420b34ed9d Bump the npm_and_yarn group across 1 directory with 2 updates (#7569)
Bumps the npm_and_yarn group with 2 updates in the /utils/vscode
directory: [fast-uri](https://github.com/fastify/fast-uri) and
[linkify-it](https://github.com/markdown-it/linkify-it).

Updates `fast-uri` from 3.1.2 to 3.1.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/fastify/fast-uri/releases">fast-uri's
releases</a>.</em></p>
<blockquote>
<h2>v3.1.4</h2>
<h2>⚠️ Security Release</h2>
<p>Fix for <a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-v2hh-gcrm-f6hx">https://github.com/fastify/fast-uri/security/advisories/GHSA-v2hh-gcrm-f6hx</a></p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/fastify/fast-uri/compare/v3.1.3...v3.1.4">https://github.com/fastify/fast-uri/compare/v3.1.3...v3.1.4</a></p>
<h2>v3.1.3</h2>
<h2>⚠️ Security Release</h2>
<ul>
<li>Fixes: <a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-4c8g-83qw-93j6">https://github.com/fastify/fast-uri/security/advisories/GHSA-4c8g-83qw-93j6</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.3">https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.3</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/fastify/fast-uri/commit/6aeece669e4166b2446a89f17c07a3b15dfb7ed4"><code>6aeece6</code></a>
Bumped v3.1.4</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/2d50fbabc80e4d0884fe0f6a98fe118ce6faa353"><code>2d50fba</code></a>
fix: reject literal backslash in URI authority</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/0549fe35b0d482233f3be2816439f3ec803603fa"><code>0549fe3</code></a>
Bumped v3.1.3</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/2a6d357a18a68e6d812824379fd3388a1ae50d05"><code>2a6d357</code></a>
Merge commit from fork</li>
<li>See full diff in <a
href="https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.4">compare
view</a></li>
</ul>
</details>
<br />

Updates `linkify-it` from 5.0.1 to 5.0.2
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/markdown-it/linkify-it/blob/master/CHANGELOG.md">linkify-it's
changelog</a>.</em></p>
<blockquote>
<h2>5.0.2 / 2026-07-02</h2>
<ul>
<li>Fixed DoS in <code>mailto:</code> links (restrict user name to 64
chars).</li>
<li>Restricted user/pass part length in links.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/markdown-it/linkify-it/commit/50a0c914f834b201cab25ff4faefd1f832b37332"><code>50a0c91</code></a>
5.0.2 released</li>
<li><a
href="https://github.com/markdown-it/linkify-it/commit/de3b88554b5e465d5fa19914e2a1801ebb3069ef"><code>de3b885</code></a>
Update package hooks</li>
<li><a
href="https://github.com/markdown-it/linkify-it/commit/13effaaa4600d1fcff9f63c38fecdd601bfd83e7"><code>13effaa</code></a>
Add package lock</li>
<li><a
href="https://github.com/markdown-it/linkify-it/commit/39d748dbfc77534e9be04d87cd9f57a13b9b4216"><code>39d748d</code></a>
Bump c8</li>
<li><a
href="https://github.com/markdown-it/linkify-it/commit/00ce8771ac0c3e6784dcacaa1625ca0fc1b62a12"><code>00ce877</code></a>
Drop tlds deps</li>
<li><a
href="https://github.com/markdown-it/linkify-it/commit/ecde82341a1b2e349b03eb01f3e1d2cc105bcd7f"><code>ecde823</code></a>
Update benchmark to mitata</li>
<li><a
href="https://github.com/markdown-it/linkify-it/commit/23c62cdd14ef36e89c175c6726e2229e5b76e75f"><code>23c62cd</code></a>
Refactor demo / doc build and publish</li>
<li><a
href="https://github.com/markdown-it/linkify-it/commit/fd63f3b4ab433ca3561304409b572eed465706cd"><code>fd63f3b</code></a>
CI config update</li>
<li><a
href="https://github.com/markdown-it/linkify-it/commit/f4ea5afaa6a8e1109c44898158d4910b3fb128fb"><code>f4ea5af</code></a>
demo: update bootstrap &amp; layout</li>
<li><a
href="https://github.com/markdown-it/linkify-it/commit/1454fb645f00c33a05edbded18640d5078ba7710"><code>1454fb6</code></a>
lint: dim warnings</li>
<li>Additional commits viewable in <a
href="https://github.com/markdown-it/linkify-it/compare/5.0.1...5.0.2">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 14:36:16 +00:00
Richard Smith 39916ad2ca Split up C++ diagnostic emitter and factor it out. (#7558)
Split the diagnostic emitter into a separate emitter (regietered with
Clang) and listener (registered with the emitter). The purpose of this
split is to make the Clang emitter not depend on the `Check::Context`,
so that we can use it, and hence the same Clang instance, with multiple
`Check::Context`s. A fallback listener is registered to collect and emit
any diagnostics produced while we don't have a `Check::Context`
registered with the emitter.

Assisted-by: Gemini via Antigravity
2026-07-24 20:14:50 +00:00
Dana Jansens 48a03e6a1c Remove TODO to check for orphan impls (#7562)
Orphan impls are found at the end of checking the file, in
`ValidateImplsInFile()`.
2026-07-24 19:11:21 +00:00
Geoff RomerandNicholas Bishop 3cb143e878 Add support for exporting Carbon functions as constructors. (#7560)
Co-authored-by: Nicholas Bishop <nbishop@nbishop.net>
2026-07-24 18:43:12 +00:00
Lucile Rose Nihlen de68b19784 fix build-runtimes subcommand for prelude (#7559)
Fixes a bug in CarbonPreludeBuilder that caused runtimes build
failures due to a incorrectly created output directory.

Also removes some debug printing that I mistakenly checked in
last time.
2026-07-23 18:42:27 +00:00
Dana Jansens 8c42d383c7 Use SubstOperandsSkipType to allow using SubstPeriodSelf on a facet type (#7556)
This makes SubstPeriodSelf more generally useful, with one less gotcha.
Previously calling it with a facet type would just do nothing.

This is possible now because we
- Have SubstOperandsSkipType to make use of
- Have banned constructs which introduce ambiguous .Self, so we don't
need to try avoid finding undesired .Self insts in facet types in other
positions (like in specifics).
2026-07-23 18:31:46 +00:00
Richard Smith 9477e32936 Add explanation of TypeInstId to check docs. (#7555) 2026-07-22 22:53:30 +00:00
Richard Smith 43df8c474a Replace typeid(T).name() with llvm::getTypeName<T>(). (#7554)
This produces prettier, demangled type names, and works when building
with `-fno-rtti`.

Before:
```
Optional N6Carbon5Parse13NodeIdForKindIL_ZNS0_8NodeKind16LibrarySpecifierEEEE: begin
```
After:
```
Optional Carbon::Parse::NodeIdForKind<Carbon::Parse::NodeKind::LibrarySpecifier>: begin
```

Assisted-by: Gemini via Antigravity
2026-07-22 20:25:41 +00:00
Dana Jansens 453b5474e4 CHECK that the self is a symbolic type when looking for a Destroy and its type is a facet (#7553)
As a follow up to #7546 (and see the discussion there), verify our
assumptions that you can't have an object of type facet where the facet
is not symbolic, and then need to find a Destroy witness for the object.
2026-07-22 18:08:35 +00:00
Nicholas Bishop 33bd953a2e Deduplicate class export and fix identifier error in ExportNameScopeToCpp (#7550)
Added method_alias.carbon test so that the class export code in
`ExportNameScopeToCpp` is tested. Refactored `ExportClassToCpp` so that
`ExportNameScopeToCpp` can reuse that code.

Moved the `identifier_info` code in `ExportNameScopeToCpp` into the
namespace block, because the name scope's name ID is not valid for
classes.

Added a call to `CompleteType` for classes exported via
`ExportNameScopeToCpp`, otherwise a "queried property of class with no
definition" assert is later reached (when adding methods) in the call
chain `BuildCppToCarbonThunkDecl` -> `DeclContext::addHiddenDecl` ->
`CXXRecordDecl::addedMember` -> `CXXRecordDecl::data`.
2026-07-22 18:02:44 +00:00
pjmlp 0b0918274c Added video link for NDC 2026 talk, Carbon: graduating from the experiment. (#7549) 2026-07-22 18:02:26 +00:00
Dana Jansens 9796b7bc78 Add tests (mostly failing) that rewrites in an impl can satisfy required constraints (#7547)
A rewrite's RHS value should be used when checking that the rewritten
associated constant impls an interface.
2026-07-22 17:00:53 +00:00
Dana Jansens d2ac9b3933 Diagnose where in a binding that introduces a .Self that does not refer to the binding (#7517)
This is in addition to finding a `where` on the RHS of another `where`.
Since a generic binding introduces `.Self`, any `where` expression that
isn't part of a facet type modifying the binding itself would introduce
an ambiguous `.Self`.

Add virtual parse nodes for let, var, and form bindings, which goes
before the type. This allows us to track if `where` appears in the
binding's type. We only need to look for an invalid `where` if any
appeared in the type. We combine these three nodes together into a
single node kind, which requires us to remove the name from it as a
child. We move it up to the Pattern node again, and rename the
PatternStart nodes to PatternTypeStart as they are now located in the
middle of the Pattern nodes, just before the type.

And we only need to thaw `.Self` in generic bindings. Non-generic
bindings can only have `.Self` through a `where` expression, since the
name is not provided otherwise to non-generic bindings. And `where`
expressions thaw their `.Self` independently. So the binding only needs
to thaw a `.Self` that it introduced, which is only for generic
bindings.
2026-07-22 16:45:00 +00:00
Dana Jansens 21dc5cde04 Ensure where requirements in named constraints are visible to lookups (#7299)
Require rewrite and same-type constraints that do not depend on `.Self`
to be satisfied when a facet type is identified, since those constraints
may not be found later.
2026-07-22 16:18:57 +00:00
josh11bandJosh L a48d14e451 Add memory safety talk links to README (#7520)
Co-authored-by: Josh L <josh11b@users.noreply.github.com>
2026-07-21 21:43:44 +00:00
Dana Jansens f8f50cb167 Don't try produce a custom witness for a symbolic value of type facet (#7546)
For a symbolic value with a type being a facet, we need to find a
witness either from the facet's type or from an impl. Custom witness is
for producing a concrete final witness, but there is no such witness in
this case. We should fail to find a witness, and defer to impl lookup to
find the Destroy witness.

Currently it tries to find a witness from the facet type, but it ignores
named constraints, which means it's incomplete at best. But there's no
value in trying to say that the value is trivially destroyable, when it
has a type that impls Destroy. It may not be trivial, and we can't
actually make a witness for it while symbolic.
2026-07-21 19:39:31 +00:00
Dana Jansens 63757d281e Require all impls constraints in impl as to be satisfied (#7531)
We checked that requirements inside the impl-as target interface were
satisfied. But we also need to check that requirements coming from the
constraint facet type, or named constraints that it targets, are
satisfied.
2026-07-21 19:31:54 +00:00
Nicholas Bishop 2e61685658 Remove duplicate check for exporting a specific class (#7541)
The check for a specific in `TryMapClassType` is unnecessary;
immediately after it calls `ExportClassToCpp`, which has the same check.
The latter also has a `context.TODO`, which provides a clearer error.

Also improved the `LocId` in `ExportClassToCpp` to use the location of
the first decl rather than the empty location of the class type. This is
the same fix as
https://github.com/carbon-language/carbon-lang/pull/7533, just applied a
little more broadly. This makes the `context.TODO` above point at the
class rather than the start of the source file.
2026-07-21 18:35:20 +00:00
dependabot[bot] d234de41e7 Bump brace-expansion from 1.1.14 to 1.1.16 in /utils/vscode in the npm_and_yarn group across 1 directory (#7543)
Bumps the npm_and_yarn group with 1 update in the /utils/vscode
directory:
[brace-expansion](https://github.com/juliangruber/brace-expansion).

Updates `brace-expansion` from 1.1.14 to 1.1.16
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/juliangruber/brace-expansion/releases">brace-expansion's
releases</a>.</em></p>
<blockquote>
<h2>v1.1.15</h2>
<ul>
<li>Backport v5.0.6 change to v1 (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/111">#111</a>)
0b09384</li>
</ul>
<hr />
<p><a
href="https://github.com/juliangruber/brace-expansion/compare/v1.1.14...v1.1.15">https://github.com/juliangruber/brace-expansion/compare/v1.1.14...v1.1.15</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/447763a91a613cfa67ac73096cbc1de9a2304f97"><code>447763a</code></a>
1.1.16</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/d74e63030c012e3b7ae81657b8d665619cd51b95"><code>d74e630</code></a>
fix: v1 backport for CVE-2026-13149 (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/122">#122</a>)</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/2203f4f4895eba16c4d408b4219ce1b8e5f6ff24"><code>2203f4f</code></a>
1.1.15</li>
<li><a
href="https://github.com/juliangruber/brace-expansion/commit/0b0938410732370559704230724ca4a44d1b29fd"><code>0b09384</code></a>
Backport v5.0.6 change to v1 (<a
href="https://redirect.github.com/juliangruber/brace-expansion/issues/111">#111</a>)</li>
<li>See full diff in <a
href="https://github.com/juliangruber/brace-expansion/compare/v1.1.14...v1.1.16">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=brace-expansion&package-manager=npm_and_yarn&previous-version=1.1.14&new-version=1.1.16)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 18:18:02 +00:00
David Blaikie 973e499f82 new_proposal.py fix for rumdl missing anchor from dangling ToC TODO entry (#7540)
rumdl error broke the proposal script execution:
```
rumdl check..............................................................Failed
- hook id: rumdl
- exit code: 1

  proposals/p000003-test-proposal.md:15:5: [MD051] Link anchor '#todo-initial-proposal-setup' does not exist in document headings

  Issues: Found 1 issues in 1 file (11ms)
```
So update the script to remove the TODO entry in the ToC to match
removing the TODO section.
2026-07-20 23:01:29 +00:00
oli-ej 0553d5f165 Update Textmate for types in a list and add unused keyword (#7535)
Setting "type" to bright red to make comparison obvious:
(screenshots taken from VSCode)
Before
<img width="446" height="150" alt="Screenshot 2026-07-20 135558"
src="https://github.com/user-attachments/assets/81249005-99f1-47bb-af4f-ad6e8f2404fb"
/>

After
<img width="429" height="149" alt="Screenshot 2026-07-20 135738"
src="https://github.com/user-attachments/assets/acc83086-fe19-4a18-a833-968daed56229"
/>

I'm not super familiar with textmate/highlightjs, but I think what was
happening on line 5 is that the "type" scope is being extended to the
`=` after `.y`.
Adding `,` as an "end" token fixes this, and I think this is what
highlightjs already does.

I've also added "unused" as a keyword.
2026-07-20 22:37:37 +00:00
Dana Jansens 4b46e63b41 Move IdentifiedFacetType to its own file (#7542)
I wanted to move it to check but there'd still be an
IdentifiedFacetTypeId and we keep all Ids in sem_ir. So leaving it in
sem_ir, but moving it to its own file. This more clearly separates
DeclaredFacetTypes and IdentifiedFacetTypes.
2026-07-20 21:45:17 +00:00
Nicholas Bishop 3a3439c681 Support exporting functions with generic return types to C++ (#7537)
Add a `return_type_id` field to `FunctionInfo` in
`toolchain/check/cpp/export.cpp`. As with the `explicit_params` field,
`ExportFunctionSpecializationToCpp` updates this to the return type in
the specific.

Refactored `BuildCppFunctionDeclForCarbonFn` into
`BuildCppFunctionDeclForNonGenericCarbonFn` and
`BuildCppFunctionDeclForGenericCarbonFn`, with `BuildCppFunctionDecl`
containing shared code.

The `generic_type_impls_interface.carbon` test is updated to include a
generic return type.
2026-07-20 20:44:58 +00:00
Lucile Rose Nihlen a502ff006e move carbon_prelude to carbon_runtimes.bzl (#7539)
The `carbon_prelude` macro was defined in bazel/carbon_rules/defs.bzl.
This file contains `carbon_library` and `carbon_binary` which are
mostly intended as example Bazel rules for building Carbon binaries.

The `carbon_prelude` macro, however, is used now to build the runtimes
for the Carbon toolchain. We move it to a more central location where
the rest of the runtimes are processed, in
toolchain/runtimes/carbon_runtimes.bzl.
2026-07-20 20:38:17 +00:00
Özgür T. Önsoy 40028098ce Rename FacetTypeInfo to DeclaredFacetType (#7528)
This resolves a TODO comment in code.
2026-07-20 19:15:09 +00:00
Dana Jansens 085e45093e Avoid consuming a token after an invalid match_first opening (#7538)
If the `match_first` is not followed by `{` avoid consuming whatever
comes after it. Recover by leaving whatever comes next alone. It could
even be the `FileEnd` token, and then we would crash when we read off
the end of the token stream looking for `FileEnd`.
2026-07-20 19:09:16 +00:00
David Blaikie 99cda60df7 Add location to clang classes created via reverse interop (#7533)
The specific location's not ideal (rather than the open curly, or
semicolon for a declaration - the two locations should be the `class`
and then the class name), but the same as we do for functions for now &
enough to get by.

This specifically also fixes a crash I found due to dtors being
generated without a location (because implicitly created functions would
use the class's location), creating a function call without a debug
location, which fails the LLVM IR verifier.
2026-07-18 00:13:05 +00:00
Richard Smith 5c544f7c2f Give thunks weak_odr linkage. (#7525)
We can end up emitting the same thunk from multiple compilations in some
cases -- in particular, when the thunk is wrapping a function that is
either synthesized by the compiler or imported from C++. When this
happens, we will have multiple-definition link errors unless we allow
redefinitions across multiple files.

It'd be nice to detect when we need to do this and when we don't, but
that's a bit tricky to do in practice. Ideally, in fact, we would use a
different strategy, and emit the thunks as discardable definitions in
each compilation that *uses* them. But for now emitting them with
weak_odr linkage seems like a good way to make progress.
2026-07-17 13:25:19 +00:00
Richard SmithandDana Jansens 643ab57f5a Refactor CppRange interface and fix a crash exposed by doing so. (#7515)
Split up the CppRange interface into smaller parts, with the intent of
improving the diagnostic quality and making the implementation easier to
understand.

This also makes the implementation details of the CppRange machinery
private, which breaks one of the existing tests; that test is split into
two files, one which tests the low-level machinery works, and another
that tests the resulting prelude behavior.

This change exposes a crash in `where` expression handling, where we
would perform a substitution that creates a new `SpecificConstant` that
refers into a region of a generic that has never been resolved. Fix that
by resolving the definition region of a generic if eval sees a
`SpecificConstant` that refers into it. This is usually not necessary
because something else should have resolved that region first, but that
doesn't happen here.

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-07-17 00:31:12 +00:00
Lucile Rose Nihlen 31cc20f6e3 move Core//range into prelude (#7524)
As discussed in the 2026-06-14 toolchain open meeting, `Range`
is a better fit for the prelude than as a general library in
`Core`. This PR moves `Range` into the prelude, and updates
the build infrastructure and various tests to reflect that
change.
2026-07-16 21:36:45 +00:00
Dana Jansens 2d9e3fee67 Use identified facet type to get impl-as target (#7519)
This supports impl lookup choosing an impl that targets a generic
interface through a named constraint, without crashing.

Instead of assuming the impl's target is a facet type containing an
interface, we use the identified facet type to find the specific
interface it targets.
2026-07-16 19:11:23 +00:00
Dana Jansens 0848cf941d Look outside constant values for designators in where constraints (#7367)
The constant value may lose the designator during eval, such as an
`ImplWitnessAccess` that resolves to some concrete type. Look in the
non-canonical instructions instead.
2026-07-16 19:00:06 +00:00
Richard Smith ee81a4e48e Bump vscode extension version to 0.0.9 for release. (#7522) 2026-07-16 18:49:59 +00:00
Dana Jansens b3e9dd3ea2 Consolidate checking for and rejecting other_requirements in impl lookup (#7518)
Since the introduction of `other_requirements`, we now have a dedicated
step in impl lookup for checking that the requirements of the query
facet type are satisfied. That is the place where we will be checking
same-type constraints, which `other_requirements` signals the presence
of.

Consolidate all checking of `other_requirements` to that step, which
reduces our use of `FacetTypeInfo` (as opposed to the
`IdentifiedFacetType`) and removes interest in same-type constraints
from code that is not related to them.
2026-07-16 18:09:46 +00:00
Lucile Rose Nihlen 625f2ca629 precompile and cache Carbon prelude (#7432)
Refactors the link driver to automatically compile and cache the carbon
prelude for use in linking.

Implements a `carbon_library` rule for compiling the Core library
dependencies in the examples.
2026-07-16 18:02:14 +00:00
Richard Smith 90f6654b48 Fix crash attempting constant evaluation of a constexpr constructor. (#7514)
We can't use a `CallExpr` to call a constructor; use a
`CXXConstructExpr` instead. While this fixes the crash and gets us past
the initial constant evaluation, we still can't map the constant value
back into Carbon, so this doesn't actually make constexpr constructors
work yet. But it does stop Clang from crashing.

Fixes #7498.
2026-07-16 15:17:08 +00:00
Richard Smith 703529fc55 Destroy temporaries at the end of expression statements. (#7513)
Instead of tracking the cleanup scope depth on entry to each scope,
track an "ambient" cleanup scope depth that's *after* the destructors of
local variables in that scope. This gets increased to include the
destructors of local variables when we create a name-binding
declaration. Then, when we reach a point where temporaries should be
destroyed, run cleanups that are after the ambient cleanup scope depth
on the stack. This happens:

* At the `;` of a statement expression.
* At the `)` of an `if` or `while` statement.
* After performing the implied `HasValue()` call in a `for` statement.

Per informal agreement with leads, this means we lifetime-extend all
temporaries created in the initializer of a name-binding declaration to
the full scope of that declaration, but that temporaries created in an
expression statement are destroyed at the `;`.
2026-07-16 15:12:39 +00:00
Dana Jansens 81e495ee53 Impls in final match_first block aren't always final (#7512)
If an earlier impl may match a more specific query, then later impls can
not be treated as final for the given query.

See
https://github.com/carbon-language/carbon-lang/blob/de8b03faa3178ae683d8e7124fbcba81eb88e00c/proposals/p005337-interface-extension-and-final-impl-update.md#using-associated-constants-from-impls-in-a-final-match_first
2026-07-16 13:10:09 +00:00
Dana Jansens cda1f256bb Use match_first to prioritize impls (#7492)
Allow (don't diagnose) impls that have the same type structure if they
are associated with the same match_first block. Similarly, allow final
impls (made final by their enclosing match_first block) that overlap in
type structure when they are associated with the same match_first block.

If the type structures are the same, choose the first impl from the
match_first block.same.

If the type structures overlap but are not the same, and share a
match_first block, then we should choose the first overlapping impl from
the match_first block. This is true both for final and non-final impls.
See
https://github.com/carbon-language/carbon-lang/blob/de8b03faa3178ae683d8e7124fbcba81eb88e00c/proposals/p005337-interface-extension-and-final-impl-update.md#impl-selection-algorithm

But in the non-final case if there is an overlapping impl outside the
match_first, it can win if it's more specific.
2026-07-15 21:25:25 +00:00
Richard Smith 9ae73d2847 Handle signature mismatch when a Carbon function overrides a C++ virtual function. (#7499)
When a Carbon virtual function overrides a C++ virtual function, we need
to export it with the C++ signature in order for it to work as an
override. Instead of mapping the C++ signature into Carbon and then back
again, use the original C++ signature from the base class as the
signature exported to C++.

Also add documentation explaining how we use thunks in C++ interop,
including in this new virtual function handling logic.
2026-07-15 18:42:03 +00:00
Chandler Carruth 14e8bffe24 Refactor ValueStore to use custom iterators and ranges (#7506)
Replace the llvm::map_range based anonymous ranges returned by values()
and enumerate() with custom range and iterator types. This avoids
exposing complex template return types and improves encapsulation. But
most importantly, it is *much* cheaper to compile.

This change:
- Defines ValueStoreIterator, ValueStoreEnumerateIterator, and
ValueStoreRange templates in the Internal namespace.
- Exposes them via aliases in ValueStore: Iterator, ConstIterator,
Range, MutableRange, and EnumerateRange.
- Supports safe implicit conversion from Iterator to ConstIterator.
- Supports operator-> even when the value type is returned by value
(e.g., llvm::StringRef) by conditionally const-qualifying the pointer
type.
- Ensures C++20 comparison consistency with custom operator<=> and
operator== definitions.
- Restricts construction of these iterators and ranges to ValueStore
methods by using private constructors and friend declarations.

Assisted-by: Antigravity with Gemini
2026-07-15 17:06:52 +00:00
Dana Jansens 168420f805 Allow impl redecl in match_first after a definition (#7491)
Previously the last decl had to be the definition. Now we allow a
declaration after a definition, so that the user can write a match_first
block last, and put (re-)declarations of impls in it, after the
definitions have already been written elsewhere.

We track the location of the decl that was associated with a match_first
block so that we can correctly point to it in diagnostics when an impl
is written twice in match_first blocks. Since impls may not be
redeclared across an import boundary, we will never have a `SemIR::Impl`
with a match_first from a different file in a redeclaration, so we don't
need to import the location of a previous decl that was in a match_first
for diagnostics. As such we just store a LocId on the `SemIR::Impl`
struct.
2026-07-15 14:12:37 +00:00
Chandler Carruth 474090f439 Record //@... directive lines as comments when lexing. (#7494)
The `//@include-in-dumps` and `//@dump-sem-ir-begin`/`-end` tooling
directives were consumed for their side effects without a comment
record, so the tokens and comments together no longer reconstructed the
source: tooling that re-emits a file from them, such as `carbon format`,
silently dropped the directive lines. Now each recognized directive line
is also recorded as an ordinary full-line comment alongside its side
effect.

Adjacent full-line comments coalesce into one comment record only within
a category, determined by the byte after the `//` introducer: ordinary
comments (whitespace, or the end of the line or file), `//@...`
directives, and invalid introducers. A transition between categories
starts a new record, so a directive next to a comment block is its own
comment, while all the invalid spellings lump together to keep the
diagnostic noise at one per run.

The category boundary also fixes a lost directive: the invalid-comment
bulk skip compared only the `//` prefix, so `//!x` directly above
`//@dump-sem-ir-begin` absorbed the directive line and its side effect
was never recorded. Invalid comment runs now skip line by line (they
start from a diagnosed error, so they are not hot) and stop at a
whitespace or `@` introducer; the SIMD bulk skip handles only ordinary
comment blocks, whose prefix comparison already includes the whitespace
byte.

Nothing outside the lexer and the formatter reads comment records, and
lex dumps do not include comments, so no other behavior changes.

Assisted-by: Claude Code
2026-07-15 03:21:04 +00:00
Chandler Carruth 1c1870cd10 Remove typed_nodes.h from tree.h and node_stack.h (#7510)
Remove toolchain/parse/typed_nodes.h from transitive imports of tree.h
and node_stack.h to reduce compile-time overhead in toolchain/check.

Add an explicit include of typed_nodes.h to tree_and_subtrees.cpp where
it is needed for instantiating templates.

Assisted-by: Antigravity with Gemini
2026-07-15 01:08:15 +00:00
Chandler Carruth 0a09ab1e3c Reduce Clang header exposure in check_unit.h (#7509)
Remove clang/Frontend/CompilerInvocation.h from check_unit.h and forward
declare clang::CompilerInvocation instead to reduce header exposure.

Add an explicit include of CompilerInvocation.h to check_unit.cpp.

Assisted-by: Antigravity with Gemini
2026-07-15 00:59:58 +00:00
Chandler Carruth 730db0f903 Reduce Clang header exposure in lower library (#7511)
Remove clang/CodeGen/ModuleBuilder.h from toolchain/lower/file_context.h
to reduce transitive dependencies. Forward declare clang::CodeGenerator
and clang::FunctionDecl instead.

Add an explicit include of ModuleBuilder.h to context.cpp where the
complete types are required.

Assisted-by: Antigravity with Gemini
2026-07-15 00:59:03 +00:00
Richard Smith 45d1c74df8 Stop using ArrayStack for the cleanup stack. (#7505)
Because we merge cleanups across scopes in various cases, and want to
use linear indexes into the complete stack, the ArrayStack abstraction
is getting in the way more than it's helping. Switch to just a
SmallVector.

This loses the unit testing of the MergeIntoGrandparent logic. This is
covered indirectly by check tests still, but direct testing of it is a
bit tricky given that ScopeStack isn't set up for use without a Context.
2026-07-15 00:47:47 +00:00
Chandler Carruth 5b48274e58 Add extern template declarations for ValueStore in inst.h and entity_name.h (#7507)
Add extern template declarations and explicit template instantiations
for ValueStore<InstId, Inst> and ValueStore<EntityNameId, EntityName> to
prevent redundant template instantiations.

Place them in their respective domain files (inst.h/cpp and
entity_name.h/cpp) to maintain modularity.

Assisted-by: Antigravity with Gemini
2026-07-14 22:58:05 +00:00
Lucile Rose Nihlen 6c010816a4 update llvm to 615644763ffc (#7504)
* Fixes some build issues in clang headers that have changed upstream
* Adds patch 0011 to work around an upstream commit that broke Bazel
builds
2026-07-14 20:32:11 +00:00
Dana Jansens d47de6443e Freeze the .Self type and make non-extend constraints available after where (#7501)
The type of `.Self` introduced by `where` may contain a `.Self` inside
it. Freeze the type so that we have a consistent view of `.Self` inside
the facet type, where they are all frozen.

The type of `.Self` only has extend constraints from the LHS of the
`where`. So we need to copy any non-extend constraints into the
`where_stack` so they are available as early-impls and can be used for
impl lookups on the RHS of the where. We need to freeze any `.Self`
references in these just as we do for rewrite constraints.
2026-07-14 19:04:33 +00:00
Chandler Carruth 2e59804b9f Remove a long-deprecated and no-op flag (#7496)
See https://github.com/bazelbuild/bazel/pull/29931 -- this is going away
in upstream Bazel as well and has been a no-op since 2021.
2026-07-14 17:08:20 +00:00
Richard SmithandGeoff Romer 6e62a7d4a2 Destroy locals at the end of blocks, not only on return (#7448)
Destroy local variables and temporaries at each `}`, and when branching
with `break` and `continue`. In `for` statements, destroy loop variables
along with anything created within the loop at the end of each loop
iteration, and destroy the cursor and range object when the loop
terminates.

Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-07-14 00:59:22 +00:00
Richard Smith 8bae79f44a Update to a more recent LLVM. (#7488)
Fix a few API issues. There's also a newly-added file in compiler-rt
that is not supposed to be built by default but is not being excluded
properly by a glob. Added a patch to exclude that and sent
https://github.com/llvm/llvm-project/pull/208861 upstream.
2026-07-13 22:29:39 +00:00
Dana Jansens 8ea32187d4 Disallow impl in match_first twice (#7493)
Prevent dead code by diagnosing putting the same `impl` in a
`match_first` block more than once.
2026-07-13 21:58:06 +00:00
Nicholas Bishop 3d71858386 Improve type substitution in ExportFunctionSpecializationToCpp (#7495)
Instead of manually creating a map from symbolic types to concrete
types, create a Specific and look up parameter types via that Specific.

This allows C++ to call a Carbon function like `fn F[T: type](unused t:
T*) {}`. See generic_pointer.carbon.
2026-07-13 21:11:31 +00:00
Richard Smith b6179ecbbb Disable prelude import in language-server tests. (#7489)
This was substantially slowing down the overall test suite.

Fixes #7453

Assisted-by: Gemini via Antigravity
2026-07-13 20:48:28 +00:00
Richard Smith a8afc45ed5 Omit empty observe: blocks from formatted SemIR. (#7487)
Also add SemIR ranges to observe test, removing 5K lines of
uninteresting SemIR output.
2026-07-13 20:38:19 +00:00
Chandler Carruth 7612b3d821 Address post-merge review comments on #7254 and terminology discussions (#7490)
Document the phase of associated constants, which the discussions of
contextual phase defaults never covered: an associated constant is
always a checked generic binding. This is deliberately not presented as
a contextual default, because no other phase is possible for the
construct; correspondingly, no phase keyword (including `template`) is
allowed on one. Also rework the associated constants section of the
design README where the migration left a non-sequitur ("set to
compile-time values ... and so are defined using a `let` declaration"):
describe the `let` syntax and the binding's contextual phase separately.

Use consistent terminology for bindings versus constants. Bindings are
"checked" or "template" (generic) bindings, replacing the "symbolic
binding" and bare "generic binding" terms, so "symbolic" now only
describes constants and values. The "Symbolic facet bindings" section of
the generics details becomes "Checked facet bindings". The
expression-phase terms "symbolic constant" and "template constant" are
unchanged, and the binding-pattern definitions now name the constant
each kind binds. Template bindings are additionally described as
dependent and late checked, with each instantiation providing the
binding's value.

Also normalize the remaining "regular parameter" mentions to "runtime
parameter" to match the binding terminology, and describe the
compile-time "let template" as introducing a template generic binding
"T: C" whose uses are template constants.

Assisted-by: Claude Code
2026-07-13 19:22:35 +00:00
Dana Jansens d46b040290 Connect impls to their containing match_first block (#7486)
When we check an `impl` decl, find the containing `match_first` block,
if any, and store a connection to it in the `SemIR::Impl` structure,
along with the impl's position in that `match_first` block so that we
can sort/prioritize the `SemIR::Impl`s later.

Also, update the `is_final` flag if the `match_first` block is modified
as `final`. But ensure we diagnose trying to put a `final impl`, or a
redeclaration of one, in a `match_first` block. Also diagnose if an impl
is attached to a `match_first` block more than once - either in two
different blocks or in the same block at different positions.

Putting the `match_first` connection on the `SemIR::Impl` structure
means we have to import it, so implement import and add a smoke test for
that, which ensures nothing explodes.

Drop the `scope_stack` entry for the `match_first` block, as it was not
needed. Once we started tracking the `match_first` size on the `Context`
class, it became more straightforward to just store the `match_first`
decl `InstId` in the same place. The `match_first` block is not supposed
to act like a different scope for the purpose of redecls anyhow, so it's
a bit simpler this way.
2026-07-13 19:14:38 +00:00
Dana Jansens d6f9559cfb Diagnose if match_first is not in a valid scope (#7482)
It must be in a namespace, function, or class. In particular, it can't
be in another match_first, doing so immediately associates any impl
inside with two different match_first scopes, but we only want them
associated with one for prioritization.
2026-07-11 14:54:42 +00:00
Dana Jansens a46f4863db Basic checking for match_first blocks (#7481)
Push a scope_stack entry for match_first blocks, and handle impls being
inside those scope entries. An impl should not use the `match_first`
block as its "enclosing scope" for the purpose of deciding if the impl
is a redecl of another impl. We should look through it to the class or
namespace the impl (and match_first) are located inside.

We don't yet store the relationship between the impl and its match_first
block, nor then can we use it in impl lookup for prioritization.
2026-07-11 14:01:38 +00:00
Chandler Carruth 8be274cf60 Replace :! binding syntax with phase keywords and contextual defaults (#7479)
Implement the toolchain side of proposal #7254, removing the `:!`
binding
syntax for generic and template parameters in favor of the keywords
`generic`,
`template`, and `runtime` plus contextual defaults for phase.

For valid programs this is semantics-preserving: each binding resolves
to the
same phase, and produces the same SemIR, as it did under `:!`/`:`. The
parser
derives a binding's phase from its syntactic context plus any explicit
phase
keyword; new diagnostics and error recovery for misused keywords are
described
below.

Implementation details for each component:

- Lexer: remove the `:!` (`ColonExclaim`) token, move its virtual
parse-node
  budget onto `:`, and add the `generic` and `runtime` keywords.
- Parser: thread a `BindingContext` (`ExplicitParam`, `DeducedParam`, or
`CompileTimeEntityParam`) from declaration introducers down through
parameter
lists to each binding pattern, using a one-token lookahead to
distinguish a
name-qualifier parameter list from a declaration's own final list.
Parameters
of a compile-time entity (`class`, `interface`, `constraint`, `choice`,
`alias`, `export`, `namespace`) and deduced `[]` parameters default to
checked
generic; explicit function parameters and local bindings default to
runtime.
`HandleBindingPattern` resolves the phase from that context plus the
keyword: a
`generic` keyword needs no node of its own (the phase is carried by the
  binding's node kind), while a `runtime` keyword is preserved as a
`RuntimeBindingName` node so `check` can name it in a diagnostic. A
phase
keyword that is merely redundant with the contextual default is
diagnosed
  here, without invalidating the parse tree.
- Check: a phase keyword that is invalid for its context (for example
`runtime`
on a checked-generic parameter) is diagnosed here, and recovers by
building an
error binding that still introduces the name so that later uses of it do
not
  produce cascading errors.

The removed `:!` syntax is now rejected as an ordinary parse error.

The `form`/`:?`/`->?` ("extended types") portion of proposal #7254 is
left for a
separate change.

Assisted-by: Claude Code
2026-07-11 01:22:44 +00:00
Dana Jansens bf106c3b4b Handle missing curlies after match_first without crashing in parse (#7480) 2026-07-11 00:03:31 +00:00
Dana Jansens 783f1601fd Include whether the impl is final in textual semir (#7485)
When formatting an impl definition, include in the textual output if the
impl is final. Since we (mostly) write them as `final impl` in the code,
use the same notation in the textual semir.
2026-07-10 21:09:10 +00:00
Dana Jansens 69433a1834 Remove the ImplicitOnly option from SubstPeriodSelf (#7461)
The implicit/explicit `.Self` concept is a heuristic at best, so we
should avoid relying on it. The ImplicitOnly option is no longer used,
as it was used to remove/disambiguate `.Self` in nested facet types, but
we have banned nesting `where` on the RHS of a `where`. So we no longer
have to worry about ambiguous `.Self`.

We can remove all the designator tracking heuristics in `.Self`
substitution as well now, as they were used for the now-removed
ExplicitOnly (removed in
https://github.com/carbon-language/carbon-lang/pull/7460) and
ImplicitOnly options.
2026-07-10 17:58:33 +00:00
Chandler Carruth de8b03faa3 Update to latest rumdl release and enable another option (#7483)
The `style = "fixed"` is partially implied by `indent = 4`, but not
fully in some specific cases. There were also bugs in fully applying it
that are now fixed, and so we can specify it explicitly to get rumdl to
canonicalize nested list indentation more thoroughly.

Assisted-by: Antigravity with Gemini
2026-07-10 16:16:28 +00:00
Dana Jansens 55dcf66584 Remove extra SubstPeriodSelf and canonicalization (#7474)
The self and interface given to TryFindMatchingWitnessFromImplLookup
come from the output of SubstPeriodSelf, so there is no need to do the
substitution again. And the self came from a LookupImplWitness
instruction so it is already fully canonicalized.
2026-07-09 22:12:18 +00:00
Dana Jansens 7b0da12696 Remove the ExplicitOnly option from SubstPeriodSelf (#7460)
The implicit/explicit `.Self` concept is a heuristic at best, so we
should avoid relying on it. We now only have one value of `.Self` in a
facet type (we have banned nested `where` on the RHS of a `where`). So
we don't need to preserve any `.Self` for the purpose of disambiguation,
and we can subst all `.Self` on the the RHS of a rewrite constraint.
2026-07-09 21:31:52 +00:00
Dana Jansens c74bb933d2 Avoid impl lookup cycles from evaluating lookup instructions inside an impl decl (#7454)
`LookupImplWitness` instructions inside the impl declaration can't use
the impl they are apart of. Previously we had an heuristic in eval which
would try to prevent finding the impl for a lookup from inside that
impl. But it breaks when the `.Self` is replaced in a generic impl with
a symbolic, and then that symbolic is replaced in a specific. The
specific's decl block contains that `LookupImplWitness` instruction and
it tries to use the impl it came from. This causes the same specific to
be formed again, but now it exists, so it's used as-is but it has no
decl block yet, and so we crash.

Now we ban an impl while we resolve its specific, both deduction of its
arguments and from any other substitution. The prevents instructions
from inside the impl (which are evaluated when resolving the specific)
from finding their own impl. We do so by adding the ImplId to a stack on
the Context, and then skipping such impls when looking for candidates
during eval.

This fixes a crash, which was demonstrated by the new test being added.
It also makes another todo test pass.

There's a whole lot of other semir churn, which seems to be mostly
reordering of constants. There are some fingerprint changes in
constants, but it appears they are the same canonical instructions, so
they don't represent a behaviour change. For example in
`toolchain/check/testdata/for/actual.carbon` the `%N.patt` constant has
been given its fingerprint suffix now as `%N.patt.aa5`. But they are
both this instruction, so it is just a formatting change:

```
inst6100001A: {kind: SymbolicBindingPattern, arg0: entity_name61000002, type: type(inst61000018)}
  - name: `N`
  - type: type(inst61000018): <pattern for Core.IntLiteral>; {kind: PatternType, arg0: inst(IntLiteralType), type: type(TypeType)} (concrete)
  - value: symbolic_constant61000001
```
2026-07-09 20:38:56 +00:00
Dana Jansens 06e31437b9 Some slight tweaks to comments in/on WitnessQueryMatchesInterface (#7475)
I attempted the TODO as stated but we can't remove the `.Self` from the
LHS of a rewrite right now, without causing evaluation to run and
potentially find a concrete value to replace the access with, which then
breaks the association with the associated constant. There's a separate
TODO about that in SubstPeriodSelfInFacetType.
2026-07-09 19:34:55 +00:00
Dana Jansens b1c7e585f9 Diagnose .Self being used in a type that is not a facet type (#7471)
`.Self` will only be replaced in a facet type, as the facet type
constrains a facet. If it's part of a (non-facet) type, then the object
of that type is not a facet, and we can never replace that `.Self`.
2026-07-09 19:09:06 +00:00
Dana Jansens 11901b1a59 Parse match_first blocks (#7478)
`match_first` is a declaration followed by a curly-brace block of
declarations.

The check phase will ensure only impl decls (or defns) are inside the
block.
2026-07-09 15:34:52 +00:00
Dana Jansens bb0d74ba39 Remove an extra canonicalization of a self that is already canonicalized (#7477)
Clarify that the functions that search for a witness in a facet type no
longer take inputs from the query directly, but now take them from an
identified facet type constructed from the query. That means the self
type is already canonicalized.
2026-07-09 13:59:51 +00:00
Geoff Romer 96c7cfe41c Emit VarStorages eagerly (#7468)
We used to have to batch these at the end of pattern traversal in order
to avoid accidentally adding them to a block that was speculatively
pushed for an expression within a pattern, but that's no longer a
concern with the more precise handling of those speculative blocks in
#7445.
2026-07-08 19:33:46 +00:00
Dana Jansens 4261bb2dd2 Track and don't replace active .Self (#7443)
In #7436 we stopped substituting `.Self` when collecting witnesses out
of a facet type. While this was correct, it did not capture all the
cases that need to avoid substituting `.Self`. And it poisoned the
`IdentifiedFacetType` cache by not replacing `.Self` but storing the
result in the cache. This led to incoherent behaviour, where the result
of an impl lookup would change depending on which ones had been done
previously.

Now we use a flag to track for each `.Self` if we're currently
type-checking inside the scope where it was introduced in a facet type.
While inside that scope, identify should not replace the `.Self`. Any
use of it should remain as-is since we don't yet know what value will
replace it. We call this state "frozen" since it should not be modified
by identify. This requires a substitution step when we leave the scope
that introduced the `.Self`, to remove the flag. The flag is set in the
`EntityName` of the `SymbolicBinding`, and is part of the canonical
value, since `.Self` can become part of types, which are constants, and
the flag needs to follow it for correct behaviour.

We also have to ensure the flag is the same when doing comparison with
constants from inside a facet type and constants from outside. For
instance in `(Z where .Z1 = ()) where .Z2 = .Z1`, when we arrive at the
second `.Z1` its `.Self` will be frozen, while the `.Z1 = ()` contains a
non-frozen `.Self`. So we add the frozen flag to the first when storing
it in `where_stack` in order to compare the constant values of the two
`.Z1`.

The `WhereExpr` requirement inst kinds now have an `InstConstantKind` of
`AlwaysUnique` instead of `Never`. This allows us to add them to the
usual InstBlocks, and in an `eval fn` body they have a constant value,
so eval does not fail when trying to call that function. We have to be
careful to not consider `AlwaysUnique` as being actually concrete
though, since their constant value erases `.Self`-dependence. This
allows us to stop special casing them when thawing the requirements
block in a `WhereExpr`, and we can just thaw each `InstId` in the block
in a straightforward manner.

We add the new flag to the instruction's fingerprint and name in
formatted semir.
2026-07-08 18:04:56 +00:00
Nicholas Bishop ae9abe615e Fix ImportCppType doc (#7476)
Fixup for #6474. The previous doc was accidentally copied from
ImportCppFunctionDecl.
2026-07-08 17:33:16 +00:00
Özgür T. ÖnsoyandDana Jansens 08385adeb5 Implement checking observe declarations (#6709)
This adds SemIR structs and implements building `observe` lists, as well
as naming, formatting, and importing `observe` declarations.

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-07-08 17:07:10 +00:00
Nicholas Bishop 918bb9364f Fix a few proposal URLs (#7473)
Fixup for #7245
2026-07-08 16:24:41 +00:00
Nicholas Bishop c8fdeef911 Add initial support for exporting generic Carbon functions to C++ (#7462)
This allows C++ to call Carbon functions with generic type parameters,
with some conditions. Example:
```carbon
interface I {
  fn Doit(self);
}
class A {
  impl as I { fn Doit(unused self) {} }
}
class B {
  impl as I { fn Doit(unused self) {} }
}
fn F[T:! I](t: T) {
  t.Doit();
}

inline Cpp '''
void G() {
  Carbon::A a;
  Carbon::B b;
  Carbon::F(a);
  Carbon::F(b);
}
''';
```

The initial support is limited; only explicit parameters are handled
currently.

`CarbonExternalASTSource::GetOrExportFunctionToCpp` now generates a
`clang::FunctionTemplateDecl` for generic Carbon functions. If C++ code
attempts to call that templated function,
`CarbonExternalASTSource::LoadExternalSpecializations` will be called
with the template argument types of that call site. Then we can generate
a specialized thunk for those argument types for C++ to call.
2026-07-08 00:51:21 +00:00
Lucile Rose Nihlen eab71802f7 prevent clangd_tidy github workflow from checking generated files (#7470)
This prevents an issue when trying to check in changes top
bazel-generated template files,
which aren't actually valid C++, making clangd_tidy issue errors and
block the commit.
2026-07-07 20:50:44 +00:00
Chandler Carruth 8aa14eab46 Introduce subprocess executing compile benchmarks (#7456)
This follows the pattern of our main compile benchmarks, but instead of
running the compile through a library API, it does so by running a
separate process. This lets us see the performance that we would expect
from `make` or another build system invoking the compiler, as opposed to
a minimal view from the library API.

Assisted-by: Claude Code
2026-07-07 20:17:39 +00:00
Geoff Romer 8945305dfc Emit NameBindingDecl after the initializer (if any) (#7467)
In some cases the pattern block can depend on the initializer, so it
must be sequenced after it. See #7469 for a more detailed explanation of
why this is necessary.
2026-07-07 19:18:33 +00:00
Geoff Romer ae3c4266d4 Add separators between files in LLVM IR dumps (#7463)
Each file dump now starts with a `; ---` comment and ends with a blank
line. This makes it easier to visually scan the dump for a file of
interest. The comment format is somewhat arbitrary; I chose `---` to
align with the `--- filename.carbon` separator in SemIR dumps, but
without the filename, because that appears on each of the next two lines
already.
2026-07-07 16:31:24 +00:00
Chandler Carruth e1cf833c45 Fix ASan heap-use-after-free in SourceGenTest (#7465)
In `SourceGenTest.IdentifierByteSumStableAcrossSeeds`, the `first`
variable was storing `llvm::StringRef`s pointing to memory allocated by
a temporary `SourceGen` instance. This memory was freed at the end of
the loop iteration, leaving `first` with dangling references that were
accessed in subsequent iterations.

Fix this by storing `std::string` copies of the identifiers in `first`
to own the memory, and use `llvm::equal` to compare them.

Assisted-by: Antigravity with Gemini
2026-07-07 15:04:40 +00:00
Dana Jansens bd3ca2b72b Subst the whole facet type to replace .Self in identify (#7449)
This performs `.Self` substitution in a single step, for the whole facet
type, instead of doing it individually for each constraint visited in
the top-level facet type. Then we don't need to track state to avoid
subst in constraints that come from other named constraints.

The semir changes are because we now generate a whole other FacetType
from the substitution.
2026-07-07 13:30:53 +00:00
Dana Jansens 11dca8f227 Add SubstResult::SubstOperandsSkipType to not subst the type_id (#7452)
Add a result for Subst() to return when you want to recurse into the
instructions operands but not the type_id. This comes up when recursing
and looking for facet types written in an instruction, but not
referenced indirectly through a type_id.

We can't skip adding the instruction to the worklist entirely, since we
need to pop it back off to rebuild the containing instruction later. So
we just mark it with a skip flag, and don't call Subst() on it.

The suggestion for a change to Subst was made in
https://github.com/carbon-language/carbon-lang/pull/7367#discussion_r3423446839.
2026-07-07 13:23:47 +00:00
Chandler Carruthandjosh11b 383cfbb023 Fix uniform identifier generation for lengths over 64 (#7459)
`GetIdentifiersImpl` unconditionally sliced the 64-entry
`IdentifierLengthCounts` table even for uniform distributions, which the
API documents as having no `max_length` limit. Requesting a uniform
distribution with `max_length > 64` therefore tripped an out-of-bounds
slice assertion. Only compute the table slice on the non-uniform path,
where `max_length <= 64` is already enforced.

Add `IdentifierByteSumStableAcrossSeeds`, which exercises this path (a
uniform request up to length 200) and checks the core invariant that the
total identifier byte count is independent of the random seed across a
spread of parameters.

Assisted-by: Claude Code

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
2026-07-07 06:52:57 +00:00
a2890716ba Systematically update syntax in the design for #7254 (#7259)
Assisted-by: Claude and Antigravity with Gemini

---------

Co-authored-by: Geoff Romer <gromer@google.com>
Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
2026-07-07 00:41:06 +00:00
Chandler Carruth 88160496e1 Fix which tokens the lexer flags as bracket-recovery tokens. (#7457)
`ErrorRecoveryBuffer::Apply` flagged each inserted token via the
pre-insertion index of the token it was inserted before. Once any
earlier insertion had been applied, that index no longer matches the
token's position in the merged list: with two closers inserted at the
same point (`{((}`), the second recovery token was left unflagged, and
with insertions at two separate points (`{(} {(}`), a real token was
flagged in the second one's place.

`IsRecoveryToken` now reports exactly the inserted tokens. This will
matter for `carbon format`: a recovery token's text exists in no source
byte range, so a minimal-edit model must know not to anchor edits on it.

Assisted-by: Claude Code
2026-07-06 14:39:50 +00:00
Chandler Carruth f0848b1f5e Trailing comments (#7441)
Carbon currently requires a comment to be the only non-whitespace on its
line. A `//` comment that follows other content on a line, called a
_trailing comment_, is a lexer error. This proposal removes that
restriction, allowing a comment to follow other content on a line.
Everything else about comments is unchanged: a comment still begins with
`//`, still requires whitespace after the `//`, and still runs to the
end of the line. Carbon continues to provide only line comments; no
block or intra-line comments are added.

Three observations motivate the change. First, trailing comments are
well suited to short _annotations_ attached to a specific entity or
value on a line. Second, the lexer design now makes it trivial to lex
trailing comments, and in fact requires extra logic and potentially cost
to reject them. Third, C++ code routinely uses trailing comments, so
allowing them lets Carbon carry the layout of migrated code over
directly, rather than reworking each comment to read well in a different
structure.

Implementation notes (beyond the proposal's design):

Keeping trailing comments cheap to lex required a few supporting
changes, all of which keep the cost off the lexer's hot path:

- The lexer already dispatches `//` to comment lexing wherever it
appears, so classifying a comment as trailing is a single O(1) check of
whether the `//` is the line's first non-whitespace (`start + indent`).
The hot comment path is otherwise unchanged.

- That check relies on each line's recorded indentation being its real
leading whitespace. Multi-line string literals previously recorded the
column where the literal opened for the lines they span; they now record
the true (closing-delimiter) indentation instead.

- Parser error recovery (`SkipPastLikelyEnd`) had relied on that
opening-column indentation to keep tokens following a multi-line string
literal attached to the same construct. It now reconstructs that
relationship directly by consulting the line on which the literal
opened, including when other tokens follow the closing delimiter (such
as `''' + "more"`). This is on the cold recovery path.

- `CommentData` records the trailing bit in the high bit of its length
field, keeping it at 8 bytes.

Assisted-by: Claude Code
2026-07-04 06:42:02 +00:00
Geoff Romer 0460f6b7ba Label VarStorage as var_storage instead of var (#7447)
This makes the textual format clearer and more self-explanatory, and
avoids ambiguity about whether this inst refers to the storage or the
pattern.
2026-07-02 01:02:19 +00:00
Geoff Romer 11eaeeda7d Restructure handling of expressions in patterns (#7445)
Instead of maintaining a stack of pending subpatterns which might or
might not contain expressions, we mark non-nesting regions during
pattern handling that might contain an expression. The implementation
remains largely the same; the difference is that callers are expected to
end a pending expression region as soon as possible, rather than wait
for the end of the subpattern. This makes it possible to emit
non-pattern insts during pattern handling, without the risk that they
will get caught in a pending expression region further up the stack.
2026-07-01 19:15:34 +00:00
Dana Jansens e7771c2f6d During identify replace .Self only in the initial facet type (#7436)
When we find a named constraint during identity, we recurse into it. The
specific args of the named constraint may contain references to `.Self`
which can then make `.Self` appear inside the named constraint, which
was making us replace `.Self` at multiple levels and incorrectly. The
first specific argument replaces `Self` in the named constraint, and we
pass in the self-type of the identify operation. This may contain
`.Self` and we should _not_ be replacing the `.Self` references with the
self-type that they are contained within. This led to infinite cycles.

In the meantime, we have made the toolchain reject any ambiguous `.Self`
from being constructed. So we know there is only one value of `.Self`
around in a facet type.

So now we replace `.Self` only in the top level facet type during
identity. That means replacing `.Self` in the specifics of the named
constraints that we recurse into. But we do _not_ replace `.Self`
anymore inside those named constraints. This resolves the infinite loop.

At the same time, when we are identifying an `impls` constraint from
earlier in the same facet type, like `C impls Z(.Self)` we are
identifying with a self-type of `C`. We want the output to use `C` as
the self-type since we should get back an identified facet type that
says `C impls Z(.Self)`. But we do _not_ want to replace the `.Self`
there since we're inside a facet type and the `.Self` does not refer to
`C`. So we parameterize `TryToIdentifyFacetType` to not replace `.Self`
when identifying an `impls` constraint from the `where_stack()`. This
resolves a large number of `fail_todo_` tests.
2026-06-30 23:33:30 +00:00
Dana Jansens 8928268a95 Add script that pulls review stats from Github (#7444)
The script grabs all PRs and dumps them into CSV format
2026-06-30 23:11:46 +00:00
Richard Smith 157ca42ab3 Support for overriding virtual functions overloaded on arity. (#7438)
Very basic support for determining which function in an overload set an
`override fn` intended to override.

Assisted-by: Gemini via Antigravity
2026-06-30 19:36:38 +00:00
Dana Jansens a068806f67 Test and mitigate infinite cycles in impl lookup (#7428)
Impl lookup identifies the types of facets in the query, replacing
`.Self` in each type with the facet. This allows references using the
facet from outside the facet type to match similar structures inside the
facet type.

However replacing `.Self` with the facet can re-evaluate symbolic impl
lookups inside the facet type. These can perform deduction in generic
`final impl`s, which can attempt to convert the facet. Convert does an
impl lookup with that facet in the query, which causes us to form an
infinite recursion cycle.

Add tests that caused such a cycle, to demonstrate we no longer crash.
Some of these tests fail impl lookups using concrete values in the query
that match values from a `final impl`, with TODOs to address them.
2026-06-30 06:03:33 +00:00
Chandler CarruthandGeoff Romer 6e6405c0bb Replace :! and :? with keywords and contextual defaults (#7254)
This proposal removes the `:!` syntax for generics and templates in
favor of keywords (`generic`, `template`, `runtime`) and contextual
defaults for phase. It also replaces `:?` with `fwd` and introduces
`exttype` for extended types.

Assisted-by: Antigravity with Gemini, and Claude

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-06-29 22:51:57 +00:00
Chandler Carruth 366cb1010b Recover an incomplete lambda as a complete expression. (#7435)
A lambda introducer (`fn` in expression context) with no parameters and
no body -- for example `(fn)`, where `)` immediately follows `fn` --
left `HandleLambdaAfterParams` calling `ReturnErrorOnState()` without
ever emitting a `Lambda` node. The orphaned `LambdaIntroducer` leaf was
then left where an expression was required, so `Parse` produced a tree
that failed its own verification and aborted via `CARBON_FATAL`.

Recover the way `HandleLambdaBody` already does for a missing body after
a return type: emit a placeholder `InvalidParse` body and finish a
complete `Lambda` node, so the lambda stays a valid expression and the
surrounding construct (here a `ParenExpr`) extracts cleanly.

Found by fuzzing.

Assisted-by: Claude Code
2026-06-29 21:12:58 +00:00
Richard Smith baa91882dc Update to newer LLVM. (#7433)
One API fix: BumpPtrAllocator no longer tracks the amount of memory it's
handed out separately from the amount of memory it has allocated from
the system.

Assisted-by: Gemini via Antigravity
2026-06-29 21:11:16 +00:00
Chandler Carruth fd62f5a58d Fix an out-of-bounds read in TokenizedBuffer::IsRawIdentifier. (#7434)
`IsRawIdentifier` checked `token_text.starts_with("r#")` and then read
`token_text[2]`, but `starts_with` only guarantees a length of two. An
`r` identifier immediately followed by `#` at the end of the source --
so the token text is exactly `r#` -- made the `token_text[2]` read run
off the end. Guard on the length first.

Found by fuzzing. The read is reached only via `GetTokenText`, so the
parser fuzzer, which does not request token text, never hit it.

Assisted-by: Claude Code
2026-06-29 20:06:39 +00:00
Dana Jansens 7a7aefe486 Add a cargo_update.py script (#7431)
Our development instructions now recommend installing a number of
binaries through `cargo`. Updating these binaries has to be done by
hand. So we can provide a script that users can use to update them
easily and regularly.
2026-06-29 19:15:41 +00:00
73744544fc Merge functions.md and lambdas.md design documents (#7425)
Implements suggestion from
https://github.com/carbon-language/carbon-lang/pull/7355#discussion_r3416055969
.

Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Geoff Romer <gromer@google.com>
2026-06-29 18:24:16 +00:00
Geoff Romer e4b0903d2e Remove ValueBinding and RefBinding (#7427)
These inst kinds are now redundant with `WrapperBinding`.
2026-06-29 17:41:23 +00:00
Chandler CarruthandChristopher Di Bella 361c832713 Add a prelude compilation benchmark (#7368)
Adds `toolchain/benchmarking/prelude_benchmark.cpp`, which measures the
time to compile the Core prelude and reports the most interesting SemIR
memory statistics as benchmark counters.

The prelude is compiled by checking a file that imports it: the implicit
prelude import causes the check phase to lex, parse, and check the full
set of prelude files, so this is a direct measure of prelude compilation
cost. Four input variations exercise increasing amounts of the prelude:
an empty file, a minimal single-type use, an operator-heavy file that
hits many impls, and a compact file that pulls in a wide swath of the
prelude.

Memory usage is queried directly: `Driver::set_mem_usage` takes a
`MemUsage` that a compile merges each file's usage into; the benchmark
passes one, compiles, and sums the entries by label. A compilation unit
collects into its own `MemUsage` whenever usage is dumped or a sink is
provided (decided in `SetMultiUnitCache`); after a file is done it dumps
that `MemUsage` per-file as before and, if a sink was provided, merges
into it via a new `MemUsage::Add(const MemUsage&)` overload. `MemUsage`
also exposes its entries via a public `Entry` type and an `entries()`
accessor.

Also extends `scripts/bench_runner.py` to (1) treat Mem-prefixed
counters as cost metrics (smaller is better) and (2) tolerate metrics
that aren't reported by every benchmark in a binary.

Assisted-by: Claude Code

---------

Co-authored-by: Christopher Di Bella <cjdb.ns@gmail.com>
2026-06-29 05:39:44 +00:00
Geoff Romer 34a63b1cae Option to run all of file_test under LLDB (#7429)
This is handy when `file_test`'s stack trace fails to report which file
caused the crash.
2026-06-27 00:53:42 +00:00
Geoff Romer 1a3966d2c4 Build VarStorage insts more efficiently. (#7422)
Instead of traversing the entire pattern block looking for
`VarPattern`s, we keep track of them on creation, and then build
`VarStorage`s directly from that list.

This also gets rid of the global `var_storage_map`, and instead keep
that information narrowly scoped to each full-pattern, and consume it in
a single linear traversal instead of with random-access lookups. To
enable that, this fixes a parse bug where nested `var` patterns were
getting diagnosed but not marked as errors.
2026-06-26 22:26:13 +00:00
Chandler CarruthandRichard Smith cfd1ed8484 Switch from Prettier to Rumdl for Markdown formatting (#7423)
Rumdl already appears to have _significantly_ fewer bugs than prettier,
and a solid LSP for editor integration.

The tool is: https://github.com/rvben/rumdl/

I've separated out the change across three commits for easier review.

The configuration tries to match the existing formatting, the changes to
the all the files are to correct issues found by the new tool.

Assisted-by: Antigravity with Gemini

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-06-26 21:42:01 +00:00
Richard Smith 6181259cf1 Language server: prelude support. (#7417)
Support multi-file compilation, and in particular imports of files from
the prelude, in `carbon language_server`.

In order to properly interface with `CompileDriver`, also switch over to
building a proper VFS from the documents we're given.

Assisted-by: Gemini via Antigravity
2026-06-26 20:16:28 +00:00
Geoff Romer f203a9c18b Give splice_inst a concrete category where possible. (#7426)
This ensures that splices are put in the pattern block when they produce
patterns, and also fixes some latent bugs in how actions were
categorized.
2026-06-26 18:53:26 +00:00
Dana JansensandDavid Blaikie fb12984713 Forbid nested where inside a where expression through eval (#7397)
In #7378 we forbid writing `where` on the RHS of another `where`
expression. However it's still possible to inject a `where` expression
through eval, using an `alias` or an `eval fn`. We address this by
looking in the constant value of constraints of a `WhereExpr` (which
sees the outcome of eval) and searching for a nested `where` there. We
can determine a facet type was written with a nested `where` by seeing
that it has some non-extend constraint.

---------

Co-authored-by: David Blaikie <dblaikie@gmail.com>
2026-06-26 18:31:26 +00:00
Geoff Romer 8158d4ffd4 Basic support for symbolic forms (#7408) 2026-06-25 22:46:48 +00:00
Dana Jansens 5aae6a1ca5 Ensure a location for monomorphization diagnostics in call argument deduction (#7401)
If the deduction fails while forming the parameter type, ensure that we
print an actual diagnostic saying what went wrong.

And always ensure that an invalid array bounds error points at a
location. The `inst_id` given to `EvalConstantInst` always has a
location, but the `bounds_id` instruction inside it may be canonical
when it's coming from inside a larger type. So when it is, fall back to
using the location of the whole array inst.
2026-06-25 22:37:14 +00:00
Richard Smith bd8c565c74 vscode: don't use carbon LSP for testdata files (#7420)
These files aren't exactly written in Carbon, but rather in some
meta-language with file splits and semantically meaningful comments, and
in any case getting red squiggles for expected errors is distracting and
largely unhelpful.

(We *could* teach the LSP to run the test and produce errors if the test
doesn't match its expectations, but it's not clear that that would be
helpful in practice either.)
2026-06-25 21:24:25 +00:00
Dana Jansens b22ee48c9b Forbid nested where inside a where expression (#7378)
This disallows building a facet type that contains another facet type
with non-extend constraints in it. Which in turn prevents the
possibility of introducing a different `.Self` into a facet type.

Eval can still insert a facet type with non-extend constraints, as we
only prevent it for `where` being written into the facet type. There is
a TODO in handle_where.cpp for this and some tests in
toolchain/check/testdata/facet/nested_facet_types_from_eval.carbon
2026-06-25 18:56:36 +00:00
Nicholas Bishop 093dc04db6 Switch back to clang's MultiplexExternalSemaSource (#7416)
The necessary changes were upstreamed, and made available in #7413.
2026-06-25 17:10:06 +00:00
Richard Smith 9108812bc2 Apply some workflow fixes generated by zizmor. (#7418)
See https://github.com/zizmorcore/zizmor
2026-06-25 15:18:33 +00:00
Richard Smith be6bcbcfd3 Narrow down overly-broad workflow permissions. (#7419) 2026-06-25 01:44:13 +00:00
Nicholas Bishop 36d9bed4ca Fix accessing members of const/partial types (#7406)
In `PerformActionHelper`, use the unqualified type for lookup.

In `PerformInstanceBinding`, propagate qualifiers to the unbound element
type's class type when doing the `ConvertToValueOrRefOfType` conversion,
and to the element type when forming the `ClassElementAccess` instr
(except for `partial`, which is only used if the member being accessed
is `base`).

In handle_operator.cpp, prevent assignment to a reference to a const
type.
2026-06-25 00:07:39 +00:00
Nicholas Bishop 885b1110d5 Allow derived->base conversions with compatible qualifiers (#7415)
Move the existing derived->base conversion earlier in
`PerformBuiltinConversion`, into the block that handles qualifier
conversions. This allows, for example, converting from `partial Derived`
to `partial Base` -- see the tests in
`toolchain/check/testdata/class/inheritance/derived_to_base.carbon`.
2026-06-24 21:08:24 +00:00
Richard Smith f2d99f31f0 Fix contention between Clang and Carbon over external name lookup. (#7411)
The function to set the visible declarations with a given name
overwrites any existing declarations imported from an AST file, so we
need to avoid calling that for declaration contexts whose names are
managed by Clang to avoid clobbering names imported from modules.

Assisted-by: Gemini via Antigravity
2026-06-24 18:36:59 +00:00
Nicholas Bishop 999dcc5bf0 Update LLVM to bab165ecb0d (#7413)
This required a minor change to patch 0009, and allows us to drop patch
0010.
2026-06-24 18:04:48 +00:00
dependabot[bot] 039b4e9b17 Bump concurrent-ruby from 1.3.4 to 1.3.7 in /website in the bundler group across 1 directory (#7409)
Bumps the bundler group with 1 update in the /website directory:
[concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).

Updates `concurrent-ruby` from 1.3.4 to 1.3.7
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ruby-concurrency/concurrent-ruby/releases">concurrent-ruby's
releases</a>.</em></p>
<blockquote>
<h2>v1.3.7</h2>
<!-- raw HTML omitted -->
<p>There are 3 security fixes in this release, so updating is
recommended.
These security vulnerabilities are not very likely to be hit in practice
and have a corresponding <code>Low</code> severity score.</p>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<ul>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/security/advisories/GHSA-h8w8-99g7-qmvj">CVE-2026-54904</a>
<code>AtomicReference#update</code> livelocks when the stored value is
<code>Float::NAN</code>. Fix by <a
href="https://github.com/joshuay03"><code>@​joshuay03</code></a> and <a
href="https://github.com/eregon"><code>@​eregon</code></a></li>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/security/advisories/GHSA-wv3x-4vxv-whpp">CVE-2026-54905</a>
<code>ReentrantReadWriteLock</code> read-count overflow grants a write
lock without exclusivity. Fix by <a
href="https://github.com/joshuay03"><code>@​joshuay03</code></a></li>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/security/advisories/GHSA-6wx8-w4f5-wwcr">CVE-2026-54906</a>
<code>ReadWriteLock</code> allows wrong-thread write release and stray
read-release counter corruption. Fix by <a
href="https://github.com/joshuay03"><code>@​joshuay03</code></a></li>
<li>concurrent-ruby-ext: fix build on Darwin 32-bit by <a
href="https://github.com/barracuda156"><code>@​barracuda156</code></a>
in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1064">ruby-concurrency/concurrent-ruby#1064</a></li>
<li>Add SECURITY.md by <a
href="https://github.com/eregon"><code>@​eregon</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1104">ruby-concurrency/concurrent-ruby#1104</a></li>
<li>Add Ruby 4.0 in CI by <a
href="https://github.com/eregon"><code>@​eregon</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1106">ruby-concurrency/concurrent-ruby#1106</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/barracuda156"><code>@​barracuda156</code></a>
made their first contribution in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1064">ruby-concurrency/concurrent-ruby#1064</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/ruby-concurrency/concurrent-ruby/compare/v1.3.6...v1.3.7">https://github.com/ruby-concurrency/concurrent-ruby/compare/v1.3.6...v1.3.7</a></p>
<h2>v1.3.6</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<ul>
<li>Run tests without the C extension in CI by <a
href="https://github.com/eregon"><code>@​eregon</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1081">ruby-concurrency/concurrent-ruby#1081</a></li>
<li>Fix typo in Promise docs by <a
href="https://github.com/danieldiekmeier"><code>@​danieldiekmeier</code></a>
in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1083">ruby-concurrency/concurrent-ruby#1083</a></li>
<li>Correct word in readme by <a
href="https://github.com/wwahammy"><code>@​wwahammy</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1084">ruby-concurrency/concurrent-ruby#1084</a></li>
<li>Fix mistakes in MVar documentation by <a
href="https://github.com/trinistr"><code>@​trinistr</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1087">ruby-concurrency/concurrent-ruby#1087</a></li>
<li>Fix multi require concurrent/executor/cached_thread_pool by <a
href="https://github.com/OuYangJinTing"><code>@​OuYangJinTing</code></a>
in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1085">ruby-concurrency/concurrent-ruby#1085</a></li>
<li>Use typed data APIs by <a
href="https://github.com/nobu"><code>@​nobu</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1096">ruby-concurrency/concurrent-ruby#1096</a></li>
<li>Add Joshua Young to the list of maintainers by <a
href="https://github.com/eregon"><code>@​eregon</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1097">ruby-concurrency/concurrent-ruby#1097</a></li>
<li>Asynchronous pruning for RubyThreadPoolExecutor by <a
href="https://github.com/joshuay03"><code>@​joshuay03</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1082">ruby-concurrency/concurrent-ruby#1082</a></li>
<li>Mark RubySingleThreadExecutor as a SerialExecutorService by <a
href="https://github.com/meineerde"><code>@​meineerde</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1070">ruby-concurrency/concurrent-ruby#1070</a></li>
<li>Allow TimerTask to be safely restarted after shutdown and avoid
duplicate tasks by <a
href="https://github.com/bensheldon"><code>@​bensheldon</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1001">ruby-concurrency/concurrent-ruby#1001</a></li>
<li>Flaky test fix: allow ThreadPool to shutdown before asserting
completed_task_count by <a
href="https://github.com/bensheldon"><code>@​bensheldon</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1098">ruby-concurrency/concurrent-ruby#1098</a></li>
<li><code>ThreadPoolExecutor#kill</code> will
<code>wait_for_termination</code> in JRuby; ensure <code>TimerSet</code>
timer thread shuts down cleanly by <a
href="https://github.com/bensheldon"><code>@​bensheldon</code></a> in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1044">ruby-concurrency/concurrent-ruby#1044</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/danieldiekmeier"><code>@​danieldiekmeier</code></a>
made their first contribution in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1083">ruby-concurrency/concurrent-ruby#1083</a></li>
<li><a href="https://github.com/wwahammy"><code>@​wwahammy</code></a>
made their first contribution in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1084">ruby-concurrency/concurrent-ruby#1084</a></li>
<li><a href="https://github.com/trinistr"><code>@​trinistr</code></a>
made their first contribution in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1087">ruby-concurrency/concurrent-ruby#1087</a></li>
<li><a
href="https://github.com/OuYangJinTing"><code>@​OuYangJinTing</code></a>
made their first contribution in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1085">ruby-concurrency/concurrent-ruby#1085</a></li>
<li><a href="https://github.com/nobu"><code>@​nobu</code></a> made their
first contribution in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1096">ruby-concurrency/concurrent-ruby#1096</a></li>
<li><a href="https://github.com/joshuay03"><code>@​joshuay03</code></a>
made their first contribution in <a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/pull/1082">ruby-concurrency/concurrent-ruby#1082</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/ruby-concurrency/concurrent-ruby/compare/v1.3.5...v1.3.6">https://github.com/ruby-concurrency/concurrent-ruby/compare/v1.3.5...v1.3.6</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ruby-concurrency/concurrent-ruby/blob/master/CHANGELOG.md">concurrent-ruby's
changelog</a>.</em></p>
<blockquote>
<h2>Release v1.3.7 (16 June 2026)</h2>
<p>concurrent-ruby:</p>
<ul>
<li>See the <a
href="https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.3.7">release
notes on GitHub</a>.</li>
</ul>
<h2>Release v1.3.6 (13 December 2025)</h2>
<p>concurrent-ruby:</p>
<ul>
<li>See the <a
href="https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.3.6">release
notes on GitHub</a>.</li>
</ul>
<h2>Release v1.3.5, edge v0.7.2 (15 January 2025)</h2>
<p>concurrent-ruby:</p>
<ul>
<li>(<a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/issues/1062">#1062</a>)
Remove dependency on logger.</li>
</ul>
<p>concurrent-ruby-edge:</p>
<ul>
<li>(<a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/issues/1062">#1062</a>)
Remove dependency on logger.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/commit/4c8fc28ab6bb9bd8258a4c0c2fa6d35ebe77b3cb"><code>4c8fc28</code></a>
Release 1.3.7</li>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/commit/d91ca9426cb819d6cc63f1bd64bfe54644d0beca"><code>d91ca94</code></a>
Fix AtomicReference#update livelock when stored value is Float::NAN on
JRuby ...</li>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/commit/7e4d711bacf7a1dac3ef6bda44004387be2dc7e6"><code>7e4d711</code></a>
Fix <code>ReentrantReadWriteLock</code> read hold overflow into
write-lock bit</li>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/commit/6e37e0644b83b182971dc540d2e4bee38df61386"><code>6e37e06</code></a>
Fix <code>AtomicReference#update</code> livelock when stored value is
<code>Float::NAN</code></li>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/commit/2825cfa12cb708b76557803957f76862eb1151a2"><code>2825cfa</code></a>
Cleanup spec</li>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/commit/3fd493283ca5f84f0ef4e84aabd43ad68df4626b"><code>3fd4932</code></a>
Fix <code>ReadWriteLock</code> wrong-thread write release and stray read
release</li>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/commit/1974b4772efc034ee8eaa562b4370343f4c5c54b"><code>1974b47</code></a>
Add Ruby 4.0 in CI</li>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/commit/df8706d40c483d76bbb0b3a35a633c68fa9e17be"><code>df8706d</code></a>
Add SECURITY.md (<a
href="https://redirect.github.com/ruby-concurrency/concurrent-ruby/issues/1104">#1104</a>)</li>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/commit/7a1b78941c081106c20a9ca0144ac73a48d254ab"><code>7a1b789</code></a>
Bump actions/upload-pages-artifact from 4 to 5</li>
<li><a
href="https://github.com/ruby-concurrency/concurrent-ruby/commit/9b2dbf712896a638a73d2fa221206961c8d6484d"><code>9b2dbf7</code></a>
Bump actions/deploy-pages from 4 to 5</li>
<li>Additional commits viewable in <a
href="https://github.com/ruby-concurrency/concurrent-ruby/compare/v1.3.4...v1.3.7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=concurrent-ruby&package-manager=bundler&previous-version=1.3.4&new-version=1.3.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 15:16:36 +00:00
Dana Jansens e2d32ab81c Don't crash on monomorphization failure when replacing .Self (#7400)
Depending on the order in which type completion identifies things in the
facet type, it may try to replace `.Self` with a facet that fails to
convert to the type of `.Self` due to a monomorphization error. We
should fail gracefully, not crash.
2026-06-24 13:50:59 +00:00
Richard Smith de6c8b0990 Delete some dead variables. (#7410) 2026-06-24 04:24:40 +00:00
Geoff Romer a8668c9b4e Remove RefineFormAction (#7393)
Wrapping symbolic forms in `RefineFormAction` was making it very
difficult to reason about them symbolically, and at least for now we
don't really need it. See [this
discussion](https://discord.com/channels/655572317891461132/655578254970716160/1516867358751195136)
for background and possible future approaches.
2026-06-23 20:12:22 +00:00
Richard Smith 2271583d6d Switch from text to curves in SVG C++ logo. (#7395)
The text SVG would sometimes be processed by vscode before it finished
initializing its font engine and then get aggressively cached, resulting
in the logo sometimes missing one of the characters.

The new SVG logo is a bit more dense than the old text one was, so shows
up as a C++ "banner" a bit better, and should be displayed more
consistently.

Assisted-by: Gemini via Antigravity
2026-06-23 19:22:53 +00:00
Geoff Romer fee00c6f3c Move FormInfo to SemIR for reuse (#7392) 2026-06-23 19:14:25 +00:00
Richard Smith b8aa0ee164 Use plain Apache 2.0 license for mirrors. (#7407)
Our mirror repositories contain vim scripts and textmate grammars,
neither of which have any need for the LLVM exception, so we can use the
base Apache 2.0 license there with no loss of relevant permissions. This
is a compatible license, so providing these repositories under these
license terms is valid.

Other than removing the LLVM exception, the new license file has one
other change compared to LICENSE: the amount of whitespace on some lines
is reduced from four spaces to three. This makes the new license
*exactly* match the Apache 2.0 license, byte-for-byte.

This is important because the license checking done by GitHub's linguist
project doesn't recognize Apache-2.0-with-LLVM-exception as an
acceptable license, but does allow plain Apache-2.0.
2026-06-23 17:18:47 +00:00
Nicholas Bishop e5cc550554 Import modified copy of MultiplexExternalSemaSource and drop LLVM patch (#7405)
Copy MultiplexExternalSemaSource.h and MultiplexExternalSemaSource.cpp
from https://github.com/llvm/llvm-project/pull/204458 into
third_party/llvm, and apply a few minor changes to allow them to compile
and pass precommit checks.

This allows
`0011-Add-empty-constructor-and-GetSources-method-to-Multi.patch` to be
removed, which brings Carbon closer to being able to compile on an
unmodified LLVM toolchain.
2026-06-22 19:42:52 +00:00
Dana Jansens 513b0c9e3b Support dumping ImportIRInstId from its printed id (#7398)
Add support to the dump command to `dump context import_ir_inst123`

Support for dumping the ids was previously added, but missed support for
parsing and creating a C++ id from its printed id.
2026-06-22 15:01:59 +00:00
Dana Jansens 02df90e265 Support imported insts in FindStorageArgForInitializer (#7399)
Formatting an instruction with an initializing expr category will call
`FindStorageArgForInitializer()` to get the target id.

`GetExprCategory()` supports imported instructions by walking to the
imported IR, in order to get the expr category. We do the same in
`FindStorageArgForInitializer()`, instead of hitting a CARBON_CHECK.

The new test crashes without the changes here.
2026-06-21 22:56:02 +00:00
Geoff Romer 4e086a6615 Include bundle operands in operand refinement (#7391)
Also some Bundle API tweaks:
- Remove support for non-canonical bundle IDs. Bundles don't have a
unique identity, so non-canonical bundle IDs would bloat the SemIR for
no benefit.
- Adjust the conversions between raw and typed bundle IDs to not be
templated. This makes the conversions easier to access in a debugger.
2026-06-19 00:22:05 +00:00
Nicholas Bishop 2ebc7cdb40 Allow abstract types to be used in Convert (#7388)
Notably this allows accessing fields in an abstract base class via a
derived class without going through `base`. E.g.
`my_obj.field_in_base_class` rather than
`my_obj.base.field_in_base_class`.
2026-06-19 00:18:48 +00:00
Geoff Romer 833e0c418e Bug fix: give output pattern splices correct types. (#7390)
Also add a CHECK to catch bugs like this.
2026-06-18 22:59:45 +00:00
2ec915eab6 Update functions design doc (#7355)
Incorporates changes from these proposals:

- #2022 
- #2875 
- #3262 
- #3763 
- #3848 
- #5434 

A small amount of updating was done to lambdas.md and variadics.md to
harmonize with these changes.

Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Geoff Romer <gromer@google.com>
2026-06-18 22:58:51 +00:00
Richard Smith 974850788c Make our TextMate bundle actually work in TextMate. (#7386)
TextMate doesn't support JSON grammars, so convert our JSON grammar to a
plist automatically as a pre-commit check. Fix malformed info.plist
file. Add missing uuid to grammar file.

Assisted-by: Gemini via Antigravity
2026-06-18 21:06:24 +00:00
Richard Smith 6fd7c84a89 Fix conflict between #7384 and #7385. (#7394) 2026-06-18 21:05:45 +00:00
josh11bandJosh L cbd79529b2 Update design docs to reflect proposal #1885: for statement and user types (#7350)
Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
2026-06-18 20:55:07 +00:00
Richard Smith 870a1a4cc6 Highlight inline C++ as C++. (#7384)
Switch into C++ syntax highlighting mode inside inline C++ fragments in
Carbon code. Add a background to them to make their boundaries stand out
a bit more.

Assisted-by: Gemini via Antigravity
2026-06-18 19:18:39 +00:00
dependabot[bot] 366ce25302 Bump undici from 7.25.0 to 7.28.0 in /utils/vscode in the npm_and_yarn group across 1 directory (#7387)
Bumps the npm_and_yarn group with 1 update in the /utils/vscode
directory: [undici](https://github.com/nodejs/undici).

Updates `undici` from 7.25.0 to 7.28.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/nodejs/undici/releases">undici's
releases</a>.</em></p>
<blockquote>
<h2>v7.28.0</h2>
<h1>⚠️ Security Release</h1>
<p>This release line addresses <strong>7 security advisories</strong>,
all shipped in <strong>v7.28.0</strong>.</p>
<blockquote>
<p><strong>Action required:</strong> Upgrade to <strong>undici
7.28.0</strong> or later.</p>
<pre lang="sh"><code>npm install undici@^7.28.0
</code></pre>
</blockquote>
<p>The v7 line is <strong>not</strong> affected by GHSA-38rv-x7px-6hhq
(CVE-2026-9675), which is
an 8.x-only regression.</p>
<blockquote>
<p><strong>Note on GHSA-hm92-r4w5-c3mj:</strong> this fix shipped in
<strong>v7.28.0</strong>, not the
earlier 7.2x line — the vulnerable single-pool code was still present
through
<code>v7.27.2</code>. The per-origin pool fix is
<a
href="https://github.com/nodejs/undici/commit/3805b8f8"><code>3805b8f8</code></a>
(<a
href="https://redirect.github.com/nodejs/undici/pull/5041">#5041</a>).</p>
</blockquote>
<h2>Summary</h2>
<table>
<thead>
<tr>
<th>Advisory</th>
<th>CVE</th>
<th>Severity (CVSS)</th>
<th>Fixed in</th>
<th>Fix commit</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-vxpw-j846-p89q">GHSA-vxpw-j846-p89q</a></td>
<td>CVE-2026-12151</td>
<td>High (7.5)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/8cb10f98"><code>8cb10f98</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-vmh5-mc38-953g">GHSA-vmh5-mc38-953g</a></td>
<td>CVE-2026-9697</td>
<td>High (7.4)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/04201f89"><code>04201f89</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-hm92-r4w5-c3mj">GHSA-hm92-r4w5-c3mj</a></td>
<td>CVE-2026-6734</td>
<td>High (7.5)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/3805b8f8"><code>3805b8f8</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-pr7r-676h-xcf6">GHSA-pr7r-676h-xcf6</a></td>
<td>CVE-2026-9678</td>
<td>Moderate (5.9)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/85a24055"><code>85a24055</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-p88m-4jfj-68fv">GHSA-p88m-4jfj-68fv</a></td>
<td>CVE-2026-9679</td>
<td>Moderate (5.9)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/d0574cc4"><code>d0574cc4</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-g8m3-5g58-fq7m">GHSA-g8m3-5g58-fq7m</a></td>
<td>CVE-2026-11525</td>
<td>Low (3.7)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/d0574cc4"><code>d0574cc4</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-35p6-xmwp-9g52">GHSA-35p6-xmwp-9g52</a></td>
<td>CVE-2026-6733</td>
<td>Low (3.7)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/ea8930cf"><code>ea8930cf</code></a></td>
</tr>
</tbody>
</table>
<hr />
<h2>High severity</h2>
<h3>WebSocket DoS via fragment count bypass — CVE-2026-12151</h3>
<p><strong><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-vxpw-j846-p89q">GHSA-vxpw-j846-p89q</a></strong>
· CWE-400, CWE-770
<strong>Fix:</strong> <a
href="https://github.com/nodejs/undici/commit/8cb10f98"><code>8cb10f98</code></a>
<em>websocket: limit the number of fragments in a message</em> (part of
backport <a
href="https://github.com/nodejs/undici/commit/a027a4a0"><code>a027a4a0</code></a>
<em>Backport WebSocket maxPayloadSize fixes to v7.x</em>, <a
href="https://redirect.github.com/nodejs/undici/pull/5423">#5423</a>)</p>
<p>A malicious WebSocket server can stream a large number of small or
empty
continuation frames. Undici enforced a limit on cumulative payload size
but did
not limit the <em>number</em> of fragments per message, leading to
unbounded memory
growth and denial of service.</p>
<ul>
<li><strong>Affected:</strong> applications using <code>new
WebSocket(...)</code> or <code>WebSocketStream</code>
against untrusted endpoints.</li>
<li><strong>Workaround:</strong> none — upgrade is required.</li>
</ul>
<h3>TLS certificate validation bypass in SOCKS5 ProxyAgent —
CVE-2026-9697</h3>
<p><strong><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-vmh5-mc38-953g">GHSA-vmh5-mc38-953g</a></strong>
· CWE-295</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/nodejs/undici/commit/f9eba0ad9134e1c0977848476bba9d49734696e4"><code>f9eba0a</code></a>
Bumped v7.28.0 (<a
href="https://redirect.github.com/nodejs/undici/issues/5430">#5430</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/a027a4a04c6c055877d1abaf5f60ee4917e7e01f"><code>a027a4a</code></a>
Backport WebSocket maxPayloadSize fixes to v7.x (<a
href="https://redirect.github.com/nodejs/undici/issues/5423">#5423</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/8cb10f983eb6005dd53f3744d95d3b6d7dbcee0f"><code>8cb10f9</code></a>
websocket: limit the number of fragments in a message</li>
<li><a
href="https://github.com/nodejs/undici/commit/04201f8947041f0f4f2ac865dbdb1677e46a8844"><code>04201f8</code></a>
fix: honor requestTls when proxy is SOCKS5</li>
<li><a
href="https://github.com/nodejs/undici/commit/fcd642ff613ea9030dec87cf622e68d4b1ae9847"><code>fcd642f</code></a>
fix(socks5): preserve dispatch backpressure return value (<a
href="https://redirect.github.com/nodejs/undici/issues/5166">#5166</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/bc98c97906abf26fa1e959b2f6111b53ade0e18f"><code>bc98c97</code></a>
fix(socks5): use configured connector in Socks5ProxyAgent (<a
href="https://redirect.github.com/nodejs/undici/issues/5168">#5168</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/9e1c74372a2b27cacd92d27c13a83a6d84f10e0e"><code>9e1c743</code></a>
fix(socks5): encode embedded IPv4 tails in IPv6 literals correctly (<a
href="https://redirect.github.com/nodejs/undici/issues/5099">#5099</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/376c8be27cb40cc17ccaad6b6ebb317fa7148d65"><code>376c8be</code></a>
fix(socks5): enforce authenticated state before CONNECT (<a
href="https://redirect.github.com/nodejs/undici/issues/5097">#5097</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/3805b8f8518882991044048c256e005dc3c10a85"><code>3805b8f</code></a>
fix(socks5-proxy-agent): use per-origin pools to prevent cross-origin
routing...</li>
<li><a
href="https://github.com/nodejs/undici/commit/85a240551c9feb8b8a0ecc56c84b2b3015add8a9"><code>85a2405</code></a>
fix(cache): trim qualified field names</li>
<li>Additional commits viewable in <a
href="https://github.com/nodejs/undici/compare/v7.25.0...v7.28.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=undici&package-manager=npm_and_yarn&previous-version=7.25.0&new-version=7.28.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 15:55:08 +00:00
Richard Smith 805dca1dc3 Use C++ formatting for C++ splits in testdata files. (#7385)
Refactor the textmate grammar to use a different scope for testdata
files. Move the existing handling for `CHECK:STDOUT:` there, and add
handling for C++ file splits there too.

Assisted-by: Gemini via Antigravity
2026-06-18 14:47:34 +00:00
dependabot[bot] c44ead3b14 Bump the npm_and_yarn group across 1 directory with 2 updates (#7382)
Bumps the npm_and_yarn group with 2 updates in the /utils/vscode
directory: [form-data](https://github.com/form-data/form-data) and
[js-yaml](https://github.com/nodeca/js-yaml).

Updates `form-data` from 4.0.5 to 4.0.6
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/form-data/form-data/blob/master/CHANGELOG.md">form-data's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/form-data/form-data/compare/v4.0.5...v4.0.6">v4.0.6</a>
- 2026-06-12</h2>
<h3>Commits</h3>
<ul>
<li>[Fix] escape CR, LF, and <code>&quot;</code> in field names and
filenames <a
href="https://github.com/form-data/form-data/commit/8dff42c6da654ed4e7ad4acb7f8ccd3831217c99"><code>8dff42c</code></a></li>
<li>[Dev Deps] update <code>@ljharb/eslint-config</code>,
<code>auto-changelog</code>, <code>tape</code> <a
href="https://github.com/form-data/form-data/commit/f31d21ef10bf46e46344c3ee4f99acbef6be43e1"><code>f31d21e</code></a></li>
<li>[Deps] update <code>hasown</code>, <code>mime-types</code> <a
href="https://github.com/form-data/form-data/commit/92ae0eb5da94d6f01925d5f4fcffb2a1e50ed7cd"><code>92ae0eb</code></a></li>
<li>[Dev Deps] update <code>js-randomness-predictor</code> <a
href="https://github.com/form-data/form-data/commit/67b0f65c2e0b065a511d42227d35e4d367644e97"><code>67b0f65</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/form-data/form-data/commit/64190db548c0179e37206858e39f27cf513e9435"><code>64190db</code></a>
v4.0.6</li>
<li><a
href="https://github.com/form-data/form-data/commit/92ae0eb5da94d6f01925d5f4fcffb2a1e50ed7cd"><code>92ae0eb</code></a>
[Deps] update <code>hasown</code>, <code>mime-types</code></li>
<li><a
href="https://github.com/form-data/form-data/commit/f31d21ef10bf46e46344c3ee4f99acbef6be43e1"><code>f31d21e</code></a>
[Dev Deps] update <code>@ljharb/eslint-config</code>,
<code>auto-changelog</code>, <code>tape</code></li>
<li><a
href="https://github.com/form-data/form-data/commit/8dff42c6da654ed4e7ad4acb7f8ccd3831217c99"><code>8dff42c</code></a>
[Fix] escape CR, LF, and <code>&quot;</code> in field names and
filenames</li>
<li><a
href="https://github.com/form-data/form-data/commit/67b0f65c2e0b065a511d42227d35e4d367644e97"><code>67b0f65</code></a>
[Dev Deps] update <code>js-randomness-predictor</code></li>
<li>See full diff in <a
href="https://github.com/form-data/form-data/compare/v4.0.5...v4.0.6">compare
view</a></li>
</ul>
</details>
<br />

Updates `js-yaml` from 4.1.1 to 4.2.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md">js-yaml's
changelog</a>.</em></p>
<blockquote>
<h2>[4.2.0] - 2026-06-01</h2>
<h3>Added</h3>
<ul>
<li>Added <code>docs/safety.md</code> with notes about processing
untrusted YAML.</li>
<li>Added <code>maxDepth</code> (100) loader option. Not a problem, but
gives a better
exception instead of RangeError on stack overflow.</li>
<li>Added <code>maxMergeSeqLength</code> (20) loader option. Not a
problem after <code>merge</code> fix,
but an additional restriction for safety.</li>
<li>Added sourcemaps to <code>dist/</code> builds.</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Stop resolving numbers with underscores as numeric scalars, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/627">#627</a>.</li>
<li>Switched dev toolchains to Vite / neostandard.</li>
<li>Updated demo.</li>
<li>Reorganized tests.</li>
<li><code>dist/</code> files are no longer kept in the repository.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fix parsing of properties on the first implicit block mapping key,
<a
href="https://redirect.github.com/nodeca/js-yaml/issues/62">#62</a>.</li>
<li>Fix trailing whitespace handling when folding flow scalar lines, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/307">#307</a>.</li>
<li>Reject top-level block scalars without content indentation, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/280">#280</a>.</li>
<li>Ensure numbers survive round-trip, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/737">#737</a>.</li>
<li>Fix test coverage for issue <a
href="https://redirect.github.com/nodeca/js-yaml/issues/221">#221</a>.</li>
<li>Fix flow scalar trailing whitespace folding, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/307">#307</a>.</li>
<li>Fix digits in YAML named tag handles.</li>
</ul>
<h3>Security</h3>
<ul>
<li>Fix potential DoS via quadratic complexity in merge - deduplicate
repeated
elements (makes sense for malformed files &gt; 10K).</li>
</ul>
<h2>[3.14.2] - 2025-11-15</h2>
<h3>Security</h3>
<ul>
<li>Backported v4.1.1 fix to v3</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/nodeca/js-yaml/commits">compare view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-18 12:45:30 +00:00
Richard Smith 4fcf818b58 Fix handling of backticks in proposal names. (#7383)
Use slugify, as it properly handles all proposal names. This also makes
our branch name consistent with the file name of the proposal.
2026-06-17 23:17:11 +00:00
dependabot[bot] fe58c5e46b Bump markdown-it from 14.1.1 to 14.2.0 in /utils/vscode in the npm_and_yarn group across 1 directory (#7380)
Bumps the npm_and_yarn group with 1 update in the /utils/vscode
directory: [markdown-it](https://github.com/markdown-it/markdown-it).

Updates `markdown-it` from 14.1.1 to 14.2.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md">markdown-it's
changelog</a>.</em></p>
<blockquote>
<h2>[14.2.0] - 2026-05-24</h2>
<h3>Added</h3>
<ul>
<li><code>isPunctCharCode</code> to utilities.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Don't end HTML comment blocks on a blank line, <a
href="https://redirect.github.com/markdown-it/markdown-it/issues/1155">#1155</a>.</li>
<li>Properly recognize astral chars (surrogates) in delimiter scans for
emphasis-like markers, <a
href="https://redirect.github.com/markdown-it/markdown-it/issues/1072">#1072</a>.
Big thanks to <a
href="https://github.com/tats-u"><code>@​tats-u</code></a> for his
global efforts
with improving CJK support.</li>
<li>Preserve unicode whitespaces when trimm headings/paragraphs, <a
href="https://redirect.github.com/markdown-it/markdown-it/issues/1074">#1074</a>.</li>
<li>More strict entities decode to avoid false positives <code>;</code>,
<a
href="https://redirect.github.com/markdown-it/markdown-it/issues/1096">#1096</a>.</li>
<li>Restore block parser state on fail in <code>lheading</code> rule, <a
href="https://redirect.github.com/markdown-it/markdown-it/issues/1131">#1131</a>.</li>
</ul>
<h3>Security</h3>
<ul>
<li>Fixed poor smartquotes perfomance on &gt; 70k quotes in single
block</li>
<li>Bumped linkify-it to 5.0.1 with fixed potential perfomance
issues.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/829797aa00353ce0b62ddeb9b4583b837b1ffd9b"><code>829797a</code></a>
14.2.0 released</li>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/9ce2087562c45d1e5ddd9f76b990f4b3fbe040e5"><code>9ce2087</code></a>
Fix smartquotes perfomance</li>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/02e73b88fdbaddf7ecee7e567a3da62b98e57a4d"><code>02e73b8</code></a>
linkify-it bump</li>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/68cfb8c0792ba87992d21ffb4d22ee6cf635afb7"><code>68cfb8c</code></a>
fix: don't end HTML comment blocks on a blank line (<a
href="https://redirect.github.com/markdown-it/markdown-it/issues/1155">#1155</a>)</li>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/108313756cfffba31166df0140e27dd58e4da115"><code>1083137</code></a>
Readme cleanup</li>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/97c7ca2571f4255ff1d0f465958dda5293d20fe8"><code>97c7ca2</code></a>
Update funding info</li>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/c471b55c10501aba7b62817df613adc5f451da43"><code>c471b55</code></a>
Changelog update</li>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/77696210d1c7c56e4ffd49ff28ba15b460cb01e4"><code>7769621</code></a>
isPunctChar =&gt; isPunctCharCode</li>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/aa2aa70b3001ed6aea67c22f1ff52e1ca158d2e1"><code>aa2aa70</code></a>
fix: always reset parentType in lheading rule (<a
href="https://redirect.github.com/markdown-it/markdown-it/issues/1131">#1131</a>)</li>
<li><a
href="https://github.com/markdown-it/markdown-it/commit/59955f2ad35cbb0e3f41ad779c7363a94b4bf38e"><code>59955f2</code></a>
Polish PRs <a
href="https://redirect.github.com/markdown-it/markdown-it/issues/1072">#1072</a>,
<a
href="https://redirect.github.com/markdown-it/markdown-it/issues/1074">#1074</a></li>
<li>Additional commits viewable in <a
href="https://github.com/markdown-it/markdown-it/compare/14.1.1...14.2.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=markdown-it&package-manager=npm_and_yarn&previous-version=14.1.1&new-version=14.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 21:53:14 +00:00
Nicholas Bishop c684183ed8 Allow accessing abstract class fields (#7379)
In `Convert`, allow forming a value or reference of abstract class type,
but not an initializer.

For now, limit the scope to just `ClassElementAccess` to avoid affecting
tests where I'm unclear if allowing abstract types is correct.
2026-06-17 20:41:08 +00:00
Christopher Di Bella f55ffe5914 Add Iterate to the list of non-Clang operators (#7369)
This allows us to use non-generic C++ range types in Carbon range-for
loops.
2026-06-17 19:13:33 +00:00
Dana Jansens 0f14882dd1 Remove fail_todo_class_with_qualified_rewrite test (#7377)
This test was assuming that we'd allow qualified lookup in rewrite
constraints, but that is not in agreement with the design. The design
says that the LHS of a rewrite must be a member access designator like
`.Member`.
2026-06-17 16:31:26 +00:00
josh11bandJosh L a7f8df0383 Update svg code snippets to reflect changes from #7354 (#7370)
Co-authored-by: Josh L <josh11b@users.noreply.github.com>
2026-06-17 15:13:23 +00:00
Richard Smith c1080b25ae Sync utils/textmate directory to carbon.tmbundle repository. (#7374)
Provide a cut-down repository containing just our textmate bundle, both
for easy installation in general and so that github's linguist in
particular can pick it up and use it for highlighting Carbon files.

The sync_repos script is automatically run by our github workflow
whenever utils/ changes.
2026-06-17 14:00:39 +00:00
Richard Smith 5fc8e1cf3e Textmate grammar: avoid variable-width lookbehind. (#7373)
This is not supported by the textmate parser in github's linguist.

Assisted-by: Gemini via Antigravity
2026-06-17 05:43:44 +00:00
Richard Smith 1106d967df Add support for enum comparisons and bitwise operators (#7356)
If C++ overload resolution selects a builtin operator candidate for an
enum comparison or bitwise operator, provide support for that operator
by generating a corresponding Carbon builtin function. This is
structured to be easily extensible to other C++ builtin overload
candidates if we so choose, but for now the operators defined in the
prelude are doing what we want in most cases.

Bitwise operators on enums produce the same enum type as a result. This
intentionally deviates from C++, where they produce a promoted integral
type.

Assisted-by: Gemini via Antigravity
2026-06-16 19:51:11 +00:00
Richard Smith 30b6c22444 Remove comment missed by #7013. (#7365)
This comment reflects a special case that no longer exists.
2026-06-16 16:29:21 +00:00
Dana Jansens d68b3fa912 Don't crash when converting a tuple to type if it contains an ErrorInst (#7352)
The conversion will just produce an ErrorInst output.

Right now I am not sure how to get an ErrorInst into that position, but
with https://github.com/carbon-language/carbon-lang/pull/7364 rejecting
`.Self` we end up with this, and it crashes otherwise.
2026-06-16 13:51:38 +00:00
David Blaikie cf6a89db3f Init vptrs in Carbon initialization of Carbon-derived-from-C++ objects (#7323)
For review convenience, the Clang patch is available as an LLVM Draft PR
here: https://github.com/llvm/llvm-project/pull/202807
2026-06-16 04:50:57 +00:00
David Blaikie 88b3605eac Fix #7289: Add debug info module flags as-needed and verify if already present (#7336)
This avoids duplicate module flags when compiling C++ interop with debug
info.

Assisted-by: Gemini via Antigravity
2026-06-16 03:18:05 +00:00
Geoff Romer d13495771a Recover from an empty Label field in debug printing (#7363)
Closes #7361
2026-06-15 22:23:18 +00:00
josh11bandJosh L 988c47631e Update design docs to reflect #6177: C++ Interop: Mapping std::string_view to Core.Str (#7349)
Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
2026-06-15 22:19:45 +00:00
Nicholas Bishop f2c94517cc Reland "Fix crash from accessing a Check::Context during lowering (#7335)" (#7359)
In generate_ast.cpp, an `CarbonExternalASTSource` is installed that has
a `Check::Context` pointer. During lowering, this `ExternalASTSource` is
still installed, and using it can cause a crash if the now-invalid
pointer is dereferenced.

Fix by adding a new `ReadOnlyASTSource` in sem_ir, and using that during
lowering.

`CarbonExternalASTSource` now inherits from `ReadOnlyASTSource` to avoid
some code duplication.

In generate_ast.cpp, we now always install a multiplex source, even if
there's only one child source. Clang internally keeps pointers to the
top-level `ExternalASTSource` installed via `setExternalSource`, and
those pointers aren't updated if `setExternalSource` is called again. By
using `MultiplexExternalSemaSource`, we can keep the top-level
`ExternalASTSource` pointer the same, and only update its children.

Using `MultiplexExternalSemaSource` this way requires a new constructor
and a method to modify its child sources; added a new LLVM patch adding
those.

Originally landed in #7335, reverted in #7353 due to ASAN errors.
Changes since original:
* Use LLVM RTTI to make `Lower::Context::Finalize` less brittle.
Add LLVM RTTI to `ReadOnlyASTSource` (and `CarbonExternalASTSource`).
Change Finalize so that instead of just deleting the last multiplex
child source, it erases any multiplex child sources that match
`ReadOnlyASTSource`; this includes `CarbonExternalASTSource` since it's
a subclass.
* Fix ASAN error by updating the `MultiplexExternalSemaSource` earlier
in lowering. It is sometimes accessed during PrepareToLower, so update
it in `Context::GetFileContext` rather than `Context::Finalize`.

Fixes https://github.com/carbon-language/carbon-lang/issues/7142
2026-06-15 20:30:07 +00:00
Dana Jansens c3b6b084d4 Find correct nested rewrite constraints (#7280)
When a facet type contains a rewrite constraint like `.(J.J1).(I.I1)`
the ImplWitnessAccess should only use its RHS value if it's trying to
find `.(J.J1).(I.I1)`. It can't just look at the `.(I.I1)` part or it
may grab the RHS for a similar `.(K.K2).(I.I1)` rewrite.

While it's not possible to write a nested access on the LHS of a rewrite
constraint like `.(J.J1).(I.I1)`, it is possible to construct a facet
type that ends up with that as its rewrite using nested facet types:
```carbon
T:! J where .J1 impls (I where .I1 = ())
```

The access `T.(J.J1).(I.I1)` should evaluate to `()`. The
ImplWitnessAccess evaluation starts by looking for a rewrite of
`.(I.I1)` in the type of the access self, which is `T.(J.J1)`. Then it
looks for `.(I.I1)` in the type of that access self, which is `T`. At
that point it finds the rewrite of `.(J.J1).(I.I1) = ()` and can use the
`()`. However it can also find other rewrites that end in `.(I.I1)`.

To do this correctly, when moving through an ImplWitnessAccess to the
next nested access self, we record the access (interface, element) pair
that we are looking through. And then we require the rewrite constraint
to be prefixed by all of the (interface, element) pairs that we have
recorded.
2026-06-15 18:12:44 +00:00
ed51ba4b1a Update design docs to reflect proposal #4682: The Core.Array type... (#7354)
Also:

- Introduce `buf` so that existing examples using arrays can be updated
to use it.
- Update the examples linked to on the Carbon front page to something
closer to what we expect, moving away from old array syntax. Uses
`slice` though that name hasn't been settled. Updating the SVGs actually
referenced will need to be done as a separate step.
- Update links to the expression operators while I'm touching that
section.

Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Nicholas Bishop <nbishop@nbishop.net>
2026-06-15 17:36:06 +00:00
Dana Jansens 3a7b49efaa Add some more test coverage of designators (#7347)
- A test showing we need to search facets in the specific interface of
an access to find a witness
- A test using a `<type> impls ...` constraint from earlier in the same
facet type
- Some tests to show a rewrite constraint from earlier in the same facet
type is not incorrectly used when it's applied to a different facet
- A test of a facet type with a rewrite as the RHS of another rewrite,
and the rewrite there should not leak or access things from the outer
facet type
2026-06-15 14:27:32 +00:00
dependabot[bot] fbf0157998 Bump esbuild from 0.25.12 to 0.28.1 in /utils/vscode in the npm_and_yarn group across 1 directory (#7358)
Bumps the npm_and_yarn group with 1 update in the /utils/vscode
directory: [esbuild](https://github.com/evanw/esbuild).

Updates `esbuild` from 0.25.12 to 0.28.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/evanw/esbuild/releases">esbuild's
releases</a>.</em></p>
<blockquote>
<h2>v0.28.1</h2>
<ul>
<li>
<p>Disallow <code>\</code> in local development server HTTP requests (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p>
<p>This release fixes a security issue where HTTP requests to esbuild's
local development server could traverse outside of the serve directory
on Windows using a <code>\</code> backslash character. It happened due
to the use of Go's <code>path.Clean()</code> function, which only
handles Unix-style <code>/</code> characters. HTTP requests with paths
containing <code>\</code> are no longer allowed.</p>
<p>Thanks to <a
href="https://github.com/dellalibera"><code>@​dellalibera</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Add integrity checks to the Deno API (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p>
<p>The previous release of esbuild added integrity checks to esbuild's
npm install script. This release also adds integrity checks to esbuild's
Deno install script. Now esbuild's Deno API will also fail with an error
if the downloaded esbuild binary contains something other than the
expected content.</p>
<p>Note that esbuild's Deno API installs from
<code>registry.npmjs.org</code> by default, but allows the
<code>NPM_CONFIG_REGISTRY</code> environment variable to override this
with a custom package registry. This change means that the esbuild
executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the
expected content.</p>
<p>Thanks to <a
href="https://github.com/sondt99"><code>@​sondt99</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Avoid inlining <code>using</code> and <code>await using</code>
declarations (<a
href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p>
<p>Previously esbuild's minifier sometimes incorrectly inlined
<code>using</code> and <code>await using</code> declarations into
subsequent uses of that declaration, which then fails to dispose of the
resource correctly. This bug happened because inlining was done for
<code>let</code> and <code>const</code> declarations by avoiding doing
it for <code>var</code> declarations, which no longer worked when more
declaration types were added. Here's an example:</p>
<pre lang="js"><code>// Original code
{
  using x = new Resource()
  x.activate()
}
<p>// Old output (with --minify)<br />
new Resource().activate();</p>
<p>// New output (with --minify)<br />
{using e=new Resource;e.activate()}<br />
</code></pre></p>
</li>
<li>
<p>Fix module evaluation when an error is thrown (<a
href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>,
<a
href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p>
<p>If an error is thrown during module evaluation, esbuild previously
didn't preserve the state of the module for subsequent module
references. This was observable if <code>import()</code> or
<code>require()</code> is used to import a module multiple times. The
thrown error is supposed to be thrown by every call to
<code>import()</code> or <code>require()</code>, not just the first.
With this release, esbuild will now throw the same error every time you
call <code>import()</code> or <code>require()</code> on a module that
throws during its evaluation.</p>
</li>
<li>
<p>Fix some edge cases around the <code>new</code> operator (<a
href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p>
<p>Previously esbuild incorrectly printed certain edge cases involving
complex expressions inside the target of a <code>new</code> expression
(specifically an optional chain and/or a tagged template literal). The
generated code for the <code>new</code> target was not correctly wrapped
with parentheses, and either contained a syntax error or had different
semantics. These edge cases have been fixed so that they now correctly
wrap the <code>new</code> target in parentheses. Here is an example of
some affected code:</p>
<pre lang="js"><code>// Original code
new (foo()`bar`)()
new (foo()?.bar)()
<p>// Old output<br />
new foo()<code>bar</code>();<br />
new (foo())?.bar();</p>
<p></code></pre></p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/evanw/esbuild/blob/main/CHANGELOG-2025.md">esbuild's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog: 2025</h1>
<p>This changelog documents all esbuild versions published in the year
2025 (versions 0.25.0 through 0.27.2).</p>
<h2>0.27.2</h2>
<ul>
<li>
<p>Allow import path specifiers starting with <code>#/</code> (<a
href="https://redirect.github.com/evanw/esbuild/pull/4361">#4361</a>)</p>
<p>Previously the specification for <code>package.json</code> disallowed
import path specifiers starting with <code>#/</code>, but this
restriction <a
href="https://redirect.github.com/nodejs/node/pull/60864">has recently
been relaxed</a> and support for it is being added across the JavaScript
ecosystem. One use case is using it for a wildcard pattern such as
mapping <code>#/*</code> to <code>./src/*</code> (previously you had to
use another character such as <code>#_*</code> instead, which was more
confusing). There is some more context in <a
href="https://redirect.github.com/nodejs/node/issues/49182">nodejs/node#49182</a>.</p>
<p>This change was contributed by <a
href="https://github.com/hybrist"><code>@​hybrist</code></a>.</p>
</li>
<li>
<p>Automatically add the <code>-webkit-mask</code> prefix (<a
href="https://redirect.github.com/evanw/esbuild/issues/4357">#4357</a>,
<a
href="https://redirect.github.com/evanw/esbuild/issues/4358">#4358</a>)</p>
<p>This release automatically adds the <code>-webkit-</code> vendor
prefix for the <a
href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/mask"><code>mask</code></a>
CSS shorthand property:</p>
<pre lang="css"><code>/* Original code */
main {
  mask: url(x.png) center/5rem no-repeat
}
<p>/* Old output (with --target=chrome110) */<br />
main {<br />
mask: url(x.png) center/5rem no-repeat;<br />
}</p>
<p>/* New output (with --target=chrome110) */<br />
main {<br />
-webkit-mask: url(x.png) center/5rem no-repeat;<br />
mask: url(x.png) center/5rem no-repeat;<br />
}<br />
</code></pre></p>
<p>This change was contributed by <a
href="https://github.com/BPJEnnova"><code>@​BPJEnnova</code></a>.</p>
</li>
<li>
<p>Additional minification of <code>switch</code> statements (<a
href="https://redirect.github.com/evanw/esbuild/issues/4176">#4176</a>,
<a
href="https://redirect.github.com/evanw/esbuild/issues/4359">#4359</a>)</p>
<p>This release contains additional minification patterns for reducing
<code>switch</code> statements. Here is an example:</p>
<pre lang="js"><code>// Original code
switch (x) {
  case 0:
    foo()
    break
  case 1:
  default:
    bar()
}
</code></pre>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/evanw/esbuild/commit/bb9db84c02433fbe37b3509f53f9f3e3cc48725e"><code>bb9db84</code></a>
publish 0.28.1 to npm</li>
<li><a
href="https://github.com/evanw/esbuild/commit/9ff053e53b8eeb990f59355dbea365277ac45ee2"><code>9ff053e</code></a>
security: add integrity checks to the Deno API</li>
<li><a
href="https://github.com/evanw/esbuild/commit/0a9bf2135b67c7e28989a5ba19f0f000805a5ab5"><code>0a9bf21</code></a>
enforce non-negative size in gzip parser</li>
<li><a
href="https://github.com/evanw/esbuild/commit/e2a1a7132058ee067fe736eac15f695861b8654e"><code>e2a1a71</code></a>
security: forbid <code>\\</code> in local dev server requests</li>
<li><a
href="https://github.com/evanw/esbuild/commit/83a2cbfc35809f4fd5152da59572d7bed7739d78"><code>83a2cbf</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>:
don't inline <code>using</code> declarations</li>
<li><a
href="https://github.com/evanw/esbuild/commit/308ad745d824c77bc607603451b257d0f2fd9a38"><code>308ad74</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4471">#4471</a>:
renaming of nested <code>var</code> declarations</li>
<li><a
href="https://github.com/evanw/esbuild/commit/f013f5f99a015bce92ec48d49181d4ad3177b29b"><code>f013f5f</code></a>
fix some typos</li>
<li><a
href="https://github.com/evanw/esbuild/commit/aafd6e48b1088336a5f5a17e930be7e840d43d8c"><code>aafd6e4</code></a>
chore: fix some minor issues in comments (<a
href="https://redirect.github.com/evanw/esbuild/issues/4462">#4462</a>)</li>
<li><a
href="https://github.com/evanw/esbuild/commit/15300c30b5e22f7cfcbed850c246d35095658386"><code>15300c3</code></a>
follow up: cjs evaluation fixes</li>
<li><a
href="https://github.com/evanw/esbuild/commit/1bda0c31d7697c0af44b3ab39b81e599e559a395"><code>1bda0c3</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>,
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4467">#4467</a>:
esm evaluation fixes</li>
<li>Additional commits viewable in <a
href="https://github.com/evanw/esbuild/compare/v0.25.12...v0.28.1">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for esbuild since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=esbuild&package-manager=npm_and_yarn&previous-version=0.25.12&new-version=0.28.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-14 22:43:32 +00:00
Geoff RomerandRichard Smith e023f75254 Implement thunking in terms of constant evaluation (#7332)
The bulk of this change is changing most pattern insts to be `Always`
rather than `AlwaysUnique` constants, so that they can be wrapped in
`SpecificConstant`s to perform substitution. That then lets thunking
rely much more on `SpecificConstant` wrappers instead of deep-copying
the inst tree with modified types.

This approach to thunking should scale better, particularly as things
like form generics make function signatures more complex, because we can
leverage the existing support for constant evaluation and substitution.
Unfortunately, applying this approach to binding patterns will require
more work; see the TODO near the top of `thunk.cpp` for details.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-06-13 00:22:00 +00:00
Nicholas Bishop 40d2d8f68c Revert "Fix crash from accessing a Check::Context during lowering (#7335)" (#7353)
This reverts commit afd679129d.

The commit introduced ASAN errors:

https://github.com/carbon-language/carbon-lang/actions/runs/27436787960/job/81100665405
2026-06-12 23:04:50 +00:00
mstr-six 250da35c3b Recover when converting to an invalid integer type (#7342)
Converting an integer value to a destination type that is not a valid
integer type -- such as `i8388609` or `i16777216`, whose bit widths are
diagnosed as invalid -- hit a CHECK failure in
`TypeStore::GetIntTypeInfo` during constant evaluation of `int.convert`
/ `int.convert_checked`:

```
CHECK failure at toolchain/sem_ir/type.cpp:189: int_info: Type type(...) is not an integer type
```

The width error is already diagnosed when forming the type
(`IntWidthNotMultipleOf8` / `IntWidthTooLarge`), so `PerformIntConvert`
and `PerformCheckedIntConvert` now use `TryGetIntTypeInfo` and produce
an error value instead of crashing, following the existing
`SemIR::ErrorInst::ConstantId` convention in `eval.cpp`.

Added a file test covering all three reproducers from the issue
(`i8388609`, `i16777216`, `Core.Int(8388609)`); it crashes without this
change. The full `//toolchain/testing:file_test` suite (1578 tests)
passes.

Fixes #7278.

I have reviewed this change and take responsibility for it.

Assisted-by: Claude
2026-06-12 21:19:57 +00:00
Nicholas Bishop afd679129d Fix crash from accessing a Check::Context during lowering (#7335)
In generate_ast.cpp, an `CarbonExternalASTSource` is installed that has
a `Check::Context` pointer. During lowering, this `ExternalASTSource` is
still installed, and using it can cause a crash if the now-invalid
pointer is dereferenced.

Fix by adding a new `ReadOnlyASTSource` in sem_ir, and using that during
lowering.

`CarbonExternalASTSource` now inherits from `ReadOnlyASTSource` to avoid
some code duplication.

In generate_ast.cpp, we now always install a multiplex source, even if
there's only one child source. Clang internally keeps pointers to the
top-level `ExternalASTSource` installed via `setExternalSource`, and
those pointers aren't updated if `setExternalSource` is called again. By
using `MultiplexExternalSemaSource`, we can keep the top-level
`ExternalASTSource` pointer the same, and only update its children.

Using `MultiplexExternalSemaSource` this way requires a new constructor
and a method to modify its child sources; added a new LLVM patch adding
those.

https://github.com/carbon-language/carbon-lang/issues/7142
2026-06-12 18:05:19 +00:00
mstr-six f3b8e231ca Recover base declarations missing a colon (#7341)
Malformed `base` declarations with an omitted colon need two different
recovery paths. For `extend base`, the consumed `extend` modifier
requires the parse tree to retain its `BaseColon` and base expression
children, so this synthesizes an errored `BaseColon` and continues
parsing the expression.

Other malformed forms, such as `base calss X {}`, now use the standard
declaration-error recovery: emit `ExpectedAfterBase`, skip past the
likely declaration end, and form an errored `BaseDecl` without inventing
a colon or cascading diagnostics.

The regression covers `extend base Foo;`, bare `base;`, and the reviewer
counterexample `base calss X {}`.

Tests:
- `prek run --files toolchain/parse/handle_base.cpp
toolchain/parse/testdata/class/fail_base.carbon`
- `./scripts/run_bazelisk.py test -c dbg //toolchain/parse/...`
- `./scripts/run_bazelisk.py test -c dbg //toolchain/testing:file_test`

AI assistance: OpenAI Codex helped inspect the parser recovery path,
implement the change, and run verification. The operator reviewed and
authorized the contribution.

Assisted-by: OpenAI Codex
2026-06-11 22:33:29 +00:00
Christopher Di Bella 103fa5dadf Remove LICENSE from exported files (#7344) 2026-06-11 22:09:42 +00:00
Richard Smith c3fc59b8b9 Don't form a bound method when calling a C++ operator. (#7345)
Because we now support calling a function with a `self` parameter
directly, we can unconditionally call `operator$(lhs, rhs)` rather than
calling `lhs.operator$(rhs)` if the selected operator function happens
to be a member function.

This makes the logic a bit simpler and the SemIR a bit smaller.

We can't do the same for Carbon operators, unfortunately, as we use the
member access to trigger impl lookup.
2026-06-11 21:49:20 +00:00
Christopher Di Bella e83422b375 Declare package licence type (#7343) 2026-06-11 20:28:37 +00:00
Richard Smith 6fe9db297d Fix crash after anonymous generic binding deduction failure (#7330)
A binding can have a constant value that is an `ErrorInst` during error
recovery. In that case, deduction would crash when attempting to
diagnose that the binding had no deduced value.
2026-06-11 19:20:22 +00:00
Chandler CarruthandGeoff Romer f7e628562f Shrink debug info emitted for CARBON_CHECK message formatting (#7339)
This is based on an idea suggested during review of the original type
erasure PR.

The type-erased check failure path lowers each check through
`CheckFailFormat<Ts...>`, which builds one format adapter per value,
down to the out-of-line `CheckFailImpl`, which takes an array of
base-class `format_adapter*` pointers. Previously a separate variadic
`CheckFailWithAdapters<Adapters...>` template sat in between: it existed
only to bind the adapter temporaries to named parameters so that
pointers to their base class could be collected into a `std::array` and
outlive the call to `CheckFailImpl`.

This removes that layer. `CheckFailFormat` now builds the pointer array
directly, in the braced-init-list of the `CheckFailImpl` call, using a
small `CheckFailFormatAdapterAddr<T>` helper to take the base-class
address of each adapter. The adapter temporaries are materialized as
named parameters of that helper within the same full-expression as the
`CheckFailImpl` call, so they remain alive across the call without a
dedicated function for that purpose.

The win is in debug info, not code. `CheckFailWithAdapters<Adapters...>`
was instantiated once per distinct adapter-type sequence in every
translation unit that uses `CARBON_CHECK`, and each instantiation
carried its own DWARF records -- type entries, string-table offsets, and
range lists. Dropping it removes those records from every such
translation unit. The generated machine code is unchanged.

Measured impact (fastbuild, the inputs to the inner-loop links):

- First-party object files shrink by 173,728 bytes (-0.233%) across the
`carbon-busybox` link inputs; 232 of 270 objects get smaller and none
grow. The largest reductions are in the check-densest translation units
(`type_completion`, `type`, `import_ref`, `function_context`,
`constant`, ...).
- Per `bloaty`, the reduction is almost entirely DWARF (`.debug_info`,
`.debug_str`, `.rela.debug_str_offsets`, `.debug_rnglists`); the loaded
code (VM size) is unchanged.

Assisted-by: Claude Code

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-06-11 17:53:31 +00:00
Richard Smith 77790e44a6 Refactor and extend tool usage skills (#7338)
Split out the `prek` tool usage instructions into a separate skill. This
should make the agent more likely to realize the skill is relevant to a
particular task and consult it. Extend the skill to include instructions
for using `prek` in a jj workspace, and add a helper script for that
situation.

Add a `jj` skill, with the main purpose being to instruct the agent to
use `jj` not `git`, and to use `--no-pager` when running it.

Extend the `bazel` tool description slightly to more strongly encourage
agents to read and follow it.

Assisted-by: Gemini via Antigravity
2026-06-11 17:18:16 +00:00
Richard Smith 25e72882fc Implement Core.CharLiteral operations from #6710 and #7314 (#7316)
Adds support for arithmetic and comparison operators on
`Core.CharLiteral`s, as well as conversions between `CharLiteral` and
integer types.

Make some minor tweaks to fix skill issues encountered while making this
change.

Assisted-by: Gemini via Antigravity
2026-06-11 16:45:39 +00:00
Christopher Di Bella a914dba888 Add utils/vscode/BUILD (#7337)
This helps with some VSCode derivatives' extension management.
2026-06-10 23:04:09 +00:00
Geoff Romer b04634a0cd Implement a custom LLDB formatter for Carbon IDs (#7333)
This removes the inheritance noise, and adds label prefixes for
consistency with the raw textual format.

Before:
```
> p function_info.first_owning_decl_id
(Carbon::SemIR::InstId) {
  Carbon::IdBase<Carbon::SemIR::InstId> = {
    Carbon::AnyIdBase = (index = 0x50000023)
  }
}

> p function_info.call_param_ranges.implicit_end_
(Carbon::SemIR::CallParamIndex) {
  Carbon::IndexBase<Carbon::SemIR::CallParamIndex> = {
    Carbon::IdBase<Carbon::SemIR::CallParamIndex> = {
      Carbon::AnyIdBase = (index = 0x00000001)
    }
  }
}
```

After:
```
> p function_info.first_owning_decl_id
(Carbon::SemIR::InstId) inst50000023

> p function_info.call_param_ranges.implicit_end_
(Carbon::SemIR::CallParamIndex) call_param1
```

The benefit compounds when printing aggregates (which often lack `Dump`
support or omit information from it). For example, this change reduces
`p function_info` from 129 lines to just 31 (including reducing the
`call_param_ranges` field from 32 lines to just 1):

```
> p function_info
(Carbon::SemIR::Function) {
  Carbon::SemIR::EntityWithParamsBase = {
    name_id = name1
    parent_scope_id = name_scope50000002
    generic_id = generic<none>
    first_param_node_id = nodeB
    last_param_node_id = node12
    pattern_block_id = inst_block5000000E
    implicit_param_patterns_id = inst_block5000000A
    param_patterns_id = inst_block0
    is_extern = false
    extern_library_id = library_name<none>
    non_owning_decl_id = inst<none>
    first_owning_decl_id = inst50000023
    definition_id = inst<none>
  }
  Carbon::SemIR::FunctionFields = {
    call_param_patterns_id = inst_block5000000C
    call_params_id = inst_block5000000D
    call_param_ranges = (implicit_end_ = call_param1, explicit_end_ = call_param1, return_end_ = call_param1)
    return_type_inst_id = inst<none>
    return_form_inst_id = inst<none>
    return_pattern_id = inst<none>
    special_function_kind = None
    virtual_modifier = None
    virtual_index = 0xffffffff
    evaluation_mode = None
    self_param_id = inst50000020
    special_function_kind_data = any_raw<none>
    body_block_ids = size=0 {}
  }
}
```
2026-06-10 18:19:07 +00:00
Chandler Carruthandjosh11b 7871237c15 Move self to the explicit () parameter list (proposal #7016) (#7272)
Implements proposal #7016: `self` moves from the deduced implicit list
(`fn F[self: Self]()`) to the front of the explicit list. Its type may
be written explicitly (`fn F(self: Self)`) or omitted, in which case it
defaults to `Self` (`fn F(self)`, `fn F(ref self)`); `self` in the
implicit list is rejected.

Throughout checking, `self` is modeled as the first explicit parameter.
Because a method is just a function whose first parameter is `self`, it
can also be called as an ordinary function with the receiver passed
explicitly (`Type.M(obj, ...)`), not only as `obj.M(...)`. A new
`SemIR::CallArgParamPatterns` helper chooses the parameters matched
against the explicit arguments, excluding a leading `self` only when it
is supplied as a method-call receiver; arity checking, conversion, and
generic deduction use it. The resulting SemIR and lowering are
unchanged: `self` is still `call_param0`, and witnesses, thunks, and
vtables are unaffected.

An omitted `self` type is parsed as a `SelfBindingPattern` node with no
type expression; checking synthesizes the `Self` type so it behaves
exactly like `self: Self`. However, the exact spelling used must match
between a forward declaration and a definition, following #3763's rules
around declaration matching.

Generated functions, thunks, and C++ interop import/export build `self`
as the first explicit parameter, and the `self`-type override (e.g.
Derived->Base for a virtual override) applies to the explicit `self`.
Placement is validated by new diagnostics: `SelfInImplicitParamList`,
`SelfNotFirstParam`, and `SelfOutsideParamList`. The benchmark source
generator and the documentation adopt the `(self)` shorthand; the
prelude, the examples, and the test data are migrated in the following
commits.

Assisted-by: Claude Code with Claude Opus 4.7

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
2026-06-10 15:30:43 +00:00
Richard Smith fb05da761f Fix formation of invalid value_of_initializer instructions. (#7329)
This is only valid when the operand is an initializing expression that
holds a copy of the value, but we were incorrectly also forming it when
the operand was an in-place initializing expression.

Fixes a crash in lowering when attempting to lower an invalid
`value_of_initializer`.
2026-06-10 15:28:56 +00:00
Chandler Carruth 41e3c9bd82 Type-erase CARBON_CHECK message formatting. (#7325)
`CARBON_CHECK` and `CARBON_FATAL` messages are formatted with
`llvm::formatv`. Previously each check site that had a message
instantiated its own copy of the formatv machinery -- a `formatv_object`
over a tuple of per-argument format adapters, plus that tuple -- in
every translation unit, keyed on the site's file, line, condition, and
format strings. A translation unit with many checks paid for that
machinery over and over.

This restructures check failure so that the formatting machinery is
compiled exactly once, and only a single small adapter is instantiated
per distinct value type per translation unit:

- `CheckFailImpl` (out-of-line) now takes the message's format string
and an array of already-type-erased `format_adapter`s, and renders the
whole failure message -- prefix plus the extra message -- directly into
one stream. The extra message is rendered in place, so no separate
string is ever materialized for it.

- `FormatvInto` (in the `.cpp`) renders a format string over that
adapter array. Rather than instantiate `llvm::formatv`, it drives the
formatv replacement loop over the public
`formatv_object_base::parseFormatString`, so this rendering code exists
exactly once. (A TODO notes that we should add a type-erased entry point
upstream in LLVM rather than reimplement the loop here.)

- The lowering from the macro down to that out-of-line call is split so
that the only per-check-site instantiation is trivial:
- `CheckFail<...>` is instantiated once per site, since its file, line,
condition, and format template-string parameters are unique to the site.
It just lowers those compile-time strings to ordinary arguments and
forwards to `CheckFailFormat`.
- `CheckFailFormat<Ts...>` is instantiated once per distinct value-type
sequence and shared across sites; it builds one type-erased adapter per
value.
- `CheckFailWithAdapters<Adapters...>` collects pointers to those
adapters into an array and calls `CheckFailImpl`. It is a distinct
function so the adapter temporaries stay alive while pointers to their
base class are in flight.

Format semantics, including runtime format-string validation, are
unchanged, and the rendered message is byte-for-byte identical.

For `DCHECK` in optimized builds the check is dead code; its arguments
are now routed through a trivial `IgnoreDeadCheckArgs` no-op rather than
`CheckFail`. This still type-checks the arguments so they cannot bitrot,
without instantiating any formatting machinery for them and without
provoking unused-variable warnings.

Measured full-rebuild impact (353 first-party translation units,
fastbuild): -112.6s CPU, -6.9% relative to trunk.

Assisted-by: Claude
2026-06-10 06:49:00 +00:00
Christopher Di Bella eaf16a5250 Adapters should only be destroyable if their adapted type is destroyable (#7271)
Adapters were erroneously satisfying `Core.Destroy` because we were
directly getting the object's representation without consideration for
abstract and adapted types. This change ensures that adapted types'
representations are used instead of the adapter types.
2026-06-10 00:48:33 +00:00
Richard Smith c7b60662f7 CharLiteral difference should be an IntLiteral (#7314)
Change the result type `CharLiteral - CharLiteral` from `i32` to
`Core.IntLiteral`.

Assisted-by: Gemini via Antigravity
2026-06-09 23:26:22 +00:00
Chandler Carruth ad1578166f Freeze the ruff pre-commit and pin ty's version (#7331)
Hopefully this will get us to more consistent behavior between local and
CI runs of these checks.

Assisted-by: Antigravity with Gemini
2026-06-09 22:48:16 +00:00
Geoff Romer d63e929135 Support dumping ImportIRInstIds (#7328) 2026-06-09 22:17:18 +00:00
Geoff RomerandRichard Smith 85c53fa00c Reimplement derived class thunk in terms of down-casting (#7322)
This helps us move away from the clone-with-modifications approach to
thunking, which gets unwieldy as signatures get more complex.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-06-09 21:10:18 +00:00
Lucile Rose Nihlen 1a26b57732 add first implementation of carbon build subcommand (#7239)
This is the first draft of the implementation of
[p6333](https://docs.carbon-lang.dev/proposals/p6333.html).

The `build` subcommand shared logic with the `compile` and `link`
subcommands,
so I've moved some of the functionality in `compile` and `link` to
shared
`CompileDriver` and `LinkDriver` classes, respectively. This also
required exposing the
`CompileOptions` and `LinkOptions` subcommand structs for re-use.

There's still some work to do on the proposal, most notably the package
include automatic path resolution and import, and the refactors to
`carbon compile`.
2026-06-09 20:24:01 +00:00
Richard Smith e7ffb559f5 Reorganize lower interop tests. (#7327)
Split existing class and function tests into
`{class,function}/{import,export}` as appropriate. Remove the now-empty
`reverse/` directory.
2026-06-09 17:50:52 +00:00
d173121dc6 Name lookup design doc update (#7317)
Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-06-09 16:50:18 +00:00
Dana Jansens 233a58fcb1 Fix ty errors for missing generic parameters (#7326)
Mostly these errors were around `dict` missing arguments, and they are
almost always `[str, Any]`.

But a real thorn here was `xml.etree.ElementTree.Element`. `ty` insists
that this is a generic type, and indeed it appears to be one, or
becoming one, in some python version. But it is not generic in python
3.12. So we are stuck in an unsolvable land where:
- `ty` gives an error unless you write `[str]` on the type, because it
thinks it is generic.
- python3.12 gives an error if you do write `[str]` on the type, because
it thinks it is not generic.

Forcing `ty` to target exactly python 3.12 does not help. So I have just
used a linter-ignore comment on that line.
2026-06-09 15:08:39 +00:00
josh11bandJosh L efb78c593c Update design for proposal #6395: Type completeness in extend (#7315)
Assisted-by: Gemini via Antigravity

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
2026-06-08 21:27:48 +00:00
Lucile Rose Nihlen 014c7346d6 Canonicalize the path to clang resource directory (#7321)
When building Carbon on Fedora, clang reports the
resource directory as `/usr/bin/../lib/clang/22`.
This fails to string match against `/usr/lib/clang/22`
and so bazel reports an error.

This PR canonicalizes the path returned by clang
so that it will string match successfully.
2026-06-08 18:56:07 +00:00
Chandler Carruth 3625e74a7d Out-of-line cold YAML and debug-printing paths from widely-included headers (#7320)
These were defined inline in headers reached by most of the toolchain,
and as non-template functions their bodies -- including some very
expensive template instantiations -- are compiled in every including TU
even though they are only cold debug/dump paths:

- BundleStore's YAML output entry points instantiate IdAndKind::Dispatch
  over all ~50 ID kinds (two ~365ms instantiations per TU) plus
  Yaml::OutputScalar/std::function machinery; moved to a new bundle.cpp.
- TypeStore, ConstantValueStore, NameStoreWrapper, and SharedValueStores
  OutputYaml bodies wrap capturing lambdas in std::function via
  Yaml::OutputMapping; moved to their existing .cpp files.

This change appears to be worth another 10% compile time reduction.

Assisted-by: Claude
2026-06-08 02:42:23 +00:00
Chandler Carruth 7901fb3857 Don't include expensive Clang headers in widely-included headers (#7319)
Fundamentally, this uses forward declarations of Clang types to reduce
the overall compile time cost of Clang headers across the codebase.

Tracing and profiling showed ~2s of every check TU's ~8-12s compile time
going just to parsing Clang frontend and AST headers pulled in via a few
sem_ir and check headers that only use the Clang types by pointer or
reference:

- sem_ir/cpp_file.h (reached via sem_ir/file.h by ~150 TUs) included
clang/Frontend/CompilerInstance.h, clang/CodeGen/ModuleBuilder.h,
clang/AST/Mangle.h, and llvm/IR/Module.h. CppFile's accessors move out
of line to a new cpp_file.cpp and the header now forward-declares the
Clang types.
- check/cpp/context.h (reached via check/context.h by ~100 TUs) included
clang/Frontend/FrontendAction.h and clang/Parse/Parser.h, pulling in
clang's Sema.h and ASTUnit.h.
- sem_ir/clang_decl.h included clang/AST/Decl.h; the three small
functions that need complete Clang types move out of line.
- sem_ir/cpp_overload_set.h included clang/Sema/Overload.h solely for
the three-field OverloadCandidateSet::OperatorRewriteInfo, which is now
mirrored as CppOverloadSet::OperatorRewriteInfo, and clang/AST/Decl.h
solely for a pointer.
- sem_ir/name_scope.h's clang/AST/DeclBase.h include was vestigial.

TUs (and more narrowly included headers) that genuinely use the Clang
definitions now include the Clang headers directly.

Representative compile times (fastbuild, aarch64), combined with the
preceding instantiation-cost changes, relative to trunk:
- check/eval.cpp: 11.85s -> 6.94s (-41%)
- check/handle_operator.cpp: 7.71s -> 3.30s (-57%)
- language_server.cpp: 6.68s -> 3.16s (-53%)
- lower/handle.cpp: 6.75s -> 3.66s (-46%)
- sem_ir/file.cpp: 8.60s -> 6.11s (-29%)
- driver.cpp: 6.68s -> 4.78s (-28%)

Measured full-rebuild impact (316 first-party TUs, fastbuild): -689.5s
CPU, -29.9% relative to trunk.

Assisted-by: Claude
2026-06-07 16:27:22 +00:00
Richard Smith 20972ec748 Don't perform access-control checks on namespace-scope entities. (#7310)
Instead of silently producing an `ErrorInst::InstId` when looking up a
private qualified name in the current package, bypass the access check.
We don't need it -- private names from other libraries are filtered out
by the import logic.

Also fix `DiagnoseInvalidQualifiedNameAccess` to actually always produce
a diagnostic, instead of silently ignoring access control failures in
non-class types. This is a no-op after the fix to the access logic,
since we only allow access control at class and namespace scope
currently, but should avoid this issue from recurring when that changes.

Assisted-by: Gemini via Antigravity
2026-06-05 23:30:09 +00:00
Richard Smith ae6846197a Remove trailing () from Core.*Literal and Core.Bool. (#7313)
We exposed `Core.IntLiteral()`, `Core.FloatLiteral()`,
`Core.CharLiteral()`, and `Core.Bool()` as functions as a workaround,
because we had no way to provide the type names without parentheses that
the design requests. But now we can do so, by using an alias. Switch all
of these over from being functions to simply being names of the
corresponding types.

Assisted-by: Gemini via Antigravity
2026-06-05 22:33:30 +00:00
Nicholas Bishop 5e62791ad2 Improve ClangDeclStore ergonomics (#7311)
Change `Lookup` by InstId to return a ClangDecl pointer. All callers
were immediately calling `Get` anyway, so this makes call sites a little
shorter. The other `Lookup` method, by ClangDeclKey, is sometimes called
without calling `Get`, so left that as-is, but renamed to `LookupId`.

Also add a `decl` method to ClangDecl so that the commonly repeated
`clang_decl->key.decl` can be written `clang_decl->decl()`.
2026-06-05 20:49:34 +00:00
Nicholas Bishop 44b17ff436 Set correct C++ access type for fields and static vars (#7312) 2026-06-05 20:07:27 +00:00
Geoff Romer bf2ed6174d Move variable naming to its own section. (#7307)
My understanding is that `auto` is encouraged even when there's no
applicable naming convention for the variable, and that suffixes like
`_id` are encouraged where applicable, even if the type is explicit, so
there's really no connection between the two policies.
2026-06-05 19:07:39 +00:00
David Blaikie b42300cfa3 Ensure exported entities aren't remapped/duplicated (#7304)
This ensures the clang_decls map is used as a cache - without this,
visiting the same entity twice could cause it to be
re-exported/duplicated. See attached test case.
2026-06-05 19:07:33 +00:00
Nicholas BishopandRichard Smith 1aa34d7642 Remove CppGlobalVarStore (#7309)
Replace all uses of CppGlobalVarStore store with ClangDeclStore.

Adding a VarStorage->VarDecl mapping to ClangDeclStore is now done with
the `AddVar` method, which takes an extra `pattern_id` arg. While the
corresponding `ClangDecl` is unchanged from before, the reverse mapping
in `inst_id_to_clang_decl_id_` now uses the `pattern_id` as the key.
This is necessary because in some places the original VarStorage
instructions gets replaced (e.g. by a call to `Convert`). The
`pattern_id` remains stable in those cases.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-06-05 02:07:46 +00:00
David Blaikie d652cb0dfd Update .python-version to match #7296 (#7306) 2026-06-05 01:46:04 +00:00
Geoff Romer 64ff3dd3be 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.
2026-06-05 00:33:51 +00:00
David Blaikie 2d87bb02d9 Use clang_decls as the source of truth for function interop mapping (#7303)
Removing the clang_decl_id on SemIR::Function - using only the
clang_decls map to create the association between SemIR::Function and
clang::FunctionDecls.

This adds an `is_external` flag to ClangDecl to indicate whether the
entity originated from Carbon or was imported from another language.
(I'm open to names - I guess for now we mostly use "is this from C++" to
be more specific than "is this external" - eg: NameScope::is_cpp_scope)
2026-06-04 23:16:11 +00:00
Dana Jansens b6774ab4c8 Remove TODO in impls handler that is already done (#7305) 2026-06-04 20:47:07 +00:00
341069fdcf Add design doc for proposal #6676: Importing C/C++ object-like macros (#7291)
Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Geoff Romer <gromer@google.com>
2026-06-04 20:07:42 +00:00
David Blaikie 2f9bbd067a Refactor: Inline global init function lowering (#7302)
This removes the need to support a function without a declaration in
these codepaths.
2026-06-04 19:13:18 +00:00
Richard Smith cefa0397bb More fixes to package and library fingerprinting. (#7297)
Fix import logic to make all imported packages be children of the
`NameScopeId::Package` scope. Previously, indirectly-imported packages
would end up as children of their importing package's scope, which
resulted in them not being treated as packages at all, and in particular
not being fingerprinted as packages.

Fixing that caused a failure in the fingerprinting logic as we started
to encounter packages with no correspoding import scopes. Instead of
looking for import scopes, use a simpler mechanism to map packages to
their package names, and clean up.

Unfortunately the latter change churns all the fingerprints again :(
Hopefully this is the last time for a while.
2026-06-04 17:41:46 +00:00
Richard Smith c69b882379 Fix crash lowering reference return. (#7301)
Fix a lowering crash when lowering a return by reference of a type with
an in-place initializing representation. We previously misinterpreted
this as an in-place initializing return.

This is addressed by changing lowering to interpret a `ReturnExpr` of a
reference expression as a reference return. However, that exposes
another issue: `return var;` produces a `ReturnExpr` of a reference
expression in the case where it returns in place! To fix that, we switch
`return var;` to producing a `ReturnExpr` of a value expression
regardless of whether the function has a return slot. This makes the
representation of `return var;` more uniform:

* If the expression is a reference, we're performing a `ref` return.
* If the expression is an initializing expression, we're performing a
normal by-initialization return.
* If the expression is a value expression, we're performing a `return
var;`.
2026-06-04 15:51:28 +00:00
Richard Smith 88c191146d Support for float <-> float conversions. (#7279)
Implement support for floating-point <-> floating-point type conversions
as described in https://github.com/carbon-language/carbon-lang/pull/820
and https://github.com/carbon-language/carbon-lang/pull/845.
Value-preserving conversions are implicit; narrowing conversions require
explicit `as`.

Assisted-by: Gemini via Antigravity
2026-06-04 00:33:33 +00:00
Richard Smith 7fe3e35aec Support for float <-> int conversions. (#7275)
Implement support for floating-point <-> integer type conversions as
described in #820 and #845, extended to support `unsafe as` conversions
for the conversions that can't be expressed as either implicit
conversions or `as` conversions.

One tricky part here is conversions from floating-point literals to
integer types. Such literals may have both a very large mantissa and a
corresponding somewhat large negative exponent, and still produce a
result that is in the range of values that a small integer type can
represent. In order to support that while avoiding building very large
2^N or 10^N constants in general, we first compute a conservative
approximation of the number of bits necessary to represent the integer
result, with an early exit if the number is either definitely too large
or definitely zero. The remaining cases have a reasonable bound on the
size of integer necessary to compute the base^exponent multiplicand.

Assisted-by: Gemini via Antigravity
2026-06-03 23:41:20 +00:00
Nicholas Bishop f2d0c4d0ae Export Carbon global variables and static vars to C++ (#7298)
Add `ExportVarToCpp`. This checks the `clang_decls` mapping and returns
an existing decl if found. Otherwise, it creates a new `VarDecl` and
adds it to the `clang_decls` mapping.

When lowering, in `FileContext::BuildGlobalVariableDecl`, the
`clang_decls` mapping is used to lookup an existing
`llvm::GlobalVariable` for the instruction. If found, use that rather
than creating a new one to avoid an unwanted second definition in the
llvm IR.
2026-06-03 20:47:54 +00:00
Chandler Carruth 76dd872ab4 Switch to prek rather than pre-commit (#7244)
This provides a native harness with several advantages over
`pre-commit`:

- Faster when initializing the cache
- Smaller cache sizes: under 43mb compared to over 62mb
- Better integration with `uv` for Python usage

Steps for migrating for existing contributors:

1.  Install `prek` following instructions in the updated docs.

2.  Replace hooks in an existing checkout with a special flag:

    ```sh
    prek install --overwrite
    ```

    The `--overwrite` flag is what removes the old hooks.

    If you used the pre-push variant:

    ```sh
    prek install --hook-type pre-push --overwrite
    ```

3.  Optional cleanups:

    ```sh
    rm -rf ~/.cache/pre-commit  # reclaim the old hook-environment cache
pipx uninstall pre-commit # if installed via pipx; or `brew uninstall
pre-commit`
    ```

Assisted-by: Antigravity with Gemini
2026-06-03 19:19:49 +00:00
josh11bandJosh L 358df53c48 Add design doc for proposal #6668: C++ interop type mapping for integer and floating-point literals (#7295)
Assisted-by: Gemini via Antigravity

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
2026-06-03 14:22:17 +00:00
Chandler Carruth b344f3af12 Switch a few stragglers to uv and update python to 3.12 (#7296)
Not sure how these got missed when moving other things to `uv`, but this
should clean them up.

The bump to Python 3.12 is so that we can use `@override` with the
simple import from `typing`. This is needed by the newest versions of
`ty` to do type checking. Added the relevant `@override` annotations.

Assisted-by: Antigravity with Gemini
2026-06-03 05:34:44 +00:00
Christopher Di Bella 61ee4edd9c Implement Iterate for types that implement CppRangeForIterate (#7294)
This change adds rudimentary support for C++ ranges in Carbon range-for
loops. Since C++ ranges are exposed through `Core.Iterate`, C++ ranges
have the same limitations as Carbon ranges (e.g. can't return
references).

`Iterate.CursorType` now requires `Destroy`, since types that implement
`CppRangeForIterate` can't be used in range-for loops unless their
cursor type can be verifiably destructible.
2026-06-03 04:51:09 +00:00
Christopher Di Bella d578e4afe7 Remove ref pattern from CppUnsafeDeref (#7293)
Since we can't currently iterate over ranges as references, the
`CppUnsafeDeref` interface can't require types that implement it to take
or return references.
2026-06-03 01:22:07 +00:00
Dana Jansens 057ef0d458 Orphan rule for scopes (#7140)
Update the orphan rule to require a name to be defined within, or by,
the same scope as the impl declaration. Since libraries can not be
nested, this also enforces the old rule, but rejects more impl
declarations. In particular, it rejects an impl declaration in a generic
context which would have no way to provide a value for the generic
bindings it inherited from its enclosing scope, making the impl
unusable.

Discussed in open discussion
[2026-05-04](https://docs.google.com/document/d/1mjllGO3ZCL4qGt9uJHUtcxKoHAGEY7Y999ie4EtBWB8/edit?tab=t.3ifnhz83n73d#heading=h.p45zfugbmdih).
2026-06-02 22:33:15 +00:00
Richard Smith f5e9c61f11 Don't include the library name in most fingerprints. (#7292)
When we import from another library in the same package, its entities
end up with our library as their parent scope, resulting in cross-file
fingerprint mismatches. Instead, only include the library ID when
fingerprinting either a package-private entity or an `ImportIRId` that
refers to a particular `SemIR::File`.
2026-06-02 19:31:30 +00:00
Dana Jansens e7ed217d4e Require all constraints in where to have a designator (#7282)
Instead of requiring just one to have a designator, require each one.
You can't write `(type where A == B) & (type where C == .Self)` because
`A == B` has no designator. If the two facet types are combined into a
single `where` syntactically, their meaning does not change, and what we
allow should not change either. That is, `type where A == B and C ==
.Self` should be rejected since `A == B` does not contain a designator.

The design is also updated to make this clear.
2026-06-02 17:11:59 +00:00
antangelo 8e477cc2c6 Fix crash when calling local function in generic function (#6912)
Pass a function's self specific in `ScopeStack::PushForFunctionBody` and
remove assertion preventing lexical scopes from having specific IDs.
This allows lexical lookup within the function to find entities
associated with its self specific.

Closes #6793
2026-06-02 16:27:21 +00:00
Richard Smith b7f11e4c61 Propagate Carbon type alignments into LLVM IR. (#7290)
Use the Carbon-computed alignment for allocas, loads, stores, and
memcpys. Previously we used whatever LLVM felt like giving us, which
would result in ABI mismatches and runtime crashes due to misalignment
when creating objects of imported C++ class types, as well as resulting
in some surprising choices like `(i32, i32)` and `()` having 8-byte
alignment instead of 4 and 1, respectively.
2026-06-02 01:35:03 +00:00
Richard Smith 2952f61095 Support for mapping array types to/from C++. (#7285)
For now, disable the use of array types as by-var paramters and by-init
return types when exporting Carbon functions to C++, as C++ does not
support raw arrays being passed or returned by value.

Assisted-by: Gemini via Antigravity
2026-06-01 21:55:16 +00:00
d01e1d2d06 Update design docs for proposal #6710: char redesign (#7281)
Note that #7266 has already updated the toolchain for `char`, but not
`Core.CharLiteral`.

Assisted-by: Gemini via Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Geoff Romer <gromer@google.com>
2026-06-01 19:34:35 +00:00
Richard Smith 49e5e15138 Fix mangling collisions for library-private entities. (#7283)
Include the library name in the fingerprint of an entity declared
`private` at namespace scope. Include the entity's fingerprint in the
mangling of a library-private entity.

This fixes miscompiles if two libraries in the same package declare
`private` entites with the same name. We can't fix this with internal
linkage because library-private entities can be reachable through
generics defined in the API file of the library.

Assisted-by: Gemini via Antigravity
2026-06-01 19:19:08 +00:00
Nicholas Bishop 682f9fef16 Add FieldStore and fix field initializer imports (#7287)
As suggested in [1], replace `FieldInitializerMap` with a `FieldStore`.
The corresponding `FieldId` is now stored in `FieldDecl`. To make room
for the `FieldId`, the `ElementIndex` is now stored in the `Field`,
along with the initializer.

In import_ref.cpp, resolving `FieldDecl` initializers is now supported,
and in convert.cpp `LoadImportRef` is called to do so. The
`field_initializer_import.carbon` test now passes.

Printing a `FieldDecl` instruction now prints the initializer as well,
if present. See field_initializer.carbon for an example.

[1]:
https://github.com/carbon-language/carbon-lang/pull/7238#discussion_r3283217158
2026-06-01 19:02:01 +00:00
Chandler CarruthandChristopher Di Bella 14a213d095 Apply the unused-without-definition check to implicit parameters (#7270)
The `unused` modifier is rejected on parameters of a function
declaration, but the check only covered the explicit parameter list, so
an implicit parameter (such as self or a compile-time binding) could
carry unused without a definition. Check the implicit parameter list
too.

The code changes and the test updates are split into two commits for
easier review.

Assisted-by: Claude Code with Claude Opus 4.7

---------

Co-authored-by: Christopher Di Bella <cjdb.ns@gmail.com>
2026-05-30 08:26:09 +00:00
Chandler CarruthandChristopher Di Bella 8a59f2a76b Fix mangling collision for C++ class template specializations (#7269)
Carbon-side thunks (for example the `Copy`/`Destroy` witness thunks
generated for imported C++ types) are mangled by Carbon, and their names
incorporate a fingerprint of the involved types. The instruction
fingerprinter identifies a class only by its name and parent scope,
which is sufficient for Carbon classes but not for imported C++ classes:
different specializations of one class template (and other cases such as
types in anonymous namespaces) share a Carbon name and parent scope. As
a result, the thunks for two distinct specializations could mangle to
the same name, producing a single LLVM function with two definitions and
failing `verifyModule` during lowering.

When fingerprinting a class imported from C++, also include the Clang
mangled name of its type.

Test: toolchain/lower/testdata/interop/cpp/thunks.carbon gains a split
with two specializations of one class template, each requiring a thunk;
their thunks now get distinct mangled names instead of colliding.

Assisted-by: Claude Code

---------

Co-authored-by: Christopher Di Bella <cjdb.ns@gmail.com>
2026-05-30 08:13:00 +00:00
Chandler Carruth 3ef128ac91 Switch to Astral Python tools: ruff and ty (#7243)
This replaces black, flake8, and mypy with the more modern and efficient
tools `ruff` and `ty` from Astral.

Assisted-by: Antigravity with Gemini
2026-05-29 22:56:27 +00:00
Richard Smith 1a8f2f3b8c Add Core.Destroy support for enums imported from C++ (#7267)
We previously only supported values of enum type, as we did not find a
suitable `Core.Destroy` implementation for enums.
2026-05-29 22:42:12 +00:00
Chandler Carruth 59e0f95a10 Fix Linux AArch64 build and add CI for that platform (#7273)
The `--dump-cpp-ast` file tests strip references to Clang builtins so
that the expected output is target-independent. The filter anchored a
`__`-prefixed builtin identifier on a preceding space or quote, which
matches the x86-64 `__va_list_tag` spelling but not the AArch64
`std::__va_list`, where `__` is preceded by the `::` namespace
qualifier. That left a single `RecordType 'std::__va_list'` line
unfiltered on AArch64, producing a spurious autoupdate diff for
`thunk_ast.carbon`.

Anchor the match on a preceding `:` as well so namespace-qualified
builtins are also filtered.

Carbon's test workflow covered Linux on x86-64 and macOS on AArch64, but
had no Linux AArch64 coverage, so AArch64-specific issues that don't
reproduce on macOS could land unnoticed. Add an `ubuntu-22.04-arm`
runner to the matrix.

The release used for Linux does not publish the monolithic
`LLVM-*-Linux-ARM64` package, only a `clang+llvm-*-aarch64-linux-gnu`
community build with a smaller tool set, so the Ubuntu setup now selects
the tarball by `runner.arch`. The prune step uses `rm -f` since the two
packages do not ship an identical set of tools to remove.

Assisted-by: Claude Code with Claude Opus 4.7
2026-05-29 15:32:18 +00:00
Chandler CarruthandRichard Smith 94d2c1c6d4 Make proposal filenames use 6 digits and include the title (#7245)
We've talked about adding the title to the filename several times over
the years and it seems really valuable. This requires us to compute a
"slug" for the title spelling that can be part of the filename.

Beyond that, we crossed 7000 recently, and so it seems likely that we
will need to add digits sooner rather than later here, so this goes
ahead and moves us to 6 digits so we don't have to adjust again for a
reasonable length of time.

To implement this and ensure we can sustain it going forward this adds a
tool to our pre-commit that validates (and corrects if needed) the
filename.

In order to update everything and keep links working, there are a _lot_
of changes, but the most interesting for direct review are in
`proposals/scripts`.

Assisted-by: Antigravity with Gemini

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-05-29 00:55:04 +00:00
Richard Smith 1cb0eecc1e Add skills for diagnostics and builtin functions. (#7277)
Also make some updates and improvements to fine-tune existing skills.

Simplify AGENTS.md to remove redundant instructions that duplicate
information that's already in skill files -- skills should be loaded
automatically and should not need to be redundantly specified in
AGENTS.md.

Assisted-by: Gemini via Antigravity
2026-05-28 23:58:37 +00:00
Chandler Carruth 1028538d05 Add the uv release endpoint (#7276) 2026-05-28 20:35:01 +00:00
Chandler Carruth b79ce84f33 Switch to uv for all of our Python scripts (#7242)
This removes the need to install any specific version of Python or
figure out how to configure it by instead asking users to install `uv`
and letting it manage Python. Among other advantages, `uv` is designed
to be fast enough to embed directly into our scripts.

We were already using this in `bench_runner.py` so that the script could
import non standard library dependencies. Moving to it for the rest of
our Python unifies the approach and will also enable dependencies
whenever needed.

I've left `github_tools` alone as it has special handling with its own
Bazel setup.

I've updated the contributing tools to explain the approach here.
2026-05-28 18:03:52 +00:00
dependabot[bot] 6aee1280e9 Bump tmp from 0.2.5 to 0.2.7 in /utils/vscode in the npm_and_yarn group across 1 directory (#7274)
Bumps the npm_and_yarn group with 1 update in the /utils/vscode
directory: [tmp](https://github.com/raszi/node-tmp).

Updates `tmp` from 0.2.5 to 0.2.7
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/raszi/node-tmp/commit/8ea1f37d75c67569e0f151448330d52f7babf211"><code>8ea1f37</code></a>
Bump up the version</li>
<li><a
href="https://github.com/raszi/node-tmp/commit/8f24f788a356b5d45c9bec894632bd4931338153"><code>8f24f78</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/raszi/node-tmp/commit/ce787f37aaacccad921ae90990c9da33481fe59c"><code>ce787f3</code></a>
Reject non-string prefix, postfix, template</li>
<li><a
href="https://github.com/raszi/node-tmp/commit/41f71598d03f104a67e0448a7cb9bd4efcdd5980"><code>41f7159</code></a>
Bump up the version</li>
<li><a
href="https://github.com/raszi/node-tmp/commit/efa4a06f24374797ae32ab2b6ae39b7a611ae429"><code>efa4a06</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/raszi/node-tmp/commit/7ef2728ce0211b8110b2033dfe62eaf030341acf"><code>7ef2728</code></a>
Check for relative values</li>
<li>See full diff in <a
href="https://github.com/raszi/node-tmp/compare/v0.2.5...v0.2.7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tmp&package-manager=npm_and_yarn&previous-version=0.2.5&new-version=0.2.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-28 05:16:54 +00:00
Richard Smith e45045d63b Implement char operations from #6710 (#7266)
This only covers `char`, and does not include the `CharLiteral`
operations.

Assisted-by: Gemini via Antigravity
2026-05-27 23:18:27 +00:00
Richard Smith 9986d0da69 Only propagate .Self dependence in facet types from extended constraints (#7253)
When forming the constant value of a `where` expression, don't consider
it to be `.Self`-dependent if the dependence only comes from the RHS of
the `where`. More generally, ignore `.Self` dependence when evaluating a
facet type unless it comes from an extended interface or named
constraint. While we can get other kinds of constraint from the
left-hand side of a `where`, such constraints must either come from the
right-hand side of other `where` expressions or be extend constraints.

This fixes a crash in lowering caused by a concrete function containing
a `.Self`-symbolic `where` constant.

Assisted-by: Gemini via Antigravity
2026-05-27 22:04:48 +00:00
Richard Smith 7569aff619 Don't convert runtime arguments during deduction. (#7265)
When performing deduction for a call to a generic function, we would
previously convert runtime arguments to match the parameter type, then
throw away the result. Instead, track whether deduction needs the value
of the argument, which will be the case only within compile-time
contexts such as generic bindings and types of instructions, and only
perform conversions during deduction for those contexts.

Fixes miscompiles when passing an argument requiring a runtime
conversion with side-effects to a generic function, where previously the
side-effects would have happened twice! (Once from deduction and once
from the real call argument conversion.)

Assisted-by: Gemini via Antigravity
2026-05-27 21:44:29 +00:00
Richard Smith 09d1331e85 Make Optional(T) copyable. (#7268)
`Optional` is already restricted to only be able to store copyable
types, so it should always implement `Core.Copy`.
2026-05-27 18:13:26 +00:00
DavidLoftus de381bded1 Fix off-by-one errors in LanguageServer's GetRange (#7251)
LSP assumes lines are index 0 to n-1, but Carbon Locs are index from 1
to n. We had the logic for this correct for the start of range but not
for the end of range (inclusive range).

Before this was the diagnostic span we would produce:

```carbon
fn F() {
  return ();
  <~~~~~~~~>
}
<~~~~~~~~~~~>
```

after:

```carbon
fn F() {
  return ();
  <~~~~~~~~>
}
```
2026-05-27 17:37:56 +00:00
Dana Jansens 518704608e Handle errors and non-constant instructions when substituting non-canonical instructions (#7262)
Subst is typically used for constant values, in which case an
`ErrorInst` anywhere results in a final `ErrorInst`. However there are
some use cases for substituting non-canonical instructions. And in that
case we need to take care in two ways:
- Some instructions have no constant value, such as the requirements in
a (non-canonical) `WhereExpr` instruction. When we rebuild them, we
can't do so by building a constant value and getting the canonical inst
id, since the constant value will be "runtime". For rebuilding a
non-canonical instruction, we should `AddInst` instead.
- When substituting something in a non-canonical instruction with an
`ErrorInst` inside it, we want to preserve the structure of the
non-canonical instruction, and leave the `ErrorInst` in place. As such,
we remove the early out in Subst so that we keep substituting after
encountering an error.

However the rewrite constraint resolution was relying on the `ErrorInst`
early out to correctly stop recursing when it found a cycle. So now we
do the checks for a cycle in its `SubstCallbacks` subclass, and avoid
substituting and rebuilding instructions once we have encountered a
cycle.
2026-05-27 15:43:41 +00:00
Chandler Carruth d313fd0b11 Add a skill file for creating proposals (#7257)
A lot of this is based on a few experiments using tools to help
synthesize a cohesive proposal or improve them. Likely more that can be
done here to get the most out of our tools in this space.

Assisted-by: Antigravity with Gemini
2026-05-27 03:15:14 +00:00
Christopher Di Bella bd7f89b1ad Remove lookup_result and function (#7264)
PR #7230 was automerged before
https://github.com/carbon-language/carbon-lang/pull/7230#pullrequestreview-4348841410
could be applied. This commit actions that feedback.
2026-05-26 22:57:03 +00:00
Richard Smith a779cc37a0 Support converted ref arguments. (#7258)
Allow a `ref`-tagged expression to be converted to match a reference
parameter. Move the `ref` checks to the start of `Convert`. Remove the
diagnostic for applying `ref` to a non-reference expression so that
non-reference expressions that convert to a reference would be accepted
(although we don't currently have any such conversions).

Assisted-by: Gemini via Antigravity
2026-05-26 20:38:49 +00:00
Dana Jansens bbca8668ae Allow witnesses to come from a facet with partially identified type in the lookup target (#7260)
We allow impl lookup to use `Self` which is partially identified, but we
were only allowing this for the `Self` facet appearing in the impl
lookup query self. We should also allow it when `Self` appears in the
impl lookup query target facet type.

But demonstrate that `Self` appearing in the interface of an
ImplWitnessAccess (a compound member access) is not sufficient at this
time, as it does not put `Self` into the type structure.
2026-05-25 23:02:06 +00:00
Dana Jansens 58b135235c Use earlier C(.Self) impls ... and .Self impls ... constraints in a facet type (#7246)
The `.Self` needs to be canonicalized when recording the witness, since
it will be a FacetAccessType in the `impls` clause.

Todo tests are also added for `C impls Y(.Self)`. The `.Self` in the
interface specific breaks everything as we end up replacing the `.Self`
with `C` in later `C as Y(.Self)` lookups, which then does not find the
witness.

Pull all the witnesses from `impls` constraints so that we capture
witnesses coming from named constraints on different self types, as `C
impls N(.Self)` may give witnesses for `C` or for other self types.
2026-05-25 15:13:20 +00:00
Chandler Carruth 4aacf2e2a5 Fix new-proposal script to correctly get GitHub username (#7255)
Assisted-by: Antigravity with Gemini
2026-05-24 23:26:28 +00:00
dependabot[bot] a49e1d94b8 Bump qs from 6.15.1 to 6.15.2 in /utils/vscode in the npm_and_yarn group across 1 directory (#7256)
Bumps the npm_and_yarn group with 1 update in the /utils/vscode
directory: [qs](https://github.com/ljharb/qs).

Updates `qs` from 6.15.1 to 6.15.2
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ljharb/qs/blob/main/CHANGELOG.md">qs's
changelog</a>.</em></p>
<blockquote>
<h2><strong>6.15.2</strong></h2>
<ul>
<li>[Fix] <code>stringify</code>: skip null/undefined entries in
<code>arrayFormat: 'comma'</code> + <code>encodeValuesOnly</code>
instead of crashing in <code>encoder</code></li>
<li>[Fix] <code>stringify</code>: use configured <code>delimiter</code>
after <code>charsetSentinel</code> (<a
href="https://redirect.github.com/ljharb/qs/issues/555">#555</a>)</li>
<li>[Fix] <code>stringify</code>: apply <code>formatter</code> to
encoded key under <code>strictNullHandling</code> (<a
href="https://redirect.github.com/ljharb/qs/issues/554">#554</a>)</li>
<li>[Fix] <code>stringify</code>: skip null/undefined filter-array
entries instead of crashing in <code>encoder</code> (<a
href="https://redirect.github.com/ljharb/qs/issues/551">#551</a>)</li>
<li>[Fix] <code>parse</code>: handle nested bracket groups and add
regression tests (<a
href="https://redirect.github.com/ljharb/qs/issues/530">#530</a>)</li>
<li>[readme] fix grammar (<a
href="https://redirect.github.com/ljharb/qs/issues/550">#550</a>)</li>
<li>[Dev Deps] update <code>@ljharb/eslint-config</code></li>
<li>[Tests] add regression tests for keys containing percent-encoded
bracket text</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ljharb/qs/commit/9aca4076fe788338c67cf7e115f0be6bc58d85a8"><code>9aca407</code></a>
v6.15.2</li>
<li><a
href="https://github.com/ljharb/qs/commit/5e33d33447ed0bf1ddab9abc41d27dea4687d992"><code>5e33d33</code></a>
[Dev Deps] update <code>@ljharb/eslint-config</code></li>
<li><a
href="https://github.com/ljharb/qs/commit/21f80b33e5c8b3f7eba1034fff0da4a4a37a1d41"><code>21f80b3</code></a>
[Fix] <code>stringify</code>: skip null/undefined entries in
<code>arrayFormat: 'comma'</code> + `e...</li>
<li><a
href="https://github.com/ljharb/qs/commit/a0a81ea2071acce3eff41a040f719ac8f5c4f64c"><code>a0a81ea</code></a>
[Fix] <code>stringify</code>: use configured <code>delimiter</code>
after <code>charsetSentinel</code></li>
<li><a
href="https://github.com/ljharb/qs/commit/e3062f78f5233b338ceeb8e8dfa5a07dea4b32a8"><code>e3062f7</code></a>
[Fix] <code>stringify</code>: apply <code>formatter</code> to encoded
key under <code>strictNullHandling</code></li>
<li><a
href="https://github.com/ljharb/qs/commit/0c180a40adb8c6703fffc85b2ff06ca209f5c1e0"><code>0c180a4</code></a>
[Fix] <code>stringify</code>: skip null/undefined filter-array entries
instead of crashi...</li>
<li><a
href="https://github.com/ljharb/qs/commit/3a8b94aec19bd664720f6f6b1e66c4a0dfe4b656"><code>3a8b94a</code></a>
[Tests] add regression tests for keys containing percent-encoded bracket
text</li>
<li><a
href="https://github.com/ljharb/qs/commit/96755abd357c0e534dd3442a84a04d08864bfe0d"><code>96755ab</code></a>
[readme] fix grammar</li>
<li><a
href="https://github.com/ljharb/qs/commit/a419ce5bbfcdb98a299f1a0bb47ea055baef20e6"><code>a419ce5</code></a>
[Fix] <code>parse</code>: handle nested bracket groups and add
regression tests</li>
<li>See full diff in <a
href="https://github.com/ljharb/qs/compare/v6.15.1...v6.15.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=qs&package-manager=npm_and_yarn&previous-version=6.15.1&new-version=6.15.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-24 09:59:14 +00:00
Richard Smith 52b45a66a3 Convert TokenInfo::ResetAs... functions to return new TokenInfos. (#7252)
As requested in #7249.
2026-05-23 01:22:22 +00:00
Richard Smith 49024c8d83 Improve diagnostics and error recovery for invalid identifiers. (#7249)
If a keyword or a sized type literal (eg, `f2`) is used in a context
where we are confident that we are expecting an identifier -- either
before a `:` in a binding pattern or after a `.` in a member access or
designator -- then recover as if a raw identifier was used.

This appears to be a particular stumbling block for coding agents, so
seems worth paying special attention to.

Add a mechanism to the tokenized buffer to track additional tokens
synthesized for error recovery so that we can keep the lexed token
sequence immutable and still satisfy the invariants throughout the rest
of the toolchain for recovery tokens. Thanks to chandlerc for suggesting
this approach!
2026-05-22 23:53:19 +00:00
Christopher Di Bella 64e890c246 Extend CppRangeForIterate to support ADL begin/end (#7230)
`CppRangeForIterate` needs to support finding `begin()`/`end()` as a
pair of methods and as a pair of ADL-findable functions. This change
adds the ADL component, which lets us also diagnose types that don't
implement the interface.

Unlike methods, we apparently have support for overloads when using ADL.
2026-05-22 21:12:06 +00:00
Nicholas Bishop e6b1679552 Add support for field initializers (#7238)
In handle_let_and_var.cpp, field initializers are now handled like
regular `var` initializers, by calling `LocalPatternMatch`.

In pattern_match.cpp, `FieldDecl`s with initializers are handled by
storing a value in `SemIR::File::field_initializers()`. This is a new
map where the keys are `FieldDecl` `InstId`s and the map values are
`InstId`s representing the initializer value.

In convert.cpp, `ConvertStructToStructOrClass` now has a `get_default`
function parameter that callers can use to provide a field default.
`ConvertStructToClass` uses this to provide a default from field
initializers.
2026-05-22 02:48:07 +00:00
Richard Smith 862b1c91f8 Fix GetTokenText for raw identifiers. (#7250)
Include the `r#` in the spelling of the identifier.
2026-05-22 01:57:13 +00:00
Richard Smith 805b3eebce Fix crash if initializing Clang fails. (#7248)
Flush diagnostics before destroying Clang. If we see an `inline Cpp` and
`Cpp` initialization failed, recover by skipping the inline code rather
than CHECK-failing.
2026-05-21 21:21:50 +00:00
Richard Smith ad97b9e3a5 Basic support for use of C++ modules via interop. (#7241)
Create a multiplex source to pull information from both the AST reader
and from our custom source.

Assisted-by: Gemini via Antigravity
2026-05-21 00:21:54 +00:00
dependabot[bot] 022995e412 Bump idna from 3.7 to 3.15 in /github_tools in the pip group across 1 directory (#7234)
Bumps the pip group with 1 update in the /github_tools directory:
[idna](https://github.com/kjd/idna).

Updates `idna` from 3.7 to 3.15
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/kjd/idna/releases">idna's
releases</a>.</em></p>
<blockquote>
<h2>v3.15</h2>
<p>No release notes provided.</p>
<h2>v3.14</h2>
<p>No release notes provided.</p>
<h2>v3.13</h2>
<p>No release notes provided.</p>
<h2>v3.12</h2>
<p>No release notes provided.</p>
<h2>v3.11</h2>
<p>No release notes provided.</p>
<h2>v3.10</h2>
<p>No release notes provided.</p>
<h2>v3.9</h2>
<p>No release notes provided.</p>
<h2>v3.8</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix regression where IDNAError exception was not being produced for
certain inputs.</li>
<li>Add support for Python 3.13, drop support for Python 3.5 as it is no
longer testable.</li>
<li>Documentation improvements</li>
<li>Updates to package testing using Github actions</li>
</ul>
<p>Thanks to Hugo van Kemenade for contributions to this release.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/kjd/idna/compare/v3.7...v3.8">https://github.com/kjd/idna/compare/v3.7...v3.8</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/kjd/idna/blob/master/HISTORY.md">idna's
changelog</a>.</em></p>
<blockquote>
<h2>3.15 (2026-05-12)</h2>
<ul>
<li>Enforce DNS-length cap on individual labels early in
<code>check_label</code>,
short-circuiting contextual-rule processing for oversized input
while staying compatible with UTS 46 usage.</li>
<li>Tidy core helpers: hoist bidi category sets to module-level
frozensets (avoiding per-codepoint list construction), simplify
length checks, and reuse the shared <code>_unicode_dots_re</code> from
<code>idna.core</code> in the codec module.</li>
<li>Use <code>raise ... from err</code> for proper exception chaining
and
switch internal string formatting to f-strings.</li>
<li>Allow <code>flit_core</code> 4.x in the build backend.</li>
<li>Expand the ruff lint set (flake8-bugbear, flake8-simplify,
pyupgrade, perflint) and apply the surfaced fixes; pin lint CI
to Python 3.14.</li>
<li>Add Dependabot configuration for GitHub Actions.</li>
<li>Convert README and HISTORY from reStructuredText to Markdown.</li>
<li>Reference CVE-2026-45409 for the 3.14 advisory in place of the
initial GHSA identifier.</li>
</ul>
<p>Thanks to Felix Yan, Stan Ulbrych, and metsw24-max for
contributions to this release.</p>
<h2>3.14 (2026-05-10)</h2>
<ul>
<li>Removed opportunity to process long inputs into quadratic
time by rejecting oversize inputs up-front. Closes a bypass
of the CVE-2024-3651 mitigation. [CVE-2026-45409]</li>
</ul>
<p>Thanks to Stan Ulbrych for reporting the issue.</p>
<h2>3.13 (2026-04-22)</h2>
<ul>
<li>Correct classification error for codepoint U+A7F1</li>
</ul>
<h2>3.12 (2026-04-21)</h2>
<ul>
<li>Update to Unicode 17.0.0.</li>
<li>Issue a deprecation warning for the transitional argument.</li>
<li>Added lazy-loading to provide some performance improvements.</li>
<li>Removed vestiges of code related to Python 2 support, including
segmentation of data structures specific to Jython.</li>
</ul>
<p>Thanks to Rodrigo Nogueira for contributions to this release.</p>
<h2>3.11 (2025-10-12)</h2>
<ul>
<li>Update to Unicode 16.0.0, including significant changes to UTS46
processing. As a result of Unicode ending support for it, transitional
processing no longer has an effect and returns the same result.</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/kjd/idna/commit/af30a092e158181d0b35ac66dfa813788126bdd8"><code>af30a09</code></a>
Release 3.15</li>
<li><a
href="https://github.com/kjd/idna/commit/30314d4628744ca14cf2b5820564e5127a9f86f2"><code>30314d4</code></a>
Pre-release 3.15rc0</li>
<li><a
href="https://github.com/kjd/idna/commit/05d4b219aa9eddc47371fcbd2000f0301016f3e9"><code>05d4b21</code></a>
Merge pull request <a
href="https://redirect.github.com/kjd/idna/issues/237">#237</a> from
kjd/convert-docs-to-markdown</li>
<li><a
href="https://github.com/kjd/idna/commit/2987fdba1962bbb2358399e0084ba062b98a0bee"><code>2987fdb</code></a>
Convert README and HISTORY from reStructuredText to Markdown</li>
<li><a
href="https://github.com/kjd/idna/commit/59fa8002d514bf4a5ce7b58f67b9ec587d53fa9c"><code>59fa800</code></a>
Merge pull request <a
href="https://redirect.github.com/kjd/idna/issues/236">#236</a> from
kjd/dependabot/github_actions/actions-f3e34333ea</li>
<li><a
href="https://github.com/kjd/idna/commit/def69834ced5d4b3c50439d8b99c4c856ec19ca2"><code>def6983</code></a>
Merge branch 'master' into
dependabot/github_actions/actions-f3e34333ea</li>
<li><a
href="https://github.com/kjd/idna/commit/bbd8004a797185d8c56bb555cd5c88fde05e0631"><code>bbd8004</code></a>
Merge pull request <a
href="https://redirect.github.com/kjd/idna/issues/234">#234</a> from
StanFromIreland/patch-1</li>
<li><a
href="https://github.com/kjd/idna/commit/edd07c05024344a6ccb517414ccb36683aee99fc"><code>edd07c0</code></a>
Bump github/codeql-action from 3.35.2 to 4.35.2 in the actions
group</li>
<li><a
href="https://github.com/kjd/idna/commit/5557db030c11bdec50d62aa5f631d705d33ba123"><code>5557db0</code></a>
Merge branch 'master' into patch-1</li>
<li><a
href="https://github.com/kjd/idna/commit/f11746cf4981d25123ef7830d3ee60f07de8ae3d"><code>f11746c</code></a>
Merge pull request <a
href="https://redirect.github.com/kjd/idna/issues/235">#235</a> from
StanFromIreland/patch-2</li>
<li>Additional commits viewable in <a
href="https://github.com/kjd/idna/compare/v3.7...v3.15">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=idna&package-manager=pip&previous-version=3.7&new-version=3.15)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/carbon-language/carbon-lang/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-21 00:07:56 +00:00
Richard Smith 05cc09daca Fix cross-package signature mismatches. (#7232)
Fixes link failures when referencing a symbol involving a fingerprint
from a different package.

Previously we included the `Namespace`'s `import_id` as part of its
fingerprint, which caused local and imported namespaces to get different
fingerprints. We now store the `import_id` on the `NameScope` instead of
on the `Namespace` inst to avoid this problem.

Also, when we reach a package-level `NameScopeId`, consistently
fingerprint it as a (package name, library name) pair. Previously the
fingerprinting depended on whether it was imported or not, as an
imported `NameScopeId` had a parent scope (the current package). We need
to include the library name here so that private entities with the same
name in different libraries have different fingerprints.
2026-05-21 00:02:29 +00:00
Richard Smith 1231098c14 Fix crash in indirect template instantiation. (#7240)
Provide a source location to the member expression used when a thunk
calls a member function. This ends up being used as the point of
instantiation when the return type triggers a template instantiation;
the absence of this location previously caused assertion failures within
clang.

Assisted-by: Gemini via Antigravity
2026-05-20 23:18:15 +00:00
Richard Smith f31c553e65 Add a failing test for Clang modules support. (#7233)
Rearrange the file_test infrastructure so that we can customize the
mapping of file names to command line arguments. Map `module.modulemap`
files to corresponding Clang driver flags. In passing, also clean up the
interface for specifying custom argument replacements so that we don't
build a string map for each file we process, and stop using
`SmallVector::insert`.

Assisted-by: Gemini via Antigravity
2026-05-20 20:26:36 +00:00
Richard Smith be70c092fa Add a text mode for fingerprinting. (#7231)
The intent is to add visibility into how the fingerprint is computed, so
that fingerprinting issues and mangling collisions can be more readily
understood and fixed.

Assisted-by: Gemini via Antigravity
2026-05-19 23:16:51 +00:00
Richard Smith ce080b3549 Support unqualified lookup into extended interfaces from classes and impls (#7217)
When a class extends an interface, referring to a member name of the
interface as an unqualified name should refer to the class's
corresponding associated entity value, not to the associated entity
itself. Similarly, in an `impl`, unqualified names of associated
entities should refer to the `impl`'s corresponding value for that
entity.

To support this, we treat `impl`s as `extend`ing their implemented facet
type, and we make lookups into an extended facet type use the `Self`
type of the extending `impl` or `class` if lookup finds an associated
entity. We already did the latter if the extending entity was an
interface; this extends the existing support for these other cases.
2026-05-19 18:01:54 +00:00
Chandler Carruth 0d7c4a99eb Explicitly escape leading - (#7226)
Previously, this relied on the subtlety that `-- ` (with the trailing
space) didn't get parsed as a flag. But there is support already for
escaping a leading `-` in a format argument, so use that to make the
code more obviously correct.

Assisted-by: Antigravity with Gemini
2026-05-19 17:32:17 +00:00
Chandler Carruth bb59a0b1d8 Optimize pre-commit checks (#7229)
First, this makes the Bazel invocations not try to uses curses which
prevents running them with `pre-commit run ... -v` showing the timings
for each check. The curses display overwrote the output.

Second, this fixes the main slowdown I was seeing. Because we passed
_all_ files to the check-build-graph hook and there are large number of
files, pre-commit would run the tool over and over on a subset of the
files. This is especially wasteful as the build graph check already
doesn't do anything with the files, it just checks `//...` on each
invocation. So this just added a (large) constant factor of cost.

Third, this tries to reduce the cost of `fix_cc_deps.py` in the case of
large numbers of files. This still isn't _super_ fast -- but the rest of
the cost is in running the `bazel query` and parsing the output. I tried
switching it to jsonproto and it wasn't any faster. I think this would
need to be in a non-Python language and use `proto` directly to
significantly improve the cost here.

Assisted-by: Antigravity with Gemini
2026-05-19 15:51:33 +00:00
Richard Smith 9c277fd820 Add missing library line to test. (#7227) 2026-05-19 03:05:31 +00:00
Chandler Carruth 22d09f7f25 Add C++ compile benchmarking (#7220)
We had source generation support for some time, but needed to get all
the runtimes set up correctly so that standard library headers are
available. Now that this is in place, we can benchmark both languages.

Also fixes a bug in the C++ source generation causing compile failures.

Assisted-by: Antigravity with Gemini
2026-05-18 23:31:14 +00:00
Richard Smith 76f7025d68 Support use of Carbon toolchain as a bazel module (#7223)
Use `Label` to mark labels that are local to this module. Remove
workspace root when forming manifest. Add explicit import for name that
is not available implicitly in an imported module.

Assisted-by: Gemini via Antigravity
2026-05-18 20:46:46 +00:00
Richard Smith b87f848db4 Fix passing mode computation for methods. (#7225)
We were incorrectly computing the index of the Clang implicit conversion
corresponding to method arguments. This led to wrong code and a crash in
lowering due to a calling convention mismatch.

Fixes #7224.

Assisted-by: Gemini via Antigravity
2026-05-18 20:30:23 +00:00
Nicholas Bishop 773ecdfac6 Implement static var class fields (#7215)
This adds the `static` token to the lexer and parses it as a modifier.

In check, `FullPatternStack::Kind::FieldDecl` is now used for both
static and non-static vars. Static vars get treated basically the same
as `NameBindingDecl`s.

Global initialization is used for static var initializers. To make the
necessary stack information available to `pattern_match.cpp`, the
`full_pattern_stack` and `decl_introducer_state_stack` are now popped
later in `handle_let_and_var.cpp`.

In lowering, each class's body is checked for `VarStorage` insts and
lowered the same as global vars.
2026-05-18 20:09:17 +00:00
David Blaikie 31db4a0931 Export virtual/abstract/override functions as virtual (#7211)
This provides enough to Clang so it can include these new virtual
functions in the vtable of Carbon types derived from C++ types.
2026-05-18 19:04:33 +00:00
josh11bandJosh L 832d46f01b Update LLVM (#7221)
Assisted-by: Google Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
2026-05-18 17:06:58 +00:00
josh11bandJosh L 4f2b3b644c Support jj workspaces in suggested jj abandon-untagged alias (#7222)
`@` refers to the current workspace's working copy, but if we want the
command to respect the working copies of other workspaces, we should use
`working_copies()` instead; see
https://docs.jj-vcs.dev/latest/revsets/#functions .

Also remove `all() &` while here, since it is always redundant.

Assisted-by: Google Antigravity

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
2026-05-17 23:03:22 +00:00
f0aa561bd6 Support jj workspaces (#7219)
In particular, bazel builds would previously fail in
`workspace_status.py` if you didn't have a `.git` with this error:

> ```
> ERROR: <builtin>: BazelWorkspaceStatusAction stable-status.txt failed:
Failed to determine workspace status: Process exited with status 1
> fatal: not a git repository (or any of the parent directories): .git
> ```

Assisted-by: Google Antigravity

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
2026-05-17 03:38:31 +00:00
David BlaikieandRichard Smith 843323b864 Export virtual functions when exporting a class definition (#7210)
This allows Clang to correctly generate the vtable for the exported
class.

There's still something wrong with new virtual functions in the Carbon
type (left a TODO) - I thought it might be related to not flagging
the CXXMethodDecl as virtual, but my initial experiments don't seem to
back that up, so I'll look into it further separately.

There's also a test regression due to an virtual (well, abstract
specifically, but I think it'd happen with a virtual one too) function
in an abstract class taking `self` by value being rejected since
the abstract class can't be instantiated. Not sure if this is a correct
change - the test's behavior could be preserved by using `ref self`
instnead of `self` in this function. Is that reasonable/expected? Should
we not require a type to be complete when passing by value if we can
compute the value representation without such completeness?

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-05-15 23:53:12 +00:00
Geoff Romer 6d5a883d6d Restructure scrutinee type handling (#7216)
This change avoids situations where a variable might be either a pattern
type or a scrutinee type, depending on the pattern matching state, and
makes it clear that the state only affects which specific is selected.
2026-05-15 21:10:40 +00:00
Dana Jansens ed1a949b47 Resolve nested ImplWitnessAccesses in rewrite constraints (#7213)
We were assuming that a nested access meant that we'd resolve the outer
one, then the inner one. But it may be that we can just resolve the
entire nested access together. Previously if we encountered this, it led
to a CHECK failure. Now we correctly resolve it.
2026-05-15 20:31:28 +00:00
Chandler Carruth e041afd98d Centralize benchmarking infrastructure and the toolchain-wide benchmarks (#7212)
The benchmarks themselves aren't really specific to `driver`.

Keeping the source generation near to the primary use case of
benchmarking also seems like a more discoverable location.

I feel a little bad doing this reorganization right after I gave a talk
with links to a bunch of this code, but seems good to reorganize a bit
before doing some work to extend things now that we have full standard
library support for C++ benchmarking and other improvements.

Assisted-by: Antigravity with Gemini
2026-05-15 20:17:23 +00:00
Dana Jansens 42a4dba9fd Makes impls constraints available to use in later constraints within a facet type (#7209)
If a facet type contains `.X impls Y` then later references to `.X`
should know that it impls `Y`. This is done through a stack on the
context like for rewrites, where impl lookup can find constraints from
the current `where` expression being checked.

Similarly, if a facet type contains `.X impls (Y where .Z = T)`, then
`.X.(Y.Z) = T` should be available to later constraints in the facet
type for early application of rewrite rules. We add these rewrites to
the rewrite stack when handling the `impls` constraint
2026-05-15 18:53:51 +00:00
Dana Jansens 835a61c385 Gracefully handle a concrete ImplWitnessAccess in the type structure (#7214)
While this can only happen when some other error is taking place, we
should handle it gracefully and report a concrete (but unmatchable)
value in the type structure instead of CHECK-failing.
2026-05-15 14:53:51 +00:00
Richard Smith dd47e41da6 Fix calls to functions with const T&& parameters. (#7208)
This also fixes passing a value expression to a forwarding reference,
since we currently deduce a `const T&&` parameter in that case.

We were accidentally looking at the type of the thunk parameter (which
is never an rvalue reference) rather than the type of the callee
parameter.
2026-05-14 20:22:32 +00:00
Dana Jansens 8e371c380a Use impl's interface to get the specific interface from the identified facet type (#7201)
Instead of doing a lookup for the identified facet type from the impl's
self+constraint, we can use `impl.interface` to get the specific
interface being implemented by the impl declaration. This is already the
specific interface returned from the identified facet type. And that
means we no longer have to identify the self+constraint for imported
impls.

This is split out from
https://github.com/carbon-language/carbon-lang/pull/7183#discussion_r3230571153
2026-05-14 19:34:23 +00:00
Dana Jansens 417531f484 Use early rewrites in compound member access (#7207)
We replace the non-canonical ImplWitnessAccess instruction with an
ImplWitnessAccessSubstituted instruction containing the RHS of a prior
rewrite constraint in the same facet type when possible, in order to
access the value of the prior constraint. We were doing this only in the
designator access path though, not in the compound member access. Join
these two code paths when they construct the ImplWitnessAccess, and
perform the early access there for both.
2026-05-14 16:37:50 +00:00
Chandler Carruth d010d52f37 Switch the ValueStore-related templates to use explicit instantiation (#7116)
As part of this, move functions that seem reasonable to make out-of-line
to a separate `_impl.h` header file that is only included where the
explicit instantiation _definition_ is provided.

By using explicit instantiation we can make these templates behave more
like non-template classes in terms of supporting out-of-line definitions
that don't need to be compiled by every translation unit. The set of
eventual instantiations here is fundamentally known, and there tend to
be headers that define a canonical "leaf" type where it makes sense to
trigger the explicit instantiation.

Where we already had a `.cpp` file to put the explicit instantiation
definition, use it. But in some places we didn't have such a `.cpp` file
so this PR adds those.

This also requires that we have precise constraints on APIs that _can't_
be instantiated for specific argument types, as now we don't do this
lazily.

Combined, this appears to reduce the sum of object file sizes in the
`check` directory by almost 40% (122mb -> 74mb) in my measurement.

My actual goal was to improve compile times, but so far I don't have a
great methodology for measuring these... But the object file size
reduction seems to confirm this is a net win and likely represents a
non-trivial improvement in compile time.

Assisted-by: Antigravity with Gemini
2026-05-14 08:29:17 +00:00
2705 changed files with 269791 additions and 141000 deletions
+45
View File
@@ -0,0 +1,45 @@
---
name: Agent tools
description:
Guidelines and restrictions on shell commands and file manipulation tools
for AI assistants.
---
# Agent tools
<!--
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
-->
AI assistants working on the Carbon repository **MUST NOT** use legacy or
generic UNIX shell search/edit commands when specialized environment tools
exist.
## Command line tools restrictions
- **DO NOT USE**: `cat`, `less`, `grep`, `sed`, or other shell utilities for
viewing, searching, or modifying files.
- **DO NOT USE**: `patch` to write and apply patch files.
- **DO NOT USE**: Writing custom scripts in other languages to circumvent this
limitation.
- **DO USE**: High-fidelity semantic API tools:
- **Viewing**: Use `view_file` instead of `cat` / `less`.
- **Searching**: Use `grep_search` / `find_by_name` instead of `grep` /
`find`.
- **Modifying**: Use `replace_file_content`, `multi_replace_file_content`,
or `write_to_file` instead of `sed` / `patch` / `python` edits.
You may only write and run temporary programs to modify source code if no
semantic tool is applicable or when performing complex, systematic transforms
across many codebase directories simultaneously.
## Temporary files management
Temporary files and scratchpad test scripts created by the assistant during
analysis, experiments, or debugging:
- **MUST** reside within the `tmp/` subdirectory under the workspace root.
- **MUST** be periodically cleaned out and deleted before ending your turn to
preserve a clean git workspace.
+4 -3
View File
@@ -1,8 +1,8 @@
---
name: Bazel usage
description:
Instructions for using Bazel or Bazelisk to build, test, and debug in the
Carbon repository.
Instructions that **MUST** be followed when using Bazel or Bazelisk to
build, test, and debug in the Carbon repository.
---
# Bazel usage
@@ -62,7 +62,8 @@ project uses Bazelisk.
You can run the Carbon driver or command line directly via Bazel:
- `bazelisk run //toolchain -- compile --phase=parse toolchain/parse/testdata/basics/empty.carbon`
- `bazelisk run //toolchain -- compile --phase=parse
toolchain/parse/testdata/basics/empty.carbon`
## Advanced configurations
+276
View File
@@ -0,0 +1,276 @@
---
name: Builtin functions
description:
Instructions for registering, mapping, constant evaluating, and lowering
builtin functions in the Carbon toolchain.
---
# Builtin Functions in the Carbon Toolchain
<!--
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
-->
Builtin functions are compiler-recognized primitives mapping directly from
Carbon code expressions (via standard prelude bindings) to optimized backend
execution. This document defines the complete structural workflow, C++ patterns,
constant evaluation logic, machine lowering mechanics, library bindings, and
validation strategies required to implement builtin functions in the Carbon
compiler.
---
## Technical Flow & Lifecycle
```mermaid
graph TD
Src[Carbon Source Code] -->|Prelude Map| Sem[Semantic Analysis / SemIR]
Sem -->|Signature Constraint| Sig[builtin_function_kind.cpp]
Sem -->|Phase Evaluation| Eval[eval.cpp Constant Interpreter]
Sem -->|Machine Codegen| Lower[handle_call.cpp LLVM Lowering]
Eval -->|Diagnostics| Diag[diagnostics/kind.def]
Lower -->|Native Instructions| LLVM[LLVM IR Generation]
```
Adding a builtin function involves a 5-step integration:
1. **Define the Builtin Kind**: Register the enum in
[builtin_function_kind.def](../../../toolchain/sem_ir/builtin_function_kind.def).
2. **Signature & Compile-Time Registry**: Declare the mapping name, parameter
constraints, and compile-time evaluation residency in
[builtin_function_kind.cpp](../../../toolchain/sem_ir/builtin_function_kind.cpp).
3. **Compile-Time Interpreter Support**: Wire constant evaluation hooks and
bounds/exception diagnostics in
[eval.cpp](../../../toolchain/check/eval.cpp).
4. **LLVM IR Lowering Support**: Connect target machine generation in
[handle_call.cpp](../../../toolchain/lower/handle_call.cpp).
5. **Prelude Library Mapping**: Bind primitive interfaces to named builtins
under [core/prelude/](../../../core/prelude/).
---
## Detailed Step-by-Step Implementation Guide
### Step 1: Kind Definition & Registration
Register your builtin function name using the X-macro in
[builtin_function_kind.def](../../../toolchain/sem_ir/builtin_function_kind.def):
```cpp
// toolchain/sem_ir/builtin_function_kind.def
// Converts an integer type to a floating-point type.
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntConvertFloat)
```
### Step 2: Signature Validation & Compile-Time Residence
Inside
[builtin_function_kind.cpp](../../../toolchain/sem_ir/builtin_function_kind.cpp):
1. **Define Parameter Constraints**: If the parameter requires novel
constraints (e.g. "must be a float type"), define a template constraint
struct checking the matching `SemIR` type instruction (such as `FloatType`
or `FloatLiteralType`). Use pre-established semantic helpers:
- `TypeParam<I, T>`: Ensures different parameters resolve to identical
type structures (e.g., generic constraint matching).
- `AnyInt`, `AnyFloat`, `AnySizedInt`, `AnySizedFloat`, `CharCompatible`,
`StdInitializerList`, `NoReturn`.
2. **Map Literal Name & Register Constraint Signature**: Declare a
`BuiltinInfo` constant inside `namespace BuiltinFunctionInfo` matching the
macro-defined name:
```cpp
// toolchain/sem_ir/builtin_function_kind.cpp
constexpr BuiltinInfo IntConvertFloat = {
"int.convert_float", ValidateSignature<auto(AnyInt)->AnyFloat>};
```
3. **Establish Compile-Time Residency Status**: Update
`BuiltinFunctionKind::IsCompTimeOnly` to determine if a call requires
compile-time evaluation:
- **Checked/Diagnostics Primitives**: Return `true` immediately. Runtime
lowering of these is illegal (e.g. `IntConvertFloatChecked`).
- **Runtime Primitives**: Return
`AnyLiteralTypes(sem_ir, arg_ids, return_type_id)` to enforce that
expressions involving unsized literal values (like `IntLiteral` or
`FloatLiteral`) are evaluated exclusively at compile-time (as they lack
runtime representation).
---
### Step 3: Constant Evaluation Support
Wire the interpreter inside [eval.cpp](../../../toolchain/check/eval.cpp) to
execute compile-time computations:
1. **Implement Constant Evaluation Logic**:
- Handle the builtin case inside `MakeConstantForBuiltinCall` (which
processes the compile-time execution of the call).
- Confirm type validation phase is `Phase::Concrete` to reject incomplete
bindings:
```cpp
case SemIR::BuiltinFunctionKind::IntConvertFloat: {
if (phase != Phase::Concrete) {
return MakeConstantResult(context, call, phase);
}
return PerformIntToFloatConvert(context, loc_id, arg_ids[0], call.type_id,
/*require_exact=*/false);
}
```
- Extract inputs safely from local value stores (e.g.
`context.ints().Get(arg.int_id)` or
`context.floats().Get(arg.float_id)`).
- Leverage high-precision LLVM mathematical structures (`llvm::APInt`,
`llvm::APFloat`, `llvm::APSInt`) to handle custom bits and signedness
safely.
2. **Diagnose Invalid Parameters or Exceptions**:
- Define compile-time diagnostics inside
[kind.def](../../../toolchain/diagnostics/kind.def):
```cpp
// toolchain/diagnostics/kind.def
CARBON_DIAGNOSTIC_KIND(IntTooLargeForFloatType)
```
- Emplace localized diagnostic formatting messages where they are caught
in `eval.cpp`:
```cpp
CARBON_DIAGNOSTIC(IntTooLargeForFloatType, Error,
"integer value {0} too large for floating-point type {1}",
llvm::APSInt, SemIR::TypeId);
context.emitter().Emit(loc_id, IntTooLargeForFloatType, val, dest_type_id);
```
- Return `SemIR::ErrorInst::ConstantId` to gracefully abort invalid
constant generation rather than crashing the compiler.
3. **Fast-Path Range Limits**:
- Before evaluating expensive math operations on giant exponents (e.g.
`1.0e1000000`), executing range limits check against `dest_width + 64`
(sized) or `IntStore::MaxIntWidth` (unsized) is mandatory to prevent
out-of-bounds calculations and compile-time memory exhaustion.
---
### Step 4: Machine Code Generation (LLVM Lowering)
Inside [handle_call.cpp](../../../toolchain/lower/handle_call.cpp):
1. **Map to Native LLVM Instructions**: For runtime-eligible builtins, map the
call inside `HandleBuiltinCall` to native LLVM IR builder methods:
```cpp
case SemIR::BuiltinFunctionKind::IntConvertFloat: {
auto* operand = context.GetValue(arg_ids[0]);
auto* dest_type = context.GetTypeOfInst(inst_id);
bool is_signed = IsSignedInt(context, arg_ids[0]);
context.SetLocal(
inst_id, is_signed
? context.builder().CreateSIToFP(operand, dest_type)
: context.builder().CreateUIToFP(operand, dest_type));
return;
}
```
2. **Assert on Compile-Time-Only Builtins**: Throw a hard assertion on
lowering-cases for checked validator builtins that should never hit code
generation:
```cpp
case SemIR::BuiltinFunctionKind::IntConvertFloatChecked: {
CARBON_CHECK(builtin_kind.IsCompTimeOnly(
context.sem_ir(), arg_ids,
context.sem_ir().insts().Get(inst_id).type_id()));
CARBON_FATAL("Missing constant value for call to comptime-only function");
}
```
---
### Step 5: Standard Library Prelude Integration
Map the standard library primitive interfaces to your newly minted named
builtins under [core/prelude/](../../../core/prelude/):
- **Primitive Mappings**: Bind Carbon methods directly to string-literal
builtin equivalents:
```carbon
fn Convert[self: Self]() -> Float(To) = "int.convert_float";
```
- **Strict Orphan Rule Compliance**: Carbon's orphan rules prohibit
implementing interfaces where neither the type nor the interface is locally
defined in the backing source module.
- **Literal Conversions**: Literal types (like `FloatLiteral`,
`IntLiteral`) do not have backing Carbon source files. Therefore, an
`impl` of `UnsafeAs` (which is defined in `as.carbon`) between two
literal types must reside inside `as.carbon` itself.
- **Sized Conversions**: Implementations targeting sized primitives (e.g.
`Int(N)`, `Float(N)`) must reside in their respective type source files
(such as [int.carbon](../../../core/prelude/types/int.carbon) or
[float.carbon](../../../core/prelude/types/float.carbon)) where the
backing target type resides to prevent duplicate symbols and structural
recursion loops.
---
## High-Fidelity Validation & Test Authoring
Follow the [Toolchain tests](../toolchain_tests/SKILL.md) skill with specialized
patterns for builtins:
### 1. Checker Builtin File Splits
Create validation splits under
[toolchain/check/testdata/builtins/](../../../toolchain/check/testdata/builtins/):
- **Test Naming Convention**: All tests under
[toolchain/check/testdata/builtins/](../../../toolchain/check/testdata/builtins/)
must be named after the builtin they are testing, replacing `.` characters
in the builtin name with `/` (directories). For example, a test for the
builtin `"char_literal.convert"` must be located at
`toolchain/check/testdata/builtins/char_literal/convert.carbon`.
- **Minimal Prelude & Direct Call Isolation**: Builtin tests must **not** test
the prelude library or operators. They must use the minimal primitive
prelude (`// INCLUDE-FILE:
toolchain/testing/testdata/min_prelude/primitives.carbon`) or a smaller
prelude, and explicitly declare and call the builtin functions under test
directly (e.g., `fn Add(a: f64, b: f64) -> f64 = "float.add";`). This
isolates the testing of compiler builtins from the library prelude.
- **Min-Prelude Limitations**: Standard operators (like `+`, `-`, `/`, `<`,
etc.) are **not** available in minimized preludes because the core operators
library isn't imported. To write tests with a minimal footprint, call
primitive builtins directly (e.g. `float.negate`, `float.div`) inside your
test code to build expressions.
- **Canonicalized Float Comparison**: In SemIR, real literal representations
with identical mathematical values can result in mismatched `RealId` objects
based on spelling variations. Verify compile-time constant conversions using
canonicalized comparison functions (e.g. passing converted results through
`Expect(X as f64)`) to completely avoid spelling mismatches in expected
outputs.
- **Locals Bypass**: If validating generic implicit conversions, compile-time
arguments cannot take local runtime variable parameters. Validate
compile-time conversions by passing literal constants directly, and sized
variable implicit conversions at runtime.
### 2. Machine Codegen Lowering Splits
Create testing splits under
[toolchain/lower/testdata/builtins/](../../../toolchain/lower/testdata/builtins/):
- Emplace a simple carbon binding to the tested builtin.
- Confirm matching LLVM metadata target definitions are mapped precisely
(e.g., matching `sitofp i32 %a to float`, `fptosi float %a to i32`).
+81
View File
@@ -33,6 +33,87 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- **Python**: Use `pre-commit run black --files <file.py>` to format Python
files.
## Comments
- **Describe the code that is there.** A comment should explain what the
current code does or why it is the way it is, not narrate what the code used
to be. Do not add a comment to justify a deletion or explain that something
is no longer necessary; a reader of the new code has no idea what is being
contrasted against, and the comment rots as soon as the old shape is
forgotten. Put that reasoning in the commit message instead.
- **Do not introduce a local variable just to host a comment.** If a call
argument reads clearly on its own, inline it rather than naming it so that a
comment has somewhere to attach.
- **Lead with a plain summary.** Start a doc comment with a direct statement
of what the function does or returns, such as "Returns true if the InstKind
is a singleton." Put nuance in a following sentence rather than qualifying
the summary into something harder to read.
- **Re-read a comment before updating it.** When a symbol is renamed or
changes meaning, a comment that mentions it is not automatically stale. Work
out what the comment actually asserts first: it may be describing what the
code _uses_, which is still true, and rewriting it loses information.
## Documentation
This covers standalone prose: `/docs`, `README.md` files, and the skill files
under `.agents/skills`.
- **Be true of the tree it lands in.** Documentation shipping in the same
commit as the code it describes should name that code freely. Documentation
that lands separately must not: a reader who greps for an identifier from an
unlanded change finds nothing, and a claim that only holds after that change
is false until it lands. This is easy to get wrong when writing up a lesson
while the change that taught it is still in flight.
- **Use placeholders for illustrations.** When an example only needs to show a
shape, name it `Foo` rather than reaching for a real symbol. Save real
identifiers for documenting that identifier, where going stale is at least
detectable.
- **Make each point stand alone.** A reader has none of the discussion that
produced it. State the rule, and enough of the reason to apply it, without
assuming knowledge of the change that motivated it.
## Naming
- **A name is a claim, so keep it true.** When a change invalidates the
invariant a name describes, rename it in the same change. A factory called
`MakeSingletonFooId` has to be renamed once `Foo` is no longer a singleton,
even though nothing forces you to.
- **Types in a signature are part of the claim.** A function returning the id
of a namespace should return `InstId`, not `TypeInstId`, because a namespace
is not a type. Do not let a convenient wider or narrower id type imply
something false.
- **Delete a predicate whose name stops distinguishing anything.** If a change
widens a test so that it no longer says what its name implies, remove it and
let callers use the underlying test, rather than keeping a wrapper that
sounds meaningful. What callers actually want is often the inverse, and is
worth adding under its own name.
- **Name a variable for its role, not its representation.** If the role a
value plays is unchanged, keep its name even when a change means it is now
held or spelled differently.
## Commit descriptions
- **Say what the change is, not how you made it.** Describe the resulting
code. Only describe process when the process is more interesting than the
change, as with a large mechanical transformation.
- **Do not narrate your own work.** Statements like "each such site was
audited" or "we investigated every caller" describe effort, not the change.
- **Omit routine mechanical steps.** Do not mention running the testdata
autoupdater or the formatter. Every change is expected to include those.
- **Do not enumerate each edit.** Describe the change as a whole rather than
listing every function or file touched; the diff already lists them.
- **Use the project's terms precisely.** Reach for the term of art the
codebase uses. Calling something a "type alias" or a "namespace" when it is
a named scope misleads, and is worse than a vaguer but accurate word.
- **Scope claims to what changed.** Say the specific thing that is now true,
rather than a broader statement that happens to contain it.
## Design
- **Do not distort the data model to improve output.** If printed or golden
output is undesirable, change the printer, not the data structures that
feed it.
## Style Guides
- **C++ style**: Follow the
+261
View File
@@ -0,0 +1,261 @@
---
name: Diagnostics
description:
Instructions for declaring, formatting, emitting, testing, and styling
diagnostic messages (errors, warnings, notes) in the Carbon toolchain.
---
# Diagnostics in the Carbon Toolchain
<!--
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
-->
The Carbon compiler features a highly-engineered, context-aware diagnostics
framework designed to deliver precise, readable, and highly targetable
diagnostic output (errors, warnings, notes). This document establishes strict
rules for declaring, formatting, emitting, testing, and styling compiler
diagnostics.
---
## Architecture Overview
```mermaid
graph TD
Kind[kind.def Registry] -->|Registration| Enum[Kind Enum ID]
Enum -->|Build/Emit| Emitter[Emitter LocT]
Emitter -->|ConvertLoc| Loc[Converted Physical Loc]
Emitter -->|formatv serialization| Formatting[format_providers.h / Custom Types]
Emitter -->|Emit Messages| Consumer[Console / Sorting Consumer]
Consumer -->|stable sort| StdErr[Compiler Standard Error]
```
Diagnostics are handled via three decoupled core components:
1. **Registry**: Globally enumerated kinds inside
[kind.def](../../../toolchain/diagnostics/kind.def).
2. **Emitters**: Specialized formatting pipelines (parameterized on custom
phase location types `LocT` like `Token` or `LocId`) that convert raw tokens
to standardized physical source locations (file, line, column, and text
snippet).
3. **Consumers**: Pipelines that process, track, filter, and sort diagnostics.
The default `SortingConsumer` buffers and stable-sorts diagnostics based on
their `last_byte_offset` matching compiler traversal order to ensure perfect
causal ordering.
---
## 1. Declaring and Registering Diagnostics
All diagnostic types must pass structural uniqueness and coverage verifications.
### The Diagnostic Registry
Every diagnostic kind must be registered globally as an enum option under
[kind.def](../../../toolchain/diagnostics/kind.def):
```cpp
// toolchain/diagnostics/kind.def
CARBON_DIAGNOSTIC_KIND(RealLiteralTooLargeForUnsizedInt)
```
### The Uniqueness Rule
To ensure optimal compile-time and analysis integrity, every diagnostic kind
declared in `kind.def` **MUST** be mapped to **one and only one** C++ macro
declaration (`CARBON_DIAGNOSTIC` or `CARBON_DIAGNOSTIC_ON_SCOPE`).
- **DO NOT** duplicate diagnostic definitions across different locations.
- The C++ representation of the diagnostic is a static/global constant of type
`DiagnosticBase<Args...>`.
- **Local Scope (Recommended)**: If the diagnostic is unique to a single
block/function body, declare it **locally** inside the function body
adjacent to its `Emit` trigger:
```cpp
void ConvertFloatValueToInt(...) {
CARBON_DIAGNOSTIC(FloatNaNConvertedToInt, Error,
"cannot convert NaN to integer type {0}", SemIR::TypeId);
context.emitter().Emit(loc_id, FloatNaNConvertedToInt, dest_type_id);
}
```
- **File Scope**: If the diagnostic is shared among multiple functions inside
the _same_ file, declare it at **file scope** inside the anonymous namespace
of the `.cpp` file.
- **Global Scope**: If a diagnostic (such as a shared helper note) is reused
_across different physical files_, define it in a shared header (e.g.
context/check helpers) and mark it `extern` where applicable, ensuring the
macro is only invoked once.
---
## 2. Formatting Diagnostic Arguments
Carbon diagnostics leverage LLVM's `formatv` engine. Parameters must be passed
using strongly-typed arguments to preserve translation capability.
### String Lifetimes & Pitfalls
- **`llvm::StringRef` is DISALLOWED**: Do not pass `StringRef` as a parameter
type to `CARBON_DIAGNOSTIC` due to unsafe lifetime and buffer-allocation
boundaries.
- **`llvm::StringLiteral` is DISALLOWED**: Do not use literal types as
arguments as they prevent future diagnostic localization and translations.
- **Use `std::string`**: If string formatting or custom allocations are
required, declare the parameter storage type as `std::string`.
### Format Selectors (`format_providers.h`)
Use specialized formatting wrappers under
[format_providers.h](../../../toolchain/diagnostics/format_providers.h) to
express clean inline options in format strings:
| Wrapper | Target Format Style | Example Usage | Output |
| :------------------------- | :------------------------------ | :----------------------------- | :------------------------------------------------------------------ |
| **`BoolAsSelect`** | `{Index:true\|false}` | `"{0:is signed\|is unsigned}"` | Maps bool to selection string. |
| **`IntAsSelect`** | `{Index:=Val:String\|:Default}` | `"{0:=1:is\|:are}"` | Matches exact options. |
| **`IntAsSelect` (Plural)** | `{Index:s}` | `"{0} argument{0:s}"` | Prints `"s"` if value != 1 (e.g., `"1 argument"`, `"3 arguments"`). |
### Custom Toolchain Type Mappings
Custom structures can define how they serialize inside diagnostics using the
`DiagnosticType` tag mapping to `Diagnostics::TypeInfo<StorageType>`:
- **Identifiers & Names** (declared in `check/diagnostic_helpers.h`):
- `NameId`: Formats raw identifier spelling, safely escaping keyword
conflicts under backticks automatically.
- `LibraryNameId`: Formats custom library descriptors cleanly (e.g.
`default library` or `library "foo"`).
- **Sized Primitives**:
- `TypedInt`: Formats an `APInt` constant exactly, extracting target
signedness representation automatically from its bound type
representation.
- **Type Formatter Hierarchy**: When choosing parameter types to print
compiler type representations, follow this priority list:
1. **`TypeOfInstId` (Preferred)**: Resolves the backing type of an
`InstId`, preserving programmatic aliasing, constraints, and source
spelling context. Enclosed under backticks automatically.
2. **`InstIdAsType`**: Converts an `InstId` for a type expression, printing
custom type layouts under backticks.
3. **`TypeId` (Fallback)**: Canonical description of the type. **Avoid when
possible** because type canonicalization loses intermediate source
program spelling and aliasing metadata.
4. **`*AsRawType` (e.g. `InstIdAsRawType`, `TypeIdAsRawType`)**: Formats
the type layout exactly like their counter-structures above, but
**omits** enclosing backticks (useful when inserting types inside larger
code snippets).
---
## 3. Fluent Emission Builders & RAII Scopes
### Fluent Builder Pattern
For compound diagnostics requiring multiple sub-notes, carets, or custom code
overrides, use `Build` to chain actions fluently:
```cpp
context.emitter()
.Build(second_node, ModifierRepeated, context.token_kind(second_node))
.Note(first_node, ModifierPrevious, context.token_kind(first_node))
.OverrideSnippet("custom snippet...")
.Emit();
```
> [!SAFETY] Emitter builders are marked `[[nodiscard]]`. To prevent a developer
> from creating a builder but failing to terminal-chain `.Emit()`, the builder
> uses an rvalue overload `Emit() &&` that triggers a compile-time
> `static_assert(false)`. You must save the builder to an lvalue or execute the
> chain exactly as `emitter.Build(...).Note(...).Emit()`.
### RAII Context & Annotation Scopes
Manage large checking structures requiring blanket note context using RAII block
scopes:
- `ContextScope`: Automatically converts any diagnostics emitted within its
scope into sub-notes under a high-level operation descriptor:
```cpp
ContextScope context_scope(&context.emitter(), [&](ContextBuilder& builder) {
builder.Context(eval_loc, InCallToEvalFn);
});
// any checker error emitted here will automatically append the 'InCallToEvalFn' note
```
- `AnnotationScope`: RAII block scope that automatically attaches blanket note
annotations to all scoped diagnostics.
---
## 4. Diagnostics Wording Style Guide
Refer to the official
[Diagnostic message style guide](../../../toolchain/docs/diagnostics.md#diagnostic-message-style-guide)
for complete details.
To maintain message consistency and integrate cleanly with Clang diagnostics in
interoperable code, adhere strictly to these rules:
- **Start with lowercase and omit periods**: Start diagnostic messages with a
lowercase letter or quoted code, and do **not** end them with a period
(e.g., `"cannot convert..."` or ``"`self` declared..."``).
- **Use backticks for quoted code**: Enclose identifiers, code constructs, and
types inside standard backticks (e.g., ``"`{0}` is bad"``).
- **Phrase as bullet points without articles**: Phrase diagnostics as
descriptive bullet points or sentence fragments rather than full sentences.
Leave out standard articles (`a`, `an`, `the`) unless necessary for logical
clarity. Semicolons can be used to separate fragments within a message.
- **Describe the situation and language rule**: Diagnostics should describe
the exact situation the toolchain observed. The language rule violated can
be mentioned if it wouldn't otherwise be clear:
- _Situation-only_: `"redeclaration of X"` (implies that redeclaration is
not permitted).
- _Rule-inclusion_: ``"`self` declared in invalid context; can only be
declared in implicit parameter list"``.
- **Wording Choice ("cannot" vs "allowed")**: Explicitly avoid `"allowed"`,
`"legal"`, `"permitted"`, `"valid"`, and related passive wording. You may
use `"cannot"` if needed, but try to use phrasing that does not require it:
- _Correct_: ``"`export` in `impl` file"`` (Avoids `"allowed"`)
- _Incorrect_: ``"`export` is only allowed in API files"``
- _Correct_: ``"`extern library` specifies current library"`` (Avoids
`"cannot"`)
- _Incorrect_: ``"`extern library` cannot specify the current library"``
- **Developer Intent Hints**: It is acceptable for a diagnostic to guess at
the developer's intent and provide a hint _after_ explaining the situation
and the rule, but never as a substitute for that:
- _Correct_: ``"cannot implicitly convert `i32` to `String`; add `as
String` for explicit conversion"``
- _Incorrect_: ``"add `as String` to convert `i32` to `String`"`` (Lacks
the core violation message).
- **Structure for Tooling API**: Try to structure diagnostics such that
parameter inputs can be programmatically extracted without string parsing
(prefer strongly-typed parameters over format placeholders where possible).
---
## 5. Diagnostics Testing & Coverage Verification
Carbon strictly enforces testing coverage at build-time.
1. **Tag Verification Requirement**: Every diagnostic kind declared in
`kind.def` (which is not blacklisted in the `UntestedKinds` array under
[coverage_test.cpp](../../../toolchain/diagnostics/coverage_test.cpp))
**MUST** be verified by at least one testcase file inside
`toolchain/*/testdata/`.
2. **Stderr Checklist Matchers**: The testcase split verifying the diagnostic
must catch it using standard CHECK matchers, explicitly tracking the
matching enum tag in standard error comments:
```carbon
// CHECK:STDERR: fail_bounds.carbon:[[@LINE+1]]:15: error: cannot convert NaN to integer type `i32` [FloatNaNConvertedToInt]
let a: i32 = Convert(nan_val);
```
3. **Build Enforcement**: Failing to provide a diagnostic test check matcher
triggers a build compilation error on the target test
`//toolchain/diagnostics:coverage_test`.
+102
View File
@@ -0,0 +1,102 @@
---
name: Jujutsu (jj) usage
description:
Instructions for using Jujutsu (jj) for version control in the Carbon
repository.
---
# Jujutsu (jj) usage
<!--
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
-->
[Jujutsu](https://github.com/jj-vcs/jj) is a Git-compatible version control
system that may be used in Carbon checkouts.
> [!IMPORTANT] You can detect if Jujutsu is in use by checking for a `.jj`
> directory in the repository root. If present, you **must** use `jj` and **must
> not** use `git`. If absent, you **must not** use `jj`.
## General usage
Always use the `--no-pager` flag when invoking `jj` to prevent the command from
blocking or waiting for terminal paging.
## Common commands
### Syncing with remote
- **Fetch from remote**: `jj --no-pager git fetch`
- **Create a new change on top of trunk**: `jj --no-pager new trunk`
- **Show repository status**: `jj --no-pager status`
- **Show commit history**: `jj --no-pager log`
### Managing changes
- **View diff of current changes**: `jj --no-pager diff`
- **Commit changes**: `jj --no-pager commit`
- _Note_: Prefer using `jj commit` over the combination of `jj describe`
and `jj new`.
- **Abandon/discard current changes**: `jj --no-pager abandon`
- **Rebase current change onto trunk**: `jj --no-pager rebase -o trunk`
### Working with a stack of changes
A change is often built as a stack of commits sent up as a single pull request.
The stack is not necessarily based on `trunk`; it may be based on another change
that is itself still in flight.
> [!WARNING] **Never rewrite the history of a change that has been submitted as
> a pull request.** Reviewers track a PR by its commits, and squashing,
> reordering, or abandoning them discards review that is already in progress.
> This cannot be undone from their side.
>
> Before rewriting history in any other case, propose the exact command and wait
> for confirmation. This applies to `squash`, `rebase`, `abandon`, and
> `describe` on an existing change.
#### Finding the base of the stack
Bookmarks delimit the stack. List the bookmarks that are ancestors of the
working copy, nearest first:
```bash
jj --no-pager log -r '::@ & bookmarks()'
```
Reading the result takes care, because two situations produce similar output:
- **Editing an existing change.** The nearest bookmark names the change being
worked on, and the bookmark below it is the base.
- **Starting a new change.** The commits above the nearest bookmark have no
bookmark of their own yet, so the nearest bookmark is itself the base.
`trunk` is only ever a base. Finding `trunk` nearest means new work is being
built on top of it, never that `trunk` itself is being worked on.
The graph does not distinguish the two cases: an unbookmarked or empty commit
above a bookmark may be the next commit of that change or the start of a new
one. Ask which it is when it is not clear, and ask before choosing where a fix
should land rather than after. Guessing wrong means squashing into a change that
may already be under review.
Once the base is known, use it to scope commands to the current stack:
```bash
jj --no-pager log -r '<base-bookmark>..@'
```
#### Managing the stack
- **Fold a fix into an earlier change**:
`jj --no-pager squash --into <change-id> [path]`. Follow-up fixes and
formatter reflows belong in the change that introduced the code, not in a
trailing "fixes" commit, unless that change has already been submitted.
Naming a path squashes only that part of the working copy, leaving unrelated
work in place.
- **Descriptions**: only one change in the stack needs a long description, the
one used as the pull request description. Every other change gets a short
one-line summary. Do not repeat the long text across the stack.
+197
View File
@@ -0,0 +1,197 @@
---
name: Language server
description:
Instructions for working on Carbon's LSP language server, including its
architecture, its file_test-based tests, and the VS Code extension.
---
# Language server
<!--
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
-->
## Introduction
This skill covers [`toolchain/language_server/`](/toolchain/language_server/),
which implements `carbon language-server`, and
[`utils/vscode/`](/utils/vscode/), the VS Code extension that launches it.
## Architecture
The server is built on clangd's LSP transport (`clang::clangd`), not on a
Carbon-specific one. That means clangd's `Protocol.h` types (`Position`,
`Range`, `Location`, `Hover`, `MarkupContent`) are the interface currency.
- `server.cpp` / `incoming_messages.cpp`: message dispatch. A handler must be
registered in `incoming_messages.cpp` before it can be called.
- `handle_*.cpp`: one file per request family, each declaring its entry point
in `handle.h`.
- `handle_initialize.cpp`: the advertised capabilities. **Adding a capability
changes `Content-Length` in every test that calls `initialize`**, so expect
a large autoupdate diff.
- `context.h` / `context.cpp`: `Context::File` per open document, plus the
compile driver. `Context::File::unit()` has a `CARBON_CHECK` on the compile
driver, so any handler that reaches for the parse tree must first rule out
documents that were never compiled.
- `position.h`, `sem_ir_index.h`: mapping source positions to SemIR
instructions for real Carbon files.
- `sem_ir_text.h`, `handle_sem_ir_text.h`: navigation within the *formatted
SemIR* in a test file's `// CHECK:STDOUT:` lines. This is a heuristic text
index, deliberately independent of the real SemIR data structures. See
[the SemIR text reader](#the-semir-text-reader).
### Document kinds
The server handles two kinds of document, distinguished by the `languageId`
from `textDocument/didOpen`, with a content sniff as a fallback:
- `carbon`: a real Carbon file. Compiled; diagnostics published.
- `carbon-testdata`: a test file. **Not compiled**, because we lack logic to
split it into one file per `// ---` split marker.
> [!IMPORTANT]
> A handler that assumes every file was compiled will crash on a test file.
> When adding one, give the test-file path an explicit early return.
## Tests
There is **no language-server-specific test target**.
`toolchain/language_server/BUILD` only declares
`filegroup(name = "testdata")`, which is pulled into
`//toolchain/testing:all_testdata` and run by `//toolchain/testing:file_test`.
```bash
# Run just the language server tests (or any subset).
bazelisk test //toolchain/testing:file_test \
--test_arg=--file_tests=toolchain/language_server/testdata/position/hover_and_goto.carbon
# See the raw output, which is much easier to read than a test failure.
bazelisk run //toolchain/testing:file_test -- --dump_output \
--file_tests=toolchain/language_server/testdata/position/hover_and_goto.carbon
# Update expectations. Never hand-write CHECK lines.
./toolchain/autoupdate_testdata.py toolchain/language_server/testdata/...
```
These tests run serially, because clangd's logging is a global singleton.
### Test file shape
The request stream is a `// --- STDIN` split written with the `[[@LSP-*]]`
keywords, and the responses land in a trailing `// --- AUTOUPDATE-SPLIT`.
Documents come from other splits by way of `"text": "FROM_FILE_SPLIT"`, which
is substituted with the content of the split whose name matches the `uri`.
```carbon
// --- position.carbon
fn Abs(n: i32) -> i32 { return n; }
// --- STDIN
[[@LSP-CALL:initialize:"capabilities": {}]]
[[@LSP-NOTIFY:textDocument/didOpen:
"textDocument": {
"uri": "file:/position.carbon",
"languageId": "carbon",
"text": "FROM_FILE_SPLIT"
}
]]
[[@LSP-CALL:textDocument/hover:
"textDocument": {"uri": "file:/position.carbon"},
"position": {"line": 0, "character": 3}
]]
[[@LSP-CALL:shutdown]]
[[@LSP-NOTIFY:exit]]
// --- AUTOUPDATE-SPLIT
```
Full keyword documentation is in
[`testing/file_test/README.md`](/testing/file_test/README.md).
### Traps
> [!WARNING]
> **A blank line inside the `STDIN` split breaks the JSON transport.** It
> terminates a header block, so clangd logs a timestamped
> `Warning: Missing Content-Length header, or zero-length message.` The
> timestamp makes the test unreproducible, so it fails on the next run.
> Comment lines between messages are fine; blank lines are not. A single blank
> line immediately before `// --- AUTOUPDATE-SPLIT` is also fine.
Other things worth knowing:
- **`positionEncoding` is UTF-16.** A `character` is a UTF-16 code unit
offset, not a byte offset.
- **Line and character numbers in requests are 0-based**, while the `locN_M`
suffixes in SemIR output are 1-based. Off-by-ones here are silent: the
request succeeds and returns the wrong thing.
- **A split can hold a document that itself contains `// CHECK:STDOUT:`
lines**, because `CHECK` lines only form expectations inside the
`AUTOUPDATE-SPLIT`. Such a document still can't contain a literal `// ---`
line, which would split the enclosing test file; write it as
`[[@0x2f]]/ --- name.carbon`.
## The SemIR text reader
`sem_ir_text.cpp` indexes the formatted SemIR inside a test file's
`// CHECK:STDOUT:` lines so that hover and go-to-definition work on operand
names. It is a heuristic reader, not a parser, and its correctness rests on
facts about `toolchain/sem_ir/formatter.cpp` and
`toolchain/sem_ir/inst_namer.cpp`. Re-check these if the formatter changes:
- There are exactly four scope keywords: `file`, `generated`, `imports`, and
`constants` (`InstNamer::GetScopeName`). Everything else is `@entityname`.
- A reference is `%name` within its own scope and `scope.%name` otherwise
(`InstNamer::GetNameFor`). Names may contain `.` and may _start_ with one,
as in `%.Self.frozen`.
- **Type annotations are printed in the `constants` scope.**
`Formatter::FormatTypeOfInst` does
`llvm::SaveAndRestore file_scope(scope_, InstNamer::ScopeId::Constants)`,
so in `%x: %foo = ...` a bare `%foo` means `constants.%foo`. This does not
apply to ordinary operands or to `[concrete = ...]` annotations.
- **`*_decl` braces hold the declared entity's scope.** The braces of
`%F.decl: ... = fn_decl @F [...] { ... } { ... }` are lexically inside
`file { }`, but their names belong to `@F`.
- **`!with Self:` switches scope without a brace**, until `!members:`
switches it back. Brace counting alone cannot see this.
- A `specific @F(args) { }` block uses `@F`'s scope and defines nothing; each
`%name => value` row references an instruction of the generic.
### Validating a change to the reader
The unit tests only cover a handful of cases. To check a change against the
real corpus, drive the server over a sample of check testdata, hovering on
every `%name`, and compare the resolved fraction before and after. Roughly 99%
of names resolve; the residue are names the formatter references but never
emits a definition line for, such as `%I.WithSelf.F`, which only ever appears
inside a `[symbolic = ...]` annotation.
Two things to get right in such a harness:
- Feed the request stream from a **file**, not a pipe. The server reads stdin
as a file and reports `error: Input/output error` on a pipe.
- Read the output as **bytes**. Python's `text=True` rewrites the `\r\n`
framing, and `Content-Length` counts bytes.
## VS Code extension
[`utils/vscode/`](/utils/vscode/) declares three languages in `package.json`:
| Language id | Applies to |
| ---------------- | --------------------------------- |
| `carbon` | `*.carbon` |
| `carbon-testdata`| `**/testdata/**/*.carbon` |
| `semir` | `*.semir` |
> [!NOTE]
> The TextMate _scope_ for SemIR is `source.carbon-semir`, but the
> _language id_ is `semir`. Markdown code fences in hover text resolve language
> ids, so a fence must say ` ```semir `.
`extension.ts` launches the server over stdio, using the `carbonPath` setting
(default `./bazel-bin/toolchain/carbon`). Its `documentSelector` controls which
files are sent to the server at all; a new document kind has to be added there
as well as in the server.
+66
View File
@@ -0,0 +1,66 @@
---
name: Prek
description:
Instructions for running prek, the Carbon pre-submit/style/lint checker,
that *MUST* be run before submitting an change.
---
# Prek
<!--
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
-->
`prek` is the Carbon pre-submit, style, and lint checker. Running it is
mandatory before submitting any changes.
## Running prek
To run `prek` on all files:
```bash
prek run -a
```
To validate a specific list of files:
```bash
prek run --files <files>
```
## Running prek in a Jujutsu (jj) workspace
If you are working in a Jujutsu workspace, running `prek` directly will fail
because it expects a standard Git repository structure. Instead, use the helper
script:
```bash
./scripts/jj_prek.sh
```
This script runs `prek` on all files that have changed between `trunk` and your
current Jujutsu `@` change.
Note that the script always compares against `trunk`. If your change is based on
another bookmark rather than on `trunk`, the script also checks the files
changed by that underlying change, so a reported failure may not be in your own
work.
## Hooks that rewrite files
Some hooks, notably `clang-format` and `rumdl`, fix problems in place rather
than only reporting them. When they do, `prek` reports a failure and exits
non-zero even though the tree is now correct.
Re-run `prek` after any failure that modified files, and treat the second, clean
run as the result. Review what it changed: a reflow is expected, but a content
change may not be what you intended.
## Prek dependency errors
> [!TIP] If `prek` fails with an error about resolving dependencies or security
> policy, you may be running in a restricted environment where the
> special-purpose `gpkg` tool is required. Prefix the command with `gpkg`, for
> example: `gpkg prek run -a` or `gpkg ./scripts/jj_prek.sh`.
+135
View File
@@ -0,0 +1,135 @@
---
name: Proposals
description:
Instructions for writing, submitting, and managing Carbon evolution
proposals.
---
# Proposals
<!--
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
-->
## Overview
This skill provides instructions and best practices for working with proposals.
Only create a proposal when explicitly directed as part of the task.
Make sure to confirm the desired title for the proposal as that will govern the
filename.
## Create a new proposal
1. **Use the helper script**: Run `./proposals/scripts/new_proposal.py "Title"`
to create a templated file and instructions for setting up the PR.
2. **Proposal file**: The file will be named `proposals/p######-title.md`,
where `######` is the 6-digit GitHub pull request number and `title` is a
slugified version of the proposal title.
3. **Template**: Follow the structure in `proposals/scripts/template.md`,
noting the specific `TODO` instructions in each section for the content that
should be included there.
4. **PR description**: Update the pull request description to match the
abstract section in the proposal document.
> [!IMPORTANT] Do _not_ mark the PR ready for review. The user must have the
> opportunity to review the proposal produced before asking for any review.
## Writing style and best practices
- **Skimmable**: Use
[BLUF](<https://en.wikipedia.org/wiki/BLUF_(communication)>) (Bottom Line Up
Front) or
[Inverted Pyramid](<https://en.wikipedia.org/wiki/Inverted_pyramid_(journalism)>)
style. Keep it brief, focused, and technical.
- **Match existing proposal style**: Review [existing proposals](/proposals)
(preferring more recent ones with higher numbers) to understand the expected
style, wording, and nature of content to include.
- **Connect to goals**: In the Rationale section, link to specific goals in
[`/docs/project/goals.md`](/docs/project/goals.md) and principles in
[`/docs/project/principles`](/docs/project/principles) (e.g.,
`error_handling.md`, `one_way.md`).
- **Living design**: If the proposal updates design documentation, include
those changes in the PR if possible. If deferred, add "TODO" comments
pointing to the proposal (e.g., `> **TODO:** Document ... adopted in
[p######](/proposals/p######-title.md)`). For pervasive changes, file a
GitHub issue instead of adding many TODOs.
## Alternatives considered and leads decisions
There are always alternatives to a proposal, and the proposal should carefully
include sections describing all of them and the rationale for not selecting
them. Any living design document updates should focus on fully describing the
end-state design, and the key motivating aspects of that design. The main
proposal should focus on _what is changing_ and _why it is changing_, and should
leave detailed description of the resulting design to the living design
document, and _why not_ rationale to the description of each alternative.
- **Cover all the alternatives**: Make sure to describe any alternatives
considered, even if minor or rejected early.
- **Be specific**: Don't be vague about any of the alternatives, or the
rationale for not choosing them.
- **Connect to goals or principles**: In addition to the rationale section,
one of the best rationale structures for rejecting an alternative connects
that choice back to the goals or principles relevant.
- **Always frame as a tradeoff**: Selecting the proposed direction instead of
an alternative is _always_ a tradeoff, with both advantages and
disadvantages.
- **Where relevant, cite the leads issue** that decides against an
alternative, in addition to summarizing the key points, tradeoffs, and
rationale for the decision.
> [!IMPORTANT] Don't just list the alternatives, create a sub-section for each
> alternative and carefully describe the alternative, the advantages,
> disadvantages and what the core of the decision is to reject each alternative.
> [!IMPORTANT] Carefully research each alternative in the leads issue in order
> to provide this clear and comprehensive explanation.
## Building from a leads issue
Sometimes a proposal is specifically documenting and formalizing a decided leads
issue. When this is the case, carefully research that leads issue, reading the
original issue text and every comment on the issue. Also read any linked Google
documents, linked issues, examples, gists, or other supplemental information
cited.
- **Summarize the leads issue**: Ensure you provide a high level summary of
the _decided_ direction of the leads issue as the proposal.
- **Capture and document** every key aspect of the decision made and factor
that led to the decision. It is important that the proposal stands alone,
and the leads issue is merely cited for context and history.
- **Stay grounded**: Only include alternatives, rationale, and arguments based
on what you find in the issue and related documents. No new information
should be in the proposal.
Use the `gh` command line tool to query leads issues in order to carefully
examine all of the comments. Follow any mentioned links to gather more data.
Refer to the [GitHub CLI usage skill](/.agents/skills/github_cli/SKILL.md) for
detailed instructions on using the `gh` tool.
Ask the user to clarify any aspects of the leads issue that are unclear rather
than continuing to edit the proposal. If there are questions that you don't find
an answer to in the issue, ask this to the user and let them provide an answer
that you use as the basis of what to include.
## Examples are golden
Heavily leverage examples to illustrate both the specifics of the design being
proposed, and the nature of the change being proposed. More examples to
illustrate more aspects, corner cases, or provide a more complete understanding
are almost always good. Comments in example code should focus on what that part
of the example illustrates from the proposal.
## Keep the PR description in sync with the abstract
Whenever you edit the abstract or notice differences from the PR description,
you should update the PR description to match the abstract. The only exception
is to retain any "Assisted-by" or other tags at the end of the description that
are only needed there and not in the abstract.
If you are creating or updating a proposal, make sure the PR description in
question contains an `Assisted-by:` tag that is appropriate for describing which
AI tool is being used.
@@ -0,0 +1,366 @@
---
name: Review testdata changes
description:
Instructions for judging whether changes to file test output
(`// CHECK:STDOUT:` and `// CHECK:STDERR:` lines) are correct, which
changes are acceptable churn, and which are regressions in disguise.
---
# Review testdata changes
<!--
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
-->
## Introduction
Most toolchain work moves file test output. `./toolchain/autoupdate_testdata.py`
rewrites the `// CHECK:STDOUT:` and `// CHECK:STDERR:` lines in
`toolchain/*/testdata/` to match current behavior, so after running it the tests
pass again whether or not the new behavior is right. Deciding that the new
output is the output you wanted is a separate, manual step, and it is the step
this skill covers.
Three skills divide the work:
- [Toolchain tests](../toolchain_tests/SKILL.md): how to _author_ tests and
generate their output.
- This skill: how to _judge_ an output diff.
- [Summarize testdata changes](../summarize_testdata_changes/SKILL.md): how to
_report_ an output diff once you believe it is correct.
> [!IMPORTANT] Never hand-edit `// CHECK:STDOUT:` or `// CHECK:STDERR:` lines.
> Everything below is about changing the _code_ until the generated output is
> right, never about editing the output to match the code.
## The rule that generates all the others
**Every line of testdata churn must have a cause you can name.** Not "it is
similar to the other changes", not "the tests pass now" — an actual sentence
saying which code change produced it and why that is the intended result.
A diff you cannot narrate is a diff you have not reviewed. Changes you cannot
explain are where regressions hide, because a regression and an intended change
look exactly alike once the autoupdater has written them down.
> [!CAUTION] Autoupdating is destructive to your evidence. Once the autoupdater
> has run, the previous expectations are gone from the working copy. Read the
> diff after _every_ autoupdate run, and if something changed for a reason you
> cannot name, fix the code before autoupdating again. Recovering the old
> expectations later means reverting and re-running.
## STDERR and STDOUT are different kinds of evidence
Treat them separately; they have different standards of proof.
**STDERR is user-visible behavior.** These are the diagnostics a Carbon
programmer sees. A change here is a change to the language implementation as
users experience it, so each one needs an individual justification. For a
refactoring, the expected STDERR diff is empty.
**STDOUT is internal representation.** SemIR dumps, parse trees, LLVM IR. Users
never see it. Churn here is normal and often unavoidable, so the standard is not
"no change" but "no change I cannot account for".
For a change that is supposed to preserve behavior, write the list of accepted
STDERR changes down _before_ you autoupdate, and keep it current. Order matters
more than form: written first, the list is a prediction the diff can falsify;
written afterwards, it is a description of whatever happened, and describes a
regression exactly as well as an intended change.
The list does not have to be a deliverable. Scratch notes you never publish do
the job, because the work is in committing to the list, not in presenting it.
What a reader needs is not the list but its exceptions: diagnostics that changed
without being predicted, and predictions that did not occur. Both are findings.
The matches are not, and reporting them buries the two entries that matter.
## Judging STDOUT churn
Sort each STDOUT change into mechanical or structural.
### Mechanical churn
Expected, and cheap to accept in bulk once you have confirmed the pattern:
- **Renaming.** An instruction, type, or scope prints under a new name.
- **Positional name renumbering.** Names like `%x.loc18_46.3` embed a line,
column, and disambiguating index. Two different events move them:
- Adding or removing an instruction at a location renumbers the rest, so a
_single_ removed instruction can show up as many changed lines in the
same block. Confirm the cascade is a cascade before accepting it as one.
- Adding or removing a _diagnostic_ moves the source lines themselves, so
the line component changes everywhere below it in the file. See
[Autoupdate to a fixed point](#autoupdate-to-a-fixed-point).
- **Fingerprint-derived names.** Mangled names and some scope names are
derived from a hash of their inputs. If you changed a hashed input, these
move. Confirm that each such difference is _only_ the fingerprint, and not a
fingerprint difference concealing a structural one.
> [!TIP] Mechanical churn is usually wide and shallow: the same substitution,
> repeated across many files. If a "mechanical" pattern needs a different
> explanation in each file, it is not mechanical.
### Structural churn
Each of these needs its own explanation:
- Instructions appearing or disappearing.
- Changed `[concrete = ...]`, `[symbolic = ...]`, or other constant-value
annotations.
- Changed types on existing instructions.
- Changed control flow: new or removed blocks, changed branch targets.
- Raw instruction ids renumbering in `--dump-raw-sem-ir` output when you did
not intend to change the id layout.
## Fewer instructions is not automatically better
A diff that removes instructions looks like an optimization. Whether it is one
depends on what the changed function owes its caller, and two functions can
produce the same shrinking diff for opposite reasons:
- A function that only needs a constant value, but built instructions on the
way to it, was doing wasted work. Dropping them and using the constant
directly is correct, and the shorter output reflects that.
- A function whose caller needs a **non-canonical instruction** can be made
shorter the same way, by using the constant instead of creating an
instruction of its own — and that is wrong. The instruction carries location
information, and symbolic constant substitution operates on it; the constant
value alone loses both.
The instruction count is identical evidence in both cases, so it cannot be the
thing you judge. Decide what the function is required to produce; the count
follows from that.
The same reasoning runs in reverse: a diff that _adds_ instructions is not
automatically a regression.
## Judging STDERR churn
### A diagnostic's authority depends on the file's prefix
The `fail_` and `todo_` prefixes (see
[Toolchain tests](../toolchain_tests/SKILL.md)) say how much the recorded
diagnostics are worth:
- **`fail_...`** — the test should and does produce errors. The recorded
diagnostic **is the specification**. Changing it is a user-visible behavior
change and needs justification on its own merits.
- **`fail_todo_...`** — the test produces errors (or crashes) but shouldn't,
or produces the wrong errors. The recorded diagnostic is explicitly **not**
the specification; the file exists to record that today's behavior is wrong.
Changing it replaces one wrong answer with another. That is acceptable when
you can say why the new message follows from your change and why it is no
further from the intended eventual behavior — which, for many such files, is
no diagnostic at all.
- **`todo_fail_...`** — the test should produce errors but does not. Gaining a
diagnostic here may be _progress_, not a regression. Either way the file
must be renamed, since the framework requires a `fail_` prefix on any file
that errors: `fail_...` if it now produces the right error, `fail_todo_...`
if the error is the wrong one. Both renames make the test pass, so record
which case it is instead of letting the rename settle it.
- **`todo_...`** — behavior is wrong but produces no errors, and shouldn't.
Gaining a diagnostic here is a regression unless you can argue otherwise.
> [!IMPORTANT] This is a reason to look at the _filename_ before judging a
> diagnostic change, not a license to ignore `todo_` files. "It was already
> broken" does not excuse making it differently broken for no reason.
### Reclassifying tests
If your change moves a test between the states above, the prefix must move with
it: when the test is fixed, when it starts failing, and when it starts failing
differently. The correspondence between the `fail_` prefix and whether
compilation actually failed is enforced by the test framework, not by the
autoupdater, so it surfaces when you run `bazelisk test` and not when you
autoupdate. **Autoupdating is not a substitute for running the tests.**
Only that half of the name is checked. Nothing enforces `todo_`, so a file whose
behavior you have just fixed can keep its `todo_` prefix indefinitely and still
pass. A missing `fail_` stops the build; a stale `todo_` is silent, and is yours
to catch.
When a fix drops a file's prefix, also check that the file still belongs where
it is and that its comments do not still describe the old broken behavior.
### Vaguer diagnostics are a signal, not a verdict
When a diagnostic becomes less specific — a general "unsupported" message
replacing one that named the problem — that usually means a code path stopped
finding information it previously had. Sometimes that is correct: the
information was misleading, and the old message was confidently wrong.
Do not accept it silently and do not reject it reflexively. Say which direction
it moved and whether the new message is closer to or further from the eventual
intended behavior.
## Signals in the shape of the diff
### Zero churn is a result
If you removed something you believed was doing work and _no_ testdata moved,
that is not a missing test run — it is the proof that the thing was a no-op.
Say so explicitly; it is one of the strongest pieces of evidence a refactoring
can produce.
The converse is also informative. If you expected a path to churn and it didn't,
either your model of the code is wrong or that path is untested. Find out which,
and consider adding a test before continuing.
### Churn should be proportional to the change
- **Wide churn from a narrow change** means your model of the code is wrong.
Do not autoupdate over it. Find the structural mistake first.
- **Narrow churn from a sweeping change** means the affected paths are
probably untested.
Set a rough expectation for the size of the diff before you run the autoupdater,
and treat a large mismatch in either direction as a finding.
### Never make the diff smaller by weakening the test
Editing test _input_ (the Carbon source, not the CHECK lines) to make a diff
look better is a behavior change in disguise. Deleting a test that now produces
awkward output is worse. If a test's input has to change, that is a separate,
explicitly-justified change, not diff cleanup.
## What the autoupdater will not fix for you
- **`NOAUTOUPDATE` files.** Their expectations are maintained by hand. They
fail under `bazelisk test` rather than being silently rewritten.
- **Hand-written C++ expectations**, for example golden output asserted in a
`_test.cpp`. When several assertions in one of these break together, often
only the first failure is reported, so fixing it can reveal another. Re-run
until clean rather than assuming one fix was the whole repair.
- **Golden files outside `testdata/`**, and documentation that quotes compiler
output.
## Review loop
### Run the autoupdater
Autoupdate everything, then read the diff a directory at a time. Passing no
paths is the default and updates every file test in the toolchain:
```bash
./toolchain/autoupdate_testdata.py
```
Narrow the scope only while iterating on one subdirectory you know you are not
done with, where each round would otherwise regenerate output you have already
read:
```bash
./toolchain/autoupdate_testdata.py toolchain/check/testdata/SUBDIR/**/*
```
The globs are expanded by the shell; the script filters its arguments to
`.carbon` files under a `testdata/` directory.
> [!TIP] If intermediate states crash on `CARBON_CHECK` failures, pass
> `--non-fatal-checks` so you can see the full set of downstream damage in one
> run instead of one crash at a time.
> [!IMPORTANT] Return to the full scope before judging the diff. Whether churn
> is proportional to the change, and whether a change to one phase moved another
> phase's testdata, are only visible across everything.
### Autoupdate to a fixed point
One pass is not always enough, because the autoupdater's output is part of its
own input. `// CHECK:STDERR:` lines sit inline, immediately above the source
line they describe, and the compiler reads them as comments in the file.
Gaining or losing a diagnostic therefore moves every source line below it.
Within a single run, the compiler has already read the file as it was, so the
two kinds of output end up in different states:
- **STDERR is correct after one pass.** These lines locate themselves
relatively, as `[[@LINE+N]]`, and the autoupdater recomputes `N` as it
places them.
- **STDOUT is stale after one pass.** SemIR names like `%x.loc18_46.3` embed
an absolute line and column with no filename attached, and the autoupdater
only remaps `file.carbon:18`-style references. Nothing rewrites the `loc`,
so it still describes where the instruction was _before_ the diagnostic
lines moved it.
Running again compiles the shifted file and the names catch up. The diagnostic
set does not change this time, so nothing shifts again and a third run is a
no-op. Keep running the autoupdater until it stops changing files: one pass when
the diagnostics held still, two when they didn't.
> [!WARNING] The intermediate state is self-inconsistent, not just unfinished:
> its `loc` names describe a file layout that no longer exists. Keep reading the
> diff after every run — that rule does not change — but do not chase positional
> churn to a cause until the file has converged, and do not present the diff
> until then either.
The converging pass should be positional renumbering and nothing else. If it
moves an instruction, a type, or a constant value, then something other than a
`loc` name is sensitive to where lines fall in the file. Find out what before
accepting it.
The file tests do catch a file left unconverged, since each test re-runs the
autoupdate in memory and fails with
`Autoupdate would make changes to the file content` when the result differs. But
that arrives at `bazelisk test` time, after you have already read a diff that
was describing a file which had moved out from under it.
### Inspect the diff
Inspect the diagnostics first, since that is the acceptance criterion:
```bash
# STDERR-only view, with jj.
jj --no-pager diff --git 'glob:toolchain/*/testdata/**' \
| grep -E '^[-+].*CHECK:STDERR'
# The same, with git.
git diff -- 'toolchain/*/testdata/*' \
| grep -E '^[-+].*CHECK:STDERR'
```
For a structured view separating test input, STDERR, and STDOUT changes, use the
helper from the
[Summarize testdata changes](../summarize_testdata_changes/SKILL.md) skill:
```bash
jj --no-pager diff --git 'glob:toolchain/*/testdata/**' \
| python3 .agents/skills/summarize_testdata_changes/scripts/parse_diff.py
git diff -- 'toolchain/*/testdata/*' \
| python3 .agents/skills/summarize_testdata_changes/scripts/parse_diff.py
```
The argument after the tool name is not interchangeable: `glob:...` is a jj
fileset, `-- ...` is a git pathspec. `--git` and `--no-pager` are jj flags; git
already emits this format and skips the pager when piped.
### Run the tests
Then run the tests, which is what catches prefix mismatches and non-autoupdated
expectations:
```bash
bazelisk test //toolchain/...
```
See the [Bazel usage](../bazel/SKILL.md) skill.
## Checklist
Before presenting a testdata diff as finished:
- [ ] The autoupdater was run until it made no further changes, so no `loc`
name describes a stale line numbering.
- [ ] The list of accepted STDERR changes was written before autoupdating, and
every change either matches it or is reported as an exception.
- [ ] Every STDOUT change is either an instance of a named mechanical pattern
or has its own explanation.
- [ ] Instructions that appeared or disappeared are justified by what the
changed function owes its caller, not by the instruction count.
- [ ] Files whose prefix no longer matches their behavior have been renamed.
- [ ] No test input was changed, and no test was deleted, to make the diff
smaller.
- [ ] The size of the diff is proportional to the size of the change.
@@ -17,6 +17,10 @@ This skill provides instructions for creating a comprehensive report summarizing
changes to Carbon testdata files (`toolchain/*/testdata`) and associating them
with related code changes.
This skill is about _reporting_ a diff. For deciding whether the diff is correct
in the first place, see the
[Review testdata changes](../review_testdata_changes/SKILL.md) skill.
## Goals
Produce a report that:
@@ -40,10 +44,11 @@ input changes.
#### For Git Users:
- **Summarize code changes**: `git diff --stat -- ':!toolchain/*/testdata'`
- **Summarize code changes**: `git diff --stat -- ':!toolchain/*/testdata/*'`
- To see content of non-testdata changes:
`git diff -- ':!toolchain/*/testdata'`
- **Identify testdata changes**: `git diff --name-only 'toolchain/*/testdata'`
`git diff -- ':!toolchain/*/testdata/*'`
- **Identify testdata changes**:
`git diff --name-only 'toolchain/*/testdata/*'`
#### For Jujutsu (jj) Users:
@@ -80,7 +85,7 @@ STDOUT changes. This script reads a unified diff from stdin.
```bash
# For Git:
git diff -- 'toolchain/*/testdata' | python3 .agents/skills/summarize_testdata_changes/scripts/parse_diff.py
git diff -- 'toolchain/*/testdata/*' | python3 .agents/skills/summarize_testdata_changes/scripts/parse_diff.py
# For Jujutsu (jj):
jj diff --git 'toolchain/*/testdata' | python3 .agents/skills/summarize_testdata_changes/scripts/parse_diff.py
@@ -110,7 +115,8 @@ gh pr diff 1234 | python3 .agents/skills/summarize_testdata_changes/scripts/pars
`// CHECK`), along with diagnostic output changes where relevant
- Diagnostic Changes: Changes to diagnostic output (lines prefixed with
`// CHECK:STDERR`) with no corresponding changes to test inputs
- [Output Type] Changes: Changes to STDOUT (lines prefixed with `// CHECK:STDOUT`)
- [Output Type] Changes: Changes to STDOUT (lines prefixed with `//
CHECK:STDOUT`)
- Create one section for each relevant kind of test. For example,
parser tests should typically be in a "Parse Tree Changes" section,
check tests should typically be in a "SemIR Changes" section, and
@@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
import sys
from collections import defaultdict
from typing import TextIO, Dict, List
from typing import Dict, List, TextIO
def parse_diff(stream: TextIO) -> None:
-34
View File
@@ -1,34 +0,0 @@
---
name: Tool usage
description:
Instructions for AI assistants on what tools to use in the carbon-lang
project.
---
# Tool usage
<!--
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
-->
## Bazelisk and Bazel
We use `bazelisk` for build and test.
**IMPORTANT**: AI assistants use `bazelisk` instead of `bazel`.
## Pre-commit
Running `pre-commit` is mandatory. To run it on all files:
```bash
pre-commit run -a
```
To validate a specific list of files:
```bash
pre-commit run --files <files>
```
+56 -2
View File
@@ -31,6 +31,14 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- Refer to [Toolchain Idioms](/toolchain/docs/idioms.md) for a
comprehensive list of patterns (for example, `ValueStore`, formatting
`.def` files, struct reflection) used throughout the implementation.
- **Builtin Functions**: Refer to the **Builtin functions** skill
([SKILL.md](../builtins/SKILL.md)) for guidelines on registering, mapping,
constant evaluating, and lowering compiler builtin primitives (e.g.
`"int.convert_float"`).
- **Language server**: Refer to the **Language server** skill
([SKILL.md](../language_server/SKILL.md)) before working on
`toolchain/language_server/` or `utils/vscode/`. Neither follows the
patterns described here.
- **Phases**: Lex -> Parse -> Check -> Lower.
- **Definitions**: Many kinds (tokens, parse nodes, SemIR instructions) are
defined in `.def` files and expanded by way of macros.
@@ -45,8 +53,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- **Test everything**: `bazelisk test //...`
- **Test specific target**: `bazelisk test //toolchain/testing:file_test`
- **Test specific file**:
`bazelisk test //toolchain/testing:file_test --test_arg=--file_tests=<path_to_carbon_file>`
- **Test specific file**: `bazelisk test //toolchain/testing:file_test
--test_arg=--file_tests=<path_to_carbon_file>`
- **Build toolchain**: `bazelisk build //toolchain/...`
### Updating test data
@@ -68,6 +76,10 @@ script:
## Debugging and diagnostics
- **Compiler Diagnostics**: Refer to the **Diagnostics** skill
([SKILL.md](../diagnostics/SKILL.md)) for strict rules on declaring,
formatting, emitting, testing, and styling compiler diagnostic messages
(errors, warnings, notes).
- **Printing to stderr**: Use `llvm::errs() << "debug info\n";`.
- Avoid `std::cout` (it may interfere with tool output).
- **SemIR Stringification**:
@@ -86,6 +98,20 @@ script:
- **`llvm::Expected<T>`**: Similar to `ErrorOr`, used when interfacing with
LLVM.
### Context-Aware Diagnostics
When declaring and emitting errors, ensure semantic wording matches the exact
context:
- **Semantic Precision**: Do not reference "types" when raising errors for
unsized expressions like `IntLiteral` or `FloatLiteral`. For example, use
`RealLiteralTooLargeForUnsizedInt` instead of a diagnostic referencing an
"integer type".
- **Wording Consistency**: Before declaring a new diagnostic in
[kind.def](../../../toolchain/diagnostics/kind.def), search for existing
diagnostics in the targeted implementation files (for example, other uses of
`MaxIntWidth`) to align message structures and parameter expectations.
### Casting (LLVM style)
- Use `llvm::cast<T>(obj)` (checked, asserts on failure).
@@ -93,6 +119,16 @@ script:
- Use `llvm::isa<T>(obj)` (boolean check).
- **Avoid** `dynamic_cast` and standard RTTI.
### Leverage LLVM APIs
Before implementing custom algorithms for mathematical, logical, or bitwise
operations, inspect target LLVM ADT class APIs:
- **Builtin APIs**: Verify if LLVM classes (such as `APInt`, `APFloat`, or
`APSInt`) already offer native equivalents (for example, `.pow()`,
`ilogb()`, `.changeSign()`, `convertFromAPInt()`). Avoid duplicate, naive,
or inefficient custom loops.
### Data structures
- Prefer APIs in `common/` and `toolchain/base/` over LLVM ADTs. For example,
@@ -113,3 +149,21 @@ script:
`clang-format`).
5. **Parse node order**: Semantics processes parse nodes in post-order; ensure
your parser transitions support this.
6. **Builtin implementation gaps**: If adding a primitive builtin function,
make sure you address all phases of the lifecycle: macro definition
registration, signature validation, compile-time constant evaluation
(interpreter), LLVM IR lowering, and prelude modular implementation bindings
(avoiding orphan rules). Refer to the **Builtin functions** skill
([SKILL.md](../builtins/SKILL.md)) for details.
7. **Premature helper abstraction**: Avoid extracting tiny helper functions
that are called from exactly one place and do not significantly modularize
complex code. Prefer inlining directly to keep the implementation compact,
readable, and localized.
8. **Redundant bounds calculations**: Avoid repeating calculations of complex
boundary estimations (such as lower and upper bound estimations). Refactor
the logic to calculate unified values once, preserving compactness.
9. **Trusting stale `clangd` diagnostics**: In-editor diagnostics are only as
good as `compile_commands.json`. If it predates a newly added file, `clangd`
falls back to a default command and reports nonsense, such as missing
standard headers or "no member named `None`". Regenerate it with
`./scripts/create_compdb.py`, which only takes a few seconds.
+60 -3
View File
@@ -23,6 +23,11 @@ Toolchain tests evaluate Carbon source files through Lexing, Parsing, Checking,
and optionally Lowering. Output (for example SemIR dumps, Clang errors) is
captured and validated using inline CHECK records.
Language server tests also use `file_test`, but with quite different
conventions (an LSP message stream, `AUTOUPDATE-SPLIT`, `FROM_FILE_SPLIT`).
Refer to the **Language server** skill
([SKILL.md](../language_server/SKILL.md)) for those.
## Structure and Authoring
### File Layout and Headers
@@ -53,6 +58,12 @@ prelude file using `// INCLUDE-FILE`. Usually, include
`primitives.carbon`. This significantly speeds up execution and minimizes STDOUT
noise.
- **Builtin Primitive Testing**: Standard operators (such as `+`, `-`, `/`,
`<`, etc.) are **not** imported or available inside minimized preludes. To
write tests with a minimal prelude footprint, call primitive builtins
directly (e.g., `float.negate`, `float.div`) inside your test code to build
expressions.
### Split Tests and `[[@TEST_NAME]]`
A single physical file can test multiple scenarios using split constraints:
@@ -68,9 +79,10 @@ library "[[@TEST_NAME]]";
// ...
```
- Use `library "[[@TEST_NAME]]";` in each split when necessary to prevent name
conflicts or redefining the default library.
- Exactly `[[@TEST_NAME]]` (including the brackets) should be used. The test
- **Always** use `library "[[@TEST_NAME]]";` in each split rather than
hardcoding the library name. This prevents name conflicts, avoids redefining
the default library, and keeps the test code clean and templateable.
- Exactly `[[@TEST_NAME]]` (including the brackets) must be used. The test
infrastructure automatically replaces it with the split's filename minus
`todo_` and `fail_` prefixes.
- **Do not put code that is expected to pass and code that is expected to fail
@@ -97,6 +109,47 @@ may omit `fail_` if it contains a least one split that has a `fail_` prefix.
Both the `fail_` and `todo_` prefixes are stripped from filename properties like
`[[@TEST_NAME]]`.
### Constant Evaluation Validation
When testing constant evaluation in semantic checker tests, follow these
conventions to ensure diagnostic stability and accuracy:
- **Literal Spelling Canonicalization**: In Semantic IR, real literals
(floating-point constants) with identical mathematical values can be
assigned distinct internal representation identifiers based on spelling
variations in source code. To completely prevent literal spelling mismatches
in expected output checks, validation tests must be performed using
canonical comparison methods (for example, passing converted values through
an `Expect(X as f64)` function).
- **Generic Parameters Validation**: To bypass compile-time constraints where
local runtime variables are rejected as generic function arguments, test
generic type conversions at runtime, and validate compile-time conversions
by passing static literal values directly into primitive builtin calls.
- **Exhaustive Edge Case Verification**: For complex mathematical algorithms
(such as floating-point to integer truncation and rounding), map and execute
test constraints covering every code branch, conditional exit, and fallback
evaluation path.
- **Rounding Threshold Boundaries**: Test cases that land extremely close to
mathematical boundaries (for example, floating-point literals representing a
tiny fraction above 1.0, such as $2^{30} \times 2^{-30}$ or
$10^{10} \times 10^{-10}$, verifying correct exact truncation down to 1 or
0).
- **Precise Float Literal Spelling**: Spell floating-point literals in test
code with exact mathematical precision targeting target thresholds. For
example, if testing the smallest fractional increment above 1.0, use the
exact hex fractional representation (e.g. `0x1.0000000000001p0`) or a highly
precise decimal fractional spelling (e.g. `1.0000000000000001`) instead of
coarse fractions like `1.1` to ensure correct boundary assertions.
- **Representation Capacity Boundaries**: Explicitly target edge cases near
representation limits of target types. Test combinations of mantissas and
exponents that yield values exactly on, just below, or just above the
capacity limits of fixed-size destination types (e.g. signed/unsigned
targets like `i32` or `u32`).
- **Zero-Value Sizing Bounds**: Verify boundary inputs of `0` and `0.0`
explicitly. Assert that zero inputs are sized and simplified correctly
without triggering calculation underflows, division-by-zero errors, or
underestimating required bit allocations.
### Test Code Comments
- **No agent thinking:** Do not include comments describing your reasoning or
@@ -134,3 +187,7 @@ updater:
Review the updated test outputs (for example, by way of `git diff`). Ensure
logic paths are correctly tested rather than producing massive boilerplate
blocks.
Autoupdating makes the tests pass whether or not the new behavior is correct, so
deciding that the new output is the output you wanted is a separate step. See
the [Review testdata changes](../review_testdata_changes/SKILL.md) skill.
+5 -2
View File
@@ -45,7 +45,11 @@ build --use_target_config_carbon_rules
# Default to using a disk cache to minimize re-building LLVM and Clang which we
# try to avoid updating too frequently to minimize rebuild cost. The location
# here can be overridden in the user configuration where needed.
common --disk_cache=~/.cache/carbon-lang-build-cache
#
# We avoid the disk cache on MacOS because it breaks debugging. When the cache
# is used, the separate debug symbol files are not perserved.
common:linux --disk_cache=~/.cache/carbon-lang-build-cache
common:windows --disk_cache=~/.cache/carbon-lang-build-cache
# If you'd like a different disk cache size, override it by copying this
# line to `user.bazelrc` in the repository root and modify the number there.
common --experimental_disk_cache_gc_max_size=100G
@@ -176,7 +180,6 @@ common --incompatible_disallow_empty_glob
common --incompatible_disallow_legacy_py_provider
common --incompatible_disallow_sdk_frameworks_attributes
common --incompatible_disallow_struct_provider_syntax
common --incompatible_do_not_split_linking_cmdline
common --incompatible_dont_enable_host_nonhost_crosstool_features
common --incompatible_dont_use_javasourceinfoprovider
common --incompatible_enable_apple_toolchain_resolution
+41
View File
@@ -38,14 +38,32 @@ Checks:
- '-readability-trailing-comma'
- '-readability-use-anyofallof'
# These are copies of older google- prefixed rules that have been moved
# out of that prefix, but the google- prefix names still exist as aliases to
# these. We enable the google- prefix rules and use those in our NOLINT
# expressions, so we disable the newer aliased rules.
#
# Alias for google-explicit-constructor.
- '-misc-explicit-constructor'
# Alias for google-readability-casting.
- '-modernize-avoid-c-style-cast'
# Warns when we have multiple empty cases in switches, which we do for comment
# reasons.
- '-bugprone-branch-clone'
# We use CRTP inheritence widely and across distant areas of the codebase,
# which makes maintaining friend lists for the constructors frustrating.
- '-bugprone-crtp-constructor-accessibility'
# We shadow methods with CRTP, instead of using virtual, such as for Print().
- '-bugprone-derived-method-shadowing-base-method'
# Frequently warns on multiple parameters of the same type.
- '-bugprone-easily-swappable-parameters'
# Finds issues like out-of-memory in main(). We don't use exceptions, so it's
# unlikely to find real issues.
- '-bugprone-exception-escape'
# We have File class types in different namespaces and we forward declare it,
# but don't find this to be problematic.
- '-bugprone-forward-declaration-namespace'
# Doesn't respect `[[clang::enum_extensibility(open)]]`.
- '-bugprone-invalid-enum-default-initialization'
# Has false positives in places such as using an argument to declare a name,
@@ -54,6 +72,8 @@ Checks:
- '-bugprone-macro-parentheses'
# Conflicts with integer type C++ style.
- '-bugprone-narrowing-conversions'
# We return const references from value stores.
- '-bugprone-return-const-ref-from-parameter'
# Complains about reasonable code like `1 << 20` and would push us away from
# our integer type C++ style rules.
- '-bugprone-signed-bitwise'
@@ -73,6 +93,8 @@ Checks:
# Extremely slow. TODO: Re-enable once
# https://github.com/llvm/llvm-project/issues/128797 is fixed.
- '-misc-confusable-identifiers'
# We use multiple inheritence without virtual extensively.
- '-misc-multiple-inheritance'
# Overlaps with `-Wno-missing-prototypes`.
- '-misc-use-internal-linkage'
# Suggests `std::array`, which we could migrate to, but conflicts with the
@@ -95,17 +117,32 @@ Checks:
- '-readability-enum-initial-value'
# Warns too frequently.
- '-readability-function-cognitive-complexity'
# Allows naming styles we don't use, and has errors on our use of `_1`, `_2`
# to have multiple unnamed vars in a destructuring declaration.
- '-readability-identifier-naming'
# Warns on use of CARBON_KIND() and can't use NOLINT effectively inside a
# macro.
- '-readability-inconsistent-ifelse-braces'
# Warns in reasonably documented situations.
- '-readability-magic-numbers'
# Warns on `= {}` which is also used to indicate which fields do not need to
# be explicitly initialized in aggregate initialization.
- '-readability-redundant-member-init'
# We generally do want to collapse if statements, and ask for it in review.
# But this check ignores when ifs are nested to place comments above/below
# the nested if block. And when the outer if block is also initializing a
# variable. There are more than a handful of cases where we want to do this,
# especially working with LLVM apis like dyn_cast.
- '-readability-redundant-nested-if'
# Broken, wants to remove parens from `*(p + 1)` and `("Foo" + s).str()`.
# TODO: Re-enable once https://github.com/llvm/llvm-project/issues/192435 and
# related bugs are fixed.
- '-readability-redundant-parentheses'
# Warns when callers use similar names as different parameters.
- '-readability-suspicious-call-argument'
# Low value check, and it's a stylistic choice to use `#if defined(...)` when
# paired with `#elif defined(...)`.
- '-readability-use-concise-preprocessor-directives'
CheckOptions:
# Don't warn on structs; done by ignoring when there are only public members.
@@ -121,6 +158,10 @@ CheckOptions:
value: CamelCase
- key: readability-identifier-naming.NamespaceCase
value: CamelCase
# Headers re-open LLVM and Clang namespaces to forward-declare their types,
# which is much cheaper to compile than including their headers.
- key: readability-identifier-naming.NamespaceIgnoredRegexp
value: '^(clang|llvm)$'
- key: readability-identifier-naming.StructCase
value: CamelCase
- key: readability-identifier-naming.TemplateParameterCase
+5 -6
View File
@@ -6,12 +6,11 @@ CompileFlags:
# Workaround for https://github.com/clangd/clangd/issues/1582
Remove: [-march=*]
Diagnostics:
# `unused-function`: has false positives due to not performing template
# instantiation. We get a more reliable version of this warning from the
# compiler.
# `unused-includes`: has false positives, reporting includes unused when
# they are used. Probably the same root cause as unused-function.
Suppress: [unused-function, unused-includes]
# `unneeded-internal-declaration`, `unused-function`, `unused-includes`,
# `unused-template`: These all have false positives due to not performing
# template instantiation. We get a more reliable version of these warnings
# from the compiler.
Suppress: [unneeded-internal-declaration, unused-function, unused-includes, unused-template]
---
+2
View File
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
AggregateT
AnyOther
ArchType
atleast
circularly
@@ -17,6 +18,7 @@ groupt
indext
inout
isELF
iterm
parameteras
pullrequest
rightt
@@ -11,13 +11,12 @@ inputs:
runs:
using: composite
steps:
# Setup Python and related tools.
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
# Setup Python and related tools with uv.
- name: Set up uv and Python
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Match the min version listed in docs/project/contribution_tools.md
# or the oldest version available on the OS.
python-version:
${{ inputs.matrix_runner == 'macos-14' && '3.11' || '3.10' }}
enable-cache: true
version: '0.11.15'
- uses: ./.github/actions/build-setup-macos
if: startsWith(inputs.matrix_runner, 'macos')
@@ -38,9 +37,10 @@ runs:
bazelisk --version
echo '*** run_bazel.py'
./scripts/run_bazel.py --version
echo '*** python'
which python
python --version
echo '*** uv'
which uv
uv --version
uv python list --only-installed
echo '*** clang'
which clang
clang --version
+7 -7
View File
@@ -23,7 +23,7 @@ runs:
xcrun simctl delete all
sudo rm -rf ~/Library/Developer/CoreSimulator/Caches/*
# Install and cache LLVM 19 from Homebrew. Some runners may have LLVM 19,
# Install and cache LLVM 21 from Homebrew. Some runners may have LLVM 21,
# but this is reliable (including with libc++), and gives us testing at the
# minimum supported LLVM version.
- name: Cache Homebrew
@@ -46,7 +46,7 @@ runs:
}}
# Note the key needs to include all the packages we're adding.
key:
Homebrew-Cache-${{ inputs.matrix_runner }}-${{ runner.arch }}-llvm@19
Homebrew-Cache-${{ inputs.matrix_runner }}-${{ runner.arch }}-llvm@21
- name: Install LLVM and Clang with Homebrew
if: steps.cache-homebrew-macos.outputs.cache-hit != 'true'
@@ -60,11 +60,11 @@ runs:
LEAVES=$(brew leaves | egrep -v '^(bazelisk|gh|git|git-lfs|gnu-tar|go@.*|jq|pipx|node@.*|openssl@.*|wget|yq|zlib)$')
brew uninstall -f --ignore-dependencies $LEAVES
echo '*** Installing LLVM deps'
brew install --force-bottle --only-dependencies llvm@19
brew install --force-bottle --only-dependencies llvm@21
echo '*** Installing LLVM itself'
brew install --force-bottle --force --verbose llvm@19
echo '*** brew info llvm@19'
brew info llvm@19
brew install --force-bottle --force --verbose llvm@21
echo '*** brew info llvm@21'
brew info llvm@21
echo '*** brew autoremove'
brew autoremove
echo '*** brew info'
@@ -77,7 +77,7 @@ runs:
- name: Setup LLVM and Clang
shell: bash
run: |
LLVM_PATH="$(brew --prefix llvm@19)"
LLVM_PATH="$(brew --prefix llvm@21)"
echo "Using ${LLVM_PATH}"
echo "${LLVM_PATH}/bin" >> $GITHUB_PATH
echo '*** ls "${LLVM_PATH}"'
+19 -6
View File
@@ -22,6 +22,16 @@ runs:
# to save time.
large-packages: false
# Select the LLVM release - by the cache key and the download.
- name: Select LLVM release
shell: bash
run: |
if [[ "${{ runner.arch }}" == "ARM64" ]]; then
echo "LLVM_RELEASE=21.1.8" >> "$GITHUB_ENV"
else
echo "LLVM_RELEASE=21.1.8" >> "$GITHUB_ENV"
fi
# Cache and install a recent version of LLVM. This uses the GitHub action
# cache to avoid directly downloading on each iteration and improve
# reliability.
@@ -30,15 +40,16 @@ runs:
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ~/llvm
key: LLVM-19.1.7-Cache-ubuntu-${{ runner.arch }}
key: LLVM-${{ env.LLVM_RELEASE }}-Cache-ubuntu-${{ runner.arch }}
- name: Download LLVM and Clang installation
if: steps.cache-llvm-ubuntu.outputs.cache-hit != 'true'
shell: bash
run: |
cd ~
LLVM_RELEASE=19.1.7
LLVM_TARBALL_NAME=LLVM-$LLVM_RELEASE-Linux-X64
# `LLVM_RELEASE` comes from the "Select LLVM release" step; `runner.arch`
# (`X64`/`ARM64`) matches the package's arch suffix.
LLVM_TARBALL_NAME=LLVM-$LLVM_RELEASE-Linux-${{ runner.arch }}
LLVM_PATH=~/llvm
echo "*** Downloading $LLVM_RELEASE"
wget --show-progress=off "https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_RELEASE/$LLVM_TARBALL_NAME.tar.xz"
@@ -50,10 +61,12 @@ runs:
echo "*** Testing `clang++ --version`"
$LLVM_PATH/bin/clang++ --version
# The installation contains *huge* parts of LLVM we don't need for the
# toolchain. Prune them here to keep our cache small.
# toolchain. Prune them here to keep our cache small. x86-64 and
# AArch64 use different LLVM releases whose tool sets differ, so `-f`
# ignores entries that are absent from a given package.
echo "*** Cleaning the 'llvm' directory"
rm $LLVM_PATH/lib/{*.a,*.so,*.so.*}
rm $LLVM_PATH/bin/{flang-*,mlir-*,clang-{scan-deps,check,repl},*-test,llvm-{lto*,reduce,bolt*,exegesis,jitlink},bugpoint,opt,llc}
rm -f $LLVM_PATH/lib/{*.a,*.so,*.so.*}
rm -f $LLVM_PATH/bin/{flang-*,mlir-*,clang-{scan-deps,check,repl},*-test,llvm-{lto*,reduce,bolt*,exegesis,jitlink},bugpoint,opt,llc}
echo "*** Size of the 'llvm' directory"
du -hs $LLVM_PATH
+1 -1
View File
@@ -19,7 +19,7 @@ Most jobs only have a few endpoints, but due to tools which do downloads, a few
have significantly more. These are:
- clangd_tidy.yaml (Bazel)
- pre_commit.yaml (Bazel, pre-commit)
- prek.yaml (Bazel, prek)
- nightly_release.yaml (Bazel)
- tests.yaml (Bazel)
+11 -6
View File
@@ -27,23 +27,28 @@ jobs:
egress-policy: block
allowed-endpoints: >
api.github.com:443 github.com:443 pypi.org:443
files.pythonhosted.org:443
files.pythonhosted.org:443 raw.githubusercontent.com:443
releases.astral.sh:443
# Note: pull_request_target checks out the base branch by default.
# This is safe as it avoids running untrusted code from the PR branch.
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install dependencies
run: |
python3 -m pip install gql==2.0.0 requests
- name: Set up uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: true
version: '0.11.15'
- name: Check Dependent PR
run: |
if [ "$EVENT_ACTION" = "closed" ]; then
python3 github_tools/check_dependent_pr.py --scan
./github_tools/check_dependent_pr.py --scan
else
python3 github_tools/check_dependent_pr.py --pr-number "${PR_NUMBER}"
./github_tools/check_dependent_pr.py --pr-number "${PR_NUMBER}"
fi
env:
GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+7 -5
View File
@@ -48,21 +48,27 @@ jobs:
oauth2.googleapis.com:443
objects.githubusercontent.com:443
pypi.org:443
raw.githubusercontent.com:443
registry.npmjs.org:443
release-assets.githubusercontent.com:443
releases.astral.sh:443
releases.bazel.build:443
storage.googleapis.com:443
uploads.github.com:443
www.googleapis.com:443
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- id: filter
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
with:
predicate-quantifier: 'every'
filters: |
has_cpp:
- added|modified: '{**/*.cpp,**/*.h}'
- '!**/*.tpl.h'
list-files: 'shell'
- uses: ./.github/actions/build-setup-common
@@ -75,13 +81,9 @@ jobs:
if: steps.filter.outputs.has_cpp == 'true'
run: ./scripts/create_compdb.py
- name: Install clangd-tidy
if: steps.filter.outputs.has_cpp == 'true'
run: pip install clangd-tidy==1.1.0.post2
- name: Run clangd-tidy
if: steps.filter.outputs.has_cpp == 'true'
env:
FILTER_FILES: ${{ steps.filter.outputs.has_cpp_files }}
run: |
clangd-tidy -p . -j 10 $FILTER_FILES
uvx --with clangd-tidy==1.1.0.post2 clangd-tidy -p . -j 10 $FILTER_FILES
+9
View File
@@ -28,6 +28,15 @@ jobs:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: true
version: '0.11.15'
- name: Prebuild actions
run: ./website/prebuild.py
- name: Setup Ruby
+18 -6
View File
@@ -18,15 +18,13 @@ concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages.
permissions:
contents: read
pages: write
id-token: write
permissions: {}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
@@ -35,6 +33,15 @@ jobs:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: true
version: '0.11.15'
- name: Prebuild actions
run: ./website/prebuild.py
- name: Setup Pages
@@ -49,11 +56,12 @@ jobs:
- name: Build with Jekyll
env:
JEKYLL_ENV: production
STEPS_PAGES_OUTPUTS_BASE_PATH: ${{ steps.pages.outputs.base_path }}
run: |
bundle exec jekyll build --verbose \
--source ./ \
--destination ./_site \
--baseurl "${{ steps.pages.outputs.base_path }}"
--baseurl "${STEPS_PAGES_OUTPUTS_BASE_PATH}"
- name: Upload artifact
# Automatically uploads an artifact from the './_site' directory by
# default.
@@ -65,6 +73,10 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages.
permissions:
pages: write
id-token: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
+7 -4
View File
@@ -59,6 +59,7 @@ jobs:
oauth2.googleapis.com:443
objects.githubusercontent.com:443
pypi.org:443
raw.githubusercontent.com:443
registry.npmjs.org:443
release-assets.githubusercontent.com:443
releases.bazel.build:443
@@ -68,6 +69,8 @@ jobs:
- name: Checkout branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up remote cache access
env:
@@ -91,7 +94,7 @@ jobs:
./scripts/run_bazel.py \
--attempts=5 --jobs-on-last-attempt=4 \
test -c opt --stamp --remote_download_toplevel \
--pre_release=nightly --nightly_date=${{ env.nightly_date }} \
--pre_release=nightly --nightly_date=${nightly_date} \
//toolchain \
//toolchain/install:carbon_toolchain_tar_gz \
//toolchain/install:carbon_toolchain_tar_gz_test
@@ -112,8 +115,8 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create \
--title "Nightly build ${{ env.nightly_date }}" \
--title "Nightly build ${nightly_date}" \
--generate-notes \
--prerelease \
v${{ env.release_version }} \
"bazel-bin/toolchain/install/carbon_toolchain-${{ env.release_version }}.tar.gz"
v${release_version} \
"bazel-bin/toolchain/install/carbon_toolchain-${release_version}.tar.gz"
@@ -2,7 +2,7 @@
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
name: pre-commit
name: prek
on:
pull_request:
@@ -14,7 +14,7 @@ permissions:
contents: read # For actions/checkout.
jobs:
pre-commit:
prek:
runs-on: ubuntu-22.04
steps:
- name: Harden Runner
@@ -40,15 +40,18 @@ jobs:
oauth2.googleapis.com:443
objects.githubusercontent.com:443
pypi.org:443
raw.githubusercontent.com:443
registry.npmjs.org:443
release-assets.githubusercontent.com:443
releases.astral.sh:443
releases.bazel.build:443
storage.googleapis.com:443
uploads.github.com:443
www.googleapis.com:443
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
persist-credentials: false
# Ensure LLVM is set up consistently.
- uses: ./.github/actions/build-setup-common
@@ -56,22 +59,22 @@ jobs:
matrix_runner: ubuntu-22.04
remote_cache_upload: '--remote_upload_local_results=false'
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
- uses: j178/prek-action@01345c78b7de7d79edf368729212760396ba9345 # v2
# We want to automatically create github suggestions for pre-commit file
# We want to automatically create github suggestions for prek file
# changes for a pull request. But `pull_request` actions never have write
# permissions to the repository, so we create the suggestions in a separate
# privileged `workflow_run` action in pre_commit_suggestions.yaml. Here,
# privileged `workflow_run` action in prek_suggestions.yaml. Here,
# we upload the diffs and event configuration to an artifact for use by
# that action.
- name: Collect pre-commit output
- name: Collect prek output
if: failure()
run: |
mkdir -p pre-commit-output
git diff > pre-commit-output/diff
cp $GITHUB_EVENT_PATH pre-commit-output/event
mkdir -p prek-output
git diff > prek-output/diff
cp $GITHUB_EVENT_PATH prek-output/event
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: failure()
with:
name: pre-commit output
path: pre-commit-output/*
name: prek output
path: prek-output/*
@@ -2,11 +2,11 @@
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# Create PR suggestions based on problems found by pre-commit action.
name: 'Add pre-commit suggestions'
# Create PR suggestions based on problems found by prek action.
name: 'Add prek suggestions'
# This action is run whenever the `pre-commit` action finishes. Because the
# `pre-commit` action is an unprivileged action running on (for example) the
# This action is run whenever the `prek` action finishes. Because the
# `prek` action is an unprivileged action running on (for example) the
# `pull_request` event, it's run without write permissions to the repository, so
# we use a separate privileged `workflow_run` action here to pick up its results
# and convert them into suggestion comments.
@@ -15,7 +15,7 @@ name: 'Add pre-commit suggestions'
# this file will not take effect until they are merged to trunk.
on:
workflow_run:
workflows: [pre-commit]
workflows: [prek]
types:
- completed
@@ -25,7 +25,7 @@ permissions:
jobs:
pull-request-suggestions:
# Only generate suggestions if pre-commit for a PR failed.
# Only generate suggestions if prek for a PR failed.
if: |
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.event == 'pull_request'
@@ -48,16 +48,18 @@ jobs:
reviewdog_version: latest
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Download pre-commit output
- name: Download prek output
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: pre-commit output
name: prek output
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
# Use https://github.com/reviewdog/reviewdog to create PR suggestions
# matching the diff that pre-commit created.
# matching the diff that prek created.
- name: Create suggestions
env:
REVIEWDOG_GITHUB_API_TOKEN:
+2
View File
@@ -32,6 +32,8 @@ jobs:
# Checkout our main repository.
- name: Checkout the main repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# Run the sync script.
- name: Sync to other repositories
+7 -2
View File
@@ -27,8 +27,9 @@ jobs:
matrix.config.name) || '' }} (${{ matrix.runner }})
strategy:
matrix:
# Test a recent version of each supported OS.
runner: ['ubuntu-22.04', 'macos-14']
# Test a recent version of each supported OS, covering both x86-64 and
# AArch64: Linux on x86-64 and AArch64, and macOS on AArch64.
runner: ['ubuntu-22.04', 'ubuntu-22.04-arm', 'macos-14']
# Create a synthetic matrix dimension with the event name for filtering.
event: ['${{ github.event_name }}']
config:
@@ -70,14 +71,18 @@ jobs:
oauth2.googleapis.com:443
objects.githubusercontent.com:443
pypi.org:443
raw.githubusercontent.com:443
registry.npmjs.org:443
release-assets.githubusercontent.com:443
releases.astral.sh:443
releases.bazel.build:443
storage.googleapis.com:443
uploads.github.com:443
www.googleapis.com:443
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- id: test-setup
uses: ./.github/actions/test-setup
+9 -1
View File
@@ -14,7 +14,8 @@
/examples/**/bazel-*
/examples/**/MODULE.bazel.lock
# Directories created by python.
# Files and directories created by python.
uv.lock
**/__pycache__/
# Ignore the user's VSCode settings and debug setup.
@@ -49,3 +50,10 @@
# Ignore the .gdb_history that's created next to the project-specific .gdbinit
.gdb_history
# Generated by scripts/create_compdb.py
/external
# Linux perftools output
perf.data
perf.data.old
+65 -39
View File
@@ -11,8 +11,7 @@ default_language_version:
python: python3 # Defaults to python2, so override it.
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
- repo: builtin
hooks:
- id: check-added-large-files
- id: check-case-conflict
@@ -29,6 +28,15 @@ repos:
exclude: '^(.*/fuzzer_corpus/.*|.*\.svg)$'
- id: trailing-whitespace
exclude: '^(.*/fuzzer_corpus/.*|.*/testdata/.*\.golden|.*\.svg)$'
# Run markdown linting early so that doc style and table-of-contents see the
# linted state.
- repo: https://github.com/rvben/rumdl-pre-commit
rev: v0.2.58
hooks:
- id: rumdl
args: [--fix]
- repo: https://github.com/google/pre-commit-tool-hooks
rev: efaea7c61c774c0b1a9805fd999e754a2d19dbd1 # frozen: v1.2.5
hooks:
@@ -39,6 +47,15 @@ repos:
.*AGENTS.md
)$
- id: markdown-toc
# Re-run markdown linting to fix any issues caused by doc style and TOC. This
# is very fast, so it shouldn't be problematic to run twice.
- repo: https://github.com/rvben/rumdl-pre-commit
rev: v0.2.58
hooks:
- id: rumdl
args: [--fix]
- repo: local
hooks:
- id: fix-cc-deps
@@ -49,10 +66,29 @@ repos:
pass_filenames: false
# Formatters should be run late so that they can re-format any prior changes.
- repo: https://github.com/psf/black
rev: c6755bb741b6481d6b3d3bb563c83fa060db96c9 # frozen: 26.3.1
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 0c7b6c989466a93942def1f84baf36ddfcd60c83 # frozen: v0.15.14
hooks:
- id: black
- id: ruff-check
args: [--fix]
- id: ruff-format
- repo: local
hooks:
- id: ty
name: ty
entry: ty check --no-progress
language: python
additional_dependencies:
- ty==0.0.46
- rich
- 'gql>=2.0.0,<3.0.0'
- PyGitHub
- types-requests
- requests
types: [python]
pass_filenames: false
- repo: local
hooks:
- id: prettier
@@ -61,7 +97,7 @@ repos:
# TODO: Not upgrading to/past 3.4.0 due to list indent changes that may
# get fixed. See: https://github.com/prettier/prettier/issues/16929
additional_dependencies: ['prettier@3.3.3']
types_or: [html, javascript, json, markdown, yaml]
types_or: [html, javascript, json, yaml]
entry: npx prettier@3.3.3 --write --log-level=warn
- repo: local
hooks:
@@ -69,7 +105,7 @@ repos:
name: Bazel buildifier
entry: scripts/run_buildifier.py
# Beyond just formatting, explicitly fix lint warnings.
args: ['--lint=fix', '--warnings=all', '-r', '.']
args: ['--lint=fix', '--warnings=all']
language: python
files: |
(?x)^(
@@ -112,6 +148,23 @@ repos:
entry: scripts/check_sha_filenames.py
language: python
files: ^.*/fuzzer_corpus/.*$
- id: check-proposal-names
name: Check proposal names
entry: proposals/scripts/check_proposal_names.py
language: python
files: ^proposals/p.*\.md$
# This edits files other than the ones passed to it, so we need each
# chunk of files to be run through the script serially.
require_serial: true
# This also renames files, invalidating the list of files provided to
# subsequent checks so we fail-fast if this makes changes.
fail_fast: true
- id: build-textmate-grammar
name: Build TextMate grammar
entry: scripts/update_tm_language.py
language: system
files: ^utils/vscode/carbon\.tmLanguage\.json$
pass_filenames: false
- id: check-toolchain-diagnostics
name: Check toolchain diagnostics
entry: toolchain/diagnostics/check_diagnostics.py
@@ -134,36 +187,6 @@ repos:
language: python
files: ^.*/BUILD$
pass_filenames: false
- repo: https://github.com/PyCQA/flake8
rev: d93590f5be797aabb60e3b09f2f52dddb02f349f # frozen: 7.3.0
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'fc0f09a29bb495f4a91f00266155d6282d52485d' # frozen: v1.20.2
hooks:
- id: mypy
# Use setup.cfg to match the command line.
args:
- --config-file=setup.cfg
# This should match the requirements added in the WORKSPACE pip_install.
additional_dependencies:
- gql >= 2.0.0, < 3.0.0
- PyGitHub
- rich
- types-requests
# Exclusions are:
# - p#### scripts because they're not tested or maintained.
# - lit.cfg.py because it has multiple copies, breaking mypy.
# - `bazel_test_runner.py` which depends on Bazel-specific imports.
# - Unit tests because they sometimes violate typing, such as by
# assigning a mock to a function.
exclude: |
(?x)^(
proposals/(?!scripts/).*|
.*/lit\.cfg\.py|
examples/bazel_test_runner\.py|
.*_test\.py
)$
- repo: https://github.com/codespell-project/codespell
rev: 2ccb47ff45ad361a21071a7eedda4c37e6ae8c5a # frozen: v2.4.2
hooks:
@@ -203,7 +226,7 @@ repos:
- ''
- '*/'
- --custom_format
- '\.(plist)$'
- '\.(plist|tmLanguage)$'
- '<!--'
- ''
- '\-->'
@@ -220,13 +243,14 @@ repos:
- --custom_format
- '\.lua$'
- ''
- '-- '
- '\-- '
- ''
exclude: |
(?x)^(
.bazelversion|
.github/pull_request_template.md|
.python-version|
LICENSE.*|
compile_flags.txt|
github_tools/requirements.txt|
third_party/.*|
@@ -246,6 +270,7 @@ repos:
name: Check build graph
entry: scripts/check_build_graph.py
language: python
pass_filenames: false
files: |
(?x)^(
.*BUILD.*|
@@ -257,6 +282,7 @@ repos:
# This excludes third-party code, and patches to third-party code.
exclude: |
(?x)^(
\.jj/.*|
MODULE.bazel.lock|
.*package-lock\.json|
bazel/bazel_clang_tidy/.*\.patch|
+1 -1
View File
@@ -1 +1 @@
3.10
3.12
+83
View File
@@ -0,0 +1,83 @@
# 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
[global]
exclude = [
".clang-tidy",
".git",
".jj",
"CHANGELOG.md",
"LICENSE.md",
".github/pull_request_template.md",
]
respect-gitignore = true
# Disable rules that produce the most noise initially. Some of these might make
# sense to re-enable.
disable = [
"MD033", # Inline HTML - commonly used in real-world markdown
"MD036", # Emphasis used instead of heading
"MD040", # Code blocks should have a language specified
"MD014", # Commands in code blocks should show output
"MD034", # Bare URLs
"MD059", # Link text should be descriptive
"MD028", # Blank line inside blockquote
]
# Line wrapping
[MD013]
reflow = true
# Note that we might want to use the "normalize" reflow mode to have more
# consistent line wrapping, however this mode is currently deeply incompatible
# with inline HTML that we use reasonably often. For now, we go with the default
# mode that doesn't try to normalize wrapping.
reflow-mode = "default"
ignore-link-urls = false
code-blocks = false
code-spans = false
atomic-spans = false
headings = false
stern = true
# Heading style
[MD003]
style = "atx"
# Narrow restriction on trailing punctuation in headings -- allows ':' and '!'.
[MD026]
punctuation = ".,;"
# Unordered list marker style
[MD004]
style = "dash"
# Ordered list numbering
[MD029]
style = "one-or-ordered"
# Unordered list indentation
[MD007]
style = "fixed"
indent = 4
[MD077]
style = "aligned"
# Spaces after list markers
[MD030]
ul-single = 3
ul-multi = 3
ol-align-column = 4
# Code block style
[MD046]
style = "fenced"
# Emphasis style
[MD049]
style = "underscore"
# Strong style
[MD050]
style = "asterisk"
+3 -2
View File
@@ -4,8 +4,9 @@
"bierner.github-markdown-preview",
"carbon-lang.carbon-vscode",
"esbenp.prettier-vscode",
"rvben.rumdl",
"llvm-vs-code-extensions.vscode-clangd",
"ms-python.black-formatter",
"ms-python.python"
"charliermarsh.ruff",
"astral-sh.ty"
]
}
+18
View File
@@ -19,6 +19,24 @@
"env TEST_TMPDIR=/tmp"
]
},
{
"type": "lldb-dap",
"request": "launch",
"name": "file_test (all files) (lldb)",
"program": "bazel-bin/toolchain/testing/file_test",
"args": [],
"debuggerRoot": "${workspaceFolder}",
"initCommands": [
"command script import external/+llvm_project+llvm-project/llvm/utils/lldbDataFormatters.py",
"command script import scripts/lldbinit.py",
"settings append target.source-map \".\" \"${workspaceFolder}\"",
"settings append target.source-map \"/proc/self/cwd\" \"${workspaceFolder}\"",
"settings set escape-non-printables false",
"settings set target.max-string-summary-length 10000",
"env TEST_TARGET=//toolchain/testing:file_test",
"env TEST_TMPDIR=/tmp"
]
},
{
"type": "lldb-dap",
"request": "launch",
+9 -37
View File
@@ -1,4 +1,4 @@
# Gemini & AI assistant guide for Carbon
# Gemini & AI Assistant Guide for Carbon
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
@@ -6,40 +6,14 @@ Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
This document provides high-density technical context for AI assistants (and
humans!) contributing to the Carbon Language project. If you are an AI
assistant, **read this first** to avoid common pitfalls.
## Table of contents
- [General instructions](#general-instructions)
- [Project structure](#project-structure)
- [Bazel usage](#bazel-usage)
- [Toolchain development](#toolchain-development)
This document provides high-density technical context for AI assistants
contributing to the Carbon Language project.
## General instructions
- **Communication**: Be concise, professional, and technical. Use GitHub-style
markdown.
- **Verification**: Always run relevant tests.
- **Tool usage**: Use web search for any research outside the immediate
codebase or KIs.
## Project structure
- **[`common/`](common/)**: Common C++ utilities used across the project.
- **[`core/`](core/)**: The Carbon standard library (Core).
- **[`docs/`](docs/)**: Project documentation, design, and style guides.
- **[`examples/`](examples/)**: Example Carbon programs and code snippets.
- **[`proposals/`](proposals/)**: Evolution proposals.
- **[`testing/`](testing/)**: Testing utilities and infrastructure.
- **[`toolchain/`](toolchain/)**: The C++ implementation of the compiler
(Toolchain).
## Tool usage
See the "Tool usage" skill for instructions on what tools to use in the
carbon-lang project.
## Bazel usage
@@ -47,12 +21,10 @@ carbon-lang project.
> Carbon project. Refer to the
> [Bazel usage skill](/.agents/skills/bazel/SKILL.md) for detailed instructions.
## Code style
## Version control
See the "Code style" skill for instructions on formatting, style guides, and
code conventions to follow.
## Toolchain development
See the "Toolchain Development" skill for instructions on architecture,
building, testing, debugging, C++ patterns, and common pitfalls.
> [!IMPORTANT] Never rewrite the history of a change that has been submitted as
> a pull request. Reviewers track a PR by its commits, and rewriting them
> discards their in-progress review. Ask before rewriting history in any case.
> Refer to the [Jujutsu (jj) usage skill](/.agents/skills/jj/SKILL.md) for
> details.
+19 -19
View File
@@ -227,7 +227,7 @@ trying to write proposals, both types of contributor access will help.
Please see our [contribution tool](/docs/project/contribution_tools.md)
documentation for information on setting up a git client for Carbon development,
as well as helpful tooling that will ease the contribution process. For example,
[pre-commit](https://pre-commit.com) is used to simplify
[prek](https://github.com/j178/prek) is used to simplify
[code review](/docs/project/code_review.md).
#### Using AI-based contribution tools
@@ -249,22 +249,22 @@ and our [guidelines and standards](#contribution-guidelines-and-standards)
below. We also emphasize two additional requirements for contributors operating
or using AI-based tools:
1. **Contributions should not become extractive of the project and community**:
the value added should outweigh the overhead of landing the contribution. The
overhead of landing contributions ranges from code review, to discussions,
distractions from the current project priorities, or growing maintenance
burden without growing maintainers.
1. **Contributions should not become extractive of the project and community**:
the value added should outweigh the overhead of landing the contribution.
The overhead of landing contributions ranges from code review, to
discussions, distractions from the current project priorities, or growing
maintenance burden without growing maintainers.
2. **Each PR should be transparent about the tooling used** in proportion to how
much of the PR was produced by the tool and whether the tool is a standard
one for the project. For example, formatting with the standard tools is
reasonable to assume without further comment. But if a PR is largely derived
from running a specific Python script, regular expression, or AI-based tool
over the codebase, we ask that its commit message is transparent about this
and include a description of how the tool was used to formulate the change.
For PRs largely derived from AI-based tooling, we suggest following the
pattern established by the Fedora Project to mark commits with
`Assisted-by: ...`.
2. **Each PR should be transparent about the tooling used** in proportion to
how much of the PR was produced by the tool and whether the tool is a
standard one for the project. For example, formatting with the standard
tools is reasonable to assume without further comment. But if a PR is
largely derived from running a specific Python script, regular expression,
or AI-based tool over the codebase, we ask that its commit message is
transparent about this and include a description of how the tool was used to
formulate the change. For PRs largely derived from AI-based tooling, we
suggest following the pattern established by the Fedora Project to mark
commits with `Assisted-by: ...`.
Our policies and practices here are inspired by and aim to be roughly compatible
with several other open source projects:
@@ -405,9 +405,9 @@ respectful, and don't drown out other discussion.
Changes to Carbon documentation follow the
[Google developer documentation style guide](https://developers.google.com/style).
Markdown files should additionally use [Prettier](https://prettier.io) for
formatting, which we automate with
[pre-commit](/docs/project/contribution_tools.md#main-tools).
Markdown files should additionally use [rumdl](https://github.com/rvben/rumdl)
for formatting, which we automate with
[prek](/docs/project/contribution_tools.md#running-prek).
Other style points to be aware of are:
+5 -4
View File
@@ -83,8 +83,8 @@ git_override(
build_file_content = "# empty",
# We pin to specific upstream commits and try to track top-of-tree
# reasonably closely rather than pinning to a specific release.
# HEAD as of 2026-04-01.
commit = "b71eacea7687f68c11299e3bda5654fbbaa1e20e",
# HEAD as of 2026-09-08.
commit = "7024b9e1b423b3c3c6ac76ab6a73cb2c9e4ef842",
patch_cmds = ["echo \"module(name='llvm-raw')\" > MODULE.bazel"],
patch_strip = 1,
patches = [
@@ -92,7 +92,8 @@ git_override(
"//bazel/llvm_project:0002_Added_Bazel_build_for_compiler_rt_fuzzer.patch",
"//bazel/llvm_project:0004_Introduce_basic_sources_exporting_for_libunwind.patch",
"//bazel/llvm_project:0005_Introduce_basic_sources_exporting_for_libcxx_and_libcxxabi.patch",
"//bazel/llvm_project:0009_Introduce_starlark_exporting_compiler-rt_build_information.patch",
"//bazel/llvm_project:0006_Add_more_libc_math_excludes.patch",
"//bazel/llvm_project:0011_Temporarily_remove_reference_to_hermetic_toolchain.patch",
],
remote = "https://github.com/llvm/llvm-project.git",
)
@@ -112,7 +113,7 @@ bazel_dep(name = "rules_python", version = "1.9.0")
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
python_version = "3.11",
python_version = "3.12",
)
use_repo(python, "python_versions")
+2 -2
View File
@@ -307,7 +307,7 @@
"moduleExtensions": {
"//bazel/cc_toolchains:clang_configuration.bzl%clang_toolchain_extension": {
"general": {
"bzlTransitiveDigest": "H3RsK0MbgutDMSlPWTwZq4Vk1U5sjDtgJ5MXQxg7GLU=",
"bzlTransitiveDigest": "IGxGFknaFQQo7RudzfwIOuxREEsnEXwlM9KuPiNQYBM=",
"usagesDigest": "lTxkeAFhR0iBEa3dg5hWvtd2HFCr5zCJx/fl27A+IKA=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@@ -323,7 +323,7 @@
},
"//bazel/llvm_project:llvm_project.bzl%llvm_project": {
"general": {
"bzlTransitiveDigest": "xDeO6VeJOhQ/KmsAtVhSrY+/XoomqAxdF2SG/XbIkX4=",
"bzlTransitiveDigest": "4DgU62e9O5rmV6Yzqa1tjFyBSVED44nXdYn84nxuJC8=",
"usagesDigest": "uwwVdRj/NhFVoOIaadPP393kgC/Uu3/nTX9ln69oWp4=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
+12 -4
View File
@@ -251,10 +251,10 @@ challenge for C++ and something a successor language needs to address.
We plan to support a two step migration process:
1. Highly automated, minimal supervision migration from C++ to a dialect of
Carbon designed for C++ interop and migration.
2. Incremental refactoring of the Carbon code to adopt memory-safe designs,
patterns, and APIs.
1. Highly automated, minimal supervision migration from C++ to a dialect of
Carbon designed for C++ interop and migration.
2. Incremental refactoring of the Carbon code to adopt memory-safe designs,
patterns, and APIs.
We also want to address important, low-hanging fruit in the safety space
immediately when migrating into Carbon:
@@ -367,8 +367,16 @@ Carbon focused talks from the community:
### 2026
- Carbon memory safety: a first deep dive (July 10,
[video](https://drive.google.com/file/d/1tQlzpnbWZfn2WtTFMoJgF93QteByBBwm/view?usp=sharing),
[transcript](https://docs.google.com/document/d/1JB9H3KzVixAPC5WIytS4AMyrvjwzC7TXqp596veLT34/edit?usp=sharing),
[slides](https://chandlerc.blog/slides/2026-memory-safety-deep-3/))
- Benchmarking and optimizing the Carbon compiler, NDC {Toronto} (May 5-8)
([video](https://www.youtube.com/watch?v=hN6KcAKfTN0),
[slides](https://chandlerc.blog/slides/2026-ndc-toronto-carbon-benchmarking))
- Carbon: graduating from the experiment, NDC {Toronto} (May 5-8)
([video](https://www.youtube.com/watch?v=WJl4ftb5Fxg),
[slides](https://chandlerc.blog/slides/2026-ndc-toronto-carbon-update/))
### 2025
-1
View File
@@ -1 +0,0 @@
bazel-out/../../_main
+212 -41
View File
@@ -26,8 +26,10 @@ def _carbon_binary_impl(ctx):
# Pass any C++ flags from our dependencies onto Carbon.
dep_flags = []
dep_hdrs = []
dep_api_files = []
dep_link_inputs = []
for dep in ctx.attr.deps:
deps = ctx.attr.deps + ctx.attr._default_deps
for dep in deps:
if CcInfo in dep:
cc_info = dep[CcInfo]
@@ -46,16 +48,16 @@ def _carbon_binary_impl(ctx):
dep_link_inputs += lib.objects
if DefaultInfo in dep:
dep_link_inputs += dep[DefaultInfo].files.to_list()
if CarbonLibraryInfo in dep:
carbon_info = dep[CarbonLibraryInfo]
dep_link_inputs += carbon_info.objs.to_list()
dep_api_files += carbon_info.apis
# Add the dependencies' link flags and inputs to the link flags.
link_flags += [dep.path for dep in dep_link_inputs]
# Build object files for the prelude and for the binary itself.
# TODO: Eventually the prelude should be build as a separate `carbon_library`.
srcs_and_flags = [
(ctx.files.prelude_srcs, ["--no-prelude-import"]),
(ctx.files.srcs, dep_flags),
]
srcs_and_flags = [(ctx.files.srcs, dep_flags)]
objs = []
for (srcs, extra_flags) in srcs_and_flags:
@@ -76,13 +78,14 @@ def _carbon_binary_impl(ctx):
src.short_path.removeprefix(ctx.label.package).removesuffix(src.extension),
))
objs.append(out)
srcs_reordered = [s for s in srcs if s != src] + [src]
srcs_reordered = dep_api_files + [s for s in srcs if s != src] + [src]
ctx.actions.run(
outputs = [out],
inputs = depset(direct = srcs_reordered, transitive = dep_hdrs),
executable = toolchain_driver,
tools = depset(toolchain_data),
arguments = ["compile", "--output=" + out.path, "--output-last-input-only"] +
["--no-include-carbon-core"] +
[s.path for s in srcs_reordered] + extra_flags + ctx.attr.flags,
mnemonic = "CarbonCompile",
progress_message = "Compiling " + src.short_path,
@@ -119,19 +122,128 @@ def _carbon_binary_impl(ctx):
ctx.actions.run(
outputs = [bin],
inputs = objs + dep_link_inputs,
inputs = depset(direct = objs + dep_link_inputs),
executable = toolchain_driver,
tools = depset(toolchain_data + prebuilt_runtimes),
tools = depset(direct = toolchain_data + prebuilt_runtimes),
arguments = full_link_flags,
mnemonic = "CarbonLink",
progress_message = "Linking " + bin.short_path,
)
return [DefaultInfo(files = depset([bin]), executable = bin)]
CarbonLibraryInfo = provider(
doc = "Contains information about a linkage unit of one or more compiled Carbon libraries.",
fields = {
"apis": "The api source files to provide to library consumers.",
"objs": "A depset of one or more compiled library files, including impl and api.",
},
)
def _carbon_library_impl(ctx):
toolchain_driver = ctx.executable.internal_exec_toolchain_driver
toolchain_data = ctx.files.internal_exec_toolchain_data
# If the exec driver isn't provided, that means we're trying to use a target
# config toolchain, likely to avoid build overhead of two configs.
if toolchain_driver == None:
toolchain_driver = ctx.executable.internal_target_toolchain_driver
toolchain_data = ctx.files.internal_target_toolchain_data
# Pass any C++ flags from our dependencies onto Carbon.
dep_flags = []
dep_hdrs = []
dep_api_srcs = []
for dep in ctx.attr.deps:
if CcInfo in dep:
cc_info = dep[CcInfo]
# TODO: We should reuse the feature-based flag generation in
# bazel/cc_toolchains here.
dep_flags += ["--clang-arg=-D{0}".format(define) for define in cc_info.compilation_context.defines.to_list()]
dep_flags += ["--clang-arg=-I{0}".format(path) for path in cc_info.compilation_context.includes.to_list()]
dep_flags += ["--clang-arg=-iquote{0}".format(path) for path in cc_info.compilation_context.quote_includes.to_list()]
dep_flags += ["--clang-arg=-isystem{0}".format(path) for path in cc_info.compilation_context.system_includes.to_list()]
dep_hdrs.append(cc_info.compilation_context.headers)
if CarbonLibraryInfo in dep:
carbon_info = dep[CarbonLibraryInfo]
dep_api_srcs += carbon_info.apis.to_list()
# Build object files for the library impls and api file
srcs_and_flags = [(ctx.files.srcs + ctx.files.hdrs, dep_flags)]
objs = []
for (srcs, extra_flags) in srcs_and_flags:
for src in srcs:
# Build each source file. For now, we pass all sources to each compile
# because we don't have visibility into dependencies and have no way to
# specify multiple output files. Object code for each input is written
# into the output file in turn, so the final carbon source file
# specified ends up determining the contents of the object file.
#
# TODO: This is a hack; replace with something better once the toolchain
# supports doing so.
#
# TODO: Switch to the `prefix` based rule similar to linking when
# the prelude moves there.
out = ctx.actions.declare_file("_objs/{0}/{1}o".format(
ctx.label.name,
src.short_path.removeprefix(ctx.label.package).removesuffix(src.extension),
))
objs.append(out)
srcs_reordered = dep_api_srcs + [s for s in srcs if s != src] + [src]
ctx.actions.run(
outputs = [out],
inputs = depset(direct = srcs_reordered, transitive = dep_hdrs),
executable = toolchain_driver,
tools = depset(toolchain_data),
arguments = ["compile", "--output=" + out.path, "--output-last-input-only"] +
["--no-include-carbon-core"] +
extra_flags + ctx.attr.flags + [s.path for s in srcs_reordered],
mnemonic = "CarbonCompile",
progress_message = "Compiling " + src.short_path,
)
return [CarbonLibraryInfo(apis = ctx.files.hdrs, objs = depset(objs))]
# We synthesize two sets of attributes from mirrored `select`s here
# because we want to select on an internal property of these attributes
# but that isn't `select`-able. Instead, we have both attributes and
# `select` which one we use.
_select_internal_exec_toolchain_driver = select({
Label("//bazel/carbon_rules:use_target_config_carbon_rules_config"): None,
"//conditions:default": Label("//toolchain/install:carbon-busybox"),
})
_select_internal_exec_toolchain_data = select({
Label("//bazel/carbon_rules:use_target_config_carbon_rules_config"): None,
"//conditions:default": Label("//toolchain/install:install_data"),
})
_select_internal_exec_prebuilt_runtimes = select({
Label("//bazel/carbon_rules:use_target_config_carbon_rules_config"): None,
"//conditions:default": Label("//toolchain/install:built_runtimes"),
})
_select_internal_target_toolchain_driver = select({
Label(
"//bazel/carbon_rules:use_target_config_carbon_rules_config",
): Label("//toolchain/install:carbon-busybox"),
"//conditions:default": None,
})
_select_internal_target_toolchain_data = select({
Label(
"//bazel/carbon_rules:use_target_config_carbon_rules_config",
): Label("//toolchain/install:install_data"),
"//conditions:default": None,
})
_select_internal_target_prebuilt_runtimes = select({
Label(
"//bazel/carbon_rules:use_target_config_carbon_rules_config",
): Label("//toolchain/install:built_runtimes"),
"//conditions:default": None,
})
_carbon_binary_internal = rule(
implementation = _carbon_binary_impl,
attrs = {
"deps": attr.label_list(allow_files = True, providers = [[CcInfo]]),
"deps": attr.label_list(allow_files = True, providers = [[CcInfo], [CarbonLibraryInfo]]),
"flags": attr.string_list(),
# The exec config toolchain attributes. These will be `None` when using
@@ -167,11 +279,58 @@ _carbon_binary_internal = rule(
executable = True,
cfg = "target",
),
"prelude_srcs": attr.label_list(allow_files = [".carbon"]),
"srcs": attr.label_list(allow_files = [".carbon"]),
"_cc_toolchain": attr.label(default = "//toolchain/install:carbon_stage1_cc_toolchain"),
"_default_deps": attr.label_list(default = [Label("//core:io")]),
},
executable = True,
fragments = ["cpp"],
)
_carbon_library_internal = rule(
implementation = _carbon_library_impl,
attrs = {
"deps": attr.label_list(allow_files = True),
"flags": attr.string_list(),
"hdrs": attr.label_list(allow_files = [".carbon"]),
# The exec config toolchain attributes. These will be `None` when using
# the target config and populated when using the exec config. We have to
# use duplicate attributes here and below to have different `cfg`
# settings, as that isn't `select`-able, and we'll use `select`s when
# populating these.
"internal_exec_prebuilt_runtimes": attr.label(
cfg = "exec",
),
"internal_exec_toolchain_data": attr.label(
cfg = "exec",
),
"internal_exec_toolchain_driver": attr.label(
allow_single_file = True,
executable = True,
cfg = "exec",
),
# The target config toolchain attributes. These will be 'None' when
# using the exec config and populated when using the target config. We
# have to use duplicate attributes here and below to have different
# `cfg` settings, as that isn't `select`-able, and we'll use `select`s
# when populating these.
"internal_target_prebuilt_runtimes": attr.label(
cfg = "target",
),
"internal_target_toolchain_data": attr.label(
cfg = "target",
),
"internal_target_toolchain_driver": attr.label(
allow_single_file = True,
executable = True,
cfg = "target",
),
"srcs": attr.label_list(allow_files = [".carbon"]),
"_cc_toolchain": attr.label(default = "//toolchain/install:carbon_stage1_cc_toolchain"),
},
executable = True,
executable = False,
fragments = ["cpp"],
)
@@ -188,37 +347,49 @@ def carbon_binary(name, srcs, deps = [], flags = [], tags = []):
_carbon_binary_internal(
name = name,
srcs = srcs,
prelude_srcs = ["//core:prelude_files"],
deps = deps,
flags = flags,
tags = tags,
internal_exec_toolchain_driver = _select_internal_exec_toolchain_driver,
internal_exec_toolchain_data = _select_internal_exec_toolchain_data,
internal_exec_prebuilt_runtimes = _select_internal_exec_prebuilt_runtimes,
internal_target_toolchain_driver = _select_internal_target_toolchain_driver,
internal_target_toolchain_data = _select_internal_target_toolchain_data,
internal_target_prebuilt_runtimes = _select_internal_target_prebuilt_runtimes,
)
# We synthesize two sets of attributes from mirrored `select`s here
# because we want to select on an internal property of these attributes
# but that isn't `select`-able. Instead, we have both attributes and
# `select` which one we use.
internal_exec_toolchain_driver = select({
"//bazel/carbon_rules:use_target_config_carbon_rules_config": None,
"//conditions:default": "//toolchain/install:carbon-busybox",
}),
internal_exec_toolchain_data = select({
"//bazel/carbon_rules:use_target_config_carbon_rules_config": None,
"//conditions:default": "//toolchain/install:install_data",
}),
internal_exec_prebuilt_runtimes = select({
"//bazel/carbon_rules:use_target_config_carbon_rules_config": None,
"//conditions:default": "//toolchain/install:built_runtimes",
}),
internal_target_toolchain_driver = select({
"//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/install:carbon-busybox",
"//conditions:default": None,
}),
internal_target_toolchain_data = select({
"//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/install:install_data",
"//conditions:default": None,
}),
internal_target_prebuilt_runtimes = select({
"//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/install:built_runtimes",
"//conditions:default": None,
}),
def carbon_library(name, hdrs = [], srcs = [], deps = [], flags = [], tags = [], visibility = []):
"""Compiles a Carbon library.
Note: This carbon_library is designed as a _linkage_unit_, and does not necessarily
have to correlate the Carbon language library concept. As such it is designed to
accommodate more than one api file.
The arguments `hdrs` and `srcs` are kept for reasons of convention and compatibility
with C++ toolchains, particularly build aspects that folks might want to reuse on
mixed projects.
Args:
name: The name of the build target.
hdrs: List of one or more api files.
srcs: List of zero or more implementation files.
deps: List of dependencies.
flags: Extra flags to pass to the Carbon compile command.
tags: Tags to apply to the rule.
visibility: Visibility rules for the library.
"""
_carbon_library_internal(
name = name,
hdrs = hdrs,
srcs = srcs,
deps = deps,
flags = flags,
tags = tags,
visibility = visibility,
internal_exec_toolchain_driver = _select_internal_exec_toolchain_driver,
internal_exec_toolchain_data = _select_internal_exec_toolchain_data,
internal_exec_prebuilt_runtimes = _select_internal_exec_prebuilt_runtimes,
internal_target_toolchain_driver = _select_internal_target_toolchain_driver,
internal_target_toolchain_data = _select_internal_target_toolchain_data,
internal_target_prebuilt_runtimes = _select_internal_target_prebuilt_runtimes,
)
@@ -11,6 +11,7 @@ load(
"flag_group",
"flag_set",
"tool",
"tool_path",
)
load(
"@rules_cc//cc:defs.bzl",
@@ -142,10 +143,15 @@ def _carbon_cc_toolchain_config_impl(ctx):
# Only use a sysroot if a non-trivial one is set in Carbon's config.
builtin_sysroot = None
sysroot_include_search = []
sdk_settings = []
if clang_sysroot != "None" and clang_sysroot != "/":
builtin_sysroot = clang_sysroot
sysroot_include_search = ["%sysroot%/usr/include"]
# On MacOS, the compiler depends on this file at the root of the SDK,
# and it ends up in the `.d` files.
sdk_settings = ["%sysroot%/SDKSettings.json"]
runtimes_path = None
if ctx.attr.runtimes:
for f in ctx.files.runtimes:
@@ -162,6 +168,7 @@ def _carbon_cc_toolchain_config_impl(ctx):
ctx.attr.target_cpu,
ctx.attr.target_os,
)
return cc_common.create_cc_toolchain_config_info(
ctx = ctx,
features = clang_cc_toolchain_features(
@@ -184,7 +191,7 @@ def _carbon_cc_toolchain_config_impl(ctx):
"runtimes/libcxxabi/include",
"{}/include".format(clang_resource_dir),
"runtimes/clang_resource_dir/include",
] + _compute_clang_system_include_dirs() + sysroot_include_search,
] + _compute_clang_system_include_dirs() + sysroot_include_search + sdk_settings,
builtin_sysroot = builtin_sysroot,
# This configuration only supports local non-cross builds so derive
@@ -197,7 +204,7 @@ def _carbon_cc_toolchain_config_impl(ctx):
# Pass in our tool paths to expose Make variables like $(NM) and
# $(OBJCOPY).
tool_paths = llvm_tool_paths(llvm_bindir, clang_bindir),
tool_paths = llvm_tool_paths(llvm_bindir, clang_bindir) + [tool_path(name = "carbon-busybox", path = "carbon-busybox")],
)
carbon_cc_toolchain_config = rule(
@@ -17,10 +17,49 @@ load(
"preprocessor_compile_actions",
)
# Sysroots and MacOS are complicated:
#
# On Darwin/MacOS, the `-isysroot` flag is used for includes *and* libraries,
# and if specified it wins over `--sysroot` which would be used for libraries
# on other platforms.
# https://discourse.llvm.org/t/silly-what-is-the-difference-between-sysroot-and-isysroot/55788/2
#
# Additionally, on a MacOS build of clang, the sysroot defaults to `/`, which
# is incorrect and it needs to be pointed to the SDK root. However, as a
# convenience, homebrew builds of clang automatically add `-isysroot` to the
# command line, so that the user doesn't have to. But the SDK it chooses does
# not always match the one returned from `xcrun --show-sdk-path`, which is the
# SDK that we want to use. So we need to override homebrew's choice and specify
# `-isysroot`. This will also supersede anything given to `--sysroot` (on
# Darwin) so we don't need to specify both. For non-homebrew clang builds on
# MacOS, specifying `-isysroot` will also work to point the compiler to the
# correct SDK instead of `--sysroot`.
_sysroot_flag_sets = [
flag_set(
actions = ACTION_NAME_GROUPS.all_cc_compile_actions + ACTION_NAME_GROUPS.all_cc_link_actions,
flag_groups = [
flag_group(
expand_if_available = "sysroot",
flags = ["--sysroot=%{sysroot}"],
),
],
with_features = [with_feature_set(not_features = ["macos_target"])],
),
flag_set(
actions = ACTION_NAME_GROUPS.all_cc_compile_actions + ACTION_NAME_GROUPS.all_cc_link_actions,
flag_groups = [
flag_group(
flags = ["-isysroot", "%{sysroot}"],
),
],
with_features = [with_feature_set(["macos_target"])],
),
]
clang_feature = feature(
name = "clang",
enabled = True,
flag_sets = [
flag_sets = _sysroot_flag_sets + [
flag_set(
actions = ACTION_NAME_GROUPS.all_cc_compile_actions + ACTION_NAME_GROUPS.all_cc_link_actions,
flag_groups = [
@@ -28,10 +67,6 @@ clang_feature = feature(
"-no-canonical-prefixes",
"-fcolor-diagnostics",
]),
flag_group(
expand_if_available = "sysroot",
flags = ["--sysroot=%{sysroot}"],
),
],
),
flag_set(
@@ -66,6 +101,8 @@ clang_feature = feature(
# Flags specific to compiling C++ sources.
actions = ACTION_NAME_GROUPS.all_cpp_compile_actions,
flag_groups = [flag_group(flags = [
"-fno-exceptions",
"-fno-rtti",
"-std=c++20",
])],
),
@@ -284,8 +321,13 @@ def libcxx_feature(llvm_bindir = None, clang_bindir = None):
"-unwindlib=libunwind",
])],
with_features = [
# libc++ is only used on non-Windows platforms.
with_feature_set(not_features = ["windows_target"]),
# libc++ is only used on non-Windows platforms, and macOS
# doesn't support a custom unwinding library (or need one)
# even when using libc++.
with_feature_set(not_features = [
"macos_target",
"windows_target",
]),
],
),
flag_set(
+1
View File
@@ -13,6 +13,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
import os
import subprocess
import sys
from bazel_tools.tools.python.runfiles import runfiles
@@ -6,6 +6,7 @@
load("@rules_cc//cc:defs.bzl", "cc_toolchain")
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
load("@rules_cc//cc/toolchains:cc_toolchain_config_info.bzl", "CcToolchainConfigInfo")
load(":cc_toolchain_carbon_project_features.bzl", "carbon_project_features")
load(":cc_toolchain_cpp_features.bzl", "libcxx_feature")
load(":cc_toolchain_features.bzl", "clang_cc_toolchain_features")
@@ -27,9 +28,14 @@ load(
def _impl(ctx):
# Only use a sysroot if one was found when detecting Clang.
sysroot = None
sdk_settings = []
if sysroot_dir != "None":
sysroot = sysroot_dir
# On MacOS, the compiler depends on this file at the root of the SDK,
# and it ends up in the `.d` files.
sdk_settings = [sysroot_dir + "/SDKSettings.json"]
identifier = "local-{0}-{1}".format(ctx.attr.target_cpu, ctx.attr.target_os)
return cc_common.create_cc_toolchain_config_info(
ctx = ctx,
@@ -40,7 +46,7 @@ def _impl(ctx):
extra_cpp_features = [libcxx_feature(llvm_bindir, clang_bindir)],
),
action_configs = llvm_action_configs(llvm_bindir, clang_bindir),
cxx_builtin_include_directories = clang_include_dirs + [
cxx_builtin_include_directories = clang_include_dirs + sdk_settings + [
# Add Clang's resource directory to the end of the builtin include
# directories to cover the use of sanitizer resource files by the
# driver.
+8 -4
View File
@@ -83,7 +83,11 @@ def _compute_clang_resource_dir(repository_ctx, clang):
).stdout
# The only line printed is this path.
return output.splitlines()[0]
dir_path = repository_ctx.path(output.splitlines()[0])
# Canonicalize the path to help ensure string matching succeeds
# even with clang installs returning a non-canonical path.
return str(dir_path.realpath)
def _compute_mac_os_sysroot(repository_ctx):
"""Runs `xcrun` to extract the correct sysroot."""
@@ -148,7 +152,7 @@ def _compute_clang_cpp_include_search_paths(repository_ctx, clang, sysroot):
if repository_ctx.os.name.lower().startswith("mac os"):
if not sysroot:
fail("Must provide a sysroot on macOS!")
cmd.append("--sysroot=" + sysroot)
cmd += ["-isysroot", sysroot]
# Note that verbose output is on stderr, not stdout!
output = _run(repository_ctx, cmd).stderr.splitlines()
@@ -184,9 +188,9 @@ def _configure_clang_toolchain_impl(repository_ctx):
(clang, clang_version, clang_version_for_cache) = _detect_system_clang(
repository_ctx,
)
if clang_version and clang_version < 19:
if clang_version and clang_version < 21:
fail("Found clang {0}. ".format(clang_version) +
"Carbon requires clang >=19. See " +
"Carbon requires clang >=21. See " +
"https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/contribution_tools.md#old-llvm-versions")
clang_cpp = clang.dirname.get_child("clang++")
+1 -1
View File
@@ -41,6 +41,6 @@ def cc_env():
macos_env = {"MallocNanoZone": "0"}
return common_env | select({
"//bazel/cc_toolchains:macos_asan": macos_env,
Label("//bazel/cc_toolchains:macos_asan"): macos_env,
"//conditions:default": {},
})
+6 -1
View File
@@ -1,4 +1,9 @@
#!/usr/bin/env python3
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# ///
"""Update the roots of the Carbon build used for dependency checking.
@@ -0,0 +1,37 @@
Removes additional libc-backed arithmetic builtins added
by https://github.com/llvm/llvm-project/pull/207092
and https://github.com/llvm/llvm-project/pull/209984
---
--- a/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
@@ -303,6 +303,30 @@
"lib/builtins/extendsfdf2.cpp",
"lib/builtins/extendsftf2.cpp",
"lib/builtins/extendxftf2.cpp",
+ "lib/builtins/fixdfdi.cpp",
+ "lib/builtins/fixdfsi.cpp",
+ "lib/builtins/fixdfti.cpp",
+ "lib/builtins/fixsfdi.cpp",
+ "lib/builtins/fixsfsi.cpp",
+ "lib/builtins/fixsfti.cpp",
+ "lib/builtins/fixunsdfdi.cpp",
+ "lib/builtins/fixunsdfsi.cpp",
+ "lib/builtins/fixunsdfti.cpp",
+ "lib/builtins/fixunssfdi.cpp",
+ "lib/builtins/fixunssfsi.cpp",
+ "lib/builtins/fixunssfti.cpp",
+ "lib/builtins/floatdidf.cpp",
+ "lib/builtins/floatdisf.cpp",
+ "lib/builtins/floatsidf.cpp",
+ "lib/builtins/floatsisf.cpp",
+ "lib/builtins/floattidf.cpp",
+ "lib/builtins/floattisf.cpp",
+ "lib/builtins/floatundidf.cpp",
+ "lib/builtins/floatundisf.cpp",
+ "lib/builtins/floatunsidf.cpp",
+ "lib/builtins/floatunsisf.cpp",
+ "lib/builtins/floatuntidf.cpp",
+ "lib/builtins/floatuntisf.cpp",
"lib/builtins/muldf3.cpp",
"lib/builtins/mulsf3.cpp",
"lib/builtins/multf3.cpp",
@@ -1,521 +0,0 @@
Commit ID: d3b82534c2546a892a27856672ed95a7db97dba3
Change ID: zyxuvzwmzsnorloyuupuurxkppkoplnw
Author : Chandler Carruth <chandlerc@gmail.com> (2026-02-16 23:17:06)
Committer: Chandler Carruth <chandlerc@gmail.com> (2026-03-11 07:54:02)
Improve compiler-rt build structure and export compilation info
This first improves the structure of the compiler-rt BUILD.bazel, fixing
bugs and exposing more carefully arranged source files.
It also exposes compilation info for builtins and CRT files for use in
compiling these source files.
diff --git a/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel b/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
index 4ded226174..3b5b8fc787 100644
--- a/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
load("@rules_cc//cc:defs.bzl", "cc_library")
+load("compiler-rt.bzl", "make_filtered_builtins_srcs_groups")
package(
default_visibility = ["//visibility:public"],
@@ -160,9 +161,15 @@
srcs = BUILTINS_CRTEND_SRCS,
)
+BUILTINS_EMUTLS_SRCS = ["lib/builtins/emutls.c"]
+
+filegroup(
+ name = "builtins_emutls_srcs",
+ srcs = BUILTINS_EMUTLS_SRCS,
+)
+
BUILTINS_HOSTED_SRCS = [
"lib/builtins/clear_cache.c",
- "lib/builtins/emutls.c",
"lib/builtins/enable_execute_stack.c",
"lib/builtins/eprintf.c",
]
@@ -224,11 +231,11 @@
),
)
-BUILTNS_ATOMICS_SRCS = ["lib/builtins/atomic.c"]
+BUILTINS_ATOMICS_SRCS = ["lib/builtins/atomic.c"]
filegroup(
name = "builtins_atomics_srcs",
- srcs = BUILTNS_ATOMICS_SRCS + ["lib/builtins/assembly.h"],
+ srcs = BUILTINS_ATOMICS_SRCS + ["lib/builtins/assembly.h"],
)
BUILTINS_MACOS_ATOMIC_SRCS_PATTERNS = [
@@ -241,6 +248,28 @@
srcs = glob(BUILTINS_MACOS_ATOMIC_SRCS_PATTERNS),
)
+# Source files for portable components of the compiler builtins library.
+filegroup(
+ name = "builtins_generic_srcs",
+ srcs = ["lib/builtins/cpu_model/cpu_model.h"] + glob(
+ [
+ "lib/builtins/*.c",
+ "lib/builtins/*.cpp",
+ "lib/builtins/*.h",
+ "lib/builtins/*.inc",
+ ],
+ allow_empty = True,
+ exclude = (
+ BUILTINS_CRTBEGIN_SRCS +
+ BUILTINS_CRTEND_SRCS +
+ BUILTINS_TF_EXCLUDES +
+ BUILTINS_TF_SRCS_PATTERNS +
+ BUILTINS_ATOMICS_SRCS +
+ BUILTINS_MACOS_ATOMIC_SRCS_PATTERNS
+ ),
+ ),
+)
+
# Apple-platform specific SME source file.
filegroup(
name = "builtins_aarch64_apple_sme_srcs",
@@ -305,10 +334,13 @@
# Source files for the AArch64 architecture-specific builtins.
filegroup(
- name = "builtins_aarch64_srcs",
+ name = "builtins_unfiltered_aarch64_srcs",
srcs = [
"lib/builtins/cpu_model/aarch64.c",
"lib/builtins/cpu_model/aarch64.h",
+ ":builtins_bf16_srcs",
+ ":builtins_generic_srcs",
+ ":builtins_tf_srcs",
] + [
AARCH64_OUTLINE_ATOMICS_FMT.format(pat, size, model)
for (pat, size, model) in AARCH64_OUTLINE_ATOMICS
@@ -328,10 +360,20 @@
"lib/builtins/aarch64/lse.S",
# These files are provided by SME-specific file groups above.
"lib/builtins/aarch64/*sme*",
+ # This is only used with MinGW.
+ "lib/builtins/aarch64/chkstk.S",
+ # TODO: Remove this once we have a way of accessing `SipHash.h`.
+ "lib/builtins/aarch64/emupac.cpp",
],
),
)
+make_filtered_builtins_srcs_groups(
+ name = "builtins_aarch64_srcs",
+ srcs = [":builtins_unfiltered_aarch64_srcs"],
+ textual_name = "builtins_aarch64_textual_srcs",
+)
+
BUILTINS_ARM_VFP_SRCS_PATTERNS = [
"lib/builtins/arm/*vfp*.S",
"lib/builtins/arm/*vfp*.c",
@@ -348,9 +390,19 @@
),
)
+BUILTINS_ARM_IMPLICIT_IT_SRCS = [
+ "lib/builtins/arm/mulsf3.S",
+ "lib/builtins/arm/divsf3.S",
+]
+
+filegroup(
+ name = "builtins_arm_implicit_it_srcs",
+ srcs = BUILTINS_ARM_IMPLICIT_IT_SRCS,
+)
+
# Source files for the ARM architecture-specific builtins.
filegroup(
- name = "builtins_arm_srcs",
+ name = "builtins_arm_arch_srcs",
srcs = glob(
[
"lib/builtins/arm/*.S",
@@ -359,14 +411,52 @@
"lib/builtins/arm/*.h",
],
allow_empty = True,
- exclude = BUILTINS_ARM_VFP_SRCS_PATTERNS,
+ exclude = (BUILTINS_ARM_VFP_SRCS_PATTERNS +
+ BUILTINS_ARM_IMPLICIT_IT_SRCS) + [
+ # This is only used with MinGW.
+ "lib/builtins/arm/chkstk.S",
+ ],
),
)
-# Source files for the PPC architecture-specific builtins.
-filegroup(
- name = "builtins_ppc_srcs",
- srcs = glob(
+filegroup(
+ name = "builtins_unfiltered_armv7_srcs",
+ srcs = [
+ ":builtins_arm_arch_srcs",
+ ":builtins_arm_vfp_srcs",
+ ":builtins_bf16_srcs",
+ ":builtins_generic_srcs",
+ ],
+)
+
+make_filtered_builtins_srcs_groups(
+ name = "builtins_armv7_srcs",
+ srcs = [":builtins_unfiltered_armv7_srcs"],
+ textual_name = "builtins_armv7_textual_srcs",
+)
+
+filegroup(
+ name = "builtins_unfiltered_aarch32_srcs",
+ srcs = [
+ ":builtins_arm_arch_srcs",
+ ":builtins_arm_vfp_srcs",
+ ":builtins_bf16_srcs",
+ ":builtins_generic_srcs",
+ ],
+)
+
+make_filtered_builtins_srcs_groups(
+ name = "builtins_aarch32_srcs",
+ srcs = [":builtins_unfiltered_aarch32_srcs"],
+ textual_name = "builtins_aarch32_textual_srcs",
+)
+
+filegroup(
+ name = "builtins_unfiltered_ppc64_srcs",
+ srcs = [
+ ":builtins_generic_srcs",
+ ":builtins_tf_srcs",
+ ] + glob(
[
"lib/builtins/ppc/*.S",
"lib/builtins/ppc/*.c",
@@ -377,17 +467,64 @@
),
)
-# Source files for the RISC-V architecture-specific builtins.
-filegroup(
- name = "builtins_riscv_srcs",
- srcs = glob(
- [
- "lib/builtins/riscv/*.S",
- "lib/builtins/riscv/*.c",
- "lib/builtins/riscv/*.cpp",
- ],
- allow_empty = True,
- ),
+make_filtered_builtins_srcs_groups(
+ name = "builtins_ppc64_srcs",
+ srcs = [":builtins_unfiltered_ppc64_srcs"],
+ textual_name = "builtins_ppc64_textual_srcs",
+)
+
+filegroup(
+ name = "builtins_unfiltered_ppc32_srcs",
+ srcs = [":builtins_generic_srcs"],
+)
+
+make_filtered_builtins_srcs_groups(
+ name = "builtins_ppc32_srcs",
+ srcs = [":builtins_unfiltered_ppc32_srcs"],
+ textual_name = "builtins_ppc32_textual_srcs",
+)
+
+filegroup(
+ name = "builtins_unfiltered_riscv64_srcs",
+ srcs = [
+ ":builtins_generic_srcs",
+ ":builtins_tf_srcs",
+ ] + glob(
+ [
+ "lib/builtins/riscv/*.S",
+ "lib/builtins/riscv/*.c",
+ "lib/builtins/riscv/*.cpp",
+ "lib/builtins/riscv/*.h",
+ ],
+ allow_empty = True,
+ ),
+)
+
+make_filtered_builtins_srcs_groups(
+ name = "builtins_riscv64_srcs",
+ srcs = [":builtins_unfiltered_riscv64_srcs"],
+ textual_name = "builtins_riscv64_textual_srcs",
+)
+
+filegroup(
+ name = "builtins_unfiltered_riscv32_srcs",
+ srcs = [
+ ":builtins_generic_srcs",
+ ] + glob(
+ [
+ "lib/builtins/riscv/*.S",
+ "lib/builtins/riscv/*.c",
+ "lib/builtins/riscv/*.cpp",
+ "lib/builtins/riscv/*.h",
+ ],
+ allow_empty = True,
+ ),
+)
+
+make_filtered_builtins_srcs_groups(
+ name = "builtins_riscv32_srcs",
+ srcs = [":builtins_unfiltered_riscv32_srcs"],
+ textual_name = "builtins_riscv32_textual_srcs",
)
# Source files for the x86 architecture specific builtins (both 32-bit and
@@ -402,8 +539,14 @@
# Source files for the x86-64 architecture specific builtins.
filegroup(
- name = "builtins_x86_64_srcs",
- srcs = glob(
+ name = "builtins_unfiltered_x86_64_srcs",
+ srcs = [
+ ":builtins_bf16_srcs",
+ ":builtins_generic_srcs",
+ ":builtins_tf_srcs",
+ ":builtins_x86_arch_srcs",
+ ":builtins_x86_fp80_srcs",
+ ] + glob(
[
"lib/builtins/x86_64/*.S",
"lib/builtins/x86_64/*.c",
@@ -411,13 +554,29 @@
"lib/builtins/x86_64/*.h",
],
allow_empty = True,
+ exclude = [
+ # This is a Windows-specific routine.
+ # TODO: We should expose this as a Windows source at some point.
+ "lib/builtins/x86_64/chkstk.S",
+ ],
),
)
+make_filtered_builtins_srcs_groups(
+ name = "builtins_x86_64_srcs",
+ srcs = [":builtins_unfiltered_x86_64_srcs"],
+ textual_name = "builtins_x86_64_textual_srcs",
+)
+
# Source files for the 32-bit-specific x86 architecture specific builtins.
filegroup(
- name = "builtins_i386_srcs",
- srcs = glob(
+ name = "builtins_unfiltered_i386_srcs",
+ srcs = [
+ ":builtins_bf16_srcs",
+ ":builtins_generic_srcs",
+ ":builtins_x86_arch_srcs",
+ ":builtins_x86_fp80_srcs",
+ ] + glob(
[
"lib/builtins/i386/*.S",
"lib/builtins/i386/*.c",
@@ -429,28 +588,16 @@
# This file is used for both i386 and x86_64 and so included in the
# broader x86 sources.
"lib/builtins/i386/fp_mode.c",
+ # These are Windows-specific routines.
+ # TODO: We should expose these as Windows source at some point.
+ "lib/builtins/i386/chkstk.S",
+ "lib/builtins/i386/chkstk2.S",
],
),
)
-# Source files for portable components of the compiler builtins library.
-filegroup(
- name = "builtins_generic_srcs",
- srcs = ["lib/builtins/cpu_model/cpu_model.h"] + glob(
- [
- "lib/builtins/*.c",
- "lib/builtins/*.cpp",
- "lib/builtins/*.h",
- "lib/builtins/*.inc",
- ],
- allow_empty = True,
- exclude = (
- BUILTINS_CRTBEGIN_SRCS +
- BUILTINS_CRTEND_SRCS +
- BUILTINS_TF_EXCLUDES +
- BUILTINS_TF_SRCS_PATTERNS +
- BUILTNS_ATOMICS_SRCS +
- BUILTINS_MACOS_ATOMIC_SRCS_PATTERNS
- ),
- ),
+make_filtered_builtins_srcs_groups(
+ name = "builtins_i386_srcs",
+ srcs = [":builtins_unfiltered_i386_srcs"],
+ textual_name = "builtins_i386_textual_srcs",
)
diff --git a/utils/bazel/llvm-project-overlay/compiler-rt/compiler-rt.bzl b/utils/bazel/llvm-project-overlay/compiler-rt/compiler-rt.bzl
new file mode 100644
index 0000000000..e33ceb6a89
--- /dev/null
+++ b/utils/bazel/llvm-project-overlay/compiler-rt/compiler-rt.bzl
@@ -0,0 +1,153 @@
+# This file is licensed under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Starlark for building parts of compiler-rt.
+
+Variables provide baseline information for how to build various parts of
+compiler-rt. These can be used to generate non-Bazel builds of the library.
+
+Rules and macros support building the relevant filegroups of source files.
+
+TODO: Add macros that provide a convenient way to construct a Bazel target for
+the Clang resource directory with builtins and crt files.
+"""
+
+_common_copts = [
+ "-O3",
+ "-fPIC",
+ "-ffreestanding",
+ "-std=c11",
+]
+
+crt_copts = _common_copts + [
+ "-DCRT_HAS_INITFINI_ARRAY",
+ "-DEH_USE_FRAME_REGISTRY",
+ "-fno-lto",
+]
+
+builtins_copts = _common_copts + [
+ "-fno-builtin",
+ "-fomit-frame-pointer",
+ "-fvisibility=hidden",
+ "-Wno-missing-prototypes",
+ "-Wno-unused-parameter",
+]
+
+def _get_rel_path(path_str):
+ rel_path = path_str.rpartition("/lib/builtins/")[2]
+ if rel_path == path_str:
+ fail("Expected '/lib/builtins/' in path " + path_str)
+ return rel_path
+
+def _filtered_builtins_srcs_impl(ctx):
+ """Implementation of filter_builtins_srcs rule."""
+
+ # Build a map from generic file basename to list of overriding files.
+ overrides = {}
+ for f in ctx.files.srcs:
+ rel_path = _get_rel_path(f.short_path)
+ if "/" in rel_path:
+ base_file = rel_path.rpartition("/")[2]
+ if base_file.endswith(".S"):
+ base_file = base_file.removesuffix(".S") + ".c"
+ overrides[base_file] = True
+
+ filtered_files = []
+ for f in ctx.files.srcs:
+ rel_path = _get_rel_path(f.short_path)
+ if "/" not in rel_path:
+ # This is a generic file. Check if it's overridden.
+ if rel_path not in overrides:
+ filtered_files.append(f)
+ else:
+ # This is an arch-specific file, include it.
+ filtered_files.append(f)
+
+ # Remove any textual sources from this list.
+ filtered_files = [
+ f
+ for f in filtered_files
+ if f.extension not in ["inc", "def"]
+ ]
+
+ return [DefaultInfo(files = depset(filtered_files))]
+
+filtered_builtins_srcs = rule(
+ implementation = _filtered_builtins_srcs_impl,
+ attrs = {
+ "srcs": attr.label_list(
+ mandatory = True,
+ allow_files = True,
+ doc = "Input files.",
+ ),
+ },
+ doc = """Build a filtered filegroup of non-textual srcs for builtins.
+
+ Accepts a filegroup whose files are in lib/builtins/, and produces a target
+ behaving like a filegroup containing filtered files.
+
+ This removes any textual source files (`.inc` or `.def`) from the input.
+
+ It also replaces generic srcs that are overridden by architecture-specific
+ sources. For example, given a list of sources from filegroup of the form:
+
+ - `.../lib/builtins/file_0.c`
+ - `.../lib/builtins/file_1.c`
+ - `.../lib/builtins/file_2.c`
+ - `.../lib/builtins/arch/file_0.c`
+ - `.../lib/builtins/arch/file_1.S`
+
+ It removes any source-file at the top level of lib/builtins/ (e.g.
+ lib/builtins/file_0.c) that has a corresponding source-file in an arch
+ directory (e.g. lib/builtins/arch/file_0.c or lib/builtins/arch/file_1.S),
+ producing a list like:
+
+ - `.../lib/builtins/file_2.c`
+ - `.../lib/builtins/arch/file_0.c`
+ - `.../lib/builtins/arch/file_1.S`
+
+ This allows a target architecture to simply add a specialized file to the
+ list of sources with the architecture prefix and have the specialized
+ version override the generic version.
+ """,
+)
+
+def _filtered_builtins_textual_srcs_impl(ctx):
+ """Implementation of filter_builtins_textual_srcs rule."""
+
+ filtered_files = [
+ f
+ for f in ctx.files.srcs
+ if f.extension in ["inc", "def"]
+ ]
+
+ return [DefaultInfo(files = depset(filtered_files))]
+
+filtered_builtins_textual_srcs = rule(
+ implementation = _filtered_builtins_textual_srcs_impl,
+ attrs = {
+ "srcs": attr.label_list(
+ mandatory = True,
+ allow_files = True,
+ doc = "Input files.",
+ ),
+ },
+ doc = """Build a filegroup of the textual srcs for builtins.
+
+ Textual sources are those that can't be compiled directly and aren't
+ recognized as header files by Bazel. The extensions recognized here are
+ `.inc` and `.def`.
+ """,
+)
+
+def make_filtered_builtins_srcs_groups(name, textual_name, srcs):
+ """Macro to expand both the non-textual and textual filtered srcs groups."""
+ filtered_builtins_srcs(
+ name = name,
+ srcs = srcs,
+ )
+ filtered_builtins_textual_srcs(
+ name = textual_name,
+ srcs = srcs,
+ )
@@ -0,0 +1,24 @@
Temporarily undo
https://github.com/llvm/llvm-project/pull/207295
Which introduces a dependency on the hermetic llvm
toolchain. A fix-forward is in progress, at which
point we can remove this patch.
---
--- a/utils/bazel/llvm-project-overlay/llvm/config.bzl
+++ b/utils/bazel/llvm-project-overlay/llvm/config.bzl
@@ -72,7 +72,6 @@
backtrace_defines = select({
"@platforms//os:emscripten": [],
"@platforms//os:windows": [],
- "@llvm//platforms/config:musl": [],
"//conditions:default": [
"HAVE_BACKTRACE=1",
"BACKTRACE_HEADER=<execinfo.h>",
@@ -80,7 +79,6 @@
})
mallinfo_defines = select({
- "@llvm//platforms/config:gnu": ["HAVE_MALLINFO=1"],
"//conditions:default": [],
})
+9 -1
View File
@@ -13,8 +13,16 @@ def _get_files(ctx):
# Files may or may not be prefixed with the bin directory, and then
# may or may not be prefixed with the package directory. Strip both.
bin_dir = ctx.bin_dir.path + "/"
workspace_root = (
ctx.label.workspace_root + "/" if ctx.label.workspace_root else ""
)
package_dir = ctx.label.package + "/"
files_stripped = [f.removeprefix(bin_dir).removeprefix(package_dir) for f in files]
files_stripped = [
f.removeprefix(bin_dir)
.removeprefix(workspace_root)
.removeprefix(package_dir)
for f in files
]
else:
files_stripped = files
+10 -1
View File
@@ -1,4 +1,13 @@
#!/usr/bin/env python3
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# ///
# NOTE: The `uv` shebang and inline metadata above are only used for direct
# execution of this script outside of Bazel. When executed by Bazel (e.g., as a
# tool in a rule or as a test), Bazel uses its own hermetic Python toolchain
# and ignores this metadata.
"""Generate a file from a template, substituting the provided key/value pairs.
+1 -1
View File
@@ -135,7 +135,7 @@ def expand_version_build_info(name, **kwargs):
expand_version_build_info_internal(
name = name,
internal_stamp_flag_detect = False if kwargs.get("stamp") == 0 else select({
"//bazel/version:internal_stamp_flag_detect": True,
Label("//bazel/version:internal_stamp_flag_detect"): True,
"//conditions:default": False,
}),
**kwargs
+30 -4
View File
@@ -287,9 +287,9 @@ sh_test(
srcs = [":filesystem_benchmark"],
args = [
"--benchmark_dry_run",
# Restrict the sizes to 4-digit ones or smaller to keep test times low.
# Restrict the sizes to 2-digit ones or smaller to keep test times low.
# The `$$` is repeated for Bazel escaping of `$`.
"--benchmark_filter=^[^/]+(/[0-9]{1,4}(/[0-9]+)?)?/real_time$$",
"--benchmark_filter=^[^/]+(/[0-9]{1,2}(/[0-9]+)?)?/real_time$$",
],
)
@@ -342,12 +342,22 @@ cc_library(
],
)
cc_library(
name = "hashing_llvm",
hdrs = ["hashing_llvm.h"],
deps = [
":hashing",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "hashing_test",
size = "small",
srcs = ["hashing_test.cpp"],
deps = [
":hashing",
":hashing_llvm",
":raw_string_ostream",
"//testing/base:gtest_main",
"@googletest//:gtest",
@@ -384,6 +394,7 @@ cc_test(
size = "small",
srcs = ["hashtable_key_context_test.cpp"],
deps = [
":hashing_llvm",
":hashtable_key_context",
"//testing/base:gtest_main",
"@googletest//:gtest",
@@ -460,6 +471,7 @@ cc_test(
":raw_hashtable_test_helpers",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//llvm:Support",
],
)
@@ -489,7 +501,7 @@ sh_test(
args = [
"--benchmark_dry_run",
# The `$$` is repeated for Bazel escaping of `$`.
"--benchmark_filter=^[^/]*/[1-9][0-9]{0,3}(/[0-9]+)?$$",
"--benchmark_filter=^[^/]*/[1-9][0-9]{0,2}(/[0-9]+)?$$",
],
)
@@ -506,6 +518,19 @@ cc_library(
],
)
cc_test(
name = "ostream_test",
size = "small",
srcs = ["ostream_test.cpp"],
deps = [
":ostream",
":raw_string_ostream",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "pretty_stack_trace_function",
hdrs = ["pretty_stack_trace_function.h"],
@@ -635,6 +660,7 @@ cc_test(
":set",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//llvm:Support",
],
)
@@ -662,7 +688,7 @@ sh_test(
args = [
"--benchmark_dry_run",
# The `$$` is repeated for Bazel escaping of `$`.
"--benchmark_filter=^[^/]*/[1-9][0-9]{0,3}(/[0-9]+)?$$",
"--benchmark_filter=^[^/]*/[1-9][0-9]{0,2}(/[0-9]+)?$$",
],
)
+50 -7
View File
@@ -8,18 +8,61 @@
#include <string>
#include "common/ostream.h"
#include "llvm/Support/FormatCommon.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/raw_ostream.h"
namespace Carbon::Internal {
auto CheckFailImpl(const char* kind, const char* file, int line,
const char* condition_str, llvm::StringRef extra_message)
namespace {
// Renders `fmt` over the externally-built, type-erased `adapters` into `out`,
// with the same semantics as `llvm::formatv` (including runtime format-string
// validation).
//
// TODO: We should add a type-erased helper to upstream LLVM instead of rolling
// our own type-erased version of `format` here.
auto FormatvInto(
llvm::raw_ostream& out, llvm::StringRef format_str,
llvm::ArrayRef<llvm::support::detail::FormatFunctorRef> adapters) -> void {
for (const llvm::ReplacementItem& replacement :
llvm::formatv_object_base::parseFormatString(format_str, adapters.size(),
/*Validate=*/true)) {
if (replacement.Type == llvm::ReplacementType::Literal ||
replacement.Index >= adapters.size()) {
out << replacement.Spec;
continue;
}
llvm::FmtAlign(adapters[replacement.Index], replacement.Where,
replacement.Width, replacement.Pad)
.format(out, replacement.Options);
}
}
} // namespace
auto CheckFailImpl(
const char* kind, const char* file, int line, const char* condition_str,
const char* extra_format,
llvm::ArrayRef<llvm::support::detail::FormatFunctorRef> extra_adapters)
-> void {
// Render the final check string here.
std::string message = llvm::formatv(
"{0} failure at {1}:{2}{3}{4}{5}{6}\n", kind, file, line,
llvm::StringRef(condition_str).empty() ? "" : ": ", condition_str,
extra_message.empty() ? "" : ": ", extra_message);
// Render the final check string directly into one stream. The extra message
// is rendered in place from its format string and type-erased adapters, so
// we never materialize a separate string just for it.
//
// `llvm::raw_string_ostream` (rather than `common/raw_string_ostream.h`) is
// used to avoid a dependency cycle: `RawStringOstream` itself uses
// `CARBON_CHECK`. It is unbuffered, so `message` is populated directly.
std::string message;
llvm::raw_string_ostream message_stream(message);
message_stream << kind << " failure at " << file << ":" << line;
if (*condition_str != '\0') {
message_stream << ": " << condition_str;
}
if (*extra_format != '\0') {
message_stream << ": ";
FormatvInto(message_stream, extra_format, extra_adapters);
}
message_stream << "\n";
// This macro is defined by `--config=non-fatal-checks`.
#ifdef CARBON_NON_FATAL_CHECKS
+59 -36
View File
@@ -31,25 +31,29 @@ CheckCondition(bool condition)
// Implements the check failure message printing.
//
// This is out-of-line and will arrange to stop the program, print any debugging
// information and this string. In `!NDEBUG` mode (`dbg` and `fastbuild`), check
// failures can be made non-fatal by a build flag, so this is not `[[noreturn]]`
// in that case.
// information and the failure message. In `!NDEBUG` mode (`dbg` and
// `fastbuild`), check failures can be made non-fatal by a build flag, so this
// is not `[[noreturn]]` in that case.
//
// This API uses `const char*` C string arguments rather than `llvm::StringRef`
// because we know that these are available as C strings and passing them that
// way lets the code size of calling it be smaller: it only needs to materialize
// a single pointer argument for each. The runtime cost of re-computing the size
// should be minimal. The extra message however might not be compile-time
// guaranteed to be a C string so we use a normal `StringRef` there.
// should be minimal.
//
// The user can provide an extra format string along with an array of
// type-erased format adapters. This will be rendered into the final message.
#ifdef NDEBUG
[[noreturn]]
#endif
auto CheckFailImpl(const char* kind, const char* file, int line,
const char* condition_str, llvm::StringRef extra_message)
auto CheckFailImpl(
const char* kind, const char* file, int line, const char* condition_str,
const char* extra_format,
llvm::ArrayRef<llvm::support::detail::FormatFunctorRef> extra_adapters)
-> void;
// Allow converting format values; the default behaviour is to just pass them
// through.
// Allow custom conversion of format values; the default behaviour is to just
// pass them through.
template <typename T>
auto ConvertFormatValue(T&& t) -> T&& {
return std::forward<T>(t);
@@ -70,36 +74,53 @@ auto ConvertFormatValue(T&& t) -> auto {
}
}
// Builds one type-erased format functor per value -- forwarding each value
// through the conversion machinery. References to each of these functors are
// then collected into an init list that can be accessed with an `ArrayRef`. All
// of this is then passed to the out-of-line rendering function `CheckFailImpl`.
//
// This is templated only on the value types, not on the per-check-site
// metadata (file, line, etc., which are passed as ordinary arguments), so the
// adapter-building is instantiated once per distinct sequence of value types in
// the TU.
template <typename... Ts>
#ifdef NDEBUG
[[noreturn]]
#endif
auto CheckFailFormat(const char* kind, const char* file, int line,
const char* condition_str, const char* extra_format,
Ts&&... values) -> void {
CheckFailImpl(kind, file, line, condition_str, extra_format,
{llvm::support::detail::FormatFunctor(
ConvertFormatValue(std::forward<Ts>(values)))...});
}
// Prints a check failure, including rendering any user-provided message using
// a format string.
//
// Most of the parameters are passed as compile-time template strings to avoid
// runtime cost of parameter setup in optimized builds. Each of these are passed
// along to the underlying implementation to include in the final printed
// message.
//
// Any user-provided format string and values are directly passed to
// `llvm::formatv` which handles all of the formatting of output.
// The check-site metadata is passed as compile-time template strings to avoid
// runtime cost of parameter setup in optimized builds. This function is
// instantiated once per check site (its template arguments are unique to the
// site), so it is kept trivial: it just lowers those template strings to
// ordinary arguments and forwards everything to `CheckFailFormat`, where the
// adapter-building is shared across sites with the same value types.
template <TemplateString Kind, TemplateString File, int Line,
TemplateString ConditionStr, TemplateString FormatStr, typename... Ts>
#ifdef NDEBUG
[[noreturn]]
#endif
[[gnu::cold, clang::noinline]] auto CheckFail(Ts&&... values) -> void {
if constexpr (llvm::StringRef(FormatStr).empty()) {
// Skip the format string rendering if empty. Note that we don't skip it
// even if there are no values as we want to have consistent handling of
// `{}`s in the format string. This case is about when there is no message
// at all, just the condition.
CheckFailImpl(Kind.c_str(), File.c_str(), Line, ConditionStr.c_str(), "");
} else {
CheckFailImpl(Kind.c_str(), File.c_str(), Line, ConditionStr.c_str(),
llvm::formatv(FormatStr.c_str(),
ConvertFormatValue(std::forward<Ts>(values))...)
.str());
}
CheckFailFormat(Kind.c_str(), File.c_str(), Line, ConditionStr.c_str(),
FormatStr.c_str(), std::forward<Ts>(values)...);
}
// Type-checks the arguments of a `DCHECK` in optimized builds, where the check
// itself is dead code, without instantiating any formatting machinery for them
// and without provoking unused-variable warnings. It is only ever named from
// dead code, so it is never actually called.
template <typename... Ts>
auto IgnoreDeadCheckArgs(Ts&&... /*values*/) -> void {}
} // namespace Carbon::Internal
// Evaluates the condition of a CHECK as a boolean value.
@@ -148,21 +169,23 @@ template <TemplateString Kind, TemplateString File, int Line,
CARBON_INTERNAL_FATAL_NORETURN_SUFFIX())
#ifdef NDEBUG
// For `DCHECK` in optimized builds we have a dead check that we want to
// potentially "use" arguments, but otherwise have the minimal overhead. We
// avoid forming interesting format strings here so that we don't have to
// repeatedly instantiate the `Check` function above. This format string would
// be an error if actually used.
// For `DCHECK` in optimized builds the check is dead code, but we still want to
// type-check its arguments so they can't bitrot. We route them through
// `IgnoreDeadCheckArgs`, which uses the arguments (avoiding unused-variable
// warnings) but builds no format adapters, so the dead check doesn't pull in
// the formatting machinery -- in particular not the per-value-type adapters
// that the live `CheckFail` path would. The format string is a literal, so it
// needs no type-checking and is dropped.
#define CARBON_INTERNAL_DEAD_DCHECK(condition, ...) \
CARBON_INTERNAL_DEAD_DCHECK_IMPL##__VA_OPT__(_FORMAT)(__VA_ARGS__)
#define CARBON_INTERNAL_DEAD_DCHECK_IMPL() \
Carbon::Internal::CheckFail<"", "", 0, "", "">()
Carbon::Internal::IgnoreDeadCheckArgs()
#define CARBON_INTERNAL_DEAD_DCHECK_IMPL_FORMAT(format_str, ...) \
Carbon::Internal::CheckFail<"", "", 0, "", "">(__VA_ARGS__)
Carbon::Internal::IgnoreDeadCheckArgs(__VA_ARGS__)
// The CheckFail function itself is noreturn in NDEBUG.
// The `CheckFail` function itself is noreturn in NDEBUG.
#define CARBON_INTERNAL_FATAL_NORETURN_SUFFIX() void()
#else
#define CARBON_INTERNAL_FATAL_NORETURN_SUFFIX() std::abort()
+2 -9
View File
@@ -103,15 +103,8 @@ auto Internal::FileRefBase::ReadFileToString()
auto Internal::FileRefBase::WriteFileFromString(llvm::StringRef str)
-> ErrorOr<Success, FdError> {
CARBON_RETURN_IF_ERROR(SeekFromBeginning(0));
auto bytes = llvm::ArrayRef<std::byte>(
reinterpret_cast<const std::byte*>(str.data()), str.size());
while (!bytes.empty()) {
auto write_result = WriteFromBuffer(bytes);
if (!write_result.ok()) {
return std::move(write_result).error();
}
bytes = *write_result;
}
CARBON_RETURN_IF_ERROR(WriteCompleteBuffer(llvm::ArrayRef<std::byte>(
reinterpret_cast<const std::byte*>(str.data()), str.size())));
CARBON_RETURN_IF_ERROR(Truncate(str.size()));
return Success();
}
+70 -7
View File
@@ -219,6 +219,24 @@ namespace Internal {
class FileRefBase;
} // namespace Internal
// Convenience type defs for the three access combinations.
using ReadFileRef = FileRef<OpenAccess::ReadOnly>;
using WriteFileRef = FileRef<OpenAccess::WriteOnly>;
using ReadWriteFileRef = FileRef<OpenAccess::ReadWrite>;
// Returns constant references to the standard streams the process is started
// with.
//
// The returned references are non-owning: the process shares these descriptors
// with whatever started it, closing them is never correct, and unrelated code
// throughout the process may be reading or writing the same descriptor.
//
// Their descriptor numbers are fixed by the platform rather than discovered at
// runtime, so these are constant expressions.
consteval auto Stdin() -> ReadFileRef;
consteval auto Stdout() -> WriteFileRef;
consteval auto Stderr() -> WriteFileRef;
// Returns a constant `Dir` object that models the open current working
// directory.
//
@@ -348,7 +366,13 @@ class Internal::FileRefBase {
FileRefBase() = default;
// Returns true if this refers to a valid open file, and false otherwise.
auto is_valid() const -> bool { return fd_ != -1; }
constexpr auto is_valid() const -> bool { return fd_ != -1; }
// Non-portable API only available on Unix-like systems. Returns the
// underlying file descriptor, for the platform calls this type doesn't wrap,
// such as `isatty` and `ioctl`. The descriptor remains owned by whatever owns
// this file.
constexpr auto unix_fd() const -> int { return fd_; }
// Reads the file status.
//
@@ -405,6 +429,24 @@ class Internal::FileRefBase {
auto WriteFromBuffer(llvm::ArrayRef<std::byte> buffer)
-> ErrorOr<llvm::ArrayRef<std::byte>, FdError>;
// Writes the complete contents of the provided buffer.
//
// Unlike `WriteFromBuffer`, this doesn't return until every byte has been
// written or an error occurs. It repeats `WriteFromBuffer` over whatever is
// left, so each write is issued for as much of the buffer as remains and the
// whole is written in as few writes as the file allows. Anything else writing
// to the same file can only interleave between those writes, which leaves no
// room to interleave at all when the file accepts the buffer in one write.
//
// On an error, an unspecified prefix of the buffer has already been written
// and can't be un-written. How much isn't reported; a caller that needs to
// know should drive `WriteFromBuffer` itself.
//
// This method retries `EINTR` on Unix-like systems and returns other errors
// to the caller.
auto WriteCompleteBuffer(llvm::ArrayRef<std::byte> buffer)
-> ErrorOr<Success, FdError>;
// Returns an LLVM `raw_fd_ostream` that writes to this file.
//
// Note that this doesn't expose any write errors here, those will surface
@@ -458,7 +500,7 @@ class Internal::FileRefBase {
Duration poll_interval = {}) -> ErrorOr<FileLock, FdError>;
protected:
explicit FileRefBase(int fd) : fd_(fd) {}
explicit constexpr FileRefBase(int fd) : fd_(fd) {}
// Note: this should only be used or made part of the public API by subclasses
// that provide *ownership* of the open file. It is implemented here to
@@ -536,6 +578,9 @@ class FileRef : public Internal::FileRefBase {
auto WriteFromBuffer(llvm::ArrayRef<std::byte> buffer)
-> ErrorOr<llvm::ArrayRef<std::byte>, FdError>
requires Writeable;
auto WriteCompleteBuffer(llvm::ArrayRef<std::byte> buffer)
-> ErrorOr<Success, FdError>
requires Writeable;
auto WriteStream() -> llvm::raw_fd_ostream
requires Writeable;
auto ReadFileToString() -> ErrorOr<std::string, FdError>
@@ -546,16 +591,14 @@ class FileRef : public Internal::FileRefBase {
protected:
friend File<A>;
friend DirRef;
friend consteval auto Stdin() -> ReadFileRef;
friend consteval auto Stdout() -> WriteFileRef;
friend consteval auto Stderr() -> WriteFileRef;
// Other constructors from the base are also available, but remain protected.
using FileRefBase::FileRefBase;
};
// Convenience type defs for the three access combinations.
using ReadFileRef = FileRef<OpenAccess::ReadOnly>;
using WriteFileRef = FileRef<OpenAccess::WriteOnly>;
using ReadWriteFileRef = FileRef<OpenAccess::ReadWrite>;
// An owning handle to an open file.
//
// This extends the `FileRef` API to provide ownership of the file handle. Most
@@ -1320,6 +1363,10 @@ inline auto DurationToTimespec(Duration d) -> timespec {
} // namespace Internal
consteval auto Stdin() -> ReadFileRef { return ReadFileRef(STDIN_FILENO); }
consteval auto Stdout() -> WriteFileRef { return WriteFileRef(STDOUT_FILENO); }
consteval auto Stderr() -> WriteFileRef { return WriteFileRef(STDERR_FILENO); }
consteval auto Cwd() -> Dir { return Dir(AT_FDCWD); }
inline auto FileLock::Destroy() -> void {
@@ -1431,6 +1478,14 @@ inline auto Internal::FileRefBase::WriteFromBuffer(
}
}
inline auto Internal::FileRefBase::WriteCompleteBuffer(
llvm::ArrayRef<std::byte> buffer) -> ErrorOr<Success, FdError> {
while (!buffer.empty()) {
CARBON_ASSIGN_OR_RETURN(buffer, WriteFromBuffer(buffer));
}
return Success();
}
inline auto Internal::FileRefBase::WriteStream() -> llvm::raw_fd_ostream {
return llvm::raw_fd_ostream(fd_, /*shouldClose=*/false);
}
@@ -1495,6 +1550,14 @@ auto FileRef<A>::WriteFromBuffer(llvm::ArrayRef<std::byte> buffer)
return FileRefBase::WriteFromBuffer(buffer);
}
template <OpenAccess A>
auto FileRef<A>::WriteCompleteBuffer(llvm::ArrayRef<std::byte> buffer)
-> ErrorOr<Success, FdError>
requires Writeable
{
return FileRefBase::WriteCompleteBuffer(buffer);
}
template <OpenAccess A>
auto FileRef<A>::WriteStream() -> llvm::raw_fd_ostream
requires Writeable
+2 -2
View File
@@ -444,9 +444,9 @@ auto BM_CreateDirectories(benchmark::State& state) -> void {
CARBON_CHECK(existing_depth <= depth);
CARBON_CHECK(depth > 0);
// Use a batch size of 10 to get avoid completely swamping the measurements
// Use a batch size of 5 to get avoid completely swamping the measurements
// with overhead from creating existing directories and cleaning up.
constexpr int BatchSize = 10;
constexpr int BatchSize = 5;
// Pre-build both the paths and the existing paths. Note that we use
// relatively short paths here, which if anything makes the benefits of the
+52
View File
@@ -411,6 +411,58 @@ TEST_F(FilesystemTest, WriteStream) {
EXPECT_THAT(dir_.ReadFileToString("test"), IsSuccess(Eq(content_str)));
}
TEST_F(FilesystemTest, WriteCompleteBuffer) {
std::string content_str = "0123456789";
auto bytes = llvm::ArrayRef<std::byte>(
reinterpret_cast<const std::byte*>(content_str.data()),
content_str.size());
auto write = dir_.OpenWriteOnly("test", CreationOptions::CreateNew);
ASSERT_THAT(write, IsSuccess(_));
EXPECT_THAT(write->WriteCompleteBuffer(bytes), IsSuccess(_));
// Writing appends rather than replacing, unlike `WriteFileFromString`.
EXPECT_THAT(write->WriteCompleteBuffer(bytes), IsSuccess(_));
// An empty buffer is a no-op rather than an error.
EXPECT_THAT(write->WriteCompleteBuffer(llvm::ArrayRef<std::byte>()),
IsSuccess(_));
(*std::move(write)).Close().Check();
EXPECT_THAT(dir_.ReadFileToString("test"),
IsSuccess(Eq(content_str + content_str)));
}
TEST_F(FilesystemTest, StandardStreams) {
// The standard streams name descriptors the process already has, so these
// are constants and never open or close anything.
static_assert(Stdin().unix_fd() == STDIN_FILENO);
static_assert(Stdout().unix_fd() == STDOUT_FILENO);
static_assert(Stderr().unix_fd() == STDERR_FILENO);
EXPECT_TRUE(Stderr().is_valid());
// Writing through one reaches the descriptor. Tests run with stdout captured,
// so this uses a pipe put in its place for the duration.
int fds[2];
ASSERT_EQ(pipe(fds), 0);
int saved = dup(STDOUT_FILENO);
ASSERT_GE(saved, 0);
ASSERT_GE(dup2(fds[1], STDOUT_FILENO), 0);
llvm::StringRef message = "through stdout";
auto result = Stdout().WriteCompleteBuffer(llvm::ArrayRef<std::byte>(
reinterpret_cast<const std::byte*>(message.data()), message.size()));
ASSERT_GE(dup2(saved, STDOUT_FILENO), 0);
ASSERT_EQ(close(saved), 0);
ASSERT_EQ(close(fds[1]), 0);
EXPECT_THAT(result, IsSuccess(_));
char buffer[64];
ssize_t n = read(fds[0], buffer, sizeof(buffer));
ASSERT_EQ(close(fds[0]), 0);
ASSERT_GT(n, 0);
EXPECT_EQ(llvm::StringRef(buffer, n), message);
}
TEST_F(FilesystemTest, Rename) {
// Rename a file within a directory.
ASSERT_THAT(dir_.WriteFileFromString("file1", "content1"), IsSuccess(_));
+3 -3
View File
@@ -15,11 +15,11 @@ namespace Carbon {
namespace Internal {
template <typename Range>
using RangePointerType = typename std::iterator_traits<decltype(std::begin(
std::declval<Range>()))>::pointer;
using RangePointerType =
std::iterator_traits<decltype(std::begin(std::declval<Range>()))>::pointer;
template <typename Range>
using RangeValueType = typename std::iterator_traits<decltype(std::begin(
using RangeValueType = std::iterator_traits<decltype(std::begin(
std::declval<Range>()))>::value_type;
template <typename Range, typename Pred>
+6
View File
@@ -6,8 +6,14 @@
#include <cstddef>
#include "llvm/Support/FormatVariadic.h"
namespace Carbon {
auto HashCode::Print(llvm::raw_ostream& out) const -> void {
out << llvm::formatv("{0:x16}", value_);
}
auto Hasher::HashSizedBytesLarge(llvm::ArrayRef<std::byte> bytes) -> void {
const std::byte* data_ptr = bytes.data();
const ssize_t size = bytes.size();
+29 -39
View File
@@ -13,12 +13,9 @@
#include "common/check.h"
#include "common/ostream.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FormatVariadic.h"
#ifdef __ARM_ACLE
#include <arm_acle.h>
@@ -72,9 +69,7 @@ class HashCode : public Printable<HashCode> {
// other recursive hashing where that is needed or more efficient.
explicit operator uint64_t() const { return value_; }
auto Print(llvm::raw_ostream& out) const -> void {
out << llvm::formatv("{0:x16}", value_);
}
auto Print(llvm::raw_ostream& out) const -> void;
private:
uint64_t value_ = 0;
@@ -527,30 +522,6 @@ inline auto CarbonHashValue(const T (&arg)[N], uint64_t seed) -> HashCode {
return CarbonHashValue(llvm::ArrayRef(arg), seed);
}
inline auto CarbonHashValue(llvm::APInt value, uint64_t seed) -> HashCode {
Hasher hasher(seed);
if (LLVM_LIKELY(value.isSingleWord())) {
hasher.Hash(value.getBitWidth(), value.getZExtValue());
} else {
hasher.HashRaw(value.getBitWidth());
hasher.HashSizedBytes(
llvm::ArrayRef(value.getRawData(), value.getNumWords()));
}
return static_cast<HashCode>(hasher);
}
inline auto CarbonHashValue(llvm::APFloat value, uint64_t seed) -> HashCode {
Hasher hasher(seed);
// Hashing floating point numbers is complex and depends on the specific
// internal semantics of `APFloat`, so delegate to the LLVM hashing framework
// here. We re-hash the result to mix in our seed. All of this is a bit
// inefficient, and we can revisit this to provide a dedicated implementation
// if it becomes a bottleneck.
using llvm::hash_value;
hasher.HashRaw(hash_value(value));
return static_cast<HashCode>(hasher);
}
template <typename... Ts>
inline auto CarbonHashValue(const std::tuple<Ts...>& value, uint64_t seed)
-> HashCode {
@@ -567,6 +538,14 @@ inline auto CarbonHashValue(const std::pair<T, U>& value, uint64_t seed)
return static_cast<HashCode>(hasher);
}
// Extension point for types defined outside of Carbon that cannot be found by
// ADL in their own namespace and cannot be declared before this point.
template <typename T>
struct CustomHashValue;
template <typename T>
concept HasCustomHashValue = requires { CustomHashValue<T>::Hash; };
// Implementation detail predicate to detect if there is a `CarbonHashValue`
// overload available for a particular type, either in this namespace or found
// via ADL. Note that this should not be moved above any overloads.
@@ -618,14 +597,19 @@ concept CanHashAsRawDataType = std::same_as<T, std::nullptr_t> ||
// `HasCarbonHashValue`, this must not be moved above any of those overloads.
template <typename T>
inline auto DispatchImpl(const T& value, uint64_t seed) -> HashCode {
// If we have an explicit overload for `CarbonHashValue`, call it. This may be
// provided above or via ADL, and is preferred as it represents an explicit
// request for how the type is hashed.
if constexpr (HasCarbonHashValue<T>) {
// If we have an explicit overload for `CarbonHashValue`, call it. This may
// be provided above or via ADL, and is preferred as it represents an
// explicit request for how the type is hashed.
return CarbonHashValue(value, seed);
} else if constexpr (HasCustomHashValue<T>) {
// If we have an explicit specialization for `CustomHashValue`, call it.
// This is a fallback explicit hashing path that doesn't require ADL or
// being in this header.
return CustomHashValue<T>::Hash(value, seed);
} else if constexpr (CanHashAsRawDataType<T>) {
// There was no explicit overload to call, but the type allows us to hash it
// as raw data, do so.
// There was no explicit overload or specialization to call, but the type
// allows us to hash it as raw data, do so.
Hasher hasher(seed);
hasher.HashRaw(MapToRawDataType(value));
return static_cast<HashCode>(hasher);
@@ -813,11 +797,13 @@ inline auto Hasher::Hash(const Ts&... values) -> void {
using InternalHashDispatch::CanHashAsRawDataType;
using InternalHashDispatch::HasCarbonHashValue;
using InternalHashDispatch::HasCustomHashValue;
using InternalHashDispatch::MapToRawDataType;
// Special-case a single element tuple that we will hash as raw data.
if constexpr (sizeof...(Ts) == 1 && (... && (!HasCarbonHashValue<Ts> &&
CanHashAsRawDataType<Ts>))) {
if constexpr (sizeof...(Ts) == 1 &&
(... && (!HasCarbonHashValue<Ts> && !HasCustomHashValue<Ts> &&
CanHashAsRawDataType<Ts>))) {
HashRaw(MapToRawDataType(values)...);
return;
}
@@ -830,12 +816,15 @@ inline auto Hasher::Hash(const Ts&... values) -> void {
// a little bit wasteful in some cases, collapsing down to a flat array of
// 64-bit integers is more efficient to hash.
auto map_value = []<typename T>(const T& value) -> uint64_t {
if constexpr (HasCarbonHashValue<T>) {
if constexpr (HasCarbonHashValue<T> || HasCustomHashValue<T>) {
// Use the top-level `HashValue` to re-dispatch to the custom
// implementation with a fixed seed.
return static_cast<uint64_t>(HashValue(value));
} else if constexpr (CanHashAsRawDataType<T>) {
auto raw_value = MapToRawDataType(value);
// If we are hashing a pointer, then `raw_value` is a pointer, but that
// is what we want the size of.
// NOLINTNEXTLINE(bugprone-sizeof-expression)
if constexpr (sizeof(raw_value) <= 8) {
return ReadSmall(raw_value);
} else {
@@ -866,11 +855,12 @@ template <typename T>
inline auto Hasher::HashArray(llvm::ArrayRef<T> values) -> void {
using InternalHashDispatch::CanHashAsRawDataType;
using InternalHashDispatch::HasCarbonHashValue;
using InternalHashDispatch::HasCustomHashValue;
// This logic similarly mirrors `InternalHashDispatch::DispatchImpl`, but is
// specialized here to allow us to efficiently process the array when it
// *doesn't* require recursive hashing.
if constexpr (HasCarbonHashValue<T>) {
if constexpr (HasCarbonHashValue<T> || HasCustomHashValue<T>) {
// Use a trivial loop to give consistent behavior for arrays requiring
// recursive hashing. This isn't terribly efficient, but if clients care
// they should specialize the entire hashing operation. For simple, tiny
+47
View File
@@ -0,0 +1,47 @@
// 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
#ifndef CARBON_COMMON_HASHING_LLVM_H_
#define CARBON_COMMON_HASHING_LLVM_H_
#include "common/hashing.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/Hashing.h"
namespace Carbon::InternalHashDispatch {
template <>
struct CustomHashValue<llvm::APInt> {
static auto Hash(llvm::APInt value, uint64_t seed) -> HashCode {
Hasher hasher(seed);
if (LLVM_LIKELY(value.isSingleWord())) {
hasher.Hash(value.getBitWidth(), value.getZExtValue());
} else {
hasher.HashRaw(value.getBitWidth());
hasher.HashSizedBytes(
llvm::ArrayRef(value.getRawData(), value.getNumWords()));
}
return static_cast<HashCode>(hasher);
}
};
template <>
struct CustomHashValue<llvm::APFloat> {
static auto Hash(llvm::APFloat value, uint64_t seed) -> HashCode {
Hasher hasher(seed);
// Hashing floating point numbers is complex and depends on the specific
// internal semantics of `APFloat`, so delegate to the LLVM hashing
// framework here. We re-hash the result to mix in our seed. All of this is
// a bit inefficient, and we can revisit this to provide a dedicated
// implementation if it becomes a bottleneck.
using llvm::hash_value;
hasher.HashRaw(hash_value(value));
return static_cast<HashCode>(hasher);
}
};
} // namespace Carbon::InternalHashDispatch
#endif // CARBON_COMMON_HASHING_LLVM_H_
+3 -37
View File
@@ -13,6 +13,7 @@
#include <type_traits>
#include <utility>
#include "common/hashing_llvm.h"
#include "common/raw_string_ostream.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/StringExtras.h"
@@ -491,41 +492,6 @@ struct HashedValue {
using HashedString = HashedValue<std::string>;
template <typename T>
auto PrintFullWidthHex(llvm::raw_ostream& os, T value) {
static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 ||
sizeof(T) == 8);
// Given the nature of a format string and the good formatting, a nested
// conditional seems like the most readable structure.
// NOLINTBEGIN(readability-avoid-nested-conditional-operator)
os << llvm::formatv(sizeof(T) == 1 ? "{0:x2}"
: sizeof(T) == 2 ? "{0:x4}"
: sizeof(T) == 4 ? "{0:x8}"
: "{0:x16}",
static_cast<uint64_t>(value));
// NOLINTEND(readability-avoid-nested-conditional-operator)
}
template <typename T>
requires std::integral<T>
auto operator<<(llvm::raw_ostream& os, HashedValue<T> hv)
-> llvm::raw_ostream& {
os << "hash " << hv.hash << " for value ";
PrintFullWidthHex(os, hv.v);
return os;
}
template <typename T, typename U>
requires std::integral<T> && std::integral<U>
auto operator<<(llvm::raw_ostream& os, HashedValue<std::pair<T, U>> hv)
-> llvm::raw_ostream& {
os << "hash " << hv.hash << " for pair of ";
PrintFullWidthHex(os, hv.v.first);
os << " and ";
PrintFullWidthHex(os, hv.v.second);
return os;
}
struct Collisions {
int total;
int median;
@@ -770,8 +736,8 @@ struct SparseHashTestParamRanges {
template <typename ParamRanges>
struct SparseHashTest : ::testing::Test {
using ByteCount = typename ParamRanges::ByteCount;
using SetBitCount = typename ParamRanges::SetBitCount;
using ByteCount = ParamRanges::ByteCount;
using SetBitCount = ParamRanges::SetBitCount;
static auto GetHashedByteStrings() {
llvm::SmallVector<HashedString> hashes;
+2
View File
@@ -7,6 +7,8 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "common/hashing_llvm.h"
namespace Carbon {
namespace {
+34 -31
View File
@@ -61,13 +61,21 @@ class MapView
: RawHashtable::ViewImpl<InputKeyT, InputValueT, InputKeyContextT> {
using ImplT =
RawHashtable::ViewImpl<InputKeyT, InputValueT, InputKeyContextT>;
using EntryT = typename ImplT::EntryT;
using EntryT = ImplT::EntryT;
public:
using KeyT = typename ImplT::KeyT;
using ValueT = typename ImplT::ValueT;
using KeyContextT = typename ImplT::KeyContextT;
using MetricsT = typename ImplT::MetricsT;
using KeyT = ImplT::KeyT;
using ValueT = ImplT::ValueT;
using KeyContextT = ImplT::KeyContextT;
using MetricsT = ImplT::MetricsT;
// A key and its value, as a pair of references. This is what iterating the
// map produces; there is no object in the table combining the two.
using Entry = ImplT::EntryRefT;
// A range over the key-value entries of the map. Bound to the lifetime of
// the viewed map, and invalidated by mutating it.
using Range = ImplT::EntryRange;
// This type represents the result of lookup operations. It encodes whether
// the lookup was a success as well as accessors for the key and value.
@@ -111,10 +119,8 @@ class MapView
auto operator[](LookupKeyT lookup_key) const -> ValueT*
requires(std::default_initializable<KeyContextT>);
// Run the provided callback for every key and value in the map.
template <typename CallbackT>
auto ForEach(CallbackT callback) -> void
requires(std::invocable<CallbackT, KeyT&, ValueT&>);
// Returns a range for iterating over all key-value entries in the map.
auto entries() const -> Range;
// This routine is relatively inefficient and only intended for use in
// benchmarking or logging of performance anomalies. The specific metrics
@@ -160,15 +166,17 @@ class MapBase : protected RawHashtable::BaseImpl<InputKeyT, InputValueT,
protected:
using ImplT =
RawHashtable::BaseImpl<InputKeyT, InputValueT, InputKeyContextT>;
using EntryT = typename ImplT::EntryT;
using EntryT = ImplT::EntryT;
public:
using KeyT = typename ImplT::KeyT;
using ValueT = typename ImplT::ValueT;
using KeyContextT = typename ImplT::KeyContextT;
using KeyT = ImplT::KeyT;
using ValueT = ImplT::ValueT;
using KeyContextT = ImplT::KeyContextT;
using ViewT = MapView<KeyT, ValueT, KeyContextT>;
using LookupKVResult = typename ViewT::LookupKVResult;
using MetricsT = typename ImplT::MetricsT;
using LookupKVResult = ViewT::LookupKVResult;
using MetricsT = ImplT::MetricsT;
using Entry = ViewT::Entry;
using Range = ViewT::Range;
// The result type for insertion operations both indicates whether an insert
// was needed (as opposed to finding an existing element), and provides access
@@ -228,12 +236,12 @@ class MapBase : protected RawHashtable::BaseImpl<InputKeyT, InputValueT,
}
// Convenience forwarder to the view type.
template <typename CallbackT>
auto ForEach(CallbackT callback) const -> void
requires(std::invocable<CallbackT, KeyT&, ValueT&>)
{
return ViewT(*this).ForEach(callback);
}
auto entries() const& -> Range { return ViewT(*this).entries(); }
// Deleted on rvalues: the range refers to storage owned by this table, so a
// range built from a temporary map would dangle. Both qualifiers are needed
// as `&&` alone would leave a const rvalue binding to the `const&` overload.
auto entries() && = delete;
auto entries() const&& = delete;
// Convenience forwarder to the view type.
auto ComputeMetrics(KeyContextT key_context = KeyContextT()) const
@@ -385,8 +393,8 @@ class Map : public RawHashtable::TableImpl<
using ImplT = RawHashtable::TableImpl<BaseT, SmallSize>;
public:
using KeyT = typename BaseT::KeyT;
using ValueT = typename BaseT::ValueT;
using KeyT = BaseT::KeyT;
using ValueT = BaseT::ValueT;
Map() = default;
Map(const Map& arg) = default;
@@ -424,14 +432,9 @@ auto MapView<InputKeyT, InputValueT, InputKeyContextT>::operator[](
}
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
template <typename CallbackT>
auto MapView<InputKeyT, InputValueT, InputKeyContextT>::ForEach(
CallbackT callback) -> void
requires(std::invocable<CallbackT, KeyT&, ValueT&>)
{
this->ForEachEntry(
[callback](EntryT& entry) { callback(entry.key(), entry.value()); },
[](auto...) {});
auto MapView<InputKeyT, InputValueT, InputKeyContextT>::entries() const
-> Range {
return this->ImplT::EntriesImpl();
}
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
+72 -14
View File
@@ -66,8 +66,8 @@ static constexpr bool IsCarbonMap =
template <typename InMapT>
struct MapWrapperImpl {
using MapT = InMapT;
using KeyT = typename MapT::key_type;
using ValueT = typename MapT::mapped_type;
using KeyT = MapT::key_type;
using ValueT = MapT::mapped_type;
MapT m;
@@ -93,6 +93,17 @@ struct MapWrapperImpl {
}
auto BenchErase(KeyT k) -> bool { return m.erase(k) != 0; }
// Visits every entry in the map, calling `cb` with the key and value of each
// one. Each map type is expected to traverse using whatever API it provides
// for this, so that the benchmark measures iterating the map rather than any
// specific iteration API.
template <typename CallbackT>
auto BenchIterate(CallbackT cb) -> void {
for (const auto& entry : m) {
cb(entry.first, entry.second);
}
}
};
// Explicit (partial) specialization for the Carbon map type that uses its
@@ -126,6 +137,13 @@ struct MapWrapperImpl<Map<KT, VT, MinSmallSize>> {
}
auto BenchErase(KeyT k) -> bool { return m.Erase(k); }
template <typename CallbackT>
auto BenchIterate(CallbackT cb) -> void {
for (auto [k, v] : m.entries()) {
cb(k, v);
}
}
};
// Provide a way to override the Carbon Map specific benchmark runs with another
@@ -218,8 +236,8 @@ auto ReportMetrics(const MapWrapper<MapT>& m_wrapper, benchmark::State& state)
template <typename MapT>
static void BM_MapContainsHit(benchmark::State& state) {
using MapWrapperT = MapWrapper<MapT>;
using KT = typename MapWrapperT::KeyT;
using VT = typename MapWrapperT::ValueT;
using KT = MapWrapperT::KeyT;
using VT = MapWrapperT::ValueT;
MapWrapperT m;
auto [keys, lookup_keys] =
GetKeysAndHitKeys<KT>(state.range(0), state.range(1));
@@ -254,8 +272,8 @@ MAP_BENCHMARK_ONE_OP(BM_MapContainsHit, HitArgs);
template <typename MapT>
static void BM_MapContainsMiss(benchmark::State& state) {
using MapWrapperT = MapWrapper<MapT>;
using KT = typename MapWrapperT::KeyT;
using VT = typename MapWrapperT::ValueT;
using KT = MapWrapperT::KeyT;
using VT = MapWrapperT::ValueT;
MapWrapperT m;
auto [keys, lookup_keys] = GetKeysAndMissKeys<KT>(state.range(0));
for (auto k : keys) {
@@ -307,8 +325,8 @@ MAP_BENCHMARK_ONE_OP(BM_MapContainsMiss, SizeArgs);
template <typename MapT>
static void BM_MapLookupHit(benchmark::State& state) {
using MapWrapperT = MapWrapper<MapT>;
using KT = typename MapWrapperT::KeyT;
using VT = typename MapWrapperT::ValueT;
using KT = MapWrapperT::KeyT;
using VT = MapWrapperT::ValueT;
MapWrapperT m;
auto [keys, lookup_keys] =
GetKeysAndHitKeys<KT>(state.range(0), state.range(1));
@@ -363,8 +381,8 @@ MAP_BENCHMARK_ONE_OP_SIZE(BM_MapLookupHit, HitArgs, LowZeroBitInt<32>, int);
template <typename MapT>
static void BM_MapUpdateHit(benchmark::State& state) {
using MapWrapperT = MapWrapper<MapT>;
using KT = typename MapWrapperT::KeyT;
using VT = typename MapWrapperT::ValueT;
using KT = MapWrapperT::KeyT;
using VT = MapWrapperT::ValueT;
MapWrapperT m;
auto [keys, lookup_keys] =
GetKeysAndHitKeys<KT>(state.range(0), state.range(1));
@@ -405,8 +423,8 @@ MAP_BENCHMARK_ONE_OP(BM_MapUpdateHit, HitArgs);
template <typename MapT>
static void BM_MapEraseUpdateHit(benchmark::State& state) {
using MapWrapperT = MapWrapper<MapT>;
using KT = typename MapWrapperT::KeyT;
using VT = typename MapWrapperT::ValueT;
using KT = MapWrapperT::KeyT;
using VT = MapWrapperT::ValueT;
MapWrapperT m;
auto [keys, lookup_keys] =
GetKeysAndHitKeys<KT>(state.range(0), state.range(1));
@@ -463,8 +481,8 @@ MAP_BENCHMARK_ONE_OP(BM_MapEraseUpdateHit, HitArgs);
template <typename MapT>
static void BM_MapInsertSeq(benchmark::State& state) {
using MapWrapperT = MapWrapper<MapT>;
using KT = typename MapWrapperT::KeyT;
using VT = typename MapWrapperT::ValueT;
using KT = MapWrapperT::KeyT;
using VT = MapWrapperT::ValueT;
constexpr ssize_t LookupKeysSize = 1 << 8;
auto [keys, lookup_keys] =
GetKeysAndHitKeys<KT>(state.range(0), LookupKeysSize);
@@ -516,5 +534,45 @@ static void BM_MapInsertSeq(benchmark::State& state) {
}
MAP_BENCHMARK_ONE_OP(BM_MapInsertSeq, SizeArgs);
// Benchmark visiting every entry in a map.
//
// Unlike the lookup benchmarks, this walks the table's storage from end to end
// rather than probing it, so it is largely a measure of how densely entries are
// packed and how cheaply empty slots can be skipped. There is no dependency
// between the entries visited, and so this is a throughput measurement.
//
// Each batch is a single complete traversal of the map, with the batch size set
// to the number of entries so that the reported time is the per-entry cost.
template <typename MapT>
static void BM_MapIterate(benchmark::State& state) {
using MapWrapperT = MapWrapper<MapT>;
using KT = typename MapWrapperT::KeyT;
using VT = typename MapWrapperT::ValueT;
MapWrapperT m;
auto [keys, _] = GetKeysAndMissKeys<KT>(state.range(0));
for (auto k : keys) {
bool inserted = m.BenchInsert(k, MakeValue<VT>());
CARBON_DCHECK(inserted, "Must be a successful insert!");
}
while (state.KeepRunningBatch(keys.size())) {
ssize_t sum = 0;
m.BenchIterate([&sum](const KT& k, const VT& v) {
// Consume both the key and the value so that neither the traversal nor
// the loads out of the entries can be optimized away.
sum += ValueToBool(k) + ValueToBool(v);
});
benchmark::DoNotOptimize(sum);
}
// The time is already per-entry, so an iteration-invariant rate of one gives
// the throughput of entries visited.
state.counters["KeyRate"] =
benchmark::Counter(1, benchmark::Counter::kIsIterationInvariantRate);
ReportMetrics(m, state);
}
MAP_BENCHMARK_ONE_OP(BM_MapIterate, SizeArgs);
} // namespace
} // namespace Carbon
+116 -5
View File
@@ -7,7 +7,10 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <concepts>
#include <initializer_list>
#include <iterator>
#include <ranges>
#include <type_traits>
#include <utility>
#include <vector>
@@ -37,19 +40,20 @@ using RawHashtable::MoveOnlyTestData;
using RawHashtable::TestData;
using RawHashtable::TestKeyContext;
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
template <typename MapT, typename MatcherRangeT>
auto ExpectMapElementsAre(MapT&& m, MatcherRangeT element_matchers) -> void {
// Now collect the elements into a container.
using KeyT = typename std::remove_reference<MapT>::type::KeyT;
using ValueT = typename std::remove_reference<MapT>::type::ValueT;
using KeyT = std::remove_reference<MapT>::type::KeyT;
using ValueT = std::remove_reference<MapT>::type::ValueT;
std::vector<
std::pair<std::reference_wrapper<KeyT>, std::reference_wrapper<ValueT>>>
map_entries;
m.ForEach([&map_entries](KeyT& k, ValueT& v) {
for (auto [k, v] : m.entries()) {
map_entries.push_back({std::ref(k), std::ref(v)});
});
}
// Use the GoogleMock unordered container matcher to validate and show errors
// on wrong elements.
@@ -68,7 +72,7 @@ auto ExpectMapElementsAre(MapT&& m,
template <typename ValueCB, typename RangeT, typename... RangeTs>
auto MakeKeyValues(ValueCB value_cb, RangeT&& range, RangeTs&&... ranges)
-> auto {
using KeyT = typename RangeT::value_type;
using KeyT = RangeT::value_type;
using ValueT = decltype(value_cb(std::declval<KeyT>()));
std::vector<std::pair<KeyT, ValueT>> elements;
auto add_range = [&](RangeT&& r) {
@@ -865,5 +869,112 @@ TEST(MapContextTest, Basic) {
m, MakeKeyValues([](int k) { return k * 100 + 1; }, llvm::seq(1, 512)));
}
TYPED_TEST(MapTest, Range) {
using MapT = TypeParam;
using Range = decltype(std::declval<const MapT&>().entries());
using Iter = typename Range::Iterator;
static_assert(std::forward_iterator<Iter>);
static_assert(std::same_as<decltype(std::declval<Range>().begin()), Iter>);
static_assert(std::same_as<decltype(std::declval<Range>().end()), Iter>);
static_assert(std::ranges::forward_range<Range>);
static_assert(std::ranges::common_range<Range>);
MapT m;
EXPECT_EQ(m.entries().begin(), m.entries().end());
for (auto [k, v] : m.entries()) {
static_cast<void>(k);
static_cast<void>(v);
FAIL() << "Empty map range should have no elements";
}
for (int i = 1; i <= 5; ++i) {
m.Insert(i, i * 10);
}
int count = 0;
for (const auto& [k, v] : m.entries()) {
EXPECT_EQ(v, m.Lookup(k).value());
++count;
}
EXPECT_EQ(count, 5);
EXPECT_THAT(m.entries(),
UnorderedElementsAre(Pair(1, 10), Pair(2, 20), Pair(3, 30),
Pair(4, 40), Pair(5, 50)));
using KeyT = typename MapT::KeyT;
using ValueT = typename MapT::ValueT;
using KeyContextT = typename MapT::KeyContextT;
MapView<const KeyT, const ValueT, KeyContextT> cv = m;
int cv_count = 0;
for (auto [k, v] : cv.entries()) {
static_assert(std::is_const_v<std::remove_reference_t<decltype(k)>>);
static_assert(std::is_const_v<std::remove_reference_t<decltype(v)>>);
EXPECT_EQ(v, m.Lookup(k).value());
++cv_count;
}
EXPECT_EQ(cv_count, 5);
EXPECT_THAT(cv.entries(),
UnorderedElementsAre(Pair(1, 10), Pair(2, 20), Pair(3, 30),
Pair(4, 40), Pair(5, 50)));
for (auto [k, v] : m.entries()) {
if constexpr (requires { v.value; }) {
v.value = 99;
} else {
v = 99;
}
}
for (const auto& [k, v] : m.entries()) {
if constexpr (requires { v.value; }) {
EXPECT_EQ(v.value, 99);
} else {
EXPECT_EQ(v, 99);
}
}
EXPECT_THAT(m.entries(),
UnorderedElementsAre(Pair(1, 99), Pair(2, 99), Pair(3, 99),
Pair(4, 99), Pair(5, 99)));
auto r = m.entries();
int iter_count = 0;
for (auto it = r.begin(); it != r.end(); ++it) {
EXPECT_EQ(it->second, m.Lookup(it->first).value());
EXPECT_EQ((*it).second, m.Lookup((*it).first).value());
++iter_count;
}
EXPECT_EQ(iter_count, 5);
auto it = r.begin();
auto prev = it++;
EXPECT_NE(it, prev);
}
TYPED_TEST(MoveOnlyMapTest, Range) {
TypeParam m;
m.Insert(1, 10);
m.Insert(2, 20);
int count = 0;
for (auto [k, v] : m.entries()) {
EXPECT_EQ(v.value, k.value * 10);
++count;
}
EXPECT_EQ(count, 2);
}
#ifndef NDEBUG
TEST(MapDeathTest, MutateDuringIterationFails) {
EXPECT_DEATH(([] {
Map<int, int> m;
m.Insert(1, 10);
auto range = m.entries();
m.Insert(2, 20);
}()),
"Hashtable mutated during iteration");
}
#endif
} // namespace
} // namespace Carbon::Testing
+22 -1
View File
@@ -7,6 +7,7 @@
// Libraries should include this header instead of raw_ostream.
#include <compare>
#include <concepts>
#include <ostream>
#include <type_traits>
@@ -17,11 +18,31 @@
namespace Carbon {
// CRTP base class for printable types. Children (DerivedT) must implement:
// CRTP base class for printable types. Derived classes (DerivedT) must
// implement:
// - auto Print(llvm::raw_ostream& out) const -> void
template <typename DerivedT>
// NOLINTNEXTLINE(bugprone-crtp-constructor-accessibility)
class Printable {
// Comparisons of the base class itself, which is empty and so always compares
// equal, allowing derived classes to default their own comparison operators.
//
// These are templated so that they are only used when the types of the
// arguments are exactly `Printable`, rather than a derived class, and are
// hidden friends so that they aren't candidates for unrelated comparisons.
template <typename T>
requires std::same_as<T, Printable>
friend constexpr auto operator==(const T& /*lhs*/, const T& /*rhs*/) noexcept
-> bool {
return true;
}
template <typename T>
requires std::same_as<T, Printable>
friend constexpr auto operator<=>(const T& /*lhs*/, const T& /*rhs*/) noexcept
-> std::strong_ordering {
return std::strong_ordering::equal;
}
// Supports printing to llvm::raw_ostream.
friend auto operator<<(llvm::raw_ostream& out, const DerivedT& obj)
-> llvm::raw_ostream& {
+253
View File
@@ -0,0 +1,253 @@
// 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
#include "common/ostream.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <compare>
#include <concepts>
#include <limits>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include "common/raw_string_ostream.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
namespace Carbon::Testing {
namespace {
using ::testing::ElementsAre;
// Whether two types can be compared with both `==` and `<=>`.
template <typename LhsT, typename RhsT>
concept Comparable = requires(const LhsT& lhs, const RhsT& rhs) {
lhs == rhs;
lhs <=> rhs;
};
// A child that defaults its comparisons with member declarations.
struct Point : Printable<Point> {
int x;
int y;
constexpr Point(int x, int y) : x(x), y(y) {}
auto Print(llvm::raw_ostream& out) const -> void {
out << "(" << x << ", " << y << ")";
}
auto operator<=>(const Point& rhs) const = default;
};
// A child that defaults its comparisons with friend declarations, and whose
// comparisons are neither trivial nor `noexcept`.
struct Label : Printable<Label> {
std::string text;
explicit Label(std::string text) : text(std::move(text)) {}
auto Print(llvm::raw_ostream& out) const -> void { out << text; }
friend auto operator<=>(const Label& lhs, const Label& rhs) = default;
};
// A child that defaults equality without providing any ordering.
struct Id : Printable<Id> {
int value;
constexpr explicit Id(int value) : value(value) {}
auto Print(llvm::raw_ostream& out) const -> void { out << "#" << value; }
auto operator==(const Id& rhs) const -> bool = default;
};
// A child whose defaulted comparison is only a partial ordering.
struct Measure : Printable<Measure> {
double value;
constexpr explicit Measure(double value) : value(value) {}
auto Print(llvm::raw_ostream& out) const -> void { out << value; }
auto operator<=>(const Measure& rhs) const = default;
};
// A child that requests a weaker ordering than its members provide.
struct Version : Printable<Version> {
int major;
int minor;
constexpr Version(int major, int minor) : major(major), minor(minor) {}
auto Print(llvm::raw_ostream& out) const -> void {
out << major << "." << minor;
}
auto operator<=>(const Version& rhs) const -> std::weak_ordering = default;
};
// A child that doesn't want to be compared at all.
struct Opaque : Printable<Opaque> {
int value;
constexpr explicit Opaque(int value) : value(value) {}
auto Print(llvm::raw_ostream& out) const -> void { out << value; }
};
// A child that compares through an implicit conversion rather than through
// operators of its own, the way `EnumBase` children do.
class Level : public Printable<Level> {
public:
enum RawLevel { Low, High };
constexpr explicit Level(RawLevel value) : value_(value) {}
// NOLINTNEXTLINE(google-explicit-constructor)
explicit(false) constexpr operator RawLevel() const { return value_; }
auto Print(llvm::raw_ostream& out) const -> void {
out << (value_ == Low ? "low" : "high");
}
private:
RawLevel value_;
};
TEST(PrintableTest, Printing) {
RawStringOstream raw_out;
raw_out << Point(1, 2) << " " << Label("label");
EXPECT_EQ(raw_out.TakeStr(), "(1, 2) label");
std::ostringstream standard_out;
standard_out << Point(1, 2) << " " << Label("label");
EXPECT_EQ(standard_out.str(), "(1, 2) label");
EXPECT_EQ(PrintToString(Point(1, 2)), "(1, 2)");
}
TEST(PrintableTest, DefaultedEquality) {
EXPECT_EQ(Point(1, 2), Point(1, 2));
EXPECT_NE(Point(1, 2), Point(1, 3));
EXPECT_NE(Point(1, 2), Point(2, 2));
static_assert(Point(1, 2) == Point(1, 2));
static_assert(Point(1, 2) != Point(1, 3));
// The base class comparisons don't make defaulted comparisons throwing.
Point point(1, 2);
static_assert(noexcept(point == point));
}
TEST(PrintableTest, DefaultedOrdering) {
// Ordering is lexicographic in declaration order, with the empty base class
// contributing nothing.
EXPECT_LT(Point(1, 2), Point(1, 3));
EXPECT_LT(Point(1, 9), Point(2, 0));
EXPECT_LE(Point(1, 2), Point(1, 2));
EXPECT_GT(Point(2, 0), Point(1, 9));
EXPECT_GE(Point(1, 2), Point(1, 2));
EXPECT_EQ(Point(1, 2) <=> Point(1, 3), std::strong_ordering::less);
EXPECT_EQ(Point(1, 2) <=> Point(1, 2), std::strong_ordering::equal);
EXPECT_EQ(Point(1, 3) <=> Point(1, 2), std::strong_ordering::greater);
static_assert(std::totally_ordered<Point>);
static_assert(std::same_as<std::compare_three_way_result_t<Point>,
std::strong_ordering>);
static_assert(Point(1, 2) < Point(1, 3));
Point point(1, 2);
static_assert(noexcept(point <=> point));
}
TEST(PrintableTest, DefaultedFriendComparison) {
EXPECT_EQ(Label("a"), Label("a"));
EXPECT_NE(Label("a"), Label("b"));
EXPECT_LT(Label("a"), Label("b"));
EXPECT_EQ(Label("a") <=> Label("b"), std::strong_ordering::less);
static_assert(std::totally_ordered<Label>);
}
TEST(PrintableTest, DefaultedEqualityWithoutOrdering) {
EXPECT_EQ(Id(1), Id(1));
EXPECT_NE(Id(1), Id(2));
static_assert(std::equality_comparable<Id>);
static_assert(!std::totally_ordered<Id>);
static_assert(!std::three_way_comparable<Id>);
}
TEST(PrintableTest, DefaultedComparisonCategories) {
static_assert(std::same_as<std::compare_three_way_result_t<Measure>,
std::partial_ordering>);
static_assert(std::same_as<std::compare_three_way_result_t<Version>,
std::weak_ordering>);
EXPECT_LT(Measure(1.0), Measure(2.0));
EXPECT_EQ(Measure(1.0) <=> Measure(2.0), std::partial_ordering::less);
// The base class comparing equal must not make unordered values ordered.
Measure nan(std::numeric_limits<double>::quiet_NaN());
EXPECT_EQ(nan <=> Measure(1.0), std::partial_ordering::unordered);
EXPECT_NE(nan, nan);
EXPECT_LT(Version(1, 0), Version(1, 1));
EXPECT_EQ(Version(1, 0) <=> Version(1, 1), std::weak_ordering::less);
}
TEST(PrintableTest, NoComparisonWithoutDefaulting) {
// Inheriting from `Printable` must not by itself make a type comparable, and
// in particular must not make distinct values compare equal.
static_assert(!std::equality_comparable<Opaque>);
static_assert(!std::totally_ordered<Opaque>);
static_assert(!std::three_way_comparable<Opaque>);
EXPECT_EQ(PrintToString(Opaque(1)), "1");
}
TEST(PrintableTest, NoComparisonBetweenBaseAndChild) {
// The base class comparisons are viable only between two base class
// subobjects, and so don't apply to comparing a child with one, whether or
// not the child provides comparisons of its own.
static_assert(Comparable<Printable<Opaque>, Printable<Opaque>>);
static_assert(!Comparable<Printable<Opaque>, Opaque>);
static_assert(!Comparable<Opaque, Printable<Opaque>>);
static_assert(!Comparable<Printable<Label>, Label>);
static_assert(!Comparable<Label, Printable<Label>>);
}
TEST(PrintableTest, ComparisonThroughConversion) {
// The base class comparisons must not displace comparisons that a child
// provides through a conversion.
EXPECT_TRUE(Level(Level::Low) == Level(Level::Low));
EXPECT_FALSE(Level(Level::Low) == Level(Level::High));
EXPECT_TRUE(Level(Level::Low) < Level(Level::High));
EXPECT_TRUE(Level(Level::High) == Level::High);
}
TEST(PrintableTest, ComparisonsUsableGenerically) {
llvm::SmallVector<Point> points = {Point(2, 1), Point(1, 2), Point(1, 1)};
llvm::sort(points);
EXPECT_THAT(points, ElementsAre(Point(1, 1), Point(1, 2), Point(2, 1)));
EXPECT_EQ(llvm::find(points, Point(1, 2)), points.begin() + 1);
}
TEST(PrintableTest, EmptyBaseClass) {
// The comparison support must not add any state to children.
static_assert(sizeof(Point) == 2 * sizeof(int));
static_assert(sizeof(Id) == sizeof(int));
static_assert(std::is_empty_v<Printable<Point>>);
}
} // namespace
} // namespace Carbon::Testing
+5
View File
@@ -10,4 +10,9 @@ namespace Carbon::RawHashtable {
volatile std::byte global_addr_seed{1};
#ifndef NDEBUG
std::atomic<HashCode> entropy_hash =
Carbon::HashValue(reinterpret_cast<uint64_t>(&global_addr_seed));
#endif
} // namespace Carbon::RawHashtable
+425 -81
View File
@@ -6,6 +6,7 @@
#define CARBON_COMMON_RAW_HASHTABLE_H_
#include <algorithm>
#include <atomic>
#include <concepts>
#include <cstddef>
#include <cstring>
@@ -18,6 +19,7 @@
#include "common/concepts.h"
#include "common/hashing.h"
#include "common/raw_hashtable_metadata_group.h"
#include "llvm/ADT/iterator.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/MathExtras.h"
@@ -122,10 +124,15 @@
// null. Since it doesn't track the exact number of filled entries in a table,
// it doesn't support a container-style `size` API.
//
// - There is no direct iterator support because of the complexity of embedding
// the group-based metadata scanning into an iterator model. Instead, there is
// just a for-each method that is passed a lambda to observe all entries. The
// order of this observation is also not guaranteed.
// - Iteration is provided by a range object rather than by iterators hanging
// directly off the table, because the debug-only checks for mutation during
// iteration need state that outlives a single iterator: see `EntryRange`
// below. Obtaining one is an explicit call (`entries()`), as scanning an
// entire table is a costly operation that shouldn't be hidden behind a bare
// `begin()`/`end()` pair.
//
// The order of iteration is not guaranteed, and debug builds actively vary it
// between ranges to keep callers from depending on it.
namespace Carbon::RawHashtable {
// Which prefetch strategies to enable can be controlled via macros to enable
@@ -152,7 +159,7 @@ inline constexpr ssize_t MinAllocatedSize = std::max<ssize_t>(64, MaxGroupSize);
// An entry in the hashtable storage of a `KeyT` and `ValueT` object.
//
// Allows manual construction, destruction, and access to these values so we can
// create arrays af the entries prior to populating them with actual keys and
// create arrays of the entries prior to populating them with actual keys and
// values.
template <typename KeyT, typename ValueT>
struct StorageEntry {
@@ -168,6 +175,20 @@ struct StorageEntry {
IsTriviallyRelocatable || (std::is_copy_constructible_v<KeyT> &&
std::is_copy_constructible_v<ValueT>);
// How iteration refers to an entry, and the iterator traits that follow.
//
// The key and value are stored side by side with nothing combining them, so
// a reference to an entry is a pair of references built on demand. That pair
// is a *proxy* reference: C++20 forward iterators permit one, but C++17
// algorithms may assume a forward iterator's reference is a real lvalue, so
// the C++17 category is `input`.
using RefT = std::pair<KeyT&, ValueT&>;
using IterValueT = RefT;
using IterPointerT = const RefT*;
using IterCategoryT = std::input_iterator_tag;
auto ref() -> RefT { return RefT(key(), value()); }
auto key() const -> const KeyT& {
// Ensure we don't need more alignment than available. Inside a method body
// to apply to the complete type.
@@ -194,11 +215,21 @@ struct StorageEntry {
// construction. As a consequence, this struct only provides the storage and
// we have to manually manage the construction, move, and destruction of the
// objects.
//
// Destroys the key and value behind an entry reference. Iteration hands back
// `RefT` rather than the entry, so this is how a walked entry is destroyed.
static auto DestroyRef(RefT ref) -> void {
ref.first.~KeyT();
ref.second.~ValueT();
}
// Destroys the key and value of this entry. The common case is destroying an
// entry found in the table's storage, where there is no reference to hand to
// `DestroyRef`.
auto Destroy() -> void {
static_assert(!IsTriviallyDestructible,
"Should never instantiate when trivial!");
key().~KeyT();
value().~ValueT();
DestroyRef(ref());
}
auto CopyFrom(const StorageEntry& entry) -> void {
@@ -241,6 +272,15 @@ struct StorageEntry<KeyT, void> {
static constexpr bool IsCopyable =
IsTriviallyRelocatable || std::is_copy_constructible_v<KeyT>;
// As above, but a set's entry is nothing but its key, so a reference to an
// entry is a true lvalue reference and the iterator is a plain forward one.
using RefT = KeyT&;
using IterValueT = std::remove_cv_t<KeyT>;
using IterPointerT = KeyT*;
using IterCategoryT = std::forward_iterator_tag;
auto ref() -> RefT { return key(); }
auto key() const -> const KeyT& {
// Ensure we don't need more alignment than available.
static_assert(
@@ -254,10 +294,12 @@ struct StorageEntry<KeyT, void> {
return const_cast<KeyT&>(const_cast<const StorageEntry*>(this)->key());
}
static auto DestroyRef(RefT ref) -> void { ref.~KeyT(); }
auto Destroy() -> void {
static_assert(!IsTriviallyDestructible,
"Should never instantiate when trivial!");
key().~KeyT();
DestroyRef(ref());
}
auto CopyFrom(const StorageEntry& entry) -> void
@@ -360,6 +402,13 @@ class ViewImpl {
using EntryT = StorageEntry<KeyT, ValueT>;
using MetricsT = Metrics;
// What iterating over the table's entries produces: a `KeyT&` for a set, and
// a `std::pair<KeyT&, ValueT&>` for a map. See `StorageEntry`.
using EntryRefT = EntryT::RefT;
// The range type produced by `EntriesImpl`.
class EntryRange;
friend class BaseImpl<KeyT, ValueT, KeyContextT>;
template <typename InputBaseT, ssize_t SmallSize>
friend class TableImpl;
@@ -385,13 +434,11 @@ class ViewImpl {
auto LookupEntry(LookupKeyT lookup_key, KeyContextT key_context) const
-> EntryT*;
// Calls `entry_callback` for each entry in the hashtable. All the entries
// within a specific group are visited first, and then `group_callback` is
// called on the group itself. The `group_callback` is typically only used by
// the internals of the hashtable.
template <typename EntryCallbackT, typename GroupCallbackT>
auto ForEachEntry(EntryCallbackT entry_callback,
GroupCallbackT group_callback) const -> void;
// Returns a range for iterating over all entries in the hashtable.
//
// The returned range copies this view, so it remains valid for as long as the
// underlying table does, independent of this view's lifetime.
auto EntriesImpl() const -> EntryRange;
// Returns a collection of informative metrics on the the current state of the
// table, useful for performance analysis. These include relatively slow to
@@ -425,7 +472,7 @@ class ViewImpl {
auto metadata() const -> uint8_t* {
return reinterpret_cast<uint8_t*>(storage_);
}
auto entries() const -> EntryT* {
auto entries_data() const -> EntryT* {
return reinterpret_cast<EntryT*>(reinterpret_cast<std::byte*>(storage_) +
EntriesOffset(alloc_size_));
}
@@ -457,6 +504,172 @@ class ViewImpl {
Storage* storage_;
};
// A range over the entries of a hashtable.
//
// A dedicated range object is used rather than a plain pair of iterators (such
// as `llvm::iterator_range`) because the range scopes two debug-only behaviors
// that a bare iterator pair has nowhere to store:
//
// - Mutation checking: the range snapshots a hash of the table's metadata on
// construction and re-checks it on destruction, catching tables that were
// mutated while iteration was active.
// - Traversal order: the group at which iteration starts, and the stride it
// walks the groups with, are drawn from an entropy pool once when the range
// is constructed.
// Deriving them here rather than in `begin()` keeps `begin()` a pure function
// of the range so that it can be called repeatedly, as forward ranges
// require, while still varying the order between separately created ranges.
//
// The range holds the view *by value*; views are two words and designed to be
// cheap to copy. It deliberately does not point back at the view it was created
// from, as views are routinely temporaries or by-value parameters whose
// lifetime is shorter than the table they refer to.
//
// This type provides only the minimal `begin()` and `end()` interface needed by
// range-based for loops and the range concepts, which also avoids any
// compile-time cost from including `<ranges>`.
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
class ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::EntryRange {
public:
class Iterator;
using value_type = typename EntryT::IterValueT;
using reference = EntryRefT;
using difference_type = ssize_t;
explicit EntryRange(ViewImpl view);
// Copyable: every member is a scalar snapshot of the table. Copying a range
// in a debug build simply validates the same table state more than once.
EntryRange(const EntryRange&) = default;
auto operator=(const EntryRange&) -> EntryRange& = default;
#ifndef NDEBUG
// Only debug builds declare a destructor, and so only they re-check the
// table on the way out. Release builds leave the range trivially
// destructible, and so trivial for the purposes of calls, letting it be
// passed and returned in registers.
~EntryRange() { CheckInvariants(); }
#endif
auto begin() const -> Iterator;
auto end() const -> Iterator;
private:
// The facade `Iterator` derives from. A class can't name one of its own
// aliases in its base-specifier, so naming it here lets `Iterator` spell it
// once instead of repeating it to get at the members it inherits.
using IteratorBase =
llvm::iterator_facade_base<Iterator, typename EntryT::IterCategoryT,
value_type, difference_type,
typename EntryT::IterPointerT, reference>;
#ifndef NDEBUG
// Checks that the table's metadata has not changed since construction.
auto CheckInvariants() const -> void;
#endif
ViewImpl view_;
#ifndef NDEBUG
HashCode initial_metadata_hash_ = {};
ssize_t start_group_ = 0;
ssize_t step_ = GroupSize;
#endif
};
// Two-level forward iterator through present hashtable entries.
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
class ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::EntryRange::Iterator
: public EntryRange::IteratorBase {
public:
// Both the set and map forms satisfy C++20's `std::forward_iterator`. A
// map's `reference` is a proxy, which pins its C++17 `iterator_category` to
// `input`, but the C++20 concept is unaffected. See `EntryRefT`.
using iterator_concept = std::forward_iterator_tag;
Iterator() = default;
using EntryRange::IteratorBase::operator++;
[[clang::always_inline]] auto operator*() const -> EntryRefT {
CARBON_DCHECK(present_bits_ != 0, "Dereferencing end iterator!");
__builtin_assume(present_bits_ != 0);
// `index_ptr` folds scaling the match index by the entry size together
// with decoding the index itself, which saves a shift on the portable
// byte-encoded code path.
return MatchIndex(present_bits_).index_ptr(group_entries())->ref();
}
[[clang::always_inline]] auto operator++() -> Iterator& {
CARBON_DCHECK(present_bits_ != 0, "Incrementing end iterator!");
__builtin_assume(present_bits_ != 0);
present_bits_ &= (present_bits_ - 1);
if (LLVM_LIKELY(present_bits_ != 0)) {
return *this;
}
AdvanceToNextPresentGroup();
return *this;
}
friend auto operator==(const Iterator& lhs, const Iterator& rhs) -> bool {
if (lhs.present_bits_ == 0 || rhs.present_bits_ == 0) {
return lhs.present_bits_ == rhs.present_bits_;
}
// The entry pointer already encodes the base and the group offset, so it
// uniquely identifies the group without a separate index.
return lhs.group_entries() == rhs.group_entries() &&
lhs.present_bits_ == rhs.present_bits_;
}
private:
friend class EntryRange;
using MatchBitsT = typename MetadataGroup::MatchPresentRange::BitsT;
using MatchIndex = typename MetadataGroup::MatchIndex;
// Builds an iterator to the first present entry of `range`, or an iterator
// equal to `end()` when the range has no entries to walk. The parameters of
// the walk differ between builds, so both are drawn from the range here
// rather than passed in.
[[clang::always_inline]] explicit Iterator(const EntryRange& range);
[[clang::always_inline]] auto AdvanceToNextPresentGroup() -> void;
// The entries of the group the iterator is currently within. Both builds
// track the current group, but they encode it differently, so the encoding
// is hidden behind this accessor.
auto group_entries() const -> EntryT* {
#ifndef NDEBUG
return group_entries_;
#else
return entries_end_ + group_offset_;
#endif
}
#ifndef NDEBUG
// Debug builds walk groups in a randomized order and so must retain the
// array bases along with the parameters of the walk. The randomized walk
// revisits no group but also never reaches the end of the array, so it does
// need an explicit count of the groups left to visit.
EntryT* group_entries_ = nullptr;
const uint8_t* metadata_ = nullptr;
EntryT* entries_ = nullptr;
ssize_t groups_remaining_ = 0;
ssize_t group_index_ = 0;
size_t probe_mask_ = 0;
ssize_t step_ = GroupSize;
#else
// Release builds walk the groups in order, tracking the position as a
// *negative* byte offset from the end of each array that counts up to zero.
// Anchoring at the ends rather than the beginnings means the walk needs only
// this one induction variable, and reaching zero is the bound.
EntryT* entries_end_ = nullptr;
const uint8_t* metadata_end_ = nullptr;
ssize_t group_offset_ = 0;
#endif
MatchBitsT present_bits_ = 0;
};
// Implementation helper for defining a read-write base type for a hashtable
// that type-erases any SSO buffer.
//
@@ -474,8 +687,8 @@ class BaseImpl {
using ValueT = InputValueT;
using KeyContextT = InputKeyContextT;
using ViewImplT = ViewImpl<KeyT, ValueT, KeyContextT>;
using EntryT = typename ViewImplT::EntryT;
using MetricsT = typename ViewImplT::MetricsT;
using EntryT = ViewImplT::EntryT;
using MetricsT = ViewImplT::MetricsT;
BaseImpl(int small_alloc_size, Storage* small_storage)
: small_alloc_size_(small_alloc_size) {
@@ -495,7 +708,10 @@ class BaseImpl {
// NOLINTNEXTLINE(google-explicit-constructor): Designed to implicitly decay.
explicit(false) operator ViewImplT() const { return view_impl(); }
auto view_impl() const -> ViewImplT { return view_impl_; }
auto view_impl() const -> const ViewImplT& { return view_impl_; }
// Destroys all non-trivially destructible entries in the table.
auto DestroyEntries() -> void;
// Looks up the provided key in the hashtable. If found, returns a pointer to
// that entry and `false`.
@@ -510,7 +726,7 @@ class BaseImpl {
// Grow the table to specific allocation size.
//
// This will grow the the table if necessary for it to have an allocation size
// This will grow the table if necessary for it to have an allocation size
// of `target_alloc_size` which must be a power of two. Note that this will
// not allow that many keys to be inserted into the hashtable, but a smaller
// number based on the load factor. If a specific number of insertions need to
@@ -561,7 +777,7 @@ class BaseImpl {
auto storage() const -> Storage* { return view_impl_.storage_; }
auto storage() -> Storage*& { return view_impl_.storage_; }
auto metadata() const -> uint8_t* { return view_impl_.metadata(); }
auto entries() const -> EntryT* { return view_impl_.entries(); }
auto entries_data() const -> EntryT* { return view_impl_.entries_data(); }
auto small_alloc_size() const -> ssize_t {
return static_cast<unsigned>(small_alloc_size_);
}
@@ -665,6 +881,25 @@ inline auto ComputeSeed() -> uint64_t {
return reinterpret_cast<uint64_t>(&global_addr_seed);
}
#ifndef NDEBUG
// A pool of entropy used to vary the iteration order of hashtables in debug
// builds. It is seeded from ASLR where available.
extern std::atomic<HashCode> entropy_hash;
// Returns a pseudo-random value from the entropy pool, advancing the pool.
//
// The load and store are separate relaxed operations rather than one atomic
// read-modify-write so that consuming entropy is just a load, and refreshing
// the pool doesn't block the iteration that follows. Racing callers can lose an
// update and draw the same value, which is fine for a debug aid.
inline auto NextRangeEntropy() -> HashCode {
HashCode prev_entropy_hash = entropy_hash.load(std::memory_order_relaxed);
entropy_hash.store(Carbon::HashValue(prev_entropy_hash),
std::memory_order_relaxed);
return prev_entropy_hash;
}
#endif
inline auto ComputeProbeMaskFromSize(ssize_t size) -> size_t {
CARBON_DCHECK(llvm::isPowerOf2_64(size),
"Size must be a power of two for a hashed buffer!");
@@ -748,7 +983,7 @@ auto ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::LookupEntry(
HashCode hash = key_context.HashKey(lookup_key, ComputeSeed());
auto [hash_index, tag] = hash.ExtractIndexAndTag<7>();
EntryT* local_entries = entries();
EntryT* local_entries = entries_data();
// Walk through groups of entries using a quadratic probe starting from
// `hash_index`.
@@ -799,41 +1034,11 @@ auto ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::LookupEntry(
} while (LLVM_UNLIKELY(true));
}
// Note that we force inlining here because we expect to be called with lambdas
// that will in turn be inlined to form the loop body. We don't want function
// boundaries within the loop for performance, and recognizing the degree of
// simplification from inlining these callbacks may be difficult to
// automatically recognize.
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
template <typename EntryCallbackT, typename GroupCallbackT>
[[clang::always_inline]] auto
ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::ForEachEntry(
EntryCallbackT entry_callback, GroupCallbackT group_callback) const
-> void {
uint8_t* local_metadata = metadata();
EntryT* local_entries = entries();
ssize_t local_size = alloc_size_;
for (ssize_t group_index = 0; group_index < local_size;
group_index += GroupSize) {
auto g = MetadataGroup::Load(local_metadata, group_index);
auto present_matched_range = g.MatchPresent();
if (!present_matched_range) {
continue;
}
for (ssize_t byte_index : present_matched_range) {
entry_callback(local_entries[group_index + byte_index]);
}
group_callback(&local_metadata[group_index]);
}
}
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
auto ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::ComputeMetricsImpl(
KeyContextT key_context) const -> Metrics {
uint8_t* local_metadata = metadata();
EntryT* local_entries = entries();
EntryT* local_entries = entries_data();
ssize_t local_size = alloc_size_;
Metrics metrics;
@@ -898,6 +1103,147 @@ auto ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::ComputeMetricsImpl(
return metrics;
}
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
[[clang::always_inline]] auto
ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::EntriesImpl() const
-> EntryRange {
return EntryRange(*this);
}
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
[[clang::always_inline]]
ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::EntryRange::Iterator::
Iterator(const EntryRange& range) {
const ViewImpl& view = range.view_;
ssize_t alloc_size = view.alloc_size_;
// An empty or moved-from table has no groups to load from, and the
// default-initialized state left behind already compares equal to `end()`.
if (alloc_size == 0 || view.storage_ == nullptr) {
return;
}
#ifndef NDEBUG
entries_ = view.entries_data();
metadata_ = view.metadata();
// The starting group and stride were drawn when the range was constructed,
// so every iterator built from it walks the same order.
group_index_ = range.start_group_;
group_entries_ = entries_ + group_index_;
groups_remaining_ = alloc_size / GroupSize - 1;
probe_mask_ = ComputeProbeMaskFromSize(alloc_size);
step_ = range.step_;
auto g = MetadataGroup::Load(metadata_, group_index_);
#else
// The allocation size bounds the metadata array directly, so anchoring at
// the ends of the arrays lets the walk run off a single induction variable
// without ever dividing by the group size.
entries_end_ = view.entries_data() + alloc_size;
metadata_end_ = view.metadata() + alloc_size;
group_offset_ = -alloc_size;
auto g = MetadataGroup::Load(metadata_end_, group_offset_);
#endif
auto present_range = g.MatchPresent();
if (present_range) {
present_bits_ = static_cast<MatchBitsT>(present_range);
} else {
AdvanceToNextPresentGroup();
}
}
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
[[clang::always_inline]] auto
ViewImpl<InputKeyT, InputValueT,
InputKeyContextT>::EntryRange::Iterator::AdvanceToNextPresentGroup()
-> void {
#ifndef NDEBUG
while (--groups_remaining_ >= 0) {
group_index_ = static_cast<ssize_t>(
static_cast<size_t>(group_index_ + step_) & probe_mask_);
auto g = MetadataGroup::Load(metadata_, group_index_);
auto range = g.MatchPresent();
if (range) {
group_entries_ = entries_ + group_index_;
present_bits_ = static_cast<MatchBitsT>(range);
return;
}
}
#else
for (group_offset_ += GroupSize; group_offset_ != 0;
group_offset_ += GroupSize) {
auto g = MetadataGroup::Load(metadata_end_, group_offset_);
auto range = g.MatchPresent();
if (range) {
present_bits_ = static_cast<MatchBitsT>(range);
return;
}
}
#endif
present_bits_ = 0;
}
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::EntryRange::EntryRange(
ViewImpl view)
: view_(view) {
#ifndef NDEBUG
if (view_.alloc_size_ <= 0 || view_.storage_ == nullptr) {
return;
}
initial_metadata_hash_ = Carbon::HashValue(
llvm::ArrayRef<uint8_t>(view_.metadata(), view_.alloc_size_));
// Draw the traversal order once, here, so that `begin()` remains a pure
// function of the range and can be called repeatedly. Two separately
// constructed ranges still walk the table in different orders.
start_group_ = NextRangeEntropy().ExtractIndex() &
ComputeProbeMaskFromSize(view_.alloc_size_);
// Walk the groups with a stride of an odd number of groups. The group count
// is always a power of two, so any odd stride is coprime with it and visits
// every group exactly once before repeating. That scrambles the group order
// far more thoroughly than a forward or reverse scan, and costs nothing in
// the loop itself as the increment already adds a stride and masks.
ssize_t num_groups = view_.alloc_size_ / GroupSize;
ssize_t stride_groups =
(NextRangeEntropy().ExtractIndex() & (num_groups - 1)) | 1;
step_ = stride_groups * GroupSize;
#endif
}
#ifndef NDEBUG
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
auto ViewImpl<InputKeyT, InputValueT,
InputKeyContextT>::EntryRange::CheckInvariants() const -> void {
if (view_.alloc_size_ <= 0 || view_.storage_ == nullptr) {
return;
}
HashCode current_hash = Carbon::HashValue(
llvm::ArrayRef<uint8_t>(view_.metadata(), view_.alloc_size_));
CARBON_CHECK(current_hash == initial_metadata_hash_,
"Hashtable mutated during iteration: metadata changed!");
}
#endif
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
[[clang::always_inline]] auto
ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::EntryRange::begin() const
-> Iterator {
// The traversal order is fixed when the range is constructed, so repeated
// calls yield equal iterators as forward ranges require.
return Iterator(*this);
}
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
[[clang::always_inline]] auto
ViewImpl<InputKeyT, InputValueT, InputKeyContextT>::EntryRange::end() const
-> Iterator {
return Iterator();
}
// TODO: Evaluate whether it is worth forcing this out-of-line given the
// reasonable ABI boundary it forms and large volume of code necessary to
// implement it.
@@ -921,7 +1267,7 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::InsertImpl(
ssize_t group_with_deleted_index;
MetadataGroup::MatchIndex deleted_match = {};
EntryT* local_entries = entries();
EntryT* local_entries = entries_data();
auto return_insert_at_index = [&](ssize_t index) -> std::pair<EntryT*, bool> {
// We'll need to insert at this index so set the control group byte to the
@@ -1017,7 +1363,7 @@ BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::GrowToAllocSizeImpl(
bool old_small = is_small();
Storage* old_storage = storage();
uint8_t* old_metadata = metadata();
EntryT* old_entries = entries();
EntryT* old_entries = entries_data();
// Configure for the new size and allocate the new storage.
alloc_size() = target_alloc_size;
@@ -1093,7 +1439,7 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::EraseImpl(
// If we mark the slot as empty, we'll also need to increase the growth
// budget.
uint8_t* local_metadata = metadata();
EntryT* local_entries = entries();
EntryT* local_entries = entries_data();
ssize_t index = entry - local_entries;
ssize_t group_index = index & ~GroupMask;
auto g = MetadataGroup::Load(local_metadata, group_index);
@@ -1114,16 +1460,10 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::EraseImpl(
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::ClearImpl() -> void {
view_impl_.ForEachEntry(
[](EntryT& entry) {
if constexpr (!EntryT::IsTriviallyDestructible) {
entry.Destroy();
}
},
[](uint8_t* metadata_group) {
// Clear the group.
std::memset(metadata_group, 0, GroupSize);
});
DestroyEntries();
if (storage() != nullptr) {
std::memset(metadata(), 0, alloc_size());
}
growth_budget_ = GrowthThresholdForAllocSize(alloc_size());
}
@@ -1186,10 +1526,7 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::Destroy() -> void {
}
// Destroy all the entries.
if constexpr (!EntryT::IsTriviallyDestructible) {
view_impl_.ForEachEntry([](EntryT& entry) { entry.Destroy(); },
[](auto...) {});
}
DestroyEntries();
// If small, nothing to deallocate.
if (is_small()) {
@@ -1201,6 +1538,16 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::Destroy() -> void {
Deallocate(storage(), alloc_size());
}
template <typename InputKeyT, typename InputValueT, typename InputKeyContextT>
auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::DestroyEntries()
-> void {
if constexpr (!EntryT::IsTriviallyDestructible) {
for (typename EntryT::RefT entry : view_impl_.EntriesImpl()) {
EntryT::DestroyRef(entry);
}
}
}
// Copy all of the slots over from another table that is exactly the same
// allocation size.
//
@@ -1224,9 +1571,9 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::CopySlotsFrom(
// all of the keys. This is especially important as we don't have an easy way
// to access the key context needed for rehashing here.
uint8_t* local_metadata = metadata();
EntryT* local_entries = entries();
EntryT* local_entries = entries_data();
const uint8_t* local_arg_metadata = arg.metadata();
const EntryT* local_arg_entries = arg.entries();
const EntryT* local_arg_entries = arg.entries_data();
memcpy(local_metadata, local_arg_metadata, local_size);
for (ssize_t group_index = 0; group_index < local_size;
@@ -1269,9 +1616,9 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::MoveFrom(
// themselves. We do this preserving their slots and even tombstones to
// avoid rehashing.
uint8_t* local_metadata = this->metadata();
EntryT* local_entries = this->entries();
EntryT* local_entries = this->entries_data();
uint8_t* local_arg_metadata = arg.metadata();
EntryT* local_arg_entries = arg.entries();
EntryT* local_arg_entries = arg.entries_data();
memcpy(local_metadata, local_arg_metadata, local_size);
if (EntryT::IsTriviallyRelocatable) {
memcpy(local_entries, local_arg_entries, local_size * sizeof(EntryT));
@@ -1306,7 +1653,7 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::InsertIntoEmpty(
HashCode hash) -> EntryT* {
auto [hash_index, tag] = hash.ExtractIndexAndTag<7>();
uint8_t* local_metadata = metadata();
EntryT* local_entries = entries();
EntryT* local_entries = entries_data();
for (ProbeSequence s(hash_index, alloc_size());; s.Next()) {
ssize_t group_index = s.index();
@@ -1392,7 +1739,7 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::GrowToNextAllocSize(
bool old_small = is_small();
Storage* old_storage = storage();
uint8_t* old_metadata = metadata();
EntryT* old_entries = entries();
EntryT* old_entries = entries_data();
#ifndef NDEBUG
// Count how many of the old table slots will end up being empty after we grow
@@ -1417,7 +1764,7 @@ auto BaseImpl<InputKeyT, InputValueT, InputKeyContextT>::GrowToNextAllocSize(
// Now extract the new components of the table.
uint8_t* new_metadata = metadata();
EntryT* new_entries = entries();
EntryT* new_entries = entries_data();
// Walk the metadata groups, clearing deleted to empty, duplicating the
// metadata for the low and high halves, and updating it based on where each
@@ -1596,10 +1943,7 @@ auto TableImpl<InputBaseT, SmallSize>::operator=(const TableImpl& arg)
return *this;
}
CARBON_DCHECK(arg.storage() != this->storage());
if constexpr (!EntryT::IsTriviallyDestructible) {
this->view_impl_.ForEachEntry([](EntryT& entry) { entry.Destroy(); },
[](auto...) {});
}
this->DestroyEntries();
} else {
// The sizes don't match so destroy everything and re-setup the table
// storage.
+2 -8
View File
@@ -70,15 +70,9 @@ struct MoveOnlyTestData : Printable<TestData> {
}
auto Print(llvm::raw_ostream& out) const -> void { out << value; }
friend auto operator==(const MoveOnlyTestData& lhs,
const MoveOnlyTestData& rhs) -> bool {
return lhs.value == rhs.value;
}
friend auto operator<=>(const MoveOnlyTestData& lhs,
const MoveOnlyTestData& rhs) -> std::strong_ordering {
return lhs.value <=> rhs.value;
}
const MoveOnlyTestData& rhs)
-> std::strong_ordering = default;
friend auto CarbonHashValue(const MoveOnlyTestData& data, uint64_t seed)
-> HashCode {
+4 -4
View File
@@ -39,6 +39,10 @@ class RawStringOstream : public llvm::raw_pwrite_stream {
auto empty() -> bool { return str_.empty(); }
auto size() -> size_t { return str_.size(); }
auto reserveExtraSpace(uint64_t extra_size) -> void override {
str_.reserve(str_.size() + extra_size);
}
private:
auto current_pos() const -> uint64_t override { return str_.size(); }
@@ -51,10 +55,6 @@ class RawStringOstream : public llvm::raw_pwrite_stream {
str_.append(ptr, size);
}
auto reserveExtraSpace(uint64_t extra_size) -> void override {
str_.reserve(str_.size() + extra_size);
}
// The actual buffer.
std::string str_;
};
+29 -30
View File
@@ -7,6 +7,7 @@
#include <concepts>
#include <type_traits>
#include <utility>
#include "common/check.h"
#include "common/hashtable_key_context.h"
@@ -56,9 +57,13 @@ class SetView : RawHashtable::ViewImpl<InputKeyT, void, InputKeyContextT> {
using ImplT = RawHashtable::ViewImpl<InputKeyT, void, InputKeyContextT>;
public:
using KeyT = typename ImplT::KeyT;
using KeyContextT = typename ImplT::KeyContextT;
using MetricsT = typename ImplT::MetricsT;
using KeyT = ImplT::KeyT;
using KeyContextT = ImplT::KeyContextT;
using MetricsT = ImplT::MetricsT;
// A range over the keys of the set. Bound to the lifetime of the viewed set,
// and invalidated by mutating it.
using Range = ImplT::EntryRange;
// This type represents the result of lookup operations. It encodes whether
// the lookup was a success as well as accessors for the key.
@@ -91,10 +96,8 @@ class SetView : RawHashtable::ViewImpl<InputKeyT, void, InputKeyContextT> {
auto Lookup(LookupKeyT lookup_key,
KeyContextT key_context = KeyContextT()) const -> LookupResult;
// Run the provided callback for every key in the set.
template <typename CallbackT>
auto ForEach(CallbackT callback) const -> void
requires(std::invocable<CallbackT, KeyT&>);
// Returns a range for iterating over all keys in the set.
auto entries() const -> Range;
// This routine is relatively inefficient and only intended for use in
// benchmarking or logging of performance anomalies. The specific metrics
@@ -110,7 +113,7 @@ class SetView : RawHashtable::ViewImpl<InputKeyT, void, InputKeyContextT> {
friend class SetBase<KeyT, KeyContextT>;
friend class SetView<const KeyT, KeyContextT>;
using EntryT = typename ImplT::EntryT;
using EntryT = ImplT::EntryT;
SetView() = default;
explicit(false) SetView(ImplT base) : ImplT(base) {}
@@ -131,18 +134,19 @@ class SetView : RawHashtable::ViewImpl<InputKeyT, void, InputKeyContextT> {
// A pointer or reference to this type is the preferred way to pass a mutable
// handle to a `Set` type across API boundaries as it avoids encoding specific
// SSO sizing information while providing a near-complete mutable API.
template <typename InputKeyT, typename InputKeyContextT>
template <typename InputKeyT, typename InputKeyContextT = DefaultKeyContext>
class SetBase
: protected RawHashtable::BaseImpl<InputKeyT, void, InputKeyContextT> {
protected:
using ImplT = RawHashtable::BaseImpl<InputKeyT, void, InputKeyContextT>;
public:
using KeyT = typename ImplT::KeyT;
using KeyContextT = typename ImplT::KeyContextT;
using KeyT = ImplT::KeyT;
using KeyContextT = ImplT::KeyContextT;
using ViewT = SetView<KeyT, KeyContextT>;
using LookupResult = typename ViewT::LookupResult;
using MetricsT = typename ImplT::MetricsT;
using LookupResult = ViewT::LookupResult;
using MetricsT = ImplT::MetricsT;
using Range = ViewT::Range;
// The result type for insertion operations both indicates whether an insert
// was needed (as opposed to the key already being in the set), and provides
@@ -190,12 +194,12 @@ class SetBase
}
// Convenience forwarder to the view type.
template <typename CallbackT>
auto ForEach(CallbackT callback) const -> void
requires(std::invocable<CallbackT, KeyT&>)
{
return ViewT(*this).ForEach(callback);
}
auto entries() const& -> Range { return ViewT(*this).entries(); }
// Deleted on rvalues: the range refers to storage owned by this table, so a
// range built from a temporary set would dangle. Both qualifiers are needed
// as `&&` alone would leave a const rvalue binding to the `const&` overload.
auto entries() && = delete;
auto entries() const&& = delete;
// Convenience forwarder to the view type.
auto ComputeMetrics(KeyContextT key_context = KeyContextT()) const
@@ -211,10 +215,10 @@ class SetBase
auto Insert(LookupKeyT lookup_key, KeyContextT key_context = KeyContextT())
-> InsertResult;
// Insert a key into the map and call the provided callback if necessary to
// produce a new key when no existing value is found.
// Insert a key into the set and call the provided callback if necessary to
// produce a new key when no existing key is found.
//
// Example: `m.Insert(key_equivalent, [] { return real_key; });`
// Example: `s.Insert(key_equivalent, [] { return real_key; });`
//
// The point of this function is when the lookup key is _different_from the
// stored key. However, we don't restrict it in case that blocks generic
@@ -299,7 +303,7 @@ class Set : public RawHashtable::TableImpl<SetBase<InputKeyT, InputKeyContextT>,
using ImplT = RawHashtable::TableImpl<BaseT, SmallSize>;
public:
using KeyT = typename BaseT::KeyT;
using KeyT = BaseT::KeyT;
Set() = default;
Set(const Set& arg) = default;
@@ -333,13 +337,8 @@ auto SetView<InputKeyT, InputKeyContextT>::Lookup(LookupKeyT lookup_key,
}
template <typename InputKeyT, typename InputKeyContextT>
template <typename CallbackT>
auto SetView<InputKeyT, InputKeyContextT>::ForEach(CallbackT callback) const
-> void
requires(std::invocable<CallbackT, KeyT&>)
{
this->ForEachEntry([callback](EntryT& entry) { callback(entry.key()); },
[](auto...) {});
auto SetView<InputKeyT, InputKeyContextT>::entries() const -> Range {
return this->ImplT::EntriesImpl();
}
template <typename InputKeyT, typename InputKeyContextT>
+76 -7
View File
@@ -35,9 +35,10 @@ static constexpr bool IsCarbonSet = IsCarbonSetImpl<SetT>::value;
// support different APIs. The primary template assumes a roughly
// `std::unordered_set` API design, and types with a different API design are
// supported through specializations.
template <typename SetT>
template <typename InSetT>
struct SetWrapperImpl {
using KeyT = typename SetT::key_type;
using SetT = InSetT;
using KeyT = SetT::key_type;
SetT s;
@@ -58,6 +59,17 @@ struct SetWrapperImpl {
}
auto BenchErase(KeyT k) -> bool { return s.erase(k) != 0; }
// Visits every key in the set, calling `cb` with each one. Each set type is
// expected to traverse using whatever API it provides for this, so that the
// benchmark measures iterating the set rather than any specific iteration
// API.
template <typename CallbackT>
auto BenchIterate(CallbackT cb) -> void {
for (const auto& k : s) {
cb(k);
}
}
};
// Explicit (partial) specialization for the Carbon map type that uses its
@@ -85,6 +97,13 @@ struct SetWrapperImpl<Set<KT, MinSmallSize>> {
}
auto BenchErase(KeyT k) -> bool { return s.Erase(k); }
template <typename CallbackT>
auto BenchIterate(CallbackT cb) -> void {
for (const auto& k : s.entries()) {
cb(k);
}
}
};
// Provide a way to override the Carbon Set specific benchmark runs with another
@@ -123,6 +142,17 @@ using SetWrapper =
SetWrapperOverride<SetT, SetOverride::CARBON_SET_BENCH_OVERRIDE>;
#endif
// Reports extra statistics about the table, when it is in fact a Carbon table.
// Note that this has to inspect the *wrapped* type in order to work correctly
// when the Carbon benchmarks are overridden with another implementation.
template <typename SetT>
auto ReportMetrics(const SetWrapper<SetT>& s_wrapper, benchmark::State& state)
-> void {
if constexpr (IsCarbonSet<typename SetWrapper<SetT>::SetT>) {
ReportTableMetrics(s_wrapper.s, state);
}
}
// NOLINTBEGIN(bugprone-macro-parentheses): Parentheses are incorrect here.
#define MAP_BENCHMARK_ONE_OP_SIZE(NAME, APPLY, KT) \
BENCHMARK(NAME<Set<KT>>)->Apply(APPLY); \
@@ -158,7 +188,7 @@ using SetWrapper =
template <typename SetT>
static void BM_SetContainsHitPtr(benchmark::State& state) {
using SetWrapperT = SetWrapper<SetT>;
using KT = typename SetWrapperT::KeyT;
using KT = SetWrapperT::KeyT;
SetWrapperT s;
auto [keys, lookup_keys] =
GetKeysAndHitKeys<KT>(state.range(0), state.range(1));
@@ -190,7 +220,7 @@ MAP_BENCHMARK_ONE_OP(BM_SetContainsHitPtr, HitArgs);
template <typename SetT>
static void BM_SetContainsMissPtr(benchmark::State& state) {
using SetWrapperT = SetWrapper<SetT>;
using KT = typename SetWrapperT::KeyT;
using KT = SetWrapperT::KeyT;
SetWrapperT s;
auto [keys, lookup_keys] = GetKeysAndMissKeys<KT>(state.range(0));
for (auto k : keys) {
@@ -225,7 +255,7 @@ MAP_BENCHMARK_ONE_OP(BM_SetContainsMissPtr, SizeArgs);
template <typename SetT>
static void BM_SetLookupHitPtr(benchmark::State& state) {
using SetWrapperT = SetWrapper<SetT>;
using KT = typename SetWrapperT::KeyT;
using KT = SetWrapperT::KeyT;
SetWrapperT s;
auto [keys, lookup_keys] =
GetKeysAndHitKeys<KT>(state.range(0), state.range(1));
@@ -265,7 +295,7 @@ MAP_BENCHMARK_ONE_OP(BM_SetLookupHitPtr, HitArgs);
template <typename SetT>
static void BM_SetEraseInsertHitPtr(benchmark::State& state) {
using SetWrapperT = SetWrapper<SetT>;
using KT = typename SetWrapperT::KeyT;
using KT = SetWrapperT::KeyT;
SetWrapperT s;
auto [keys, lookup_keys] =
GetKeysAndHitKeys<KT>(state.range(0), state.range(1));
@@ -324,7 +354,7 @@ MAP_BENCHMARK_ONE_OP(BM_SetEraseInsertHitPtr, HitArgs);
template <typename SetT>
static void BM_SetInsertSeq(benchmark::State& state) {
using SetWrapperT = SetWrapper<SetT>;
using KT = typename SetWrapperT::KeyT;
using KT = SetWrapperT::KeyT;
constexpr ssize_t LookupKeysSize = 1 << 8;
auto [keys, lookup_keys] =
GetKeysAndHitKeys<KT>(state.range(0), LookupKeysSize);
@@ -375,5 +405,44 @@ static void BM_SetInsertSeq(benchmark::State& state) {
}
MAP_BENCHMARK_OP_SEQ(BM_SetInsertSeq);
// Benchmark visiting every key in a set.
//
// Unlike the lookup benchmarks, this walks the table's storage from end to end
// rather than probing it, so it is largely a measure of how densely keys are
// packed and how cheaply empty slots can be skipped. There is no dependency
// between the keys visited, and so this is a throughput measurement.
//
// Each batch is a single complete traversal of the set, with the batch size set
// to the number of keys so that the reported time is the per-key cost.
template <typename SetT>
static void BM_SetIterate(benchmark::State& state) {
using SetWrapperT = SetWrapper<SetT>;
using KT = typename SetWrapperT::KeyT;
SetWrapperT s;
auto [keys, _] = GetKeysAndMissKeys<KT>(state.range(0));
for (auto k : keys) {
bool inserted = s.BenchInsert(k);
CARBON_DCHECK(inserted, "Must be a successful insert!");
}
while (state.KeepRunningBatch(keys.size())) {
ssize_t sum = 0;
s.BenchIterate([&sum](const KT& k) {
// Consume the key so that neither the traversal nor the loads out of the
// entries can be optimized away.
sum += ValueToBool(k);
});
benchmark::DoNotOptimize(sum);
}
// The time is already per-key, so an iteration-invariant rate of one gives
// the throughput of keys visited.
state.counters["KeyRate"] =
benchmark::Counter(1, benchmark::Counter::kIsIterationInvariantRate);
ReportMetrics(s, state);
}
MAP_BENCHMARK_ONE_OP(BM_SetIterate, SizeArgs);
} // namespace
} // namespace Carbon
+182 -2
View File
@@ -7,7 +7,12 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <concepts>
#include <initializer_list>
#include <iterator>
#include <ranges>
#include <set>
#include <string>
#include <type_traits>
#include <vector>
@@ -19,14 +24,17 @@ namespace {
using RawHashtable::IndexKeyContext;
using RawHashtable::MoveOnlyTestData;
using RawHashtable::TestData;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
template <typename SetT, typename MatcherRangeT>
auto ExpectSetElementsAre(SetT&& s, MatcherRangeT element_matchers) -> void {
// Collect the elements into a container.
using KeyT = typename std::remove_reference<SetT>::type::KeyT;
using KeyT = std::remove_reference<SetT>::type::KeyT;
std::vector<std::reference_wrapper<KeyT>> entries;
s.ForEach([&entries](KeyT& k) { entries.push_back(std::ref(k)); });
for (auto& k : s.entries()) {
entries.push_back(std::ref(k));
}
// Use the GoogleMock unordered container matcher to validate and show errors
// on wrong elements.
@@ -176,6 +184,8 @@ TYPED_TEST(SetTest, Move) {
SetT other_s1 = std::move(s);
ExpectSetElementsAre(other_s1, MakeElements(llvm::seq(1, 24)));
// A moved-from set has a size but no storage, and must iterate as empty.
EXPECT_EQ(s.entries().begin(), s.entries().end());
// Add some more elements.
for (int i : llvm::seq(24, 32)) {
@@ -432,5 +442,175 @@ TEST(SetContextTest, Basic) {
ExpectSetElementsAre(s, MakeElements(llvm::seq(1, 512)));
}
TYPED_TEST(SetTest, Range) {
using SetT = TypeParam;
using Range = decltype(std::declval<const SetT&>().entries());
using Iter = typename Range::Iterator;
static_assert(std::forward_iterator<Iter>);
static_assert(std::same_as<decltype(std::declval<Range>().begin()), Iter>);
static_assert(std::same_as<decltype(std::declval<Range>().end()), Iter>);
static_assert(std::ranges::forward_range<Range>);
static_assert(std::ranges::common_range<Range>);
SetT s;
EXPECT_EQ(s.entries().begin(), s.entries().end());
for (const auto& k : s.entries()) {
static_cast<void>(k);
FAIL() << "Empty set range should have no elements";
}
for (int i = 1; i <= 5; ++i) {
s.Insert(i);
}
// Range-for traversal by const ref.
int count = 0;
for (const auto& k : s.entries()) {
EXPECT_GE(k, 1);
EXPECT_LE(k, 5);
++count;
}
EXPECT_EQ(count, 5);
// Direct GMock container matching.
EXPECT_THAT(s.entries(), UnorderedElementsAre(1, 2, 3, 4, 5));
// Const view range iteration.
using KeyT = typename SetT::KeyT;
using KeyContextT = typename SetT::KeyContextT;
SetView<const KeyT, KeyContextT> cv = s;
int cv_count = 0;
for (const auto& k : cv.entries()) {
static_assert(std::is_const_v<std::remove_reference_t<decltype(k)>>);
EXPECT_GE(k, 1);
EXPECT_LE(k, 5);
++cv_count;
}
EXPECT_EQ(cv_count, 5);
EXPECT_THAT(cv.entries(), UnorderedElementsAre(1, 2, 3, 4, 5));
// Explicit iterator traversal, dereference, and post-increment.
auto r = s.entries();
int iter_count = 0;
for (auto it = r.begin(); it != r.end(); ++it) {
EXPECT_NE(*it, 0);
++iter_count;
}
EXPECT_EQ(iter_count, 5);
auto it = r.begin();
auto prev = it++;
EXPECT_NE(it, prev);
}
TYPED_TEST(MoveOnlySetTest, Range) {
TypeParam s;
s.Insert(1);
s.Insert(2);
int count = 0;
for (const auto& k : s.entries()) {
EXPECT_GT(k.value, 0);
++count;
}
EXPECT_EQ(count, 2);
}
#ifndef NDEBUG
TEST(SetDeathTest, MutateDuringIterationFails) {
EXPECT_DEATH(([] {
Set<int> s;
s.Insert(1);
auto range = s.entries();
s.Insert(2);
}()),
"Hashtable mutated during iteration");
}
#endif
// A range outlives the *view* it was built from: views don't own storage, and
// the range copies the view rather than pointing at it.
TEST(SetTest, RangeOutlivesTemporaryView) {
Set<int> s;
s.Insert(1);
auto make_view = [&s]() -> SetView<int> { return s; };
auto range = make_view().entries();
EXPECT_THAT(range, UnorderedElementsAre(1));
}
#ifdef NDEBUG
// Release iteration state is two end pointers, a group offset, and the
// present-bit mask; it needs to stay small enough to live in registers across
// the loop. Debug builds add the randomized walk and mutation-check state.
static_assert(sizeof(Set<int>::Range::Iterator) <= 4 * sizeof(void*));
#endif
// Forward ranges guarantee multi-pass: `begin()` must be a pure function of the
// range. Debug builds draw their traversal entropy when the range is
// constructed rather than in `begin()` precisely so that repeated calls start
// from the same group.
TEST(SetTest, RangeIsMultiPass) {
Set<int, 16> s;
for (int i = 1; i <= 64; ++i) {
s.Insert(i);
}
auto range = s.entries();
EXPECT_EQ(range.begin(), range.begin());
// Two passes over the same range must agree on both the keys visited and the
// order they're visited in.
std::vector<int> first;
for (int k : range) {
first.push_back(k);
}
std::vector<int> second;
for (int k : range) {
second.push_back(k);
}
EXPECT_EQ(first, second);
EXPECT_EQ(static_cast<ssize_t>(first.size()), 64);
}
// Whatever order a range picks, it has to be a genuine permutation of the
// table. Debug builds additionally vary that order between ranges over the same
// table so that callers can't come to depend on it.
TEST(SetTest, TraversalOrderIsAVaryingPermutation) {
Set<int, 64> s;
std::vector<int> inserted;
// Enough keys to populate every group of the small storage.
for (int i = 0; i < 36; ++i) {
int key = i * 17 + 7;
EXPECT_TRUE(s.Insert(key).is_inserted());
inserted.push_back(key);
}
std::set<std::vector<int>> distinct_orders;
for (int i = 0; i < 64; ++i) {
std::vector<int> visited;
for (int k : s.entries()) {
visited.push_back(k);
}
// A walk that skipped a group would drop keys and one that revisited a
// group would duplicate them, so comparing as a multiset covers both. This
// is what makes an odd group stride a valid traversal.
EXPECT_THAT(visited, UnorderedElementsAreArray(inserted));
distinct_orders.insert(visited);
}
#ifndef NDEBUG
// Debug builds randomize both the starting group and the stride, so across
// this many ranges we should see more than the two orders (pure forward and
// pure reverse) that a simple direction flip would produce.
EXPECT_GT(distinct_orders.size(), 2)
<< "Debug traversal order does not appear to be randomized.";
#else
// Release builds always scan the groups in order.
EXPECT_EQ(distinct_orders.size(), 1);
#endif
}
} // namespace
} // namespace Carbon
+2 -8
View File
@@ -55,13 +55,7 @@ struct AnyField {
// Detector for whether we can list-initialize T from the given list of fields.
template <typename T, typename... Fields>
constexpr auto CanListInitialize(decltype(T{Fields()...})* /*unused*/) -> bool {
return true;
}
template <typename T, typename... Fields>
constexpr auto CanListInitialize(...) -> bool {
return false;
}
concept CanListInitialize = requires { T{Fields()...}; };
#pragma clang diagnostic pop
@@ -72,7 +66,7 @@ constexpr auto CanListInitialize(...) -> bool {
// 2) Add more AnyField<T>s until we can't initialize any more.
template <typename T, bool AnyWorkedSoFar = false, typename... Fields>
constexpr auto CountFields() -> int {
if constexpr (CanListInitialize<T, Fields...>(nullptr)) {
if constexpr (CanListInitialize<T, Fields...>) {
return CountFields<T, true, Fields..., AnyField<T>>();
} else if constexpr (AnyWorkedSoFar) {
constexpr int NumFields = sizeof...(Fields) - 1;
+6 -6
View File
@@ -53,17 +53,17 @@ TEST(StructReflectionTest, CanListInitialize) {
{
using Type = OneField;
using Field = Internal::AnyField<Type>;
static_assert(Internal::CanListInitialize<Type>(nullptr));
static_assert(Internal::CanListInitialize<Type, Field>(nullptr));
static_assert(!Internal::CanListInitialize<Type, Field, Field>(0));
static_assert(Internal::CanListInitialize<Type>);
static_assert(Internal::CanListInitialize<Type, Field>);
static_assert(!Internal::CanListInitialize<Type, Field, Field>);
}
{
using Type = OneFieldNoDefaultConstructor;
using Field = Internal::AnyField<Type>;
static_assert(!Internal::CanListInitialize<Type>(0));
static_assert(Internal::CanListInitialize<Type, Field>(nullptr));
static_assert(!Internal::CanListInitialize<Type, Field, Field>(0));
static_assert(!Internal::CanListInitialize<Type>);
static_assert(Internal::CanListInitialize<Type, Field>);
static_assert(!Internal::CanListInitialize<Type, Field, Field>);
}
}
+195
View File
@@ -0,0 +1,195 @@
# 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
# Terminal rendering: what the attached terminal can do, and how to draw styled
# text for it. Used for diagnostic rendering and for command line output.
load("@rules_shell//shell:sh_test.bzl", "sh_test")
load("//bazel/cc_rules:defs.bzl", "cc_binary", "cc_library", "cc_test")
package(default_visibility = ["//visibility:public"])
cc_library(
name = "output_buffer_ref",
hdrs = ["output_buffer_ref.h"],
deps = ["@llvm-project//llvm:Support"],
)
cc_test(
name = "output_buffer_ref_test",
size = "small",
srcs = ["output_buffer_ref_test.cpp"],
deps = [
":output_buffer_ref",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "color",
srcs = ["color.cpp"],
hdrs = ["color.h"],
deps = [
":output_buffer_ref",
"//common:check",
"//common:ostream",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "color_test",
size = "small",
srcs = ["color_test.cpp"],
deps = [
":color",
"//common:ostream",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "style",
srcs = ["style.cpp"],
hdrs = ["style.h"],
deps = [
":color",
":output_buffer_ref",
"//common:check",
"//common:ostream",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "style_test",
size = "small",
srcs = ["style_test.cpp"],
deps = [
":style",
"//common:ostream",
"//common:raw_string_ostream",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "capabilities",
srcs = ["capabilities.cpp"],
hdrs = ["capabilities.h"],
deps = [
":color",
"//common:filesystem",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "capabilities_test",
size = "small",
srcs = ["capabilities_test.cpp"],
deps = [
":capabilities",
"//common:filesystem",
"//testing/base:gtest_main",
"@googletest//:gtest",
],
)
cc_library(
name = "metrics",
srcs = ["metrics.cpp"],
hdrs = ["metrics.h"],
deps = [
":capabilities",
"//common:check",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "metrics_test",
size = "small",
srcs = ["metrics_test.cpp"],
deps = [
":metrics",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "buffer",
srcs = ["buffer.cpp"],
hdrs = ["buffer.h"],
deps = [
":capabilities",
":color",
":metrics",
":output_buffer_ref",
":style",
"//common:check",
"//common:filesystem",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "buffer_test",
size = "small",
srcs = ["buffer_test.cpp"],
deps = [
":buffer",
":metrics",
"//common:filesystem",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "pressure_test",
size = "small",
srcs = ["pressure_test.cpp"],
deps = [
":buffer",
":capabilities",
":metrics",
":style",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//llvm:Support",
],
)
cc_binary(
name = "terminal_benchmark",
testonly = 1,
srcs = ["terminal_benchmark.cpp"],
deps = [
":buffer",
":capabilities",
":color",
":style",
"//testing/base:benchmark_main",
"@abseil-cpp//absl/random",
"@google_benchmark//:benchmark",
"@llvm-project//llvm:Support",
],
)
sh_test(
name = "terminal_benchmark_test",
size = "small",
srcs = [":terminal_benchmark"],
args = ["--benchmark_dry_run"],
)
+580
View File
@@ -0,0 +1,580 @@
// 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
#include "common/terminal/buffer.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <utility>
#include "common/check.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Unicode.h"
namespace Carbon::Terminal {
// The most bytes of combining marks kept on one cell. Text stacking more than
// this is either adversarial or already illegible, and keeping all of it would
// let a single column of output carry unbounded bytes.
static constexpr size_t MaxCombiningBytes = 32;
// Glyphs for every combination of line directions, indexed by the direction
// bits.
static constexpr std::array<char32_t, 16> Utf8LineGlyphs = {
U'·', // (none): a line between one center and itself, which is a point
U'╴', // left
U'╶', // right
U'─', // left, right
U'╵', // up
U'╯', // left, up
U'╰', // right, up
U'┴', // left, right, up
U'╷', // down
U'╮', // left, down
U'╭', // right, down
U'┬', // left, right, down
U'│', // up, down
U'┤', // left, up, down
U'├', // right, up, down
U'┼', // left, right, up, down
};
// The ASCII stand-ins. Each keeps the axis its line runs through, which leaves
// `+` meaning a crossing and nothing else:
//
// - Running through horizontally is `-`, vertically `|`, and both ways `+`.
// - A tee keeps its through-stroke and leaves the branch to what is drawn
// beside it: the dashes either side of a `|` are what `├` and `┤` reach, and
// the line under a `-` is what a `┬` reaches. Drawing a tee as `+` reads as
// the crossing it is not.
// - A corner is `.` where its line leaves downward and `'` where it arrives
// from above, which is where those characters sit in their cells.
// - A point, a line between one center and itself, is `.`.
//
// What a diagnostic draws is then still told apart: the rule closing a snippet
// from the one separating two, and the anchor opening a diagnostic from the one
// carrying it on.
static constexpr std::array<char32_t, 16> AsciiLineGlyphs = {
U'.', // (none): a point
U'-', // left
U'-', // right
U'-', // left, right
U'|', // up
U'\'', // left, up
U'\'', // right, up
U'-', // left, right, up
U'|', // down
U'.', // left, down
U'.', // right, down
U'-', // left, right, down
U'|', // up, down
U'|', // left, up, down
U'|', // right, up, down
U'+', // left, right, up, down
};
// Returns the next tab stop after `x` on a line whose stops are `tab_width`
// columns apart counting from `origin`, which `x` must not be left of.
static auto NextTabStop(int x, int origin, int tab_width) -> int {
CARBON_DCHECK(x >= origin, "Column {0} is left of the origin {1}.", x,
origin);
return origin + ((x - origin) / tab_width + 1) * tab_width;
}
Buffer::Buffer(int columns, Charset charset, int tab_width)
: columns_(columns),
width_(columns),
tab_width_(tab_width),
metrics_(charset) {
CARBON_CHECK(columns > 0 && columns <= MaxColumns,
"Buffer width must be in [1, {0}], but was {1}.", MaxColumns,
columns);
CARBON_CHECK(tab_width > 0 && tab_width <= MaxTabWidth,
"Tab width must be in [1, {0}], but was {1}.", MaxTabWidth,
tab_width);
}
auto Buffer::height() const -> int {
return static_cast<int>(cells_.size()) / width_;
}
auto Buffer::EnsureRow(int y) -> void {
CARBON_CHECK(y >= 0 && y < MaxRows, "Row {0} is outside [0, {1}).", y,
MaxRows);
if (y < height()) {
return;
}
// Rows are added at the end and nothing already in the grid moves, so this
// asks for exactly the rows wanted and lets the vector amortize the growing.
cells_.resize(static_cast<size_t>(y + 1) * width_);
}
auto Buffer::EnsureColumn(int x) -> void {
CARBON_CHECK(x >= 0 && x < MaxColumns, "Column {0} is outside [0, {1}).", x,
MaxColumns);
if (x < width_) {
return;
}
// Widening moves every row, so it grows by halves rather than to exactly what
// was asked: a row drawn one code point at a time would otherwise copy the
// whole grid on every one of them. Growth stops at the bound, which is what
// holds the product of the two dimensions inside what a cell index can
// represent.
int width = std::min(std::max(x + 1, width_ + width_ / 2), MaxColumns);
int rows = height();
llvm::SmallVector<Cell, 0> new_cells(static_cast<size_t>(rows) * width);
for (int y : llvm::seq(rows)) {
llvm::copy(
llvm::ArrayRef(cells_).slice(static_cast<size_t>(y) * width_, width_),
new_cells.begin() + static_cast<size_t>(y) * width);
}
cells_ = std::move(new_cells);
// A mark's key is a cell index, which depends on the width, so each is
// recomputed for the new one.
llvm::DenseMap<int, std::string> new_combining_marks;
new_combining_marks.reserve(combining_marks_.size());
for (auto& [index, marks] : combining_marks_) {
new_combining_marks.insert(
{index / width_ * width + index % width_, std::move(marks)});
}
combining_marks_ = std::move(new_combining_marks);
width_ = width;
}
auto Buffer::ClearCells(int x, int y, int width) -> void {
CARBON_CHECK(
x >= 0 && width >= 0 && x + width <= width_ && y >= 0 && y < height(),
"Clearing [{0}, {1}) of row {2} reaches outside the {3}x{4} cells the "
"buffer holds.",
x, x + width, y, width_, height());
// A cleared range must not leave half of a double-width character behind, so
// it extends over either half that crosses its edges.
int begin = x;
if (begin > 0 && CellAt(begin, y).is_continuation) {
--begin;
}
int end = x + width;
if (end < width_ && CellAt(end, y).is_continuation) {
++end;
}
for (int i = begin; i < end; ++i) {
CellAt(i, y) = Cell();
combining_marks_.erase(CellIndex(i, y));
}
}
auto Buffer::AttachCombiningMark(int x, int y, char32_t code_point) -> void {
// A mark has nowhere to go when no cell precedes it, so it is dropped.
if (x <= 0 || x > width_ || y < 0 || y >= height()) {
return;
}
// The left half of a double-width character is never itself a continuation,
// so stepping back from one always lands on a real character.
int base = x - 1;
if (CellAt(base, y).is_continuation) {
--base;
}
CARBON_CHECK(base >= 0, "A continuation cell at column zero has no base.");
Utf8Storage storage;
llvm::StringRef encoded = EncodeUtf8(code_point, storage);
std::string& marks = combining_marks_[CellIndex(base, y)];
if (marks.size() + encoded.size() > MaxCombiningBytes) {
return;
}
marks.append(encoded.data(), encoded.size());
}
auto Buffer::DrawCodePoint(int x, int y, char32_t code_point,
const Style& style) -> DrawEnd {
CheckTextOrigin(x, y);
return {.x = PlaceCodePoint(x, y, code_point, style), .y = y};
}
auto Buffer::PlaceCodePoint(int x, int y, char32_t code_point,
const Style& style) -> int {
CARBON_DCHECK(x >= 0 && y >= 0,
"Placing at ({0}, {1}), which no walk should reach.", x, y);
int width = metrics_.CodePointWidth(code_point);
if (width == 0) {
AttachCombiningMark(x, y, code_point);
return x;
}
code_point = metrics_.RenderedCodePoint(code_point);
// Both bounds are reached by what the text holds rather than by where the
// caller aimed -- a word overhanging the target width, or newlines running
// past the rows a grid can index -- so past either one nothing is drawn and
// the column still advances, which is what keeps measuring and drawing
// answering the same thing. A double-width character needs both its columns,
// so one that would only half fit is past the edge like any other: splitting
// it would leave the terminal rendering half a character.
if (y >= MaxRows || x > MaxColumns - width) {
return x + width;
}
EnsureColumn(x + width - 1);
EnsureRow(y);
ClearCells(x, y, width);
Cell& cell = CellAt(x, y);
cell.code_point = code_point;
cell.style = style;
// Nothing is wider than two columns, so the second is the only continuation
// there can be.
if (width > 1) {
Cell& continuation = CellAt(x + 1, y);
continuation.style = style;
continuation.is_continuation = true;
}
return x + width;
}
// Returns the glyphs a cell's directions are read from.
static auto LineGlyphs(Charset charset) -> const std::array<char32_t, 16>& {
return charset == Charset::Utf8 ? Utf8LineGlyphs : AsciiLineGlyphs;
}
auto Buffer::DrawLine(int x, int y, uint8_t directions, const Style& style)
-> void {
CARBON_DCHECK(directions <= LineDirections,
"Direction bits {0} name no glyph.", directions);
EnsureColumn(x);
EnsureRow(y);
uint8_t existing = CellAt(x, y).lines;
if (existing == 0) {
// Whatever is here isn't a line. Clearing also removes either half of a
// double-width character the cell was part of.
ClearCells(x, y, 1);
}
Cell& cell = CellAt(x, y);
cell.lines = existing | directions | LineCell;
cell.code_point = LineGlyphs(metrics_.charset())[cell.lines & LineDirections];
cell.style = style;
}
// Checks that a line of `length` starting at `position` stays within `limit`,
// which is the width for a horizontal line and `MaxRows` for a vertical one.
//
// Unlike text, a line has no reason to reach outside what it is being drawn
// into: nothing about it is unbreakable, and a layout that put one there
// computed the wrong extent.
static auto CheckLineFits(int position, int length, int limit) -> void {
CARBON_CHECK(length >= 0 && position <= limit - length,
"A line of {0} at {1} runs outside the {2} available to it.",
length, position, limit);
}
auto Buffer::DrawHorizontalLine(int x, int y, int length, const Style& style,
LineEnd start, LineEnd end) -> DrawEnd {
CheckOrigin(x, y);
CheckLineFits(x, length, columns_);
for (int i : llvm::seq(length)) {
// A cell in the middle of the line is entered from one side and left by the
// other. An end cell is only left towards the rest of the line, unless that
// end runs out through the cell's own side.
uint8_t directions =
(i > 0 || start == LineEnd::Edge ? LineLeft : 0) |
(i + 1 < length || end == LineEnd::Edge ? LineRight : 0);
DrawLine(x + i, y, directions, style);
}
return {.x = x + length, .y = y};
}
auto Buffer::DrawVerticalLine(int x, int y, int length, const Style& style,
LineEnd start, LineEnd end) -> DrawEnd {
CheckOrigin(x, y);
CheckLineFits(y, length, MaxRows);
for (int i : llvm::seq(length)) {
uint8_t directions =
(i > 0 || start == LineEnd::Edge ? LineUp : 0) |
(i + 1 < length || end == LineEnd::Edge ? LineDown : 0);
DrawLine(x, y + i, directions, style);
}
return {.x = x, .y = y + length};
}
auto Buffer::DrawBox(int x, int y, int box_width, int box_height,
const Style& style) -> DrawEnd {
CheckOrigin(x, y);
CheckLineFits(x, box_width, columns_);
CheckLineFits(y, box_height, MaxRows);
if (box_width == 0 || box_height == 0) {
return {.x = x, .y = y};
}
DrawHorizontalLine(x, y, box_width, style);
DrawHorizontalLine(x, y + box_height - 1, box_width, style);
DrawVerticalLine(x, y, box_height, style);
DrawVerticalLine(x + box_width - 1, y, box_height, style);
return {.x = x + box_width, .y = y + box_height};
}
template <typename PlaceFn>
auto Buffer::WalkText(int x, int y, int margin, llvm::StringRef text,
PlaceFn place) const -> DrawEnd {
CheckTextSize(text);
CARBON_CHECK(margin >= 0 && margin <= x && y >= 0 && y < MaxRows,
"Text at ({0}, {1}) with a margin of {2} is outside the {3} "
"rows a buffer covers, or left of its margin.",
x, y, margin, MaxRows);
int cur_x = x;
int cur_y = y;
while (!text.empty()) {
char32_t code_point = metrics_.TakeCodePoint(text);
if (code_point == '\n') {
cur_x = margin;
++cur_y;
continue;
}
if (code_point == '\r') {
cur_x = margin;
continue;
}
if (code_point == '\t') {
int stop = NextTabStop(cur_x, margin, tab_width_);
for (; cur_x < stop; ++cur_x) {
place(cur_x, cur_y, U' ');
}
continue;
}
cur_x = place(cur_x, cur_y, code_point);
}
return {.x = cur_x, .y = cur_y};
}
auto Buffer::DrawText(int x, int y, int margin, llvm::StringRef text,
const Style& style) -> DrawEnd {
return WalkText(x, y, margin, text,
[&](int cur_x, int cur_y, char32_t code_point) {
return PlaceCodePoint(cur_x, cur_y, code_point, style);
});
}
auto Buffer::MeasureText(int x, int y, int margin, llvm::StringRef text) const
-> DrawEnd {
return WalkText(x, y, margin, text,
[&](int cur_x, int /*cur_y*/, char32_t code_point) {
return cur_x + metrics_.CodePointWidth(code_point);
});
}
// Returns whether wrapped text can be broken at `c`.
//
// This is the one definition of where wrapping may introduce a break, so that
// measuring what text wraps into and drawing it wrapped agree about it.
// Carriage returns count so that a CRLF ending is whitespace rather than part
// of the word before it; what becomes of the `\r` is then up to the drawing.
static constexpr auto IsWrapBreak(char c) -> bool {
return c == ' ' || c == '\t' || c == '\r';
}
template <typename PlaceFn>
auto Buffer::WalkWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text, PlaceFn place) const
-> DrawEnd {
CheckTextSize(text);
// The block runs from the margin to `margin + max_width`, lies within the
// buffer, and holds the column the text starts in, which is every bound on
// the three of them read in one order.
CARBON_CHECK(llvm::is_sorted(std::array{0, margin, x, x + 1,
margin + max_width, columns_}) &&
y >= 0 && y < MaxRows,
"A block of {0} columns at {1} holding text from ({2}, {3}) "
"does not fit the {4} columns and {5} rows a buffer covers.",
max_width, margin, x, y, columns_, MaxRows);
// The column a row runs out of room at. The block lies within the buffer's
// width, so this is a column like any other rather than a sum that has to be
// kept from overflowing.
int limit = margin + max_width;
int cur_x = x;
int cur_y = y;
// Splitting on bytes is safe because every character text can break at is
// ASCII, and UTF-8 never encodes anything else using an ASCII byte. Only
// words are decoded; whitespace is handled a byte at a time.
while (!text.empty()) {
if (text.front() == '\n') {
text = text.drop_front();
cur_x = margin;
++cur_y;
continue;
}
if (IsWrapBreak(text.front())) {
llvm::StringRef breaks = text.take_while(IsWrapBreak);
text = text.drop_front(breaks.size());
for (char c : breaks) {
if (c == '\r') {
continue;
}
// Whitespace stops at the block's edge, leaving the word after it to
// wrap.
int next = std::min(
c == '\t' ? NextTabStop(cur_x, margin, tab_width_) : cur_x + 1,
limit);
while (cur_x < next) {
cur_x = place(cur_x, cur_y, U' ');
}
}
// A combining mark renders into the column before it, so one following
// whitespace belongs to that whitespace and goes with it. Left to begin
// the next word, it would move to another row whenever that word wrapped
// and attach to whatever preceded it there.
while (!text.empty()) {
llvm::StringRef rest = text;
char32_t code_point = metrics_.TakeCodePoint(rest);
if (metrics_.CodePointWidth(code_point) != 0) {
break;
}
text = rest;
cur_x = place(cur_x, cur_y, code_point);
}
continue;
}
llvm::StringRef word =
text.take_until([](char c) { return c == '\n' || IsWrapBreak(c); });
text = text.drop_front(word.size());
// Move a word that doesn't fit down to the next row, which minimizes the
// overhang when it doesn't fit there either. The word is drawn into the row
// this starts before anything else can reach it, so a wrapped row begins at
// the margin rather than with the whitespace the wrap came after.
if (cur_x > margin && cur_x + metrics_.Width(word) > limit) {
cur_x = margin;
++cur_y;
}
while (!word.empty()) {
cur_x = place(cur_x, cur_y, metrics_.TakeCodePoint(word));
}
}
return {.x = cur_x, .y = cur_y};
}
auto Buffer::DrawWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text, const Style& style)
-> DrawEnd {
return WalkWrappedText(x, y, margin, max_width, text,
[&](int cur_x, int cur_y, char32_t code_point) {
return PlaceCodePoint(cur_x, cur_y, code_point,
style);
});
}
auto Buffer::MeasureWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text) const -> DrawEnd {
return WalkWrappedText(x, y, margin, max_width, text,
[&](int cur_x, int /*cur_y*/, char32_t code_point) {
return cur_x + metrics_.CodePointWidth(code_point);
});
}
auto Buffer::MeasureWrapWidth(llvm::StringRef text) const -> int {
int width = 0;
while (!text.empty()) {
llvm::StringRef word =
text.take_until([](char c) { return c == '\n' || IsWrapBreak(c); });
width = std::max(width, metrics_.Width(word));
text = text.drop_front(std::max<size_t>(word.size(), 1));
}
return width;
}
auto Buffer::LastVisibleColumn(int y, ColorMode mode) const -> int {
// A style only paints a blank cell if it is rendered at all, so with color
// off a blank cell is padding whatever style it carries.
bool styles_render = mode != ColorMode::NoColor;
for (int x = width_ - 1; x >= 0; --x) {
const Cell& cell = CellAt(x, y);
if (cell.is_continuation || cell.code_point != ' ' ||
(styles_render && cell.style.IsVisibleOnBlank()) ||
(!combining_marks_.empty() &&
combining_marks_.contains(CellIndex(x, y)))) {
return x;
}
}
return -1;
}
auto Buffer::Render(OutputBufferRef out, ColorMode mode) const -> void {
Utf8Storage storage;
// The style a terminal starts in, and the one it is left in.
const Style default_style;
// Cells outlive this loop, so the active style is tracked by pointing at one
// rather than copying a whole style per cell. It carries across rows: a style
// is usually still in use on the row below, and turning it off and back on
// costs a reset and a fresh start for nothing.
const Style* active = &default_style;
int rows = height();
for (int y = 0; y < rows; ++y) {
int last = LastVisibleColumn(y, mode);
for (int x = 0; x <= last; ++x) {
const Cell& cell = CellAt(x, y);
if (cell.is_continuation) {
continue;
}
active->AppendTransitionTo(out, cell.style, mode);
active = &cell.style;
out.Append(EncodeUtf8(cell.code_point, storage));
// Almost nothing has combining marks, so the lookup is worth skipping
// outright rather than doing it for every cell on the screen.
if (!combining_marks_.empty()) {
auto marks = combining_marks_.find(CellIndex(x, y));
if (marks != combining_marks_.end()) {
out.Append(marks->second);
}
}
}
// A style is turned off before the newline in two cases. On the last row,
// so that nothing is left set for whatever is printed after this and the
// escape that turns it off still falls inside the rendering. And whenever
// it paints where there is no glyph, because a terminal fills the rest of
// the row with the background it is in when the row ends, so leaving one
// set would paint a stripe out to the right edge that nothing asked for.
if (y + 1 == rows || active->IsVisibleOnBlank()) {
active->AppendTransitionTo(out, default_style, mode);
active = &default_style;
}
out.Append("\n");
}
}
auto Buffer::WriteTo(Filesystem::WriteFileRef file, ColorMode mode) const
-> ErrorOr<Success, Filesystem::FdError> {
// Sized for the few short lines a diagnostic renders to. A full screen with
// color runs well past it and allocates once.
llvm::SmallString<1024> bytes;
Render(bytes, mode);
return file.WriteCompleteBuffer(llvm::ArrayRef<std::byte>(
reinterpret_cast<const std::byte*>(bytes.data()), bytes.size()));
}
} // namespace Carbon::Terminal
+546
View File
@@ -0,0 +1,546 @@
// 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
#ifndef CARBON_COMMON_TERMINAL_BUFFER_H_
#define CARBON_COMMON_TERMINAL_BUFFER_H_
#include <algorithm>
#include <cstdint>
#include <string>
#include "common/check.h"
#include "common/filesystem.h"
#include "common/terminal/capabilities.h"
#include "common/terminal/color.h"
#include "common/terminal/metrics.h"
#include "common/terminal/output_buffer_ref.h"
#include "common/terminal/style.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
namespace Carbon::Terminal {
// Where a line stops within the cell at one of its ends.
//
// A line runs between points, and in a grid of cells the two points it can
// name are a cell's center and a cell's outer edge. Which one an end is decides
// what a line meeting it there becomes: a line ending at a center and another
// leaving that center form a corner, while a line running out through an edge
// carries on past whatever meets it, which is a tee.
//
// This is the distinction a vector graphics stroke draws between a butt cap and
// a square cap, where the square cap extends the stroke by half its width past
// the endpoint. Half a stroke here is half a cell.
//
// Unicode has a glyph for a line reaching only the middle of its cell (U+2574
// through U+2577), so a `Center` end is drawn as one and the reader sees where
// the line really stops rather than having to infer it from the junctions. With
// `Charset::Ascii` there is nothing to draw half a line with, so both ends fill
// their cell and only the junctions around them say which was which.
enum class LineEnd : int8_t {
// The line stops at the center of its end cell. Lines meeting there corner.
Center,
// The line runs out through the outer edge of its end cell, joining whatever
// is beyond it. Lines meeting there tee.
Edge,
};
// A grid of styled cells staged for rendering to a terminal.
//
// Coordinates are 0-based with (0, 0) at the top left, `x` counting terminal
// columns and `y` counting rows.
//
// A buffer renders once, top to bottom, the way a compiler writes diagnostics.
// There is no cursor addressing and nothing is ever redrawn, so a rendered
// buffer is just as valid in a file or a pipe as on a terminal.
//
// Every row is a line, ended by a newline of its own, so nothing is left for
// the terminal to break. A break introduced to fit a width is an ordinary
// newline like any other, which is what lets wrapped text carry an indent or
// sit in a column beside a gutter: a terminal wrapping a row of its own accord
// continues at column zero, under the gutter rather than beside it. It also
// means text copied out of the output holds the lines that were displayed.
//
// The cost is that such a break is in whatever a reader copies, so wrapping
// never puts one inside a word. A path or a URL stays whole and overhangs the
// width when it doesn't fit, which is what keeps it selectable in one piece and
// clickable where a terminal recognizes one. Wrapping only adds breaks as well:
// the newlines already in a caller's text are kept as they are. A row is a row
// once something is drawn into it, so a break the text ends with closes its
// last line rather than opening an empty one after it.
//
// Staging into a grid lets layout position content directly, rather than
// interleaving text, padding, and escape sequences as it goes. That separation
// is what makes the two hard parts tractable: escape sequences are minimized
// once, in `Render`, and the drawing APIs reason about columns on screen rather
// than bytes in a stream.
//
// Which bytes make up a column depends on the charset, and the buffer handles
// that rather than leaving it to callers, because getting it wrong misaligns
// everything downstream of it:
//
// - Under `Charset::Ascii` no UTF-8 processing happens at all. Every byte is
// one column, exactly as a terminal decoding some single-byte encoding will
// treat it, and bytes outside printable ASCII are replaced with `?` because
// there is no telling what such a terminal would draw for them.
// - Under `Charset::Utf8` bytes are decoded as UTF-8. Double-width characters
// occupy both of the columns they will really take, and drawing over either
// column erases the whole character instead of leaving half of one behind.
// Combining marks render into the column before them, so a base character
// and its marks stay in one cell. Carbon source is in Unicode normalization
// form C, which still spells out marks for characters that have no
// precomposed form, so this comes up in ordinary input. Anything with no
// printable rendering, including invalid UTF-8, becomes U+FFFD.
//
// A buffer is `columns()` wide, and that width is the whole point of it: it is
// what wrapping fits text into, and it comes from the terminal where one was
// measured and from `DefaultColumns` where none was. Rows are the direction
// there is no bound in -- a buffer grows downward to whatever is drawn into it,
// up to `MaxRows` -- so laying out is a question of how many rows something
// takes, never of how wide the grid will turn out to be.
//
// The two ways of drawing text differ in whether what they draw is held to the
// width. `DrawText` does not wrap, so text it is given has nowhere else to go:
// it widens the buffer, and `width()` grows past `columns()`.
// `DrawWrappedText` and line drawing are held to the width, since wrapping has
// the next row and a line running outside it came from a wrong extent. A
// drawing of either that starts or ends outside the width is a programming
// error and is checked; a caller placing one already knows the width, since it
// is what decided the layout.
//
// A wrapped block widens the buffer only by the words in it, never by where it
// was told to start: a word it cannot break overhangs, for the reason above.
//
// Nothing is drawn left of the origin or past `MaxColumns` either way, and text
// running off the bottom on its own newlines is clipped rather than checked.
//
// A combining mark renders into the cell before it, so one with no cell before
// it -- at column zero, or on a row nothing has been drawn on -- has nowhere to
// go and is dropped. That is data rather than a coordinate, which is why it is
// dropped rather than checked: source files contain such text.
//
// TODO: None of this handles bidirectional text. A right-to-left run reorders
// on screen, so the column a character occupies stops following from the
// characters before it, which is the assumption every position here rests on:
// that drawing advances left to right by the width of what was drawn. Getting
// this right needs the reordering to happen before anything is placed, which
// makes it a question about where the boundary between a client's layout and
// this buffer should sit -- whether the buffer takes runs that are already in
// visual order, or takes logical order and reorders as it draws, and what it
// then means for a caller to name a column at all. Marking a span and drawing a
// line under it are the hard cases, since a logically contiguous span need not
// be contiguous on screen.
class Buffer {
public:
// The bounds a buffer exists within.
//
// These are far past anything a terminal displays, and exist so that a cell
// index stays representable rather than to ration anything. Unlike
// `columns()`, every way of drawing is held to them: past them nothing is
// drawn and the column still advances, so measuring and drawing agree.
// Clipped rather than checked, since how far unwrapped text or an overhang
// runs is a fact about the text.
static constexpr int MaxColumns = 1 << 14;
static constexpr int MaxRows = 1 << 16;
// The most bytes of text one operation draws or measures.
//
// The column advances by the width of what was drawn whether or not a cell
// was written, so without this a long enough run would carry it past what an
// `int` holds and come back negative. Far more text than any terminal shows,
// and a caller with this much has built it rather than read it off a line.
static constexpr int MaxTextBytes = 1 << 24;
// The widest tab stops a buffer draws to.
//
// Far past any terminal, and small enough that even text made entirely of
// tabs measures into a column an `int` holds: a tab is the one character
// that occupies more columns than it does bytes, so this is what bounds
// `MaxTextBytes` of them.
static constexpr int MaxTabWidth = 64;
// Where a drawing ended: for text, the row it ended on and the column after
// its last code point there; for a line or a box, the cell past the end of
// what it drew.
//
// Everything that draws returns one, so that a caller placing something
// after a drawing advances from this rather than measuring the same text a
// second time. The `Measure` operations return one too, and answer for text
// that hasn't been drawn yet what drawing it would answer.
struct DrawEnd {
int x;
int y;
friend auto operator==(DrawEnd lhs, DrawEnd rhs) -> bool = default;
};
// Constructs an empty buffer holding `charset`, laying out for
// `DefaultColumns`.
explicit Buffer(Charset charset) : Buffer(DefaultColumns, charset) {}
// Constructs an empty buffer `columns` wide, which must be in
// [1, `MaxColumns`], and whose tabs advance to stops `tab_width` columns
// apart.
//
// The width is what everything drawn into the buffer is laid out for and
// checked against, not a starting size. The grid holds it from the start, so
// a row is only ever reallocated for something that overhangs it.
Buffer(int columns, Charset charset, int tab_width = DefaultTabWidth);
// Constructs an empty buffer holding `capabilities`'s charset and tab stops,
// laying out for its width, or for `DefaultColumns` where it has none.
//
// Both numbers are clamped rather than checked. They describe a terminal
// rather than coming from a caller -- `columns` by way of `COLUMNS`, which
// anyone can export as anything -- so a value a grid cannot hold is bad input
// rather than a mistake, and the nearest usable one lays out no worse than
// the fallback would.
explicit Buffer(const Capabilities& capabilities)
: Buffer(std::clamp(capabilities.columns.value_or(DefaultColumns), 1,
MaxColumns),
capabilities.charset,
std::clamp(capabilities.tab_width, 1, MaxTabWidth)) {}
// Returns the width everything drawn into the buffer is laid out for.
auto columns() const -> int { return columns_; }
// Returns the columns the grid currently holds: `columns()` until unwrapped
// text or an overhanging word reached past it, and at least enough to hold
// what did after that.
auto width() const -> int { return width_; }
// Returns the number of rows the grid holds, which is one past the last row
// drawn into.
auto height() const -> int;
auto charset() const -> Charset { return metrics_.charset(); }
// Returns how text is measured for this buffer's charset.
//
// The buffer lays its cells out with this, so a caller deciding where to put
// something asks the same thing the drawing will.
auto metrics() const -> Metrics { return metrics_; }
// Returns where `DrawText` would end for these arguments, without drawing.
//
// Measuring and drawing walk the text with the same code, differing only in
// whether they write a cell, so a layout decision made from this can't
// disagree with what drawing then does.
//
// This is for text that a tab, a newline, or a carriage return makes
// positional. Text with none of them is as wide wherever it is drawn, and
// `Metrics::Width` answers for it without a buffer to draw into.
auto MeasureText(int x, int y, int margin, llvm::StringRef text) const
-> DrawEnd;
// Returns where the `DrawText` taking no margin would end, which draws `text`
// as text of its own beginning at (x, y).
auto MeasureText(int x, int y, llvm::StringRef text) const -> DrawEnd {
return MeasureText(x, y, x, text);
}
// Returns where `DrawWrappedText` would end for these arguments, without
// drawing.
//
// The block and the origin are checked as drawing checks them, so measuring
// answers only for arguments drawing would accept.
auto MeasureWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text) const -> DrawEnd;
// Returns the fewest columns `text` wraps into without overhanging them,
// which is the width of its widest word since wrapping never breaks one.
//
// Wrapping into fewer columns still draws everything; the excess overhangs.
// So this is a layout preference rather than a minimum.
auto MeasureWrapWidth(llvm::StringRef text) const -> int;
// Draws `code_point` at (x, y), which must be a non-negative column and a row
// inside `MaxRows`, widening the buffer and adding rows as needed to reach
// it. One code point is unwrapped text, so it is not held to the width.
//
// Returns the column after it, which is `x` again for a combining mark since
// one renders into the column before it. A double-width character takes both
// its columns wherever it starts: half a character is not something a
// terminal can render, so the choice is between the whole of it and none.
auto DrawCodePoint(int x, int y, char32_t code_point, const Style& style)
-> DrawEnd;
// Draws a horizontal line across `length` columns starting at (x, y).
//
// By default the line runs between the centers of its first and last cells,
// which is what a line connecting two things is: `DrawBox` draws its four
// sides this way, and each pair meets at a corner. `LineEnd::Edge` instead
// runs that end out through the side of its cell, which is what a line
// bounding `length` whole columns of something is, and what makes a line
// meeting it there a tee. A line of one column between two centers is a
// point, and is drawn as one.
//
// Lines join wherever they overlap: a cell records which directions lines
// leave it in, and its glyph follows from those bits alone, so crossings,
// corners, and tees all appear without being asked for and whatever order
// the lines were drawn in. This is the only way to produce a junction, and
// it suffices because a junction in real line art always has the lines that
// imply it running through it. Only line drawing records directions, so text
// containing `-` or `+` is never redrawn as line art.
//
// A cell's style is whatever was drawn there last, so crossing lines of
// different styles do depend on order.
auto DrawHorizontalLine(int x, int y, int length, const Style& style,
LineEnd start = LineEnd::Center,
LineEnd end = LineEnd::Center) -> DrawEnd;
// Draws a vertical line down `length` rows starting at (x, y), with the same
// meaning for its ends. Returns the row after it, in the column it ran down.
auto DrawVerticalLine(int x, int y, int length, const Style& style,
LineEnd start = LineEnd::Center,
LineEnd end = LineEnd::Center) -> DrawEnd;
// Draws the outline of a box with its top-left corner at (x, y).
//
// Each side runs between the centers of the cells it ends in, so the four
// corners come out of the sides meeting there. A box with no interior is
// then the single line that bounds it, and one with no extent in either
// direction is a point, without either being a case of its own.
auto DrawBox(int x, int y, int box_width, int box_height, const Style& style)
-> DrawEnd;
// Draws `text` starting at (x, y), which must be a column at or right of
// `margin` and a row inside `MaxRows`, as part of text whose left edge is
// `margin`.
//
// Nothing here wraps, so text runs off the right of the width when it is
// longer than the room left, and the buffer widens to hold it. That is what
// this is for: text that must not be broken, such as a source line quoted as
// it was written. A caller that wants the text held to the width wants
// `DrawWrappedText`.
//
// Newlines return to column `margin` on the next row, carriage returns to
// column `margin` on the same row, and tabs advance to the next tab stop,
// with stops measured from `margin` so that a quoted source line keeps the
// tab alignment it had in the file wherever the quote is placed. Returns
// where it ended, which for text with a newline in it is on a later row than
// it started.
//
// The margin is what lets text with newlines in it be drawn as differently
// styled spans, each starting where the last ended and all naming the same
// margin, the way `DrawWrappedText` does for a block: a newline in the middle
// of such a run returns to the text's own left edge rather than to wherever
// the span it fell in happened to start.
auto DrawText(int x, int y, int margin, llvm::StringRef text,
const Style& style) -> DrawEnd;
// Draws `text` as text of its own beginning at (x, y), which is then both
// where it starts and the margin its later rows return to.
auto DrawText(int x, int y, llvm::StringRef text, const Style& style)
-> DrawEnd {
return DrawText(x, y, x, text, style);
}
// Draws `text` starting at (x, y), into the block of `max_width` columns
// beginning at `margin`.
//
// The block must lie within `columns()` and `x` within the block, so
// `0 <= margin <= x < margin + max_width <= columns()`. A block is a division
// of the width rather than something that can exceed it: what a caller wants
// when it has nothing to divide is `max_width` of `columns() - margin`, the
// whole of what is left.
//
// The block is what the text wraps within, and (x, y) is only where this run
// of it starts: rows after the first begin at `margin`, and how much room a
// row has is measured from there. A block whose spans are styled differently
// is drawn as one call per span, each starting where the last ended and all
// naming the same margin and width. Passing `x` as the margin draws a block
// in one call.
//
// Wrapping breaks at ASCII spaces, tabs, and carriage returns, and only
// there. A word here is whatever lies between two of them, so a URL is one
// word, and one too long for a row of its own is moved down to one and then
// overhangs it rather than being broken.
//
// Whitespace stops at the block's edge rather than running past it, so the
// spaces between two words stay on the row the first of them ended and the
// row the second wraps onto begins at the margin. Spaces the text opens with,
// or that follow a newline in it, are kept as they are, since those are
// indentation the caller wrote.
//
// Newlines are breaks the caller already made, and are kept as they are:
// wrapping only adds breaks to the text it is given. They break the line as a
// wrap does, continuing at `margin` on the next row, and carriage returns are
// dropped so that CRLF endings break exactly once.
//
// A tab is both a break opportunity and a jump to the next tab stop, with
// stops measured from `margin` rather than from `x`. The margin is the one
// column every row of the block begins at, so the stops are the same on each
// of them and a tabbed column stays a column however the text wraps; stops
// from `x` would move with the span that happened to be drawn first. A tab
// that would reach past the block stops at its edge, like the spaces do,
// leaving the word after it to wrap.
//
// `DrawText` is the way to draw text that should not wrap at all, and differs
// in more than that: it keeps every space, and returns to the margin on a
// carriage return rather than dropping it.
//
// Returns where it ended.
//
// TODO: There is no mode that reflows, treating the newlines in `text` as
// breaks to be chosen again rather than kept. Text that arrives wrapped to
// some other width keeps that wrapping, which is wrong for it wherever that
// width isn't the one it is being drawn into. Add one when there is a caller
// with such text, since which breaks a reflow may discard -- every newline,
// or only those a previous wrapping introduced -- is a question about where
// that text came from.
auto DrawWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text, const Style& style) -> DrawEnd;
// Renders the grid, appending the bytes that draw it to `out`.
//
// Each row ends in a newline, with trailing blank cells dropped so output
// carries no invisible padding. The rendering ends with the style turned off
// so nothing bleeds into what is printed next, and a style that paints blank
// cells is turned off at each row's end so a background does not run to the
// right edge. Color is chosen here rather than at construction because it
// affects only how cells are serialized, while the charset decides how
// content is laid out into them.
auto Render(OutputBufferRef out, ColorMode mode) const -> void;
// Renders the grid and writes it to `file`.
//
// The whole grid goes out in one `write` where the destination accepts it,
// which is what gives the output whatever atomicity the descriptor offers
// against other writers: a terminal or a pipe interleaves at write
// boundaries, so one call per rendered buffer is the most that can be had
// without a lock.
auto WriteTo(Filesystem::WriteFileRef file, ColorMode mode) const
-> ErrorOr<Success, Filesystem::FdError>;
private:
// The directions in which drawn lines leave a cell, and whether the cell
// holds line art at all. A cell's glyph is a function of the directions
// alone.
enum LineDirection : uint8_t {
LineLeft = 1 << 0,
LineRight = 1 << 1,
LineUp = 1 << 2,
LineDown = 1 << 3,
LineDirections = 0b1111,
// Set on every cell line drawing writes. A cell can hold line art and no
// directions -- a line between one center and itself is a point -- and
// without this such a cell would be indistinguishable from one holding
// text, so nothing drawn later would join it.
LineCell = 1 << 4,
};
struct Cell {
// The code point rendered here. For a cell with `lines` set, this is
// derived from those bits and the charset.
char32_t code_point = ' ';
Style style;
// Which directions drawn lines leave this cell in, with `LineCell` set,
// or zero for a cell holding text.
uint8_t lines = 0;
// Whether this cell is the right half of a double-width character, and so
// renders nothing of its own.
bool is_continuation = false;
};
// Checks that `text` is short enough to measure without overflowing a column.
static auto CheckTextSize(llvm::StringRef text) -> void {
CARBON_CHECK(text.size() <= MaxTextBytes,
"Laying out {0} bytes of text is past the {1} one operation "
"handles.",
text.size(), MaxTextBytes);
}
auto CellIndex(int x, int y) const -> int { return y * width_ + x; }
auto CellAt(int x, int y) -> Cell& { return cells_[CellIndex(x, y)]; }
auto CellAt(int x, int y) const -> const Cell& {
return cells_[CellIndex(x, y)];
}
// Checks that (x, y) is somewhere unwrapped text may start, which the width
// does not decide.
//
// The text walks check this themselves, together with the bounds particular
// to each: they are inlined into every text operation, and one check there
// costs measurably less than two.
auto CheckTextOrigin(int x, int y) const -> void {
CARBON_CHECK(x >= 0 && y >= 0 && y < MaxRows,
"Drawing text at ({0}, {1}) is outside the {2} rows a buffer "
"covers.",
x, y, MaxRows);
}
// Checks that (x, y) is somewhere a drawing held to the width may start.
auto CheckOrigin(int x, int y) const -> void {
CARBON_CHECK(
x >= 0 && x < columns_ && y >= 0 && y < MaxRows,
"Drawing at ({0}, {1}) is outside the {2} columns and {3} rows "
"a buffer covers.",
x, y, columns_, MaxRows);
}
// Places `code_point` at (x, y) without checking it against the target width
// or `MaxRows`, which text reaches on its own by overhanging or by carrying
// newlines. Past either, nothing is drawn and the column still advances. The
// coordinates must be non-negative, which follows from the origin the walk
// was checked at.
auto PlaceCodePoint(int x, int y, char32_t code_point, const Style& style)
-> int;
// The walks behind the text operations, over which drawing and measuring are
// the same code. `place` is called with each code point and where it goes,
// and returns the column after it: `PlaceCodePoint` when drawing, and the
// width alone when measuring.
template <typename PlaceFn>
auto WalkText(int x, int y, int margin, llvm::StringRef text,
PlaceFn place) const -> DrawEnd;
template <typename PlaceFn>
auto WalkWrappedText(int x, int y, int margin, int max_width,
llvm::StringRef text, PlaceFn place) const -> DrawEnd;
// Adds rows until row `y` exists.
auto EnsureRow(int y) -> void;
// Widens the grid until column `x` exists, reflowing the rows it already
// holds, which are stored back to back. Only something overhanging the target
// width reaches past it, so this runs for nothing else.
auto EnsureColumn(int x) -> void;
// Resets the cells in row `y` spanning columns [x, x + width), along with
// either half of a double-width character that straddles the range's edges.
auto ClearCells(int x, int y, int width) -> void;
// Appends `code_point` to the marks rendered with the cell before column `x`.
auto AttachCombiningMark(int x, int y, char32_t code_point) -> void;
// Adds `directions` to the lines through (x, y) and updates its glyph.
auto DrawLine(int x, int y, uint8_t directions, const Style& style) -> void;
// Returns the last column in row `y` that renders anything under `mode`, or
// -1 when the row renders nothing.
auto LastVisibleColumn(int y, ColorMode mode) const -> int;
// The width laid out for, and the width the grid holds. They differ only
// where something overhung the first.
int columns_;
int width_;
int tab_width_;
Metrics metrics_;
llvm::SmallVector<Cell, 0> cells_;
// Combining marks, as UTF-8, for the few cells that have any, keyed by cell
// index. Kept out of `Cell` so that the common case of no marks costs
// nothing per cell. Always empty under `Charset::Ascii`.
llvm::DenseMap<int, std::string> combining_marks_;
};
} // namespace Carbon::Terminal
#endif // CARBON_COMMON_TERMINAL_BUFFER_H_
File diff suppressed because it is too large Load Diff
+243
View File
@@ -0,0 +1,243 @@
// 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
#include "common/terminal/capabilities.h"
#include <sys/ioctl.h>
#include <unistd.h>
#include <cstdlib>
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
namespace Carbon::Terminal {
// Returns the value of `name` in the process environment, empty when unset.
static auto GetEnv(const char* name) -> llvm::StringRef {
const char* value = std::getenv(name);
return value ? llvm::StringRef(value) : llvm::StringRef();
}
auto ColorEnvironment::FromProcess() -> ColorEnvironment {
return {.no_color = GetEnv("NO_COLOR"),
.clicolor_force = GetEnv("CLICOLOR_FORCE"),
.force_color = GetEnv("FORCE_COLOR"),
.clicolor = GetEnv("CLICOLOR"),
.colorterm = GetEnv("COLORTERM"),
.term_program = GetEnv("TERM_PROGRAM"),
.term = GetEnv("TERM")};
}
// Returns whether the environment and the stream call for color, ignoring any
// explicit preference. See `ChooseColorMode` for the precedence this
// implements and where it comes from.
static auto EnvironmentEnablesColor(const ColorEnvironment& env,
bool is_terminal) -> bool {
if (!env.no_color.empty()) {
return false;
}
// The forcing variables use `0` to decline to force, and `FORCE_COLOR` takes
// it further as a request to disable.
if (env.force_color == "0") {
return false;
}
if (!env.force_color.empty() ||
(!env.clicolor_force.empty() && env.clicolor_force != "0")) {
return true;
}
if (env.clicolor == "0") {
return false;
}
if (!is_terminal) {
return false;
}
// `dumb` says outright that escape sequences won't render, which outranks
// anything below claiming they will.
if (env.term == "dumb") {
return false;
}
// Any of these identifies the terminal as something that renders escapes. A
// terminal that none of them describe can't be assumed to.
//
// `COLORTERM` and `TERM_PROGRAM` stand on their own rather than refining
// `TERM`: `TERM` names a terminfo entry, while these name the emulator and
// the color it handles.
return !env.term.empty() || !env.colorterm.empty() ||
!env.term_program.empty();
}
// Returns the richest color escapes the terminal is believed to accept.
//
// Every signal here is a heuristic: there is no way to ask a terminal what it
// supports without writing to it and parsing a reply, which would be far too
// invasive for a compiler. Guessing too high garbles color on a terminal that
// can't keep up, and guessing too low only makes output plainer, so unknown
// terminals get the conservative answer.
static auto DetectColorDepth(const ColorEnvironment& env) -> ColorMode {
// `FORCE_COLOR`'s levels name a depth outright.
if (auto mode = llvm::StringSwitch<std::optional<ColorMode>>(env.force_color)
.Case("1", ColorMode::Ansi16)
.Case("2", ColorMode::Ansi256)
.Case("3", ColorMode::Truecolor)
.Default(std::nullopt)) {
return *mode;
}
// The convention documented at
// https://github.com/termstandard/colors#checking-for-colorterm.
if (env.colorterm == "truecolor" || env.colorterm == "24bit") {
return ColorMode::Truecolor;
}
// `TERM_PROGRAM` identifies the emulator regardless of how `TERM` is set,
// which matters because several of these ship a conservative `TERM` while
// rendering far more than it claims.
{
if (auto mode =
llvm::StringSwitch<std::optional<ColorMode>>(env.term_program)
.Case("vscode", ColorMode::Truecolor)
.Case("iTerm.app", ColorMode::Truecolor)
.Case("WarpTerminal", ColorMode::Truecolor)
.Case("Hyper", ColorMode::Truecolor)
.Case("Tabby", ColorMode::Truecolor)
.Case("Terminus", ColorMode::Truecolor)
// Apple's Terminal renders only the 256-color palette.
.Case("Apple_Terminal", ColorMode::Ansi256)
.Default(std::nullopt)) {
return *mode;
}
}
// The enumerated terminals that stand in for a terminfo lookup.
{
if (auto mode = llvm::StringSwitch<std::optional<ColorMode>>(env.term)
.Case("xterm-kitty", ColorMode::Truecolor)
.Case("alacritty", ColorMode::Truecolor)
.Case("wezterm", ColorMode::Truecolor)
.Case("ghostty", ColorMode::Truecolor)
.StartsWith("foot", ColorMode::Truecolor)
.StartsWith("contour", ColorMode::Truecolor)
.StartsWith("vte", ColorMode::Truecolor)
.EndsWith("-direct", ColorMode::Truecolor)
.EndsWith("-truecolor", ColorMode::Truecolor)
.EndsWith("-256color", ColorMode::Ansi256)
.EndsWith("-256", ColorMode::Ansi256)
.Default(std::nullopt)) {
return *mode;
}
}
// Color is called for, but nothing said how much of it works.
return ColorMode::Ansi16;
}
auto ChooseColorMode(Preference preference, const ColorEnvironment& env,
bool is_terminal) -> ColorMode {
switch (preference) {
case Preference::Never:
return ColorMode::NoColor;
case Preference::Always:
break;
case Preference::Auto:
if (!EnvironmentEnablesColor(env, is_terminal)) {
return ColorMode::NoColor;
}
break;
}
return DetectColorDepth(env);
}
auto ChooseCharset(Preference preference, llvm::StringRef locale) -> Charset {
switch (preference) {
case Preference::Never:
return Charset::Ascii;
case Preference::Always:
return Charset::Utf8;
case Preference::Auto:
break;
}
// Locale names spell the encoding several ways: `en_US.UTF-8`, `C.utf8`, and
// bare `UTF-8` all appear in the wild.
return locale.contains_insensitive("utf-8") ||
locale.contains_insensitive("utf8")
? Charset::Utf8
: Charset::Ascii;
}
auto ChooseBackground(BackgroundPreference preference,
llvm::StringRef colorfgbg) -> Background {
switch (preference) {
case BackgroundPreference::Dark:
return Background::Dark;
case BackgroundPreference::Light:
return Background::Light;
case BackgroundPreference::Auto:
break;
}
// The background is the last field, since some terminals write a third one
// between the foreground and it.
llvm::StringRef background = colorfgbg.rsplit(';').second;
unsigned index = 0;
if (!llvm::to_integer(background, index) || index > 15) {
// Anything else, including the `default` some terminals write and the
// variable being unset, says nothing.
return Background::Dark;
}
// The first eight palette entries are the dark half, except that the eighth
// is white and the ninth is the dark gray that follows it.
return (index <= 6 || index == 8) ? Background::Dark : Background::Light;
}
// Returns the locale that determines the terminal's character encoding,
// following the precedence POSIX defines for `LC_CTYPE`.
static auto GetLocale() -> llvm::StringRef {
for (const char* name : {"LC_ALL", "LC_CTYPE", "LANG"}) {
if (llvm::StringRef value = GetEnv(name); !value.empty()) {
return value;
}
}
return "";
}
// Returns the terminal's width in columns, or nullopt when there is nothing to
// ask.
//
// `COLUMNS` comes first: when it is exported, the user has deliberately
// overridden the real width.
static auto GetColumns(int fd) -> std::optional<int> {
int columns = 0;
if (llvm::to_integer(GetEnv("COLUMNS"), columns) && columns > 0) {
return columns;
}
struct winsize size = {};
if (ioctl(fd, TIOCGWINSZ, &size) == 0 && size.ws_col > 0) {
return size.ws_col;
}
return std::nullopt;
}
auto Capabilities::Detect(Filesystem::WriteFileRef file,
Preferences preferences) -> Capabilities {
int fd = file.unix_fd();
Capabilities capabilities;
capabilities.is_terminal = isatty(fd) != 0;
capabilities.color_mode =
ChooseColorMode(preferences.color, ColorEnvironment::FromProcess(),
capabilities.is_terminal);
capabilities.charset = ChooseCharset(preferences.utf8, GetLocale());
capabilities.background =
ChooseBackground(preferences.background, GetEnv("COLORFGBG"));
capabilities.columns = GetColumns(fd);
return capabilities;
}
} // namespace Carbon::Terminal
+253
View File
@@ -0,0 +1,253 @@
// 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
#ifndef CARBON_COMMON_TERMINAL_CAPABILITIES_H_
#define CARBON_COMMON_TERMINAL_CAPABILITIES_H_
#include <cstdint>
#include <optional>
#include "common/filesystem.h"
#include "common/terminal/color.h"
#include "llvm/ADT/StringRef.h"
namespace Carbon::Terminal {
// The encoding the terminal decodes output with.
//
// This decides far more than which characters can be drawn: rendering has to
// count the columns a run of bytes will occupy, and that count only follows
// from the code points those bytes encode if the terminal agrees about the
// encoding. Disagreeing misaligns the entire line rather than drawing a single
// character wrong.
//
// No other encoding is modeled. A terminal decoding something else, an ISO 8859
// part for example, is treated as `Ascii`. That is correct output for any of
// them, as they all encode printable ASCII as itself, and rendering in one
// natively would mean carrying its conversion and column-width tables to gain
// nothing but nicer line drawing, which `Ascii` already has a fallback for. So
// `Utf8` is used only where the environment says outright that the terminal
// decodes UTF-8, and `Ascii` does no UTF-8 processing at all.
enum class Charset : int8_t {
// Every byte is one column, and lines are drawn from `-`, `|`, and `+`.
//
// Bytes outside printable ASCII are replaced rather than passed through,
// because a terminal decoding some single-byte encoding will render them as
// something, and there is no way to know what.
Ascii,
// Bytes are decoded as UTF-8, giving double-width characters two columns and
// combining marks none, and lines are drawn with box-drawing characters.
Utf8,
};
// Whether to use one of the terminal features detection decides about, where
// an explicit request overrides what detection would conclude.
//
// This is the tri-state that a `--color=never` style flag parses into. It says
// nothing about which feature is being requested; `Preferences` holds one of
// these per feature.
enum class Preference : int8_t {
// Decide from the environment and the stream.
Auto,
// Never use the feature, whatever the environment says.
Never,
// Use the feature even when the stream isn't a terminal. This is what a
// caller wants when piping into a pager, capturing output for later replay,
// or writing a test.
Always,
};
// What the terminal draws its text on.
//
// Nothing about the rendering depends on the exact color, only on which side of
// the middle it sits: a color chosen to read on one is hard to read on the
// other.
enum class Background : int8_t {
Dark,
Light,
};
// An explicit statement of what the terminal draws its text on, where `Auto`
// leaves it to detection.
//
// This is a tri-state like `Preference`, but its two settings name the answer
// rather than turning a feature on and off, so it is an enum of its own.
enum class BackgroundPreference : int8_t {
Auto,
Dark,
Light,
};
// An explicit preference for each feature detection decides about, normally
// parsed from command line flags.
struct Preferences {
Preference color = Preference::Auto;
Preference utf8 = Preference::Auto;
BackgroundPreference background = BackgroundPreference::Auto;
};
// The environment variables that control whether and how color is used.
//
// An unset variable and one set to the empty string mean the same thing
// throughout: no opinion. Values point into the process environment and are
// invalidated by anything that modifies it.
struct ColorEnvironment {
// Reads the variables from the process environment.
static auto FromProcess() -> ColorEnvironment;
llvm::StringRef no_color;
llvm::StringRef clicolor_force;
llvm::StringRef force_color;
llvm::StringRef clicolor;
llvm::StringRef colorterm;
llvm::StringRef term_program;
llvm::StringRef term;
};
// Returns the color mode to render with.
//
// Whether to use color at all is decided first, from highest priority to
// lowest:
//
// - An explicit `Never` or `Always` preference.
// - `NO_COLOR` set to any non-empty value disables color: see
// https://no-color.org.
// - `FORCE_COLOR=0` disables color, following the Node convention.
// - Any other non-empty `FORCE_COLOR`, or a non-empty `CLICOLOR_FORCE` other
// than `0`, enables color even when the stream isn't a terminal.
// - `CLICOLOR=0` disables color.
// - `TERM=dumb` disables color, being an explicit statement that escape
// sequences won't render.
// - Otherwise color is used only when the stream is a terminal and something
// identifies that terminal: `TERM` set to anything else, or `COLORTERM` or
// `TERM_PROGRAM` set at all. The latter two stand on their own rather than
// refining `TERM`, because the emulator sets them itself and they are
// specifically about color, while `TERM` is left unset by anything not
// launched from a shell.
//
// How much color to use is then guessed from `FORCE_COLOR`'s level,
// `COLORTERM`, `TERM_PROGRAM`, and `TERM`, falling back to `Ansi16` when color
// is called for but nothing says how much of it works. Apart from
// `FORCE_COLOR`, which is a request rather than a description, none of these
// enable color on their own, so a rich `COLORTERM` inherited by a redirected
// stream can't put escape sequences into it.
//
// Depth comes from enumerating known terminals rather than from terminfo,
// which trades a list to maintain here for not depending on databases that are
// routinely absent from the containers and CI images this runs in. An
// unrecognized terminal gets the conservative answer.
//
// This is separated from `Capabilities::Detect` so that the policy can be
// tested without touching the process environment.
auto ChooseColorMode(Preference preference, const ColorEnvironment& env,
bool is_terminal) -> ColorMode;
// Returns the encoding to render with, where `locale` is the value of the
// first set variable among `LC_ALL`, `LC_CTYPE`, and `LANG`.
//
// Only a locale that names UTF-8 gets `Utf8`. Guessing wrong in that direction
// costs alignment on every line that isn't pure ASCII, while guessing wrong
// the other way only makes output plainer.
auto ChooseCharset(Preference preference, llvm::StringRef locale) -> Charset;
// Returns what the terminal draws its text on, where `colorfgbg` is the value
// of `COLORFGBG`.
//
// That variable is the only thing a process can read without talking to the
// terminal. `rxvt` and its derivatives set it, as do a few others, to the
// foreground and background palette indices separated by `;` -- sometimes with
// a third field between them -- so the background is the last of them. An index
// of 0 through 6 or 8 is a dark one, 7 and 9 through 15 a light one, and
// anything outside that range says nothing.
//
// It is missing far more often than it is present, and stale when the user
// changes their theme without restarting, so anything it doesn't answer is
// treated as dark. Guessing wrong that way costs contrast; guessing wrong the
// other way puts light text on a light background.
auto ChooseBackground(BackgroundPreference preference,
llvm::StringRef colorfgbg) -> Background;
// The width to lay out for when nothing says how wide the output is.
//
// Layout always has a width to fit, because the alternative is output laid out
// as if nothing bounded it, which a terminal then wraps at column zero --
// breaking every indent and gutter it was given, and in the middle of whatever
// word it lands on. The cost of guessing is asymmetric: a viewer wider than
// this sees slack on the right, while one narrower sees the wrapping done
// twice, ours and then its own.
//
// Eighty is the traditional terminal width, and narrower ones are rare enough
// that fitting them would cost more in wasted width everywhere else.
inline constexpr int DefaultColumns = 80;
// The columns between tab stops, absent anything saying otherwise.
//
// Eight is the interval terminfo records as `it#8` for all but a handful of
// legacy entries. Nothing measures a terminal's stops, so unlike its width this
// stands in for no measurement: `Capabilities` carries it as a plain value
// rather than as one a caller can tell apart from an absence.
inline constexpr int DefaultTabWidth = 8;
// What the terminal behind a stream can render, and how wide it is.
//
// Detect this once per stream at startup and pass it down; the fields come from
// environment queries and system calls that shouldn't be repeated per
// diagnostic.
struct Capabilities {
// Detects the capabilities of the terminal behind `file`, honoring
// `preferences`.
//
// Detection reads the descriptor directly, because `isatty` and `TIOCGWINSZ`
// are what answer the question and no stream abstraction exposes them.
// LLVM's `raw_ostream::has_colors()` is not a substitute for the enablement
// rule above, which recognizes terminals its `TERM` list doesn't.
//
// An `OSC 11` query would ask the terminal what it draws on, which is the
// only accurate answer and what `vim`, `delta`, and `bat` do. It isn't one a
// non-interactive tool can use: the reply has to be waited for, and drawing
// with it only when it arrives in time would leave the colors depending on
// that. Reading it also consumes whatever was typed ahead for the next shell
// command, along with the input a compile may be taking from stdin.
// `COLORFGBG` is the passive stand-in that costs none of this.
static auto Detect(Filesystem::WriteFileRef file,
Preferences preferences = {}) -> Capabilities;
// The richest color escapes the terminal is believed to understand.
ColorMode color_mode = ColorMode::NoColor;
// The encoding the terminal decodes output with.
Charset charset = Charset::Ascii;
// What the terminal draws its text on.
Background background = Background::Dark;
// Whether the stream is attached to a terminal at all. Note that color can
// still be in use when this is false, if the environment forces it.
bool is_terminal = false;
// The terminal's width, or none when nothing says how wide the output is.
// Positive whenever it is set, so layout can divide by it freely.
//
// This says what was measured, and nothing is invented to fill it in: an
// absence is a real answer about a pipe nobody described. Laying out still
// needs a width, and `DefaultColumns` is what a layout falls back to, so
// whether this is set decides whether output is fitted to the terminal in
// front of it or to a width chosen to be safe wherever it ends up.
std::optional<int> columns;
// The columns between the terminal's tab stops, which is what a tab in text
// advances to the next of.
//
// TODO: Nothing sets this away from `DefaultTabWidth`. A terminal's stops are
// mutable at runtime -- `hts` sets one and `tbc` clears them -- so the only
// report of the live ones is `DECRQPSR`, which few emulators outside `xterm`
// answer, or a `DSR-CPR` round trip after writing a tab, which nearly all do.
// Either means putting the descriptor in raw mode and reading a reply with a
// timeout. Add it when a terminal that disagrees with eight is worth that.
int tab_width = DefaultTabWidth;
};
} // namespace Carbon::Terminal
#endif // CARBON_COMMON_TERMINAL_CAPABILITIES_H_
+340
View File
@@ -0,0 +1,340 @@
// 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
#include "common/terminal/capabilities.h"
#include <gtest/gtest.h>
#include <utility>
#include "common/filesystem.h"
namespace Carbon::Terminal {
namespace {
// Detection policy is a pure function of the environment and whether the
// stream is a terminal, so these tests build the environment directly instead
// of mutating the process environment, which would leak between tests and race
// with anything else running.
auto OnTerminal(const ColorEnvironment& env) -> ColorMode {
return ChooseColorMode(Preference::Auto, env, /*is_terminal=*/true);
}
auto OffTerminal(const ColorEnvironment& env) -> ColorMode {
return ChooseColorMode(Preference::Auto, env, /*is_terminal=*/false);
}
// A terminal that supports color, for tests varying one other variable.
auto ColorTerminal() -> ColorEnvironment { return {.term = "xterm-256color"}; }
TEST(CapabilitiesTest, ColorNeedsATerminal) {
EXPECT_EQ(OnTerminal(ColorTerminal()), ColorMode::Ansi256);
// Writing to a file or a pipe must stay plain, or every redirected build log
// fills with escape sequences.
EXPECT_EQ(OffTerminal(ColorTerminal()), ColorMode::NoColor);
// A terminal that nothing says anything about can't be assumed to render
// escapes.
EXPECT_EQ(OnTerminal({}), ColorMode::NoColor);
EXPECT_EQ(OnTerminal({.term = ""}), ColorMode::NoColor);
EXPECT_EQ(OnTerminal({.term = "dumb"}), ColorMode::NoColor);
}
TEST(CapabilitiesTest, ColorFromTheEmulatorWithoutTerm) {
// `TERM` is unset for anything not launched from a shell, but the emulator
// sets `COLORTERM` and `TERM_PROGRAM` itself, and both are specifically
// about color. Either one identifies the terminal on its own, at whatever
// depth it names.
EXPECT_EQ(OnTerminal({.colorterm = "truecolor"}), ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.colorterm = "yes"}), ColorMode::Ansi16);
EXPECT_EQ(OnTerminal({.term_program = "vscode"}), ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term_program = "unknown"}), ColorMode::Ansi16);
// An empty value says nothing at all.
EXPECT_EQ(OnTerminal({.colorterm = "", .term_program = ""}),
ColorMode::NoColor);
// `dumb` outranks them: it states outright that escapes won't render.
EXPECT_EQ(OnTerminal({.colorterm = "truecolor", .term = "dumb"}),
ColorMode::NoColor);
// And none of them enable color off a terminal.
EXPECT_EQ(OffTerminal({.colorterm = "truecolor"}), ColorMode::NoColor);
EXPECT_EQ(OffTerminal({.term_program = "vscode"}), ColorMode::NoColor);
}
TEST(CapabilitiesTest, ExplicitPreferenceWins) {
ColorEnvironment forcing = {.force_color = "3", .term = "xterm-256color"};
EXPECT_EQ(ChooseColorMode(Preference::Never, forcing, /*is_terminal=*/true),
ColorMode::NoColor);
ColorEnvironment disabling = {.no_color = "1", .term = "dumb"};
EXPECT_EQ(ChooseColorMode(Preference::Always, disabling,
/*is_terminal=*/false),
ColorMode::Ansi16);
// Forcing color on without any hint of what the terminal handles gets the
// depth every color terminal supports.
EXPECT_EQ(ChooseColorMode(Preference::Always, {}, /*is_terminal=*/false),
ColorMode::Ansi16);
EXPECT_EQ(ChooseColorMode(Preference::Always, ColorTerminal(),
/*is_terminal=*/false),
ColorMode::Ansi256);
}
TEST(CapabilitiesTest, NoColor) {
// https://no-color.org: any non-empty value disables color, whatever it is.
EXPECT_EQ(OnTerminal({.no_color = "1", .term = "xterm-256color"}),
ColorMode::NoColor);
EXPECT_EQ(OnTerminal({.no_color = "0", .term = "xterm-256color"}),
ColorMode::NoColor);
// Being set to the empty string carries no meaning, so it must not disable
// color: an empty variable inherited from a wrapper script would otherwise
// silently turn color off everywhere.
EXPECT_EQ(OnTerminal({.no_color = "", .term = "xterm-256color"}),
ColorMode::Ansi256);
// It outranks the forcing variables.
EXPECT_EQ(OnTerminal({.no_color = "1", .force_color = "3"}),
ColorMode::NoColor);
EXPECT_EQ(OnTerminal({.no_color = "1", .clicolor_force = "1"}),
ColorMode::NoColor);
}
TEST(CapabilitiesTest, ForceColor) {
// Color even without a terminal, at the depth the level names.
EXPECT_EQ(OffTerminal({.force_color = "1"}), ColorMode::Ansi16);
EXPECT_EQ(OffTerminal({.force_color = "2"}), ColorMode::Ansi256);
EXPECT_EQ(OffTerminal({.force_color = "3"}), ColorMode::Truecolor);
// The level overrides what the terminal claims.
EXPECT_EQ(OnTerminal({.force_color = "1", .colorterm = "truecolor"}),
ColorMode::Ansi16);
// Any other non-empty value enables color without naming a depth.
EXPECT_EQ(OffTerminal({.force_color = "true"}), ColorMode::Ansi16);
EXPECT_EQ(OffTerminal({.force_color = "true", .term = "xterm-256color"}),
ColorMode::Ansi256);
// Zero disables color outright, even on a capable terminal.
EXPECT_EQ(OnTerminal({.force_color = "0", .term = "xterm-256color"}),
ColorMode::NoColor);
}
TEST(CapabilitiesTest, EmptyValuesCarryNoOpinion) {
// An empty variable means the same as an unset one throughout, so a wrapper
// script that exports one without a value changes nothing.
EXPECT_EQ(OffTerminal({.force_color = ""}), ColorMode::NoColor);
EXPECT_EQ(OffTerminal({.clicolor_force = ""}), ColorMode::NoColor);
EXPECT_EQ(OnTerminal({.no_color = "", .term = "xterm-256color"}),
ColorMode::Ansi256);
EXPECT_EQ(OnTerminal({.clicolor = "", .term = "xterm-256color"}),
ColorMode::Ansi256);
EXPECT_EQ(OnTerminal({.force_color = "", .term = "xterm-256color"}),
ColorMode::Ansi256);
}
TEST(CapabilitiesTest, CliColor) {
// The BSD convention: `CLICOLOR_FORCE` enables color off a terminal, and
// `CLICOLOR=0` disables it on one.
EXPECT_EQ(OffTerminal({.clicolor_force = "1"}), ColorMode::Ansi16);
EXPECT_EQ(OffTerminal({.clicolor_force = "1", .term = "xterm-256color"}),
ColorMode::Ansi256);
// `0` means "don't force", not "disable", so a terminal still gets color.
EXPECT_EQ(OnTerminal({.clicolor_force = "0", .term = "xterm-256color"}),
ColorMode::Ansi256);
EXPECT_EQ(OffTerminal({.clicolor_force = "0"}), ColorMode::NoColor);
EXPECT_EQ(OnTerminal({.clicolor = "0", .term = "xterm-256color"}),
ColorMode::NoColor);
EXPECT_EQ(OnTerminal({.clicolor = "1", .term = "xterm-256color"}),
ColorMode::Ansi256);
// Forcing beats disabling.
EXPECT_EQ(OnTerminal({.clicolor_force = "1", .clicolor = "0"}),
ColorMode::Ansi16);
}
TEST(CapabilitiesTest, ColorDepthFromColorterm) {
EXPECT_EQ(OnTerminal({.colorterm = "truecolor", .term = "xterm"}),
ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.colorterm = "24bit", .term = "xterm"}),
ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.colorterm = "", .term = "xterm"}), ColorMode::Ansi16);
// `COLORTERM` can't enable color off a terminal, so a rich value inherited
// by a redirected stream can't smuggle escapes into it.
EXPECT_EQ(OffTerminal({.colorterm = "truecolor", .term = "xterm"}),
ColorMode::NoColor);
}
TEST(CapabilitiesTest, ColorDepthFromTermProgram) {
EXPECT_EQ(OnTerminal({.term_program = "vscode", .term = "xterm"}),
ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term_program = "iTerm.app", .term = "xterm"}),
ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term_program = "WarpTerminal", .term = "xterm"}),
ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term_program = "Hyper", .term = "xterm"}),
ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term_program = "Tabby", .term = "xterm"}),
ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term_program = "Terminus", .term = "xterm"}),
ColorMode::Truecolor);
// Apple's Terminal renders only the 256-color palette.
EXPECT_EQ(OnTerminal({.term_program = "Apple_Terminal", .term = "xterm"}),
ColorMode::Ansi256);
EXPECT_EQ(OnTerminal({.term_program = "unknown", .term = "xterm-256color"}),
ColorMode::Ansi256);
}
TEST(CapabilitiesTest, ColorDepthFromTerm) {
EXPECT_EQ(OnTerminal({.term = "xterm-kitty"}), ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term = "alacritty"}), ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term = "wezterm"}), ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term = "foot-extra"}), ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term = "xterm-direct"}), ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term = "ghostty"}), ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term = "contour-latest"}), ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term = "xterm-truecolor"}), ColorMode::Truecolor);
EXPECT_EQ(OnTerminal({.term = "xterm-256color"}), ColorMode::Ansi256);
EXPECT_EQ(OnTerminal({.term = "screen-256color"}), ColorMode::Ansi256);
EXPECT_EQ(OnTerminal({.term = "putty-256"}), ColorMode::Ansi256);
// A terminal matching both a truecolor and a 256-color pattern takes the
// richer one, so the order these are tried in is load-bearing.
EXPECT_EQ(OnTerminal({.term = "vte-256color"}), ColorMode::Truecolor);
// Known to render color, but with nothing saying how much.
EXPECT_EQ(OnTerminal({.term = "xterm"}), ColorMode::Ansi16);
EXPECT_EQ(OnTerminal({.term = "linux"}), ColorMode::Ansi16);
}
TEST(CapabilitiesTest, Charset) {
EXPECT_EQ(ChooseCharset(Preference::Auto, "en_US.UTF-8"), Charset::Utf8);
EXPECT_EQ(ChooseCharset(Preference::Auto, "C.utf8"), Charset::Utf8);
EXPECT_EQ(ChooseCharset(Preference::Auto, "en_US.utf-8"), Charset::Utf8);
// Drawing box characters into a terminal decoding something else turns them
// into several bytes of mojibake and destroys the alignment they were for.
EXPECT_EQ(ChooseCharset(Preference::Auto, "C"), Charset::Ascii);
EXPECT_EQ(ChooseCharset(Preference::Auto, "POSIX"), Charset::Ascii);
EXPECT_EQ(ChooseCharset(Preference::Auto, "en_US.ISO-8859-1"),
Charset::Ascii);
EXPECT_EQ(ChooseCharset(Preference::Auto, ""), Charset::Ascii);
EXPECT_EQ(ChooseCharset(Preference::Never, "en_US.UTF-8"), Charset::Ascii);
EXPECT_EQ(ChooseCharset(Preference::Always, "C"), Charset::Utf8);
}
TEST(CapabilitiesTest, BackgroundFromColorFgBg) {
// `fg;bg`, which is what `rxvt` and its derivatives write.
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "15;0"),
Background::Dark);
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "0;15"),
Background::Light);
// The eighth entry is white and the ninth the dark gray after it, so the
// halves are not simply the low and high eight.
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "0;7"),
Background::Light);
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "15;8"),
Background::Dark);
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "15;6"),
Background::Dark);
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "0;9"),
Background::Light);
// Some terminals write a third field between the two.
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "15;default;0"),
Background::Dark);
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "0;default;15"),
Background::Light);
}
TEST(CapabilitiesTest, BackgroundWithNothingToGoOn) {
// Unset, unparsable, and out of range all say nothing, and what nothing
// gets is the assumption that costs contrast rather than legibility.
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, ""), Background::Dark);
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "default;default"),
Background::Dark);
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "0;99"),
Background::Dark);
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "15"),
Background::Dark);
EXPECT_EQ(ChooseBackground(BackgroundPreference::Auto, "15;"),
Background::Dark);
}
TEST(CapabilitiesTest, BackgroundPreferenceWins) {
EXPECT_EQ(ChooseBackground(BackgroundPreference::Dark, "0;15"),
Background::Dark);
EXPECT_EQ(ChooseBackground(BackgroundPreference::Light, "15;0"),
Background::Light);
}
TEST(CapabilitiesTest, Defaults) {
// The defaults describe a plain-text sink, which is what a file or a pipe
// gets and what tests should use unless exercising something richer.
Capabilities capabilities;
EXPECT_EQ(capabilities.color_mode, ColorMode::NoColor);
EXPECT_EQ(capabilities.charset, Charset::Ascii);
EXPECT_EQ(capabilities.background, Background::Dark);
EXPECT_FALSE(capabilities.is_terminal);
EXPECT_FALSE(capabilities.columns.has_value());
}
TEST(CapabilitiesTest, Detect) {
// Detection reads the process environment and the descriptor it is handed, so
// only what neither can change is pinned here. What the policy decides from
// given inputs is tested above, against `ChooseColorMode` and `ChooseCharset`
// directly.
//
// It detects against a file rather than the process's own streams: those are
// a pipe under the test runner but a terminal under a debugger, and an
// exported `FORCE_COLOR` turns color on for either.
auto dir = Filesystem::MakeTmpDir();
ASSERT_TRUE(dir.ok()) << dir.error();
auto file = dir->OpenWriteOnly("out", Filesystem::CreationOptions::CreateNew);
ASSERT_TRUE(file.ok()) << file.error();
Capabilities capabilities = Capabilities::Detect(*file);
// A file is never a terminal.
EXPECT_FALSE(capabilities.is_terminal);
// `COLUMNS` reaches detection from the environment, so whether a width is
// found depends on it, but one that is found is usable.
if (capabilities.columns) {
EXPECT_GT(*capabilities.columns, 0);
}
EXPECT_GT(capabilities.tab_width, 0);
// A preference decides on its own, whatever the environment holds. Color
// forced on picks a depth from the environment, so only that it is on can be
// pinned here.
EXPECT_EQ(Capabilities::Detect(
*file, {.color = Preference::Never, .utf8 = Preference::Never})
.color_mode,
ColorMode::NoColor);
EXPECT_NE(
Capabilities::Detect(*file, {.color = Preference::Always}).color_mode,
ColorMode::NoColor);
EXPECT_EQ(Capabilities::Detect(*file, {.utf8 = Preference::Always}).charset,
Charset::Utf8);
EXPECT_EQ(Capabilities::Detect(*file, {.utf8 = Preference::Never}).charset,
Charset::Ascii);
EXPECT_EQ(
Capabilities::Detect(*file, {.background = BackgroundPreference::Light})
.background,
Background::Light);
EXPECT_EQ(
Capabilities::Detect(*file, {.background = BackgroundPreference::Dark})
.background,
Background::Dark);
(*std::move(file)).Close().Check();
}
} // namespace
} // namespace Carbon::Terminal
+239
View File
@@ -0,0 +1,239 @@
// 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
#include "common/terminal/color.h"
#include <algorithm>
#include <array>
#include "common/check.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Format.h"
namespace Carbon::Terminal {
static constexpr int AnsiColorCount = 16;
// Reference values for the 16 ANSI colors.
//
// Nothing standardizes these: a terminal draws them from the user's palette,
// which is exactly what makes them worth using. But downsampling an RGB color
// still needs some notion of where each named color sits, so these use the
// xterm defaults, which terminals vary from but stay recognizably near.
static constexpr std::array<Color::RgbValue, AnsiColorCount> AnsiColorRgbs = {{
{.r = 0, .g = 0, .b = 0}, // Black
{.r = 205, .g = 0, .b = 0}, // Red
{.r = 0, .g = 205, .b = 0}, // Green
{.r = 205, .g = 205, .b = 0}, // Yellow
{.r = 0, .g = 0, .b = 238}, // Blue
{.r = 205, .g = 0, .b = 205}, // Magenta
{.r = 0, .g = 205, .b = 205}, // Cyan
{.r = 229, .g = 229, .b = 229}, // White
{.r = 127, .g = 127, .b = 127}, // BrightBlack
{.r = 255, .g = 0, .b = 0}, // BrightRed
{.r = 0, .g = 255, .b = 0}, // BrightGreen
{.r = 255, .g = 255, .b = 0}, // BrightYellow
{.r = 92, .g = 92, .b = 255}, // BrightBlue
{.r = 255, .g = 0, .b = 255}, // BrightMagenta
{.r = 0, .g = 255, .b = 255}, // BrightCyan
{.r = 255, .g = 255, .b = 255}, // BrightWhite
}};
static constexpr std::array<llvm::StringRef, AnsiColorCount> AnsiColorNames = {
"Black", "Red", "Green", "Yellow",
"Blue", "Magenta", "Cyan", "White",
"BrightBlack", "BrightRed", "BrightGreen", "BrightYellow",
"BrightBlue", "BrightMagenta", "BrightCyan", "BrightWhite"};
// Returns the "redmean" distance between two colors, squared and scaled by 256
// to keep it in integer arithmetic.
//
// Treating the channels as orthogonal axes is cheaper but sits a long way from
// perceived difference, and downsampling is exactly where that shows: a color
// picked for a diagnostic lands on whichever of a small fixed set the
// arithmetic says is closest, and a plain Euclidean fit underweights green,
// where the eye is most sensitive. Redmean weights the
// channels by where the pair sits on the red axis, which tracks perception far
// better for a couple of extra multiplies:
// https://en.wikipedia.org/wiki/Color_difference#sRGB
//
// The formula ends in a square root, which is dropped because only the ordering
// is used. Scaling by 256 turns the two fractional weights into integers; the
// result peaks just under 150 million, well inside the range.
static auto DistanceSquared(Color::RgbValue lhs, Color::RgbValue rhs) -> int {
int red_mean = (static_cast<int>(lhs.r) + static_cast<int>(rhs.r)) / 2;
int dr = static_cast<int>(lhs.r) - static_cast<int>(rhs.r);
int dg = static_cast<int>(lhs.g) - static_cast<int>(rhs.g);
int db = static_cast<int>(lhs.b) - static_cast<int>(rhs.b);
return (512 + red_mean) * dr * dr + 1024 * dg * dg +
(767 - red_mean) * db * db;
}
// Returns the ANSI color whose reference value is nearest to `rgb`.
static auto NearestAnsiColor(Color::RgbValue rgb) -> AnsiColor {
int best_index = 0;
int best_distance = DistanceSquared(rgb, AnsiColorRgbs[0]);
for (int i = 1; i < AnsiColorCount; ++i) {
int distance = DistanceSquared(rgb, AnsiColorRgbs[i]);
if (distance < best_distance) {
best_distance = distance;
best_index = i;
}
}
return static_cast<AnsiColor>(best_index);
}
// The channel values of the 6x6x6 color cube at palette indices 16 through
// 231. The first step is much larger than the rest, so a channel can't be
// rounded to the nearest level by dividing.
static constexpr std::array<uint8_t, 6> CubeLevels = {0, 95, 135,
175, 215, 255};
// The midpoints between adjacent entries of `CubeLevels`, which are where the
// nearest level changes.
static constexpr std::array<uint8_t, 5> CubeLevelMidpoints = {48, 115, 155, 195,
235};
static_assert(
[] {
for (size_t i = 0; i < CubeLevelMidpoints.size(); ++i) {
// Rounded up, so that a value exactly between two levels takes the
// higher one.
if (CubeLevelMidpoints[i] !=
(CubeLevels[i] + CubeLevels[i + 1] + 1) / 2) {
return false;
}
}
return true;
}(),
"Midpoints must stay in step with the levels they separate.");
// Returns the index into `CubeLevels` of the level nearest `value`.
static auto NearestCubeLevel(uint8_t value) -> int {
int level = 0;
while (level < static_cast<int>(CubeLevelMidpoints.size()) &&
value >= CubeLevelMidpoints[level]) {
++level;
}
return level;
}
// Returns the 256-color palette index whose color is nearest to `rgb`.
static auto NearestPaletteIndex(Color::RgbValue rgb) -> uint8_t {
// Only the color cube and the gray ramp are considered. Indices 0 through 15
// alias the ANSI colors, whose appearance comes from the user's palette, so
// an exact RGB request must never be answered with one.
int r_level = NearestCubeLevel(rgb.r);
int g_level = NearestCubeLevel(rgb.g);
int b_level = NearestCubeLevel(rgb.b);
Color::RgbValue cube = {.r = CubeLevels[r_level],
.g = CubeLevels[g_level],
.b = CubeLevels[b_level]};
// The gray ramp at indices 232 through 255 runs from 8 to 238 in steps of
// 10, and is finer than the cube's gray diagonal for near-neutral colors.
int average = (static_cast<int>(rgb.r) + static_cast<int>(rgb.g) +
static_cast<int>(rgb.b)) /
3;
int gray_step = std::clamp((average - 8 + 5) / 10, 0, 23);
auto gray_value = static_cast<uint8_t>(8 + 10 * gray_step);
Color::RgbValue gray = {.r = gray_value, .g = gray_value, .b = gray_value};
if (DistanceSquared(rgb, gray) < DistanceSquared(rgb, cube)) {
return 232 + gray_step;
}
return 16 + 36 * r_level + 6 * g_level + b_level;
}
// Returns the SGR parameter selecting `color` for `target`.
//
// The original ANSI codes cover the first eight colors, and the later "bright"
// codes cover the rest at a fixed offset.
static auto AnsiSgrCode(AnsiColor color, ColorTarget target) -> uint8_t {
CARBON_CHECK(target != ColorTarget::Underline,
"Underline color has no direct ANSI form.");
int index = static_cast<int>(color);
int base = target == ColorTarget::Background ? 40 : 30;
if (index >= 8) {
// Bright foregrounds are 90-97 and bright backgrounds 100-107.
base += 60;
}
return base + (index % 8);
}
// Returns the SGR parameter introducing an extended color for `target`, which
// is followed by either `;5;<index>` or `;2;<r>;<g>;<b>`.
static auto ExtendedSgrCode(ColorTarget target) -> uint8_t {
switch (target) {
case ColorTarget::Foreground:
return 38;
case ColorTarget::Background:
return 48;
case ColorTarget::Underline:
return 58;
}
}
auto Color::AppendEscape(OutputBufferRef out, ColorMode mode,
ColorTarget target) const -> void {
if (mode == ColorMode::NoColor) {
return;
}
// Underline colors are only expressible through the extended-color escape,
// which `Ansi16` doesn't use.
if (target == ColorTarget::Underline && mode == ColorMode::Ansi16) {
return;
}
CARBON_CHECK(is_set(), "Only a color that is set can be selected.");
if (kind_ == Kind::Ansi) {
if (target == ColorTarget::Underline) {
// Named underline colors go through the palette form of the extended
// escape, as there is no direct code for them.
out.Append("\x1b[58;5;", static_cast<uint8_t>(ansi()), "m");
} else {
out.Append("\x1b[", AnsiSgrCode(ansi(), target), "m");
}
return;
}
switch (mode) {
case ColorMode::Truecolor:
out.Append("\x1b[", ExtendedSgrCode(target), ";2;", channels_.r, ";",
channels_.g, ";", channels_.b, "m");
break;
case ColorMode::Ansi256:
out.Append("\x1b[", ExtendedSgrCode(target), ";5;",
NearestPaletteIndex(channels_), "m");
break;
case ColorMode::Ansi16:
out.Append("\x1b[", AnsiSgrCode(NearestAnsiColor(channels_), target),
"m");
break;
case ColorMode::NoColor:
CARBON_FATAL("Returned above without emitting anything.");
}
}
auto Color::Print(llvm::raw_ostream& out) const -> void {
switch (kind_) {
case Kind::None:
out << "None";
return;
case Kind::Ansi:
out << AnsiColorNames[static_cast<int>(ansi())];
return;
case Kind::Rgb:
out << llvm::format("#%02x%02x%02x", channels_.r, channels_.g,
channels_.b);
return;
}
}
} // namespace Carbon::Terminal
+167
View File
@@ -0,0 +1,167 @@
// 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
#ifndef CARBON_COMMON_TERMINAL_COLOR_H_
#define CARBON_COMMON_TERMINAL_COLOR_H_
#include <cstdint>
#include "common/check.h"
#include "common/ostream.h"
#include "common/terminal/output_buffer_ref.h"
namespace Carbon::Terminal {
// The color escape sequences a terminal understands.
//
// Colors, like the other text attributes, are selected with Select Graphic
// Rendition (SGR) escape sequences. The sequence and the codes for the first
// eight colors come from ECMA-48, published in parallel as ANSI X3.64;
// terminal emulators added the bright variants, the 256-color palette, and the
// 24-bit form:
// https://ecma-international.org/publications-and-standards/standards/ecma-48/
//
// These form a ladder: each mode can express everything the modes before it
// can. Colors that the active mode can't express exactly are downsampled to
// the nearest color it can, so callers author in the richest form and let
// rendering degrade on its own.
enum class ColorMode : int8_t {
// Emit no escape sequences at all, producing plain text.
NoColor,
// The 16 colors with SGR codes of their own.
Ansi16,
// The 256-color palette: the 16 ANSI colors, a 6x6x6 RGB cube, and a 24-step
// gray ramp.
Ansi256,
// Direct 24-bit RGB, commonly called "truecolor".
Truecolor,
};
// The 16 colors with SGR codes of their own.
//
// Terminals render these through the user's configured palette, which makes
// them the right choice for output that should blend with the user's theme.
// The tradeoff is that their rendered appearance is outside our control: a
// user's "red" may be any color at all.
enum class AnsiColor : uint8_t {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
};
// Which part of a cell's rendering a color applies to.
enum class ColorTarget : int8_t {
Foreground,
Background,
// The color of the underline itself, independent of the foreground. Only
// `Ansi256` and richer modes can express this.
Underline,
};
// A color to render with: one of the 16 named ANSI colors, a 24-bit RGB value,
// or no color at all.
//
// RGB colors render exactly where the terminal supports them, and are
// downsampled where it doesn't. Downsampling to `Ansi16` measures distance
// against fixed reference values, but the terminal renders the result from the
// user's palette, so a downsampled color can land far from the original.
// Prefer `AnsiColor` wherever output should track the user's theme, and RGB
// only where an exact color matters.
//
// A default-constructed color selects nothing. That is how a `Style` spells
// leaving one of its colors to the terminal, so this is a value with an empty
// state rather than something wrapped in an `optional` to get one.
class Color : public Printable<Color> {
public:
// Whether a color names a palette entry, gives channel values directly, or
// selects nothing.
enum class Kind : uint8_t {
None,
Ansi,
Rgb,
};
// The channel values of a 24-bit color.
struct RgbValue {
uint8_t r;
uint8_t g;
uint8_t b;
friend auto operator==(RgbValue lhs, RgbValue rhs) -> bool = default;
};
constexpr Color() = default;
// Colors convert implicitly from `AnsiColor` so that call sites can read as
// `style.Foreground(AnsiColor::Red)`.
//
// NOLINTNEXTLINE(google-explicit-constructor)
constexpr Color(AnsiColor ansi)
: kind_(Kind::Ansi), channels_{.r = static_cast<uint8_t>(ansi)} {}
constexpr Color(uint8_t r, uint8_t g, uint8_t b)
: kind_(Kind::Rgb), channels_{.r = r, .g = g, .b = b} {}
auto kind() const -> Kind { return kind_; }
// Returns whether this selects a color at all.
auto is_set() const -> bool { return kind_ != Kind::None; }
// Returns the named color. Valid only when `kind()` is `Ansi`.
auto ansi() const -> AnsiColor {
CARBON_CHECK(kind_ == Kind::Ansi,
"Only a named color has a palette index.");
return static_cast<AnsiColor>(channels_.r);
}
// Returns the channel values. Valid only when `kind()` is `Rgb`.
auto rgb() const -> RgbValue {
CARBON_CHECK(kind_ == Kind::Rgb, "Only an RGB color has channel values.");
return channels_;
}
// Appends the escape sequence selecting this color for `target`, which
// requires that one is set.
//
// Appends nothing when `mode` is `NoColor`, or when `target` is `Underline`
// and `mode` is `Ansi16`, which has no way to express an underline color.
auto AppendEscape(OutputBufferRef out, ColorMode mode,
ColorTarget target) const -> void;
auto Print(llvm::raw_ostream& out) const -> void;
// Written out rather than defaulted because the `Printable` base has no
// comparison of its own, which would leave a defaulted one deleted.
friend auto operator==(Color lhs, Color rhs) -> bool {
return lhs.kind_ == rhs.kind_ && lhs.channels_ == rhs.channels_;
}
private:
Kind kind_ = Kind::None;
// The palette index in `r` with the rest zero for `Ansi`, the channel values
// for `Rgb`, and all zero for `None`.
//
// Overlapping the two in a union would leave the bytes past a palette index
// unwritten. Every byte carrying part of the value is what lets a whole
// `Style` be compared as bytes, and an index fits in a channel anyway.
RgbValue channels_ = {.r = 0, .g = 0, .b = 0};
};
} // namespace Carbon::Terminal
#endif // CARBON_COMMON_TERMINAL_COLOR_H_
+171
View File
@@ -0,0 +1,171 @@
// 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
#include "common/terminal/color.h"
#include <gtest/gtest.h>
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
namespace Carbon::Terminal {
namespace {
auto Escape(Color color, ColorMode mode,
ColorTarget target = ColorTarget::Foreground) -> std::string {
llvm::SmallString<32> escape;
color.AppendEscape(escape, mode, target);
return std::string(escape);
}
TEST(ColorTest, AnsiEscapes) {
// Named colors use their own SGR codes rather than the extended forms, in
// every mode that has color at all, so the terminal renders them from the
// user's palette.
for (ColorMode mode :
{ColorMode::Ansi16, ColorMode::Ansi256, ColorMode::Truecolor}) {
EXPECT_EQ(Escape(AnsiColor::Red, mode), "\x1b[31m");
EXPECT_EQ(Escape(AnsiColor::Black, mode), "\x1b[30m");
EXPECT_EQ(Escape(AnsiColor::BrightCyan, mode), "\x1b[96m");
EXPECT_EQ(Escape(AnsiColor::Red, mode, ColorTarget::Background),
"\x1b[41m");
EXPECT_EQ(Escape(AnsiColor::BrightWhite, mode, ColorTarget::Background),
"\x1b[107m");
}
EXPECT_EQ(Escape(AnsiColor::Red, ColorMode::NoColor), "");
}
TEST(ColorTest, RgbEscapes) {
Color red(255, 0, 0);
EXPECT_EQ(Escape(red, ColorMode::Truecolor), "\x1b[38;2;255;0;0m");
EXPECT_EQ(Escape(red, ColorMode::Truecolor, ColorTarget::Background),
"\x1b[48;2;255;0;0m");
EXPECT_EQ(Escape(red, ColorMode::Ansi256), "\x1b[38;5;196m");
EXPECT_EQ(Escape(red, ColorMode::Ansi16), "\x1b[91m");
EXPECT_EQ(Escape(red, ColorMode::NoColor), "");
}
TEST(ColorTest, UnderlineEscapes) {
// Underline colors only exist in the extended-color escapes, so in `Ansi16`
// the terminal draws the underline in the foreground color.
EXPECT_EQ(
Escape(AnsiColor::Red, ColorMode::Truecolor, ColorTarget::Underline),
"\x1b[58;5;1m");
EXPECT_EQ(Escape(AnsiColor::Red, ColorMode::Ansi256, ColorTarget::Underline),
"\x1b[58;5;1m");
EXPECT_EQ(Escape(AnsiColor::Red, ColorMode::Ansi16, ColorTarget::Underline),
"");
Color green(0, 255, 0);
EXPECT_EQ(Escape(green, ColorMode::Truecolor, ColorTarget::Underline),
"\x1b[58;2;0;255;0m");
EXPECT_EQ(Escape(green, ColorMode::Ansi256, ColorTarget::Underline),
"\x1b[58;5;46m");
EXPECT_EQ(Escape(green, ColorMode::Ansi16, ColorTarget::Underline), "");
}
TEST(ColorTest, DownsampleToAnsi16) {
// The reference value of each ANSI color must come back as that color, or
// downsampling would shift colors that were already expressible. Spelling
// the values out here rather than reading them back from the same table the
// implementation uses is what makes this catch a wrong table.
struct Expected {
Color color;
llvm::StringRef escape;
};
Expected cases[] = {
{Color(0, 0, 0), "\x1b[30m"}, {Color(205, 0, 0), "\x1b[31m"},
{Color(0, 205, 0), "\x1b[32m"}, {Color(205, 205, 0), "\x1b[33m"},
{Color(0, 0, 238), "\x1b[34m"}, {Color(205, 0, 205), "\x1b[35m"},
{Color(0, 205, 205), "\x1b[36m"}, {Color(229, 229, 229), "\x1b[37m"},
{Color(127, 127, 127), "\x1b[90m"}, {Color(255, 0, 0), "\x1b[91m"},
{Color(0, 255, 0), "\x1b[92m"}, {Color(255, 255, 0), "\x1b[93m"},
{Color(92, 92, 255), "\x1b[94m"}, {Color(255, 0, 255), "\x1b[95m"},
{Color(0, 255, 255), "\x1b[96m"}, {Color(255, 255, 255), "\x1b[97m"},
};
for (const Expected& expected : cases) {
EXPECT_EQ(Escape(expected.color, ColorMode::Ansi16), expected.escape)
<< expected.color;
}
// Colors between the reference values land on the nearest one.
EXPECT_EQ(Escape(Color(250, 10, 10), ColorMode::Ansi16), "\x1b[91m");
EXPECT_EQ(Escape(Color(10, 10, 10), ColorMode::Ansi16), "\x1b[30m");
EXPECT_EQ(Escape(Color(120, 120, 120), ColorMode::Ansi16), "\x1b[90m");
}
TEST(ColorTest, DownsampleToPalette) {
// The corners of the 6x6x6 cube are exactly representable.
EXPECT_EQ(Escape(Color(0, 0, 0), ColorMode::Ansi256), "\x1b[38;5;16m");
EXPECT_EQ(Escape(Color(255, 255, 255), ColorMode::Ansi256), "\x1b[38;5;231m");
EXPECT_EQ(Escape(Color(255, 0, 0), ColorMode::Ansi256), "\x1b[38;5;196m");
EXPECT_EQ(Escape(Color(0, 0, 255), ColorMode::Ansi256), "\x1b[38;5;21m");
// The cube's levels are unevenly spaced, so rounding has to account for that
// rather than divide: 95 and 135 are adjacent levels only 40 apart.
EXPECT_EQ(Escape(Color(95, 0, 0), ColorMode::Ansi256), "\x1b[38;5;52m");
EXPECT_EQ(Escape(Color(130, 0, 0), ColorMode::Ansi256), "\x1b[38;5;88m");
// Near-neutral colors land on the gray ramp, which is far finer than the
// cube's diagonal, except at the ends where the cube wins.
EXPECT_EQ(Escape(Color(8, 8, 8), ColorMode::Ansi256), "\x1b[38;5;232m");
EXPECT_EQ(Escape(Color(128, 128, 128), ColorMode::Ansi256), "\x1b[38;5;244m");
EXPECT_EQ(Escape(Color(238, 238, 238), ColorMode::Ansi256), "\x1b[38;5;255m");
}
TEST(ColorTest, DownsampleAvoidsPaletteEntries) {
// Indices 0 through 15 render from the user's palette, so an exact RGB
// request must never be answered with one.
for (int r = 0; r < 256; r += 17) {
for (int g = 0; g < 256; g += 17) {
for (int b = 0; b < 256; b += 17) {
Color color(r, g, b);
std::string escape = Escape(color, ColorMode::Ansi256);
int index = 0;
ASSERT_TRUE(llvm::to_integer(
llvm::StringRef(escape).drop_front(7).drop_back(1), index))
<< color;
EXPECT_GE(index, 16) << color;
}
}
}
}
TEST(ColorTest, Equality) {
EXPECT_EQ(Color(AnsiColor::Red), Color(AnsiColor::Red));
EXPECT_NE(Color(AnsiColor::Red), Color(AnsiColor::Blue));
EXPECT_EQ(Color(1, 2, 3), Color(1, 2, 3));
EXPECT_NE(Color(1, 2, 3), Color(1, 2, 4));
// A named color and its reference value are different colors: the terminal
// renders one from the palette and the other exactly.
EXPECT_NE(Color(AnsiColor::BrightRed), Color(255, 0, 0));
// A palette index occupies the same byte as the red channel, so these pairs
// hold identical channel bytes and are told apart only by their kind.
EXPECT_NE(Color(AnsiColor::Red), Color(1, 0, 0));
EXPECT_NE(Color(AnsiColor::Black), Color(0, 0, 0));
EXPECT_NE(Color(AnsiColor::Black), Color());
EXPECT_NE(Color(0, 0, 0), Color());
EXPECT_EQ(Color(), Color());
}
TEST(ColorTest, Unset) {
EXPECT_FALSE(Color().is_set());
EXPECT_EQ(Color().kind(), Color::Kind::None);
// Black is a color like any other, however little of it there is.
EXPECT_TRUE(Color(AnsiColor::Black).is_set());
EXPECT_TRUE(Color(0, 0, 0).is_set());
}
TEST(ColorTest, Print) {
EXPECT_EQ(PrintToString(Color(AnsiColor::BrightMagenta)), "BrightMagenta");
EXPECT_EQ(PrintToString(Color(0x12, 0xab, 0xff)), "#12abff");
EXPECT_EQ(PrintToString(Color()), "None");
}
} // namespace
} // namespace Carbon::Terminal
+190
View File
@@ -0,0 +1,190 @@
// 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
#include "common/terminal/metrics.h"
#include "common/check.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Unicode.h"
namespace Carbon::Terminal {
// Stands in for anything a UTF-8 terminal has no rendering for: invalid UTF-8,
// control characters, and unassigned code points.
static constexpr char32_t Utf8Replacement = U'�';
// Returns whether an ASCII terminal renders `code_point` as itself, in one
// column.
static auto IsPrintableAscii(char32_t code_point) -> bool {
return code_point >= 0x20 && code_point < 0x7f;
}
// Spelled out rather than handed to a general converter, which walks a range
// and checks bounds this already knows. Box-drawing characters go through here
// for every cell of every line drawn.
//
// TODO: Offer this to LLVM, whose `ConvertCodePointToUTF8` is the general
// converter this replaces. Encoding one code point at a time is what anything
// writing UTF-8 out of a grid does, so this belongs beside it rather than
// here; drop this once it is there.
auto EncodeUtf8(char32_t code_point, Utf8Storage& storage) -> llvm::StringRef {
// Most of what gets rendered is ASCII, and encoding it is a single byte.
if (code_point < 0x80) {
storage[0] = static_cast<char>(code_point);
return llvm::StringRef(storage.data(), 1);
}
// Surrogates have no encoding of their own, and nothing past the last code
// point has one at all.
if (code_point > 0x10ffff || (code_point >= 0xd800 && code_point < 0xe000)) {
code_point = Utf8Replacement;
}
auto trailing = [code_point](int shift) {
return static_cast<char>(0b1000'0000 | ((code_point >> shift) & 0b11'1111));
};
if (code_point < 0x800) {
storage[0] = static_cast<char>(0b1100'0000 | (code_point >> 6));
storage[1] = trailing(0);
return llvm::StringRef(storage.data(), 2);
}
if (code_point < 0x10000) {
storage[0] = static_cast<char>(0b1110'0000 | (code_point >> 12));
storage[1] = trailing(6);
storage[2] = trailing(0);
return llvm::StringRef(storage.data(), 3);
}
storage[0] = static_cast<char>(0b1111'0000 | (code_point >> 18));
storage[1] = trailing(12);
storage[2] = trailing(6);
storage[3] = trailing(0);
return llvm::StringRef(storage.data(), 4);
}
// Returns the columns `code_point` occupies on a UTF-8 terminal: zero for a
// combining mark, one or two for one with a glyph of its own, and a
// negative value when there is no printable rendering for it.
//
// TODO: This encodes a code point only for LLVM to decode it again.
// `llvm::sys::unicode::charWidth` computes exactly this and is what
// `columnWidthUTF8` calls once per code point, but it is file-local to LLVM's
// `Unicode.cpp`. Exposing it there would let this call it directly. LLVM's own
// contract already says a string's width is the sum of its code points', so
// there is nothing in the way of it.
static auto Utf8CodePointWidth(char32_t code_point) -> int {
// Printable ASCII is one column, and is most of what gets measured. The
// general path parses a UTF-8 sequence and searches several code point
// range tables, which is far more than this needs.
if (IsPrintableAscii(code_point)) {
return 1;
}
Utf8Storage storage;
return llvm::sys::unicode::columnWidthUTF8(EncodeUtf8(code_point, storage));
}
// Removes the first UTF-8 sequence from `text` and returns the code point it
// encodes.
static auto TakeUtf8CodePoint(llvm::StringRef& text) -> char32_t {
const auto* begin = reinterpret_cast<const llvm::UTF8*>(text.data());
const auto* pos = begin;
llvm::UTF32 code_point = 0;
if (llvm::convertUTF8Sequence(&pos, begin + text.size(), &code_point,
llvm::strictConversion) != llvm::conversionOK) {
text = text.drop_front(1);
return Utf8Replacement;
}
text = text.drop_front(pos - begin);
return code_point;
}
auto Metrics::TakeCodePoint(llvm::StringRef& text) const -> char32_t {
CARBON_CHECK(!text.empty(), "No code point to take.");
if (charset_ == Charset::Ascii) {
auto byte = static_cast<unsigned char>(text.front());
text = text.drop_front();
return byte;
}
return TakeUtf8CodePoint(text);
}
auto Metrics::CodePointWidth(char32_t code_point) const -> int {
if (charset_ == Charset::Ascii) {
return 1;
}
int width = Utf8CodePointWidth(code_point);
// A code point with no rendering is drawn as the replacement character, which
// takes one column.
return width < 0 ? 1 : width;
}
auto Metrics::RenderedCodePoint(char32_t code_point) const -> char32_t {
// Printable ASCII is most of what gets drawn, and settling it here keeps it
// out of the range tables the general answer searches.
if (IsPrintableAscii(code_point)) {
return code_point;
}
// Fallback if we can't use unicode.
if (charset_ == Charset::Ascii) {
return U'?';
}
// Which code points have no rendering is what a negative width names as well,
// asked directly rather than through a width that has to encode one to
// answer.
return llvm::sys::unicode::isPrintable(static_cast<int>(code_point))
? code_point
: Utf8Replacement;
}
auto Metrics::Width(llvm::StringRef text) const -> int {
// Checked rather than debug-checked: text with one of these in it measures as
// though each took one column, which is not what drawing does, and measuring
// wrong is invisible in the output. The scan is one more linear pass over
// text that is walked linearly anyway.
CARBON_CHECK(
text.find_first_of("\t\n\r") == llvm::StringRef::npos,
"Width is only for text whose width is its code points', but got `{0}`.",
text);
if (charset_ == Charset::Ascii) {
return static_cast<int>(text.size());
}
// Text that is valid UTF-8 throughout and printable throughout is the common
// case, and LLVM measures a whole run of it in one pass. It answers with a
// negative value rather than a width when the text holds anything it can't
// measure, which is what the walk below is for: each such code point still
// takes the one column the replacement character drawn for it will.
int width = llvm::sys::unicode::columnWidthUTF8(text);
if (width >= 0) {
return width;
}
width = 0;
while (!text.empty()) {
width += CodePointWidth(TakeUtf8CodePoint(text));
}
return width;
}
auto Metrics::TakeColumns(llvm::StringRef& text, int columns) const
-> llvm::StringRef {
llvm::StringRef rest = text;
int taken = 0;
while (!rest.empty()) {
llvm::StringRef next = rest;
int width = CodePointWidth(TakeCodePoint(next));
if (taken + width > columns) {
break;
}
taken += width;
rest = next;
}
llvm::StringRef prefix = text.drop_back(rest.size());
text = rest;
return prefix;
}
} // namespace Carbon::Terminal
+114
View File
@@ -0,0 +1,114 @@
// 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
#ifndef CARBON_COMMON_TERMINAL_METRICS_H_
#define CARBON_COMMON_TERMINAL_METRICS_H_
#include <array>
#include <cstddef>
#include "common/terminal/capabilities.h"
#include "llvm/ADT/StringRef.h"
namespace Carbon::Terminal {
// The most bytes one code point encodes to in UTF-8, and storage for one.
inline constexpr size_t MaxUtf8Bytes = 4;
using Utf8Storage = std::array<char, MaxUtf8Bytes>;
// Encodes `code_point` as UTF-8 into `storage`, returning the bytes written.
//
// Code points with no valid encoding, including surrogates and anything past
// U+10FFFF, become the replacement character.
auto EncodeUtf8(char32_t code_point, Utf8Storage& storage) -> llvm::StringRef;
// How many columns a terminal spends on text, given the charset it decodes
// with.
//
// Which bytes make up a column depends on the charset, so every question about
// the size of text is a question about the charset as well, and this is what
// answers both at once. `Buffer` holds one and lays its cells out with it;
// anything deciding where to put something asks one directly rather than
// keeping its own idea of how wide a string is.
//
// Nothing here converts between a byte offset and a column: which byte a column
// lands on depends on the encoding, and which column a byte lands in depends on
// the width of everything before it. `TakeColumns` hands back the text it cut
// rather than an offset into it, so a caller never holds one count where the
// other belongs.
//
// TODO: Every width here is a sum over code points taken in logical order,
// which is only the width on screen for left-to-right text. Bidirectional text
// reorders, so a run's width still adds up but `TakeColumns` has no meaning:
// the prefix occupying the first N columns need not be a prefix of the string.
// Settle this together with the question `Buffer`'s own TODO describes, since
// both turn on what a client hands over.
class Metrics {
public:
explicit constexpr Metrics(Charset charset) : charset_(charset) {}
constexpr auto charset() const -> Charset { return charset_; }
// Removes the next code point from `text`, which must not be empty, and
// returns it: one byte under `Charset::Ascii`, and one decoded code point
// under `Charset::Utf8`.
//
// A byte that doesn't start a valid sequence yields the replacement
// character and is consumed on its own, so decoding resynchronizes at the
// next byte rather than discarding the rest of the text.
auto TakeCodePoint(llvm::StringRef& text) const -> char32_t;
// Returns the columns `code_point` occupies once drawn, which is what drawing
// it advances by.
//
// Under `Charset::Ascii` every code point is one column. Under
// `Charset::Utf8` a combining mark is zero, since it renders into the column
// before it, and anything with no printable rendering is one, since it is
// drawn as a replacement character.
//
// A combining mark is the only thing zero is ever the answer for, which is
// what lets `Buffer` read a zero as one: a code point to fold into the cell
// before it rather than give a cell of its own. A code point that takes no
// column without combining with anything, such as U+200C ZERO WIDTH
// NON-JOINER, has no printable rendering here and takes the column its
// replacement character does. Terminals disagree about those -- some give
// them a column and some don't -- so drawing one as itself would leave the
// columns counted here and the columns painted disagreeing from there on.
auto CodePointWidth(char32_t code_point) const -> int;
// Returns the code point to render for `code_point`, which is a replacement
// character where it has no dependable rendering of its own.
//
// Under `Charset::Ascii` that is anything outside printable ASCII, because a
// terminal decoding some single-byte encoding will draw such a byte as
// something and there is no way to know what. Under `Charset::Utf8` it is
// anything with no printable rendering at all, which includes the surrogates
// and so covers everything UTF-8 has no encoding for as well.
auto RenderedCodePoint(char32_t code_point) const -> char32_t;
// Returns the columns `text` occupies once drawn.
//
// `text` must hold no character that drawing gives a width other than its
// code points', so no tab, newline, or carriage return. Those are positional
// -- what a tab advances by depends on where the text began -- which makes
// them questions about a drawing rather than about the text, and `Buffer`
// answers those.
auto Width(llvm::StringRef text) const -> int;
// Removes and returns the longest prefix of `text` that occupies at most
// `columns` columns.
//
// A code point that would straddle the end stops the walk before it, so a cut
// never lands inside one and the prefix is never wider than asked for -- it
// can be one column narrower, where a double-width character sits on the
// boundary.
auto TakeColumns(llvm::StringRef& text, int columns) const -> llvm::StringRef;
private:
Charset charset_;
};
} // namespace Carbon::Terminal
#endif // CARBON_COMMON_TERMINAL_METRICS_H_
+161
View File
@@ -0,0 +1,161 @@
// 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
#include "common/terminal/metrics.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <cstdint>
#include "llvm/ADT/StringRef.h"
namespace Carbon::Terminal {
namespace {
// "e" followed by U+0301 COMBINING ACUTE ACCENT, which is one column because
// the mark renders into the column the "e" is in.
static constexpr llvm::StringLiteral AcuteE = "é";
TEST(MetricsTest, Width) {
Metrics utf8(Charset::Utf8);
EXPECT_EQ(utf8.Width(""), 0);
EXPECT_EQ(utf8.Width("hello"), 5);
EXPECT_EQ(utf8.Width("中中"), 4);
EXPECT_EQ(utf8.Width("a中b"), 4);
EXPECT_EQ(utf8.Width(AcuteE), 1);
// Every byte is a column when the terminal isn't decoding UTF-8.
Metrics ascii(Charset::Ascii);
EXPECT_EQ(ascii.Width("hello"), 5);
EXPECT_EQ(ascii.Width("中中"), 6);
EXPECT_EQ(ascii.Width(AcuteE), 3);
}
TEST(MetricsTest, CodePointWidth) {
Metrics utf8(Charset::Utf8);
EXPECT_EQ(utf8.CodePointWidth(U'a'), 1);
EXPECT_EQ(utf8.CodePointWidth(U'中'), 2);
// A combining mark renders into the column before it.
EXPECT_EQ(utf8.CodePointWidth(U'́'), 0);
// Something with no rendering is drawn as a replacement, which is a column.
EXPECT_EQ(utf8.CodePointWidth(U''), 1);
Metrics ascii(Charset::Ascii);
EXPECT_EQ(ascii.CodePointWidth(U'a'), 1);
EXPECT_EQ(ascii.CodePointWidth(U'中'), 1);
EXPECT_EQ(ascii.CodePointWidth(U'́'), 1);
}
TEST(MetricsTest, OnlyCombiningMarksAreZeroColumns) {
// Drawing reads a width of zero as "renders into the cell before this one",
// so a code point that takes no column without combining with anything has to
// measure as something else. Terminals disagree about these -- Terminal.app
// gives U+200C a column and VS Code's terminal gives it none -- so each is
// drawn as a replacement character, which takes exactly one.
Metrics utf8(Charset::Utf8);
for (char32_t code_point : {U'\u200b', U'\u200c', U'\u200d', U'\ufeff'}) {
EXPECT_EQ(utf8.CodePointWidth(code_point), 1)
<< static_cast<uint32_t>(code_point);
EXPECT_EQ(utf8.RenderedCodePoint(code_point), U'�')
<< static_cast<uint32_t>(code_point);
}
}
TEST(MetricsTest, RenderedCodePoint) {
Metrics utf8(Charset::Utf8);
EXPECT_EQ(utf8.RenderedCodePoint(U'a'), U'a');
EXPECT_EQ(utf8.RenderedCodePoint(U'中'), U'中');
EXPECT_EQ(utf8.RenderedCodePoint(U''), U'�');
// Code points that UTF-8 has no encoding for have no rendering either.
EXPECT_EQ(utf8.RenderedCodePoint(static_cast<char32_t>(0xd800)), U'�');
EXPECT_EQ(utf8.RenderedCodePoint(static_cast<char32_t>(0x110000)), U'�');
// An ASCII terminal is only given what it draws as itself, because there is
// no telling what it would draw for anything else.
Metrics ascii(Charset::Ascii);
EXPECT_EQ(ascii.RenderedCodePoint(U'a'), U'a');
EXPECT_EQ(ascii.RenderedCodePoint(U'中'), U'?');
EXPECT_EQ(ascii.RenderedCodePoint(U''), U'?');
}
TEST(MetricsTest, TakeColumns) {
Metrics utf8(Charset::Utf8);
llvm::StringRef text = "abcde";
EXPECT_EQ(utf8.TakeColumns(text, 3), "abc");
EXPECT_EQ(text, "de");
// Taking more than there is takes all of it.
EXPECT_EQ(utf8.TakeColumns(text, 10), "de");
EXPECT_EQ(text, "");
// Taking nothing takes nothing, and a negative width is no different.
text = "abcde";
EXPECT_EQ(utf8.TakeColumns(text, 0), "");
EXPECT_EQ(utf8.TakeColumns(text, -1), "");
EXPECT_EQ(text, "abcde");
}
TEST(MetricsTest, TakeColumnsKeepsWideCharactersWhole) {
Metrics utf8(Charset::Utf8);
// A character that would straddle the end stops the walk before it, so the
// prefix comes back a column short rather than half a character wide.
llvm::StringRef text = "中中中";
llvm::StringRef prefix = utf8.TakeColumns(text, 3);
EXPECT_EQ(prefix, "中");
EXPECT_EQ(utf8.Width(prefix), 2);
EXPECT_EQ(text, "中中");
// A request landing on a character boundary takes the whole prefix.
text = "中中中";
EXPECT_EQ(utf8.TakeColumns(text, 4), "中中");
EXPECT_EQ(text, "中");
}
TEST(MetricsTest, TakeColumnsUnderAscii) {
// Every byte is a column, so a multi-byte character is cut like any other
// run of bytes.
Metrics ascii(Charset::Ascii);
llvm::StringRef text = "中";
EXPECT_EQ(ascii.TakeColumns(text, 2).size(), 2U);
EXPECT_EQ(text.size(), 1U);
}
TEST(MetricsTest, TakeCodePointResynchronizesOnInvalidUtf8) {
Metrics utf8(Charset::Utf8);
// A byte that starts no valid sequence is consumed on its own, so the text
// after it is still decoded rather than being discarded.
llvm::StringRef text =
"\xff"
"a";
EXPECT_EQ(utf8.TakeCodePoint(text), U'�');
EXPECT_EQ(utf8.TakeCodePoint(text), U'a');
EXPECT_TRUE(text.empty());
}
TEST(MetricsTest, EncodeUtf8) {
Utf8Storage storage;
EXPECT_EQ(EncodeUtf8(U'a', storage), "a");
EXPECT_EQ(EncodeUtf8(U'é', storage), "é");
EXPECT_EQ(EncodeUtf8(U'中', storage), "中");
EXPECT_EQ(EncodeUtf8(U'\U0001f525', storage), "\U0001f525");
// A code point with no encoding of its own becomes the replacement.
EXPECT_EQ(EncodeUtf8(static_cast<char32_t>(0xd800), storage), "�");
EXPECT_EQ(EncodeUtf8(static_cast<char32_t>(0x110000), storage), "�");
}
TEST(MetricsDeathTest, WidthRejectsPositionalCharacters) {
// A tab's width is a fact about a drawing rather than about the text, so
// answering for one here would be answering a question this can't see the
// inputs to.
Metrics metrics(Charset::Utf8);
EXPECT_DEATH((void)metrics.Width("a\tb"), "Width is only for text whose");
EXPECT_DEATH((void)metrics.Width("a\nb"), "Width is only for text whose");
EXPECT_DEATH((void)metrics.Width("a\rb"), "Width is only for text whose");
}
} // namespace
} // namespace Carbon::Terminal
+163
View File
@@ -0,0 +1,163 @@
// 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
#ifndef CARBON_COMMON_TERMINAL_OUTPUT_BUFFER_REF_H_
#define CARBON_COMMON_TERMINAL_OUTPUT_BUFFER_REF_H_
#include <array>
#include <concepts>
#include <cstdint>
#include <cstring>
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
namespace Carbon::Terminal {
// A reference to the buffer a terminal rendering is assembled into.
//
// This owns nothing. It refers to a buffer the caller holds, which must outlive
// it, and converts implicitly from one so that rendering goes into storage the
// caller already has.
//
// Rendering assembles bytes here rather than streaming them: a stream call per
// literal and per number costs measurably more than handing over a finished
// sequence, and code that wants a stream prints the buffer once it is complete.
//
// Appending is shaped around what terminal output is made of, which is a great
// many short escape sequences, each a handful of literal bytes around a number
// that never exceeds 255. Taking a whole sequence at a time grows the buffer
// once per sequence rather than once per byte, and that difference is much of
// what rendering costs.
class OutputBufferRef {
public:
// Implicit, so that call sites pass the buffer they already hold rather than
// naming this type.
//
// NOLINTNEXTLINE(google-explicit-constructor)
OutputBufferRef(llvm::SmallVectorImpl<char>& bytes) : bytes_(&bytes) {}
// Appends `pieces`, each of which is either text, appended as it is, or a
// `uint8_t`, appended in decimal.
//
// No other type is accepted, so the two can never be taken for each other,
// and nothing needs one: the literal bytes of an escape sequence are always
// text, and every number one carries is a channel value, a palette index, or
// an SGR code, none of which exceed 255.
//
// No piece may point into the buffer, which appending can reallocate.
template <typename... PieceT>
auto Append(const PieceT&... pieces) -> void {
if constexpr (sizeof...(pieces) == 1) {
// A lone piece has nothing to assemble, and the buffer's own append is
// already the single growth and single copy this is after.
(AppendPiece(pieces), ...);
} else {
// Growing to the bound before writing keeps how far the buffer grows
// independent of the piece values, so computing one can't hold that up.
// Only the trim afterwards depends on how many digits a number took.
size_t begin = bytes_->size();
bytes_->resize_for_overwrite(begin + (AppendedSize(pieces) + ... + 0));
char* data = bytes_->data();
char* cursor = data + begin;
((cursor = WritePiece(cursor, pieces)), ...);
bytes_->truncate(cursor - data);
}
}
private:
// The room a number needs: its three digits, plus one more because it is
// written as a single four-byte store whose last byte is discarded.
static constexpr size_t NumberBytes = 4;
// The decimal text of a number, and how many digits it took. The digits are
// at the front and the length in the byte after them, so a whole entry is one
// store and the length says how far of it to keep.
struct NumberText {
std::array<char, NumberBytes - 1> digits;
uint8_t length;
};
static_assert(sizeof(NumberText) == NumberBytes,
"A number is written by storing a whole entry at once.");
// The text of every value a number piece can hold. A kilobyte of table, in
// exchange for a lookup where computing the digits would branch on the value
// three times.
static constexpr std::array<NumberText, 256> NumberTexts = [] {
std::array<NumberText, 256> texts = {};
for (int value = 0; value < 256; ++value) {
NumberText& text = texts[value];
text.length = 1 + (value >= 10) + (value >= 100);
int rest = value;
for (int digit = text.length; digit > 0; --digit) {
text.digits[digit - 1] = static_cast<char>('0' + rest % 10);
rest /= 10;
}
}
return texts;
}();
// Returns the most bytes a piece can append. A number contributes the bound
// above rather than the digits it will take, so the bound for a sequence
// doesn't depend on any of the values in it.
template <size_t N>
static constexpr auto AppendedSize(const char (& /*piece*/)[N]) -> size_t {
return N - 1;
}
static constexpr auto AppendedSize(llvm::StringRef piece) -> size_t {
return piece.size();
}
template <std::same_as<uint8_t> T>
static constexpr auto AppendedSize(T /*piece*/) -> size_t {
return NumberBytes;
}
// Writes a piece at `out` and returns the position past it. There must be
// `AppendedSize(piece)` bytes of room, as nothing here checks.
template <size_t N>
static auto WritePiece(char* out, const char (&piece)[N]) -> char* {
std::memcpy(out, piece, N - 1);
return out + N - 1;
}
static auto WritePiece(char* out, llvm::StringRef piece) -> char* {
// An empty `StringRef` may hold a null pointer, which `memcpy` doesn't
// accept even for an empty copy.
if (!piece.empty()) {
std::memcpy(out, piece.data(), piece.size());
}
return out + piece.size();
}
template <std::same_as<uint8_t> T>
static auto WritePiece(char* out, T piece) -> char* {
// One load and one store, with no branch on the value. Escape sequences
// carry color channels and palette indices, which are spread across the
// whole range, so a branch per digit is one the processor can't predict,
// and there are four numbers in a truecolor escape. The store always covers
// four bytes, which is why a number reserves that many, and the cursor
// advances only over the digits that count.
const NumberText& text = NumberTexts[piece];
std::memcpy(out, &text, sizeof(text));
return out + text.length;
}
// Appends a piece on its own, growing the buffer to fit it.
template <size_t N>
auto AppendPiece(const char (&piece)[N]) -> void {
bytes_->append(piece, piece + N - 1);
}
auto AppendPiece(llvm::StringRef piece) -> void {
bytes_->append(piece.begin(), piece.end());
}
template <std::same_as<uint8_t> T>
auto AppendPiece(T piece) -> void {
std::array<char, NumberBytes> digits;
bytes_->append(digits.data(), WritePiece(digits.data(), piece));
}
llvm::SmallVectorImpl<char>* bytes_;
};
} // namespace Carbon::Terminal
#endif // CARBON_COMMON_TERMINAL_OUTPUT_BUFFER_REF_H_
@@ -0,0 +1,99 @@
// 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
#include "common/terminal/output_buffer_ref.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "llvm/ADT/SmallString.h"
namespace Carbon::Terminal {
namespace {
using ::testing::Eq;
// A single piece and several pieces are appended by different code, so both
// appear throughout these tests rather than in one case of their own.
TEST(OutputBufferRefTest, Text) {
llvm::SmallString<16> bytes;
OutputBufferRef out = bytes;
out.Append("one");
out.Append(llvm::StringRef(" two"));
out.Append(std::string(" three"));
out.Append(" four", llvm::StringRef(" five"));
EXPECT_THAT(bytes, Eq("one two three four five"));
}
TEST(OutputBufferRefTest, EmptyPieces) {
llvm::SmallString<16> bytes;
OutputBufferRef out = bytes;
out.Append();
out.Append("");
out.Append("", llvm::StringRef(), "kept", llvm::StringRef(""));
EXPECT_THAT(bytes, Eq("kept"));
}
TEST(OutputBufferRefTest, NumbersUseEveryDigitCount) {
llvm::SmallString<16> bytes;
OutputBufferRef out = bytes;
for (uint8_t value : {0, 9, 10, 99, 100, 255}) {
out.Append(value);
out.Append(" ", value, " ");
}
EXPECT_THAT(bytes, Eq("0 0 9 9 10 10 99 99 100 100 255 255 "));
}
// A number always writes fewer bytes than it reserves, so pieces after one in
// the same call are what catch a misplaced write.
TEST(OutputBufferRefTest, NumbersFollowedByMorePieces) {
llvm::SmallString<32> bytes;
OutputBufferRef out = bytes;
out.Append("\x1b[", static_cast<uint8_t>(38), ";2;", static_cast<uint8_t>(1),
";", static_cast<uint8_t>(22), ";", static_cast<uint8_t>(255),
"m");
EXPECT_THAT(bytes, Eq("\x1b[38;2;1;22;255m"));
}
TEST(OutputBufferRefTest, AppendsAfterExistingContents) {
llvm::SmallString<16> bytes = llvm::StringRef("before:");
OutputBufferRef out = bytes;
out.Append(static_cast<uint8_t>(7));
EXPECT_THAT(bytes, Eq("before:7"));
}
// Appending has to work the same however the buffer is laid out, and a number
// leaves the buffer grown further than it wrote, so reallocation is where a
// size mistake would show up.
TEST(OutputBufferRefTest, AppendsPastInlineCapacity) {
llvm::SmallString<8> bytes;
OutputBufferRef out = bytes;
std::string expected;
for (int i = 0; i < 100; ++i) {
out.Append("x", static_cast<uint8_t>(i));
expected += "x" + std::to_string(i);
}
EXPECT_THAT(bytes, Eq(expected));
EXPECT_THAT(bytes.size(), Eq(expected.size()));
}
// References to one buffer all append to it, and none of them own it, so the
// buffer keeps everything written through any of them.
TEST(OutputBufferRefTest, ReferencesShareTheirBuffer) {
llvm::SmallString<16> bytes;
OutputBufferRef first = bytes;
first.Append("a");
{
OutputBufferRef second = bytes;
second.Append("b");
}
OutputBufferRef copy = first;
copy.Append("c");
first.Append("d");
EXPECT_THAT(bytes, Eq("abcd"));
}
} // namespace
} // namespace Carbon::Terminal

Some files were not shown because too many files have changed in this diff Show More